diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index c41b16c3..5036fe47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 4.0.0 + +* Compatibility with react-admin v5 + ## 3.4.5 * Fix validation errors shown as "Server communication error" when creating an entity diff --git a/UPGRADE.md b/UPGRADE.md index 295def84..40ce33e1 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,24 +1,7 @@ -# Upgrade to 3.0 +# Upgrade to 4.0 -First of all, read the [UPGRADE guide from react-admin](https://marmelab.com/react-admin/doc/4.0/Upgrade.html). +API Platform Admin 4.0 has the same API as API Platform admin 3.4, but now requires react-admin v5. -Since API Platform Admin is built on top of react-admin, almost everything in the react-admin upgrade guide applies to API Platform Admin as well. +If your application only uses components from the '@api-platform/admin' package, it should work out of the box with API Platform Admin 4.0. -This UPGRADE guide will only cover the specific changes for API Platform Admin. - -## Authentication Support - -Since the way to define custom routes has completely changed in react-admin, the way to add authentication support in API Platform Admin has also been modified. - -In short, you need to use the `` component inside the `` or `` component, with a redirect condition on its child. -The condition is taken from a state variable, and the state updater function is given to the data provider and will be used when there is an unauthorized error. - -To see the full updated example, please [go to the related documentation page](https://api-platform.com/docs/main/admin/authentication-support/). - -## Mercure Support - -Since react-admin does not use Redux anymore, it's also the case for Mercure in API Platform Admin. - -Instead it uses react-query cache to update the received changes in real time. - -You will not see the Redux data action when a resource is updated by Mercure anymore. +If you have done some customization based on the 'react-admin' package, you will probably have to make some changes. Read the [UPGRADE guide from react-admin](https://marmelab.com/react-admin/doc/5.0/Upgrade.html) for further details. diff --git a/api/src/Entity/Greeting.php b/api/src/Entity/Greeting.php index a3e457e2..adec4c04 100644 --- a/api/src/Entity/Greeting.php +++ b/api/src/Entity/Greeting.php @@ -5,12 +5,15 @@ use ApiPlatform\Metadata\ApiResource; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; + -/** - * This is a dummy entity. Remove it! - */ #[ApiResource(mercure: true)] #[ORM\Entity] +#[ApiFilter(SearchFilter::class, properties: ['name' => 'partial'])] +#[ApiFilter(OrderFilter::class, properties: ['name'], arguments: ['orderParameterName' => 'order'])] class Greeting { /** diff --git a/jest.config.ts b/jest.config.ts index 44d5dab0..13b51465 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -9,6 +9,8 @@ const config: JestConfigWithTsJest = { moduleNameMapper: { '^(\\.{1,2}/.*/llhttp\\.wasm\\.js)$': '$1', '^(\\.{1,2}/.*)\\.js$': '$1', + '^@tanstack/react-query$': + '/node_modules/@tanstack/react-query/build/modern/index.cjs', }, transform: { '^.+\\.tsx?$': [ diff --git a/package.json b/package.json index 36043d97..071d6271 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,9 @@ "sideEffects": false, "dependencies": { "@api-platform/api-doc-parser": "^0.16.2", - "history": "^5.0.0", "jsonld": "^8.1.0", "lodash.isplainobject": "^4.0.6", - "prop-types": "^15.6.2", - "react-admin": "^4.4.0", - "react-error-boundary": "^4.0.13" + "react-admin": "^5.0.3" }, "devDependencies": { "@babel/preset-env": "^7.23.3", diff --git a/src/AdminGuesser.tsx b/src/AdminGuesser.tsx deleted file mode 100644 index f9de4564..00000000 --- a/src/AdminGuesser.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import PropTypes from 'prop-types'; -import { - AdminContext, - AdminUI, - ComponentPropType, - Loading, - defaultI18nProvider, -} from 'react-admin'; -import { ErrorBoundary } from 'react-error-boundary'; -import type { FallbackProps } from 'react-error-boundary'; -import type { ComponentType, ErrorInfo } from 'react'; -import type { AdminProps, ErrorProps } from 'react-admin'; -import type { Resource } from '@api-platform/api-doc-parser'; - -import IntrospectionContext from './IntrospectionContext.js'; -import ResourceGuesser from './ResourceGuesser.js'; -import SchemaAnalyzerContext from './SchemaAnalyzerContext.js'; -import { - Error as DefaultError, - Layout, - LoginPage, - darkTheme, - lightTheme, -} from './layout/index.js'; -import getRoutesAndResourcesFromNodes, { - isSingleChildFunction, -} from './getRoutesAndResourcesFromNodes.js'; -import useDisplayOverrideCode from './useDisplayOverrideCode.js'; -import type { ApiPlatformAdminDataProvider, SchemaAnalyzer } from './types.js'; - -export interface AdminGuesserProps extends AdminProps { - admin?: ComponentType; - dataProvider: ApiPlatformAdminDataProvider; - schemaAnalyzer: SchemaAnalyzer; - includeDeprecated?: boolean; -} - -interface AdminGuesserWithErrorProps extends AdminGuesserProps { - error?: ComponentType; -} - -interface AdminResourcesGuesserProps extends Omit { - admin?: ComponentType; - includeDeprecated: boolean; - loading: boolean; - loadingPage?: ComponentType; - resources: Resource[]; -} - -const getOverrideCode = (resources: Resource[]) => { - let code = - 'If you want to override at least one resource, paste this content in the component of your app:\n\n'; - - resources.forEach((r) => { - code += `\n`; - }); - - return code; -}; - -/** - * AdminResourcesGuesser automatically renders an `` component for resources exposed by a web API documented with Hydra, OpenAPI or any other format supported by `@api-platform/api-doc-parser`. - * If child components are passed (usually `` or `` components, but it can be any other React component), they are rendered in the given order. - * If no children are passed, a `` component is created for each resource type exposed by the API, in the order they are specified in the API documentation. - */ -export const AdminResourcesGuesser = ({ - // Admin props - loadingPage: LoadingPage = Loading, - admin: AdminEl = AdminUI, - // Props - children, - includeDeprecated, - resources, - loading, - ...rest -}: AdminResourcesGuesserProps) => { - const displayOverrideCode = useDisplayOverrideCode(); - - if (loading) { - return ; - } - - let adminChildren = children; - const { resources: resourceChildren, customRoutes } = - getRoutesAndResourcesFromNodes(children); - if ( - !isSingleChildFunction(adminChildren) && - resourceChildren.length === 0 && - resources - ) { - const guessResources = includeDeprecated - ? resources - : resources.filter((r) => !r.deprecated); - adminChildren = [ - ...customRoutes, - ...guessResources.map((r) => ( - - )), - ]; - displayOverrideCode(getOverrideCode(guessResources)); - } - - return ( - - {adminChildren} - - ); -}; - -const AdminGuesser = ({ - // Props for SchemaAnalyzerContext - schemaAnalyzer, - // Props for AdminResourcesGuesser - includeDeprecated = false, - // Admin props - basename, - store, - dataProvider, - i18nProvider, - authProvider, - queryClient, - defaultTheme, - layout = Layout, - loginPage = LoginPage, - loading: loadingPage, - theme = lightTheme, - // Other props - children, - ...rest -}: AdminGuesserProps) => { - const [resources, setResources] = useState([]); - const [loading, setLoading] = useState(true); - const [, setError] = useState(); - const [introspect, setIntrospect] = useState(true); - - useEffect(() => { - if (typeof dataProvider.introspect !== 'function') { - throw new Error( - 'The given dataProvider needs to expose an "introspect" function returning a parsed API documentation from api-doc-parser', - ); - } - - if (!introspect) { - return; - } - - dataProvider - .introspect() - .then(({ data }) => { - setResources(data.resources ?? []); - setIntrospect(false); - setLoading(false); - }) - .catch((error) => { - // Allow error to be caught by the error boundary - setError(() => { - throw error; - }); - }); - }, [introspect, dataProvider]); - - const introspectionContext = useMemo( - () => ({ - introspect: () => { - setLoading(true); - setIntrospect(true); - }, - }), - [setLoading, setIntrospect], - ); - - return ( - - - - - {children} - - - - - ); -}; - -/* eslint-disable tree-shaking/no-side-effects-in-initialization */ -AdminGuesser.propTypes = { - dataProvider: PropTypes.oneOfType([PropTypes.object, PropTypes.func]) - .isRequired, - authProvider: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - i18nProvider: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - history: PropTypes.object, - customSagas: PropTypes.array, - initialState: PropTypes.object, - schemaAnalyzer: PropTypes.object.isRequired, - children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), - theme: PropTypes.object, - includeDeprecated: PropTypes.bool, - admin: PropTypes.elementType, -}; -/* eslint-enable tree-shaking/no-side-effects-in-initialization */ - -const AdminGuesserWithError = ({ - error: ErrorComponent = DefaultError, - i18nProvider = defaultI18nProvider, - theme = lightTheme, - ...props -}: AdminGuesserWithErrorProps) => { - const [errorInfo, setErrorInfo] = useState(); - - const handleError = (_error: Error, info: ErrorInfo) => { - setErrorInfo(info); - }; - - const renderError = useCallback( - (fallbackRenderProps: FallbackProps) => ( - - ), - [ErrorComponent, errorInfo], - ); - - return ( - - - - ); -}; - -AdminGuesserWithError.propTypes = { - error: ComponentPropType, -}; - -export default AdminGuesserWithError; diff --git a/src/AdminGuesser.test.tsx b/src/core/AdminGuesser.test.tsx similarity index 91% rename from src/AdminGuesser.test.tsx rename to src/core/AdminGuesser.test.tsx index 10ec3b53..b2d701d0 100644 --- a/src/AdminGuesser.test.tsx +++ b/src/core/AdminGuesser.test.tsx @@ -2,15 +2,16 @@ import React from 'react'; import { AdminUI, AuthContext } from 'react-admin'; import type { AdminProps, AuthProvider } from 'react-admin'; import ReactTestRenderer from 'react-test-renderer/shallow'; -import AdminGuesser, { AdminResourcesGuesser } from './AdminGuesser.js'; +import AdminGuesser from './AdminGuesser.js'; +import { AdminResourcesGuesser } from './AdminResourcesGuesser.js'; import ResourceGuesser from './ResourceGuesser.js'; -import schemaAnalyzer from './hydra/schemaAnalyzer.js'; -import resources from './__fixtures__/resources.js'; -import { API_DATA } from './__fixtures__/parsedData.js'; +import schemaAnalyzer from '../hydra/schemaAnalyzer.js'; +import resources from '../__fixtures__/resources.js'; +import { API_DATA } from '../__fixtures__/parsedData.js'; import type { ApiPlatformAdminDataProvider, ApiPlatformAdminRecord, -} from './types.js'; +} from '../types.js'; const dataProvider: ApiPlatformAdminDataProvider = { getList: () => Promise.resolve({ data: [], total: 0 }), diff --git a/src/core/AdminGuesser.tsx b/src/core/AdminGuesser.tsx new file mode 100644 index 00000000..f5edbd74 --- /dev/null +++ b/src/core/AdminGuesser.tsx @@ -0,0 +1,124 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { AdminContext, defaultI18nProvider } from 'react-admin'; + +import type { ComponentType } from 'react'; +import type { AdminProps } from 'react-admin'; +import type { Resource } from '@api-platform/api-doc-parser'; + +import { AdminResourcesGuesser } from './AdminResourcesGuesser.js'; +import IntrospectionContext from '../introspection/IntrospectionContext.js'; +import SchemaAnalyzerContext from '../introspection/SchemaAnalyzerContext.js'; +import { + Error as DefaultError, + Layout, + LoginPage, + darkTheme, + lightTheme, +} from '../layout/index.js'; +import type { ApiPlatformAdminDataProvider, SchemaAnalyzer } from '../types.js'; + +export interface AdminGuesserProps extends AdminProps { + admin?: ComponentType; + dataProvider: ApiPlatformAdminDataProvider; + schemaAnalyzer: SchemaAnalyzer; + includeDeprecated?: boolean; +} + +const AdminGuesser = ({ + // Props for SchemaAnalyzerContext + schemaAnalyzer, + // Props for AdminResourcesGuesser + includeDeprecated = false, + // Admin props + basename, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + error = DefaultError as any, + store, + dataProvider, + i18nProvider = defaultI18nProvider, + authProvider, + queryClient, + defaultTheme, + layout = Layout, + loginPage = LoginPage, + loading: loadingPage, + theme = lightTheme, + // Other props + children, + ...rest +}: AdminGuesserProps) => { + const [resources, setResources] = useState([]); + const [loading, setLoading] = useState(true); + const [, setError] = useState(); + const [introspect, setIntrospect] = useState(true); + + useEffect(() => { + if (typeof dataProvider.introspect !== 'function') { + throw new Error( + 'The given dataProvider needs to expose an "introspect" function returning a parsed API documentation from api-doc-parser', + ); + } + + if (!introspect) { + return; + } + + dataProvider + .introspect() + .then(({ data }) => { + setResources(data.resources ?? []); + setIntrospect(false); + setLoading(false); + }) + .catch((err) => { + // Allow err to be caught by the error boundary + setError(() => { + throw err; + }); + }); + }, [introspect, dataProvider]); + + const introspectionContext = useMemo( + () => ({ + introspect: () => { + setLoading(true); + setIntrospect(true); + }, + }), + [setLoading, setIntrospect], + ); + + return ( + + + + + {children} + + + + + ); +}; + +export default AdminGuesser; diff --git a/src/core/AdminResourcesGuesser.tsx b/src/core/AdminResourcesGuesser.tsx new file mode 100644 index 00000000..bd2fbca9 --- /dev/null +++ b/src/core/AdminResourcesGuesser.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { AdminUI, Loading } from 'react-admin'; +import type { ComponentType } from 'react'; +import type { AdminProps } from 'react-admin'; +import type { Resource } from '@api-platform/api-doc-parser'; + +import ResourceGuesser from './ResourceGuesser.js'; + +import getRoutesAndResourcesFromNodes, { + isSingleChildFunction, +} from '../introspection/getRoutesAndResourcesFromNodes.js'; +import useDisplayOverrideCode from '../useDisplayOverrideCode.js'; +import type { ApiPlatformAdminDataProvider, SchemaAnalyzer } from '../types.js'; + +export interface AdminGuesserProps extends AdminProps { + admin?: ComponentType; + dataProvider: ApiPlatformAdminDataProvider; + schemaAnalyzer: SchemaAnalyzer; + includeDeprecated?: boolean; +} + +interface AdminResourcesGuesserProps extends Omit { + admin?: ComponentType; + includeDeprecated: boolean; + loading: boolean; + loadingPage?: ComponentType; + resources: Resource[]; +} + +const getOverrideCode = (resources: Resource[]) => { + let code = + 'If you want to override at least one resource, paste this content in the component of your app:\n\n'; + + resources.forEach((r) => { + code += `\n`; + }); + + return code; +}; + +/** + * AdminResourcesGuesser automatically renders an `` component + * for resources exposed by a web API documented with Hydra, OpenAPI + * or any other format supported by `@api-platform/api-doc-parser`. + * + * If child components are passed (usually `` or `` + * components, but it can be any other React component), they are rendered in + * the given order. + * If no children are passed, a `` component is created for + * each resource type exposed by the API, in the order they are specified in + * the API documentation. + */ +export const AdminResourcesGuesser = ({ + // Admin props + loadingPage: LoadingPage = Loading, + admin: AdminEl = AdminUI, + // Props + children, + includeDeprecated, + resources, + loading, + ...rest +}: AdminResourcesGuesserProps) => { + const displayOverrideCode = useDisplayOverrideCode(); + + if (loading) { + return ; + } + + let adminChildren = children; + const { resources: resourceChildren, customRoutes } = + getRoutesAndResourcesFromNodes(children); + if ( + !isSingleChildFunction(adminChildren) && + resourceChildren.length === 0 && + resources + ) { + const guessedResources = includeDeprecated + ? resources + : resources.filter((r) => !r.deprecated); + adminChildren = [ + ...customRoutes, + ...guessedResources.map((r) => ( + + )), + ]; + displayOverrideCode(getOverrideCode(guessedResources)); + } + + return ( + + {adminChildren} + + ); +}; diff --git a/src/ResourceGuesser.test.tsx b/src/core/ResourceGuesser.test.tsx similarity index 100% rename from src/ResourceGuesser.test.tsx rename to src/core/ResourceGuesser.test.tsx diff --git a/src/ResourceGuesser.tsx b/src/core/ResourceGuesser.tsx similarity index 86% rename from src/ResourceGuesser.tsx rename to src/core/ResourceGuesser.tsx index e5e8785a..6f74ab4d 100644 --- a/src/ResourceGuesser.tsx +++ b/src/core/ResourceGuesser.tsx @@ -1,20 +1,19 @@ import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; import { Resource, useResourceDefinition, useResourceDefinitionContext, } from 'react-admin'; import type { ResourceDefinition, ResourceProps } from 'react-admin'; -import ListGuesser from './ListGuesser.js'; -import CreateGuesser from './CreateGuesser.js'; -import EditGuesser from './EditGuesser.js'; -import ShowGuesser from './ShowGuesser.js'; -import Introspecter from './Introspecter.js'; +import ListGuesser from '../list/ListGuesser.js'; +import CreateGuesser from '../create/CreateGuesser.js'; +import EditGuesser from '../edit/EditGuesser.js'; +import ShowGuesser from '../show/ShowGuesser.js'; +import Introspecter from '../introspection/Introspecter.js'; import type { IntrospectedResourceGuesserProps, ResourceGuesserProps, -} from './types.js'; +} from '../types.js'; export const IntrospectedResourceGuesser = ({ resource, @@ -111,8 +110,4 @@ ResourceGuesser.registerResource = ( hasShow: true, }); -ResourceGuesser.propTypes = { - name: PropTypes.string.isRequired, -}; - export default ResourceGuesser; diff --git a/src/__snapshots__/AdminGuesser.test.tsx.snap b/src/core/__snapshots__/AdminGuesser.test.tsx.snap similarity index 100% rename from src/__snapshots__/AdminGuesser.test.tsx.snap rename to src/core/__snapshots__/AdminGuesser.test.tsx.snap diff --git a/src/__snapshots__/ResourceGuesser.test.tsx.snap b/src/core/__snapshots__/ResourceGuesser.test.tsx.snap similarity index 100% rename from src/__snapshots__/ResourceGuesser.test.tsx.snap rename to src/core/__snapshots__/ResourceGuesser.test.tsx.snap diff --git a/src/CreateGuesser.test.tsx b/src/create/CreateGuesser.test.tsx similarity index 94% rename from src/CreateGuesser.test.tsx rename to src/create/CreateGuesser.test.tsx index 7ac15acd..4e5bd9c2 100644 --- a/src/CreateGuesser.test.tsx +++ b/src/create/CreateGuesser.test.tsx @@ -2,17 +2,18 @@ import React from 'react'; import { AdminContext, FormTab, TextInput } from 'react-admin'; import { Resource } from '@api-platform/api-doc-parser'; import { render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import CreateGuesser from './CreateGuesser.js'; -import SchemaAnalyzerContext from './SchemaAnalyzerContext.js'; -import schemaAnalyzer from './hydra/schemaAnalyzer.js'; +import SchemaAnalyzerContext from '../introspection/SchemaAnalyzerContext.js'; +import schemaAnalyzer from '../hydra/schemaAnalyzer.js'; import type { ApiPlatformAdminDataProvider, ApiPlatformAdminRecord, -} from './types.js'; +} from '../types.js'; -import { API_FIELDS_DATA } from './__fixtures__/parsedData.js'; +import { API_FIELDS_DATA } from '../__fixtures__/parsedData.js'; const hydraSchemaAnalyzer = schemaAnalyzer(); const dataProvider: ApiPlatformAdminDataProvider = { diff --git a/src/CreateGuesser.tsx b/src/create/CreateGuesser.tsx similarity index 75% rename from src/CreateGuesser.tsx rename to src/create/CreateGuesser.tsx index 40a7e4c6..1515b03e 100644 --- a/src/CreateGuesser.tsx +++ b/src/create/CreateGuesser.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Create, FormTab, @@ -9,30 +8,30 @@ import { } from 'react-admin'; import type { Field, Resource } from '@api-platform/api-doc-parser'; -import InputGuesser from './InputGuesser.js'; -import Introspecter from './Introspecter.js'; -import useDisplayOverrideCode from './useDisplayOverrideCode.js'; -import useOnSubmit from './useOnSubmit.js'; +import InputGuesser from '../input/InputGuesser.js'; +import Introspecter from '../introspection/Introspecter.js'; +import useDisplayOverrideCode from '../useDisplayOverrideCode.js'; +import useOnSubmit from '../useOnSubmit.js'; import type { CreateGuesserProps, IntrospectedCreateGuesserProps, -} from './types.js'; +} from '../types.js'; const getOverrideCode = (schema: Resource, fields: Field[]) => { let code = 'If you want to override at least one input, paste this content in the component of your resource:\n\n'; - code += `const ${schema.title}Create = props => (\n`; - code += ` \n`; + code += `const ${schema.title}Create = () => (\n`; + code += ` \n`; fields.forEach((field) => { - code += ` \n`; + code += ` \n`; }); code += ` \n`; code += `);\n`; code += `\n`; code += `And don't forget update your component:\n`; - code += ``; + code += ``; return code; }; @@ -102,6 +101,9 @@ export const IntrospectedCreateGuesser = ({ const CreateGuesser = (props: CreateGuesserProps) => { const resource = useResourceContext(props); + if (!resource) { + throw new Error('CreateGuesser must be used with a resource'); + } return ( { ); }; -/* eslint-disable tree-shaking/no-side-effects-in-initialization */ -CreateGuesser.propTypes = { - children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), - resource: PropTypes.string, -}; -/* eslint-enable tree-shaking/no-side-effects-in-initialization */ - export default CreateGuesser; diff --git a/src/dataProvider/restDataProvider.ts b/src/dataProvider/restDataProvider.ts index 77b7824e..39470b56 100644 --- a/src/dataProvider/restDataProvider.ts +++ b/src/dataProvider/restDataProvider.ts @@ -13,8 +13,8 @@ export default ( return { getList: async (resource, params) => { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; + const { page, perPage } = params.pagination ?? { page: 1, perPage: 25 }; + const { field, order } = params.sort ?? { field: 'id', order: 'DESC' }; const rangeStart = (page - 1) * perPage; const rangeEnd = page * perPage - 1; diff --git a/src/dataProvider/useUpdateCache.ts b/src/dataProvider/useUpdateCache.ts index 51da8cd1..ff3d8bb9 100644 --- a/src/dataProvider/useUpdateCache.ts +++ b/src/dataProvider/useUpdateCache.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import type { GetListResult, Identifier } from 'react-admin'; -import { useQueryClient } from 'react-query'; +import { useQueryClient } from '@tanstack/react-query'; import type { ApiPlatformAdminRecord } from '../types.js'; const useUpdateCache = () => { @@ -40,19 +40,19 @@ const useUpdateCache = () => { }), ); queryClient.setQueriesData( - [resource, 'getList'], + { queryKey: [resource, 'getList'] }, (res: GetListResult | undefined) => res?.data ? { data: updateColl(res.data), total: res.total } : { data: [data] }, ); queryClient.setQueriesData( - [resource, 'getMany'], + { queryKey: [resource, 'getMany'] }, (coll: ApiPlatformAdminRecord[] | undefined) => coll && coll.length > 0 ? updateColl(coll) : [data], ); queryClient.setQueriesData( - [resource, 'getManyReference'], + { queryKey: [resource, 'getManyReference'] }, (res: GetListResult | undefined) => res?.data ? { data: updateColl(res.data), total: res.total } diff --git a/src/EditGuesser.test.tsx b/src/edit/EditGuesser.test.tsx similarity index 95% rename from src/EditGuesser.test.tsx rename to src/edit/EditGuesser.test.tsx index 8ac8ae8a..35004556 100644 --- a/src/EditGuesser.test.tsx +++ b/src/edit/EditGuesser.test.tsx @@ -2,17 +2,18 @@ import React from 'react'; import { AdminContext, FormTab, TextInput } from 'react-admin'; import { Resource } from '@api-platform/api-doc-parser'; import { render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import EditGuesser from './EditGuesser.js'; -import SchemaAnalyzerContext from './SchemaAnalyzerContext.js'; -import schemaAnalyzer from './hydra/schemaAnalyzer.js'; +import SchemaAnalyzerContext from '../introspection/SchemaAnalyzerContext.js'; +import schemaAnalyzer from '../hydra/schemaAnalyzer.js'; import type { ApiPlatformAdminDataProvider, ApiPlatformAdminRecord, -} from './types.js'; +} from '../types.js'; -import { API_FIELDS_DATA } from './__fixtures__/parsedData.js'; +import { API_FIELDS_DATA } from '../__fixtures__/parsedData.js'; const hydraSchemaAnalyzer = schemaAnalyzer(); const dataProvider: ApiPlatformAdminDataProvider = { diff --git a/src/EditGuesser.tsx b/src/edit/EditGuesser.tsx similarity index 76% rename from src/EditGuesser.tsx rename to src/edit/EditGuesser.tsx index 5f7ffd4b..6602a207 100644 --- a/src/EditGuesser.tsx +++ b/src/edit/EditGuesser.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Edit, FormTab, @@ -10,31 +9,31 @@ import { import { useParams } from 'react-router-dom'; import type { Field, Resource } from '@api-platform/api-doc-parser'; -import InputGuesser from './InputGuesser.js'; -import Introspecter from './Introspecter.js'; -import useMercureSubscription from './useMercureSubscription.js'; -import useDisplayOverrideCode from './useDisplayOverrideCode.js'; -import useOnSubmit from './useOnSubmit.js'; +import InputGuesser from '../input/InputGuesser.js'; +import Introspecter from '../introspection/Introspecter.js'; +import useMercureSubscription from '../mercure/useMercureSubscription.js'; +import useDisplayOverrideCode from '../useDisplayOverrideCode.js'; +import useOnSubmit from '../useOnSubmit.js'; import type { EditGuesserProps, IntrospectedEditGuesserProps, -} from './types.js'; +} from '../types.js'; const getOverrideCode = (schema: Resource, fields: Field[]) => { let code = 'If you want to override at least one input, paste this content in the component of your resource:\n\n'; - code += `const ${schema.title}Edit = props => (\n`; - code += ` \n`; + code += `const ${schema.title}Edit = () => (\n`; + code += ` \n`; fields.forEach((field) => { - code += ` \n`; + code += ` \n`; }); code += ` \n`; code += `);\n`; code += `\n`; code += `And don't forget update your component:\n`; - code += ``; + code += ``; return code; }; @@ -114,6 +113,9 @@ export const IntrospectedEditGuesser = ({ const EditGuesser = (props: EditGuesserProps) => { const resource = useResourceContext(props); + if (!resource) { + throw new Error('EditGuesser must be used with a resource'); + } return ( { ); }; -/* eslint-disable tree-shaking/no-side-effects-in-initialization */ -EditGuesser.propTypes = { - children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), - resource: PropTypes.string, -}; -/* eslint-enable tree-shaking/no-side-effects-in-initialization */ - export default EditGuesser; diff --git a/src/EnumField.tsx b/src/field/EnumField.tsx similarity index 82% rename from src/EnumField.tsx rename to src/field/EnumField.tsx index 50b9bc21..ddf67b72 100644 --- a/src/EnumField.tsx +++ b/src/field/EnumField.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { ArrayField, SingleFieldList, @@ -7,7 +6,7 @@ import { useRecordContext, } from 'react-admin'; import type { TextFieldProps } from 'react-admin'; -import type { EnumFieldProps } from './types.js'; +import type { EnumFieldProps } from '../types.js'; const EnumField = ({ transformEnum, source, ...props }: EnumFieldProps) => { const record = useRecordContext(); @@ -34,9 +33,4 @@ const EnumField = ({ transformEnum, source, ...props }: EnumFieldProps) => { EnumField.displayName = 'EnumField'; -EnumField.propTypes = { - ...TextField.propTypes, - transformEnum: PropTypes.func, -}; - export default EnumField; diff --git a/src/FieldGuesser.test.tsx b/src/field/FieldGuesser.test.tsx similarity index 94% rename from src/FieldGuesser.test.tsx rename to src/field/FieldGuesser.test.tsx index f23c6ae5..61475cc2 100644 --- a/src/FieldGuesser.test.tsx +++ b/src/field/FieldGuesser.test.tsx @@ -4,14 +4,14 @@ import { Resource } from '@api-platform/api-doc-parser'; import { render, screen, waitFor } from '@testing-library/react'; import FieldGuesser from './FieldGuesser.js'; -import SchemaAnalyzerContext from './SchemaAnalyzerContext.js'; -import schemaAnalyzer from './hydra/schemaAnalyzer.js'; +import SchemaAnalyzerContext from '../introspection/SchemaAnalyzerContext.js'; +import schemaAnalyzer from '../hydra/schemaAnalyzer.js'; import type { ApiPlatformAdminDataProvider, ApiPlatformAdminRecord, -} from './types.js'; +} from '../types.js'; -import { API_FIELDS_DATA } from './__fixtures__/parsedData.js'; +import { API_FIELDS_DATA } from '../__fixtures__/parsedData.js'; const hydraSchemaAnalyzer = schemaAnalyzer(); const dataProvider: ApiPlatformAdminDataProvider = { diff --git a/src/FieldGuesser.tsx b/src/field/FieldGuesser.tsx similarity index 92% rename from src/FieldGuesser.tsx rename to src/field/FieldGuesser.tsx index d715d817..194de03a 100644 --- a/src/FieldGuesser.tsx +++ b/src/field/FieldGuesser.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { ArrayField, BooleanField, @@ -29,7 +28,7 @@ import type { } from 'react-admin'; import type { Field, Resource } from '@api-platform/api-doc-parser'; -import Introspecter from './Introspecter.js'; +import Introspecter from '../introspection/Introspecter.js'; import EnumField from './EnumField.js'; import type { EnumFieldProps, @@ -37,7 +36,7 @@ import type { FieldProps, IntrospectedFieldGuesserProps, SchemaAnalyzer, -} from './types.js'; +} from '../types.js'; const isFieldSortable = (field: Field, schema: Resource) => !!schema.parameters && @@ -138,6 +137,11 @@ export const IntrospectedFieldGuesser = ({ schemaAnalyzer, ...props }: IntrospectedFieldGuesserProps) => { + if (!props.source) { + // eslint-disable-next-line no-console + console.error('FieldGuesser: missing source property.'); + return null; + } const field = fields.find((f) => f.name === props.source); if (!field) { @@ -152,11 +156,15 @@ export const IntrospectedFieldGuesser = ({ return renderField(field, schemaAnalyzer, { sortable: isFieldSortable(field, schema), ...props, + source: props.source, }); }; const FieldGuesser = (props: FieldGuesserProps) => { const resource = useResourceContext(props); + if (!resource) { + throw new Error('FieldGuesser must be used with a resource'); + } return ( { ); }; -FieldGuesser.propTypes = { - source: PropTypes.string.isRequired, - resource: PropTypes.string, - sortable: PropTypes.bool, - sortBy: PropTypes.string, -}; - export default FieldGuesser; diff --git a/src/getIdentifierValue.test.ts b/src/getIdentifierValue.test.ts index 426c90ef..8e0ed3db 100644 --- a/src/getIdentifierValue.test.ts +++ b/src/getIdentifierValue.test.ts @@ -3,7 +3,7 @@ import getIdentifierValue from './getIdentifierValue.js'; import { getFiltersParametersFromSchema, getOrderParametersFromSchema, -} from './schemaAnalyzer.js'; +} from './introspection/schemaAnalyzer.js'; import type { SchemaAnalyzer } from './types.js'; const schemaAnalyzer: SchemaAnalyzer = { diff --git a/src/hydra/HydraAdmin.tsx b/src/hydra/HydraAdmin.tsx index 79d9b143..02616045 100644 --- a/src/hydra/HydraAdmin.tsx +++ b/src/hydra/HydraAdmin.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import PropTypes from 'prop-types'; import dataProviderFactory from './dataProvider.js'; import /* tree-shaking no-side-effects-when-called */ schemaAnalyzer from './schemaAnalyzer.js'; -import AdminGuesser from '../AdminGuesser.js'; -import type { AdminGuesserProps } from '../AdminGuesser.js'; +import AdminGuesser from '../core/AdminGuesser.js'; +import type { AdminGuesserProps } from '../core/AdminGuesser.js'; import type { MercureOptions } from '../types.js'; type AdminGuesserPartialProps = Omit< @@ -36,8 +35,4 @@ const HydraAdmin = ({ /> ); -HydraAdmin.propTypes = { - entrypoint: PropTypes.string.isRequired, -}; - export default HydraAdmin; diff --git a/src/hydra/dataProvider.ts b/src/hydra/dataProvider.ts index 153c610f..48009285 100644 --- a/src/hydra/dataProvider.ts +++ b/src/hydra/dataProvider.ts @@ -23,7 +23,7 @@ import type { } from 'react-admin'; import fetchHydra from './fetchHydra.js'; -import { resolveSchemaParameters } from '../schemaAnalyzer.js'; +import { resolveSchemaParameters } from '../introspection/schemaAnalyzer.js'; import { adminDataProvider } from '../dataProvider/index.js'; import { mercureManager } from '../mercure/index.js'; import { removeTrailingSlash } from '../removeTrailingSlash.js'; @@ -381,12 +381,11 @@ function dataProvider( case GET_LIST: case GET_MANY_REFERENCE: { - const { - pagination: { page, perPage }, - sort: { field, order }, - filter, - } = params as GetListParams | GetManyReferenceParams; - + const { pagination, sort, filter } = params as + | GetListParams + | GetManyReferenceParams; + const { page, perPage } = pagination ?? { page: 1, perPage: 25 }; + const { field, order } = sort ?? { field: 'id', order: 'DESC' }; if (order && field) { field.split(',').forEach((fieldName) => { url.searchParams.set(`order[${fieldName.trim()}]`, order); @@ -670,7 +669,10 @@ function dataProvider( } // Minimalist infinite loop protection - if (pageParams.pagination.page >= result.data.length) { + if ( + pageParams.pagination?.page && + pageParams.pagination?.page >= result.data.length + ) { return result; } @@ -679,7 +681,11 @@ function dataProvider( ((!!result.total && result.data.length < result.total) || result.pageInfo?.hasNextPage) ) { - pageParams.pagination.page += 1; + if (pageParams.pagination) { + pageParams.pagination.page += 1; + } else { + pageParams.pagination = { page: 2, perPage: 25 }; + } return fetchAllPages(type, resource, pageParams, result); } diff --git a/src/hydra/schemaAnalyzer.ts b/src/hydra/schemaAnalyzer.ts index b8d53029..36b8da86 100644 --- a/src/hydra/schemaAnalyzer.ts +++ b/src/hydra/schemaAnalyzer.ts @@ -4,7 +4,7 @@ import type { JsonLdObj } from 'jsonld/jsonld-spec'; import { getFiltersParametersFromSchema, getOrderParametersFromSchema, -} from '../schemaAnalyzer.js'; +} from '../introspection/schemaAnalyzer.js'; import type { SchemaAnalyzer, SubmissionErrors } from '../types.js'; const withHttpScheme = (value: string | null | undefined) => diff --git a/src/index.ts b/src/index.ts index 5232bafa..8bed6758 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,16 @@ -import AdminGuesser from './AdminGuesser.js'; -import CreateGuesser from './CreateGuesser.js'; -import EditGuesser from './EditGuesser.js'; -import FieldGuesser from './FieldGuesser.js'; -import InputGuesser from './InputGuesser.js'; -import Introspecter from './Introspecter.js'; -import ListGuesser from './ListGuesser.js'; -import ResourceGuesser from './ResourceGuesser.js'; -import SchemaAnalyzerContext from './SchemaAnalyzerContext.js'; -import ShowGuesser from './ShowGuesser.js'; -import useIntrospect from './useIntrospect.js'; -import useIntrospection from './useIntrospection.js'; -import useMercureSubscription from './useMercureSubscription.js'; +import AdminGuesser from './core/AdminGuesser.js'; +import CreateGuesser from './create/CreateGuesser.js'; +import EditGuesser from './edit/EditGuesser.js'; +import FieldGuesser from './field/FieldGuesser.js'; +import InputGuesser from './input/InputGuesser.js'; +import Introspecter from './introspection/Introspecter.js'; +import ListGuesser from './list/ListGuesser.js'; +import ResourceGuesser from './core/ResourceGuesser.js'; +import SchemaAnalyzerContext from './introspection/SchemaAnalyzerContext.js'; +import ShowGuesser from './show/ShowGuesser.js'; +import useIntrospect from './introspection/useIntrospect.js'; +import useIntrospection from './introspection/useIntrospection.js'; +import useMercureSubscription from './mercure/useMercureSubscription.js'; import useOnSubmit from './useOnSubmit.js'; export { diff --git a/src/InputGuesser.test.tsx b/src/input/InputGuesser.test.tsx similarity index 96% rename from src/InputGuesser.test.tsx rename to src/input/InputGuesser.test.tsx index 3f50646f..e13fe010 100644 --- a/src/InputGuesser.test.tsx +++ b/src/input/InputGuesser.test.tsx @@ -8,17 +8,18 @@ import { } from 'react-admin'; import { Resource } from '@api-platform/api-doc-parser'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import InputGuesser from './InputGuesser.js'; -import SchemaAnalyzerContext from './SchemaAnalyzerContext.js'; -import schemaAnalyzer from './hydra/schemaAnalyzer.js'; +import SchemaAnalyzerContext from '../introspection/SchemaAnalyzerContext.js'; +import schemaAnalyzer from '../hydra/schemaAnalyzer.js'; import type { ApiPlatformAdminDataProvider, ApiPlatformAdminRecord, -} from './types.js'; +} from '../types.js'; -import { API_FIELDS_DATA } from './__fixtures__/parsedData.js'; +import { API_FIELDS_DATA } from '../__fixtures__/parsedData.js'; const hydraSchemaAnalyzer = schemaAnalyzer(); const dataProvider: ApiPlatformAdminDataProvider = { @@ -209,11 +210,8 @@ describe('', () => { 'resources.users.fields.embedded', ); expect(embeddedField).toHaveValue('{"address":"91 rue du Temple"}'); - expect( - await screen.findAllByText('resources.users.fields.embeddeds.0'), - ).toHaveLength(1); - const embeddedsField = screen.getByLabelText( - 'resources.users.fields.embeddeds.0', + const embeddedsField = await screen.findByLabelText( + 'resources.users.fields.embeddeds', ); expect(embeddedsField).toHaveValue('{"address":"16 avenue de Rivoli"}'); diff --git a/src/InputGuesser.tsx b/src/input/InputGuesser.tsx similarity index 95% rename from src/InputGuesser.tsx rename to src/input/InputGuesser.tsx index 1a6e699b..a380e6ac 100644 --- a/src/InputGuesser.tsx +++ b/src/input/InputGuesser.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { ArrayInput, BooleanInput, @@ -28,12 +27,12 @@ import type { TextInputProps, } from 'react-admin'; import isPlainObject from 'lodash.isplainobject'; -import Introspecter from './Introspecter.js'; -import getIdentifierValue, { isIdentifier } from './getIdentifierValue.js'; +import Introspecter from '../introspection/Introspecter.js'; +import getIdentifierValue, { isIdentifier } from '../getIdentifierValue.js'; import type { InputGuesserProps, IntrospectedInputGuesserProps, -} from './types.js'; +} from '../types.js'; export const IntrospectedInputGuesser = ({ fields, @@ -178,6 +177,7 @@ export const IntrospectedInputGuesser = ({ @@ -263,6 +263,9 @@ export const IntrospectedInputGuesser = ({ const InputGuesser = (props: InputGuesserProps) => { const resource = useResourceContext(props); + if (!resource) { + throw new Error('guesser must be used with a resource'); + } return ( { ); }; -InputGuesser.propTypes = { - source: PropTypes.string.isRequired, - alwaysOn: PropTypes.bool, -}; - export default InputGuesser; diff --git a/src/Introspecter.tsx b/src/introspection/Introspecter.tsx similarity index 50% rename from src/Introspecter.tsx rename to src/introspection/Introspecter.tsx index 06493712..61164338 100644 --- a/src/Introspecter.tsx +++ b/src/introspection/Introspecter.tsx @@ -1,70 +1,10 @@ import React, { useContext, useEffect, useMemo } from 'react'; -import PropTypes from 'prop-types'; import { useLogoutIfAccessDenied } from 'react-admin'; + import SchemaAnalyzerContext from './SchemaAnalyzerContext.js'; import useIntrospect from './useIntrospect.js'; -import type { - IntrospecterProps, - ResourcesIntrospecterProps, - SchemaAnalyzer, -} from './types.js'; - -const ResourcesIntrospecter = ({ - component: Component, - schemaAnalyzer, - includeDeprecated, - resource, - resources, - loading, - error, - ...rest -}: ResourcesIntrospecterProps) => { - if (loading) { - return null; - } - - if (error) { - if (process.env.NODE_ENV === 'production') { - // eslint-disable-next-line no-console - console.error(error); - } - - throw new Error('API schema is not readable'); - } - - const schema = resources.find((r) => r.name === resource); - - if (!schema?.fields || !schema?.readableFields || !schema?.writableFields) { - if (process.env.NODE_ENV === 'production') { - // eslint-disable-next-line no-console - console.error(`Resource ${resource} not present inside API description`); - } - - throw new Error(`Resource ${resource} not present inside API description`); - } - - const fields = includeDeprecated - ? schema.fields - : schema.fields.filter(({ deprecated }) => !deprecated); - const readableFields = includeDeprecated - ? schema.readableFields - : schema.readableFields.filter(({ deprecated }) => !deprecated); - const writableFields = includeDeprecated - ? schema.writableFields - : schema.writableFields.filter(({ deprecated }) => !deprecated); - - return ( - - ); -}; +import type { IntrospecterProps, SchemaAnalyzer } from '../types.js'; +import ResourcesIntrospecter from './ResourcesIntrospecter.js'; const Introspecter = ({ component, @@ -107,7 +47,8 @@ const Introspecter = ({ }, }); }, [schemaAnalyzer, logoutIfAccessDenied]); - const { refetch, data, isLoading, isIdle, error } = useIntrospect(); + + const { refetch, data, isPending, error } = useIntrospect(); const resources = data ? data.data.resources : null; useEffect(() => { @@ -127,17 +68,11 @@ const Introspecter = ({ includeDeprecated={includeDeprecated} resource={resource} resources={resources ?? []} - loading={isLoading || isIdle} + loading={isPending} error={error} {...rest} /> ); }; -Introspecter.propTypes = { - component: PropTypes.elementType.isRequired, - includeDeprecated: PropTypes.bool, - resource: PropTypes.string, -}; - export default Introspecter; diff --git a/src/IntrospectionContext.ts b/src/introspection/IntrospectionContext.ts similarity index 100% rename from src/IntrospectionContext.ts rename to src/introspection/IntrospectionContext.ts diff --git a/src/introspection/ResourcesIntrospecter.tsx b/src/introspection/ResourcesIntrospecter.tsx new file mode 100644 index 00000000..44365a7a --- /dev/null +++ b/src/introspection/ResourcesIntrospecter.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import type { ResourcesIntrospecterProps } from '../types.js'; + +const ResourcesIntrospecter = ({ + component: Component, + schemaAnalyzer, + includeDeprecated, + resource, + resources, + loading, + error, + ...rest +}: ResourcesIntrospecterProps) => { + if (loading) { + return null; + } + + if (error) { + if (process.env.NODE_ENV === 'production') { + // eslint-disable-next-line no-console + console.error(error); + } + + throw new Error('API schema is not readable'); + } + + const schema = resources.find((r) => r.name === resource); + + if (!schema?.fields || !schema?.readableFields || !schema?.writableFields) { + if (process.env.NODE_ENV === 'production') { + // eslint-disable-next-line no-console + console.error(`Resource ${resource} not present inside API description`); + } + + throw new Error(`Resource ${resource} not present inside API description`); + } + + const fields = includeDeprecated + ? schema.fields + : schema.fields.filter(({ deprecated }) => !deprecated); + const readableFields = includeDeprecated + ? schema.readableFields + : schema.readableFields.filter(({ deprecated }) => !deprecated); + const writableFields = includeDeprecated + ? schema.writableFields + : schema.writableFields.filter(({ deprecated }) => !deprecated); + + return ( + + ); +}; + +export default ResourcesIntrospecter; diff --git a/src/SchemaAnalyzerContext.ts b/src/introspection/SchemaAnalyzerContext.ts similarity index 79% rename from src/SchemaAnalyzerContext.ts rename to src/introspection/SchemaAnalyzerContext.ts index 5a32d4db..687c93ed 100644 --- a/src/SchemaAnalyzerContext.ts +++ b/src/introspection/SchemaAnalyzerContext.ts @@ -1,7 +1,7 @@ import { /* tree-shaking no-side-effects-when-called */ createContext, } from 'react'; -import type { SchemaAnalyzer } from './types.js'; +import type { SchemaAnalyzer } from '../types.js'; const SchemaAnalyzerContext = createContext(null); diff --git a/src/getRoutesAndResourcesFromNodes.tsx b/src/introspection/getRoutesAndResourcesFromNodes.tsx similarity index 94% rename from src/getRoutesAndResourcesFromNodes.tsx rename to src/introspection/getRoutesAndResourcesFromNodes.tsx index 0dbadc41..cc48d832 100644 --- a/src/getRoutesAndResourcesFromNodes.tsx +++ b/src/introspection/getRoutesAndResourcesFromNodes.tsx @@ -53,6 +53,7 @@ const getRoutesAndResourcesFromNodes = (children: AdminChildren) => { }; } + // @ts-expect-error for some reason, typescript doesn't narrow down the type after calling the isSingleChildFunction type guard Children.forEach(children, (element) => { if (!React.isValidElement(element)) { // Ignore non-elements. This allows people to more easily inline diff --git a/src/schemaAnalyzer.ts b/src/introspection/schemaAnalyzer.ts similarity index 97% rename from src/schemaAnalyzer.ts rename to src/introspection/schemaAnalyzer.ts index 4795abc4..174aee5e 100644 --- a/src/schemaAnalyzer.ts +++ b/src/introspection/schemaAnalyzer.ts @@ -1,5 +1,5 @@ import type { Resource } from '@api-platform/api-doc-parser'; -import type { FilterParameter } from './types.js'; +import type { FilterParameter } from '../types.js'; /** * @param schema The schema of a resource diff --git a/src/useIntrospect.ts b/src/introspection/useIntrospect.ts similarity index 50% rename from src/useIntrospect.ts rename to src/introspection/useIntrospect.ts index 59c7d1e5..2dfe1e4b 100644 --- a/src/useIntrospect.ts +++ b/src/introspection/useIntrospect.ts @@ -1,22 +1,20 @@ -import { useQuery } from 'react-query'; +import { useQuery } from '@tanstack/react-query'; import { useDataProvider } from 'react-admin'; -import type { UseQueryOptions } from 'react-query'; +import type { UseQueryOptions } from '@tanstack/react-query'; import type { ApiPlatformAdminDataProvider, IntrospectPayload, -} from './types.js'; +} from '../types.js'; const useIntrospect = (options?: UseQueryOptions) => { const dataProvider = useDataProvider(); - return useQuery( - 'introspect', - () => dataProvider.introspect(), - { - enabled: false, - ...options, - }, - ); + return useQuery({ + queryKey: ['introspect'], + queryFn: () => dataProvider.introspect(), + enabled: false, + ...options, + }); }; export default useIntrospect; diff --git a/src/useIntrospection.ts b/src/introspection/useIntrospection.ts similarity index 100% rename from src/useIntrospection.ts rename to src/introspection/useIntrospection.ts diff --git a/src/layout/AppBar.tsx b/src/layout/AppBar.tsx index 429b6f9d..f4b42e62 100644 --- a/src/layout/AppBar.tsx +++ b/src/layout/AppBar.tsx @@ -1,25 +1,23 @@ import React from 'react'; -import { AppBar, AppBarClasses, useAuthProvider } from 'react-admin'; +import { AppBar as RaAppBAr, TitlePortal, useAuthProvider } from 'react-admin'; import type { AppBarProps } from 'react-admin'; -import { Box, Typography } from '@mui/material'; +import { Box, useMediaQuery } from '@mui/material'; +import type { Theme } from '@mui/material'; import Logo from './Logo.js'; -const CustomAppBar = ({ classes, userMenu, ...props }: AppBarProps) => { +const AppBar = ({ classes, userMenu, ...props }: AppBarProps) => { const authProvider = useAuthProvider(); - + const isLargeEnough = useMediaQuery((theme) => + theme.breakpoints.up('sm'), + ); return ( - - - - - + + + {isLargeEnough && } + {isLargeEnough && } + ); }; -export default CustomAppBar; +export default AppBar; diff --git a/src/layout/Error.tsx b/src/layout/Error.tsx index 9cde3449..bcc3a7bc 100644 --- a/src/layout/Error.tsx +++ b/src/layout/Error.tsx @@ -1,6 +1,11 @@ import React from 'react'; -import type { HtmlHTMLAttributes } from 'react'; -import { Title, useTranslate } from 'react-admin'; +import type { ComponentType, ErrorInfo, HtmlHTMLAttributes } from 'react'; +import { + Title, + useDefaultTitle, + useResetErrorBoundaryOnLocationChange, + useTranslate, +} from 'react-admin'; import type { ErrorProps } from 'react-admin'; import { Accordion, @@ -30,13 +35,6 @@ export const ErrorClasses = { advice: `${PREFIX}-advice`, }; -interface InternalErrorProps - extends Omit, 'title'>, - Partial>, - ErrorProps { - className?: string; -} - // eslint-disable-next-line tree-shaking/no-side-effects-in-initialization const Root = styled('div', { name: PREFIX, @@ -87,15 +85,30 @@ const goBack = () => { window.history.go(-1); }; -const CustomError = ({ +interface InternalErrorProps + extends Omit, 'title'>, + FallbackProps { + className?: string; + errorInfo?: ErrorInfo; +} + +const Error = ({ error, + errorComponent: ErrorComponent, errorInfo, - title, resetErrorBoundary, className, ...rest -}: InternalErrorProps) => { +}: InternalErrorProps & { + errorComponent?: ComponentType; +}) => { const translate = useTranslate(); + const title = useDefaultTitle(); + useResetErrorBoundaryOnLocationChange(resetErrorBoundary); + + if (ErrorComponent) { + return ; + } return ( <> @@ -195,4 +208,4 @@ const CustomError = ({ ); }; -export default CustomError; +export default Error; diff --git a/src/layout/Layout.tsx b/src/layout/Layout.tsx index 96b85668..29e5926f 100644 --- a/src/layout/Layout.tsx +++ b/src/layout/Layout.tsx @@ -1,11 +1,13 @@ +import type { FunctionComponent } from 'react'; import React from 'react'; import { Layout } from 'react-admin'; import type { LayoutProps } from 'react-admin'; + import AppBar from './AppBar.js'; -import DefaultError from './Error.js'; +import Error from './Error.js'; const CustomLayout = (props: LayoutProps) => ( - + ); export default CustomLayout; diff --git a/src/FilterGuesser.tsx b/src/list/FilterGuesser.tsx similarity index 78% rename from src/FilterGuesser.tsx rename to src/list/FilterGuesser.tsx index 18836a83..65d01bf5 100644 --- a/src/FilterGuesser.tsx +++ b/src/list/FilterGuesser.tsx @@ -1,13 +1,18 @@ import React, { useEffect, useState } from 'react'; import { Filter, useResourceContext } from 'react-admin'; -import InputGuesser from './InputGuesser.js'; -import Introspecter from './Introspecter.js'; +import InputGuesser from '../input/InputGuesser.js'; +import Introspecter from '../introspection/Introspecter.js'; import type { FilterGuesserProps, FilterParameter, IntrospectedFiterGuesserProps, -} from './types.js'; +} from '../types.js'; +/** + * Adds filters based on the #ApiFilters attribute + * + * @see https://api-platform.com/docs/core/filters/ + */ export const IntrospectedFilterGuesser = ({ fields, readableFields, @@ -49,6 +54,9 @@ export const IntrospectedFilterGuesser = ({ const FilterGuesser = (props: FilterGuesserProps) => { const resource = useResourceContext(props); + if (!resource) { + throw new Error('FilterGuesser must be used with a resource'); + } return ( { let code = 'If you want to override at least one field, paste this content in the component of your resource:\n\n'; - code += `const ${schema.title}List = props => (\n`; - code += ` \n`; + code += `const ${schema.title}List = () => (\n`; + code += ` \n`; fields.forEach((field) => { - code += ` \n`; + code += ` \n`; }); code += ` \n`; code += `);\n`; code += `\n`; code += `And don't forget update your component:\n`; - code += ``; + code += ``; return code; }; @@ -140,6 +139,9 @@ const ListGuesser = ({ ...props }: ListGuesserProps) => { const resource = useResourceContext(props); + if (!resource) { + throw new Error('ListGuesser must be used with a resource'); + } return ( ); -OpenApiAdmin.propTypes = { - entrypoint: PropTypes.string.isRequired, -}; - export default OpenApiAdmin; diff --git a/src/openapi/schemaAnalyzer.ts b/src/openapi/schemaAnalyzer.ts index f3892f2b..06c2b532 100644 --- a/src/openapi/schemaAnalyzer.ts +++ b/src/openapi/schemaAnalyzer.ts @@ -2,7 +2,7 @@ import type { Field, Resource } from '@api-platform/api-doc-parser'; import { getFiltersParametersFromSchema, getOrderParametersFromSchema, -} from '../schemaAnalyzer.js'; +} from '../introspection/schemaAnalyzer.js'; import type { SchemaAnalyzer } from '../types.js'; /** diff --git a/src/ShowGuesser.test.tsx b/src/show/ShowGuesser.test.tsx similarity index 96% rename from src/ShowGuesser.test.tsx rename to src/show/ShowGuesser.test.tsx index ee8b13ed..55fef4e9 100644 --- a/src/ShowGuesser.test.tsx +++ b/src/show/ShowGuesser.test.tsx @@ -10,14 +10,14 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import ShowGuesser from './ShowGuesser.js'; -import SchemaAnalyzerContext from './SchemaAnalyzerContext.js'; -import schemaAnalyzer from './hydra/schemaAnalyzer.js'; +import SchemaAnalyzerContext from '../introspection/SchemaAnalyzerContext.js'; +import schemaAnalyzer from '../hydra/schemaAnalyzer.js'; import type { ApiPlatformAdminDataProvider, ApiPlatformAdminRecord, -} from './types.js'; +} from '../types.js'; -import { API_FIELDS_DATA } from './__fixtures__/parsedData.js'; +import { API_FIELDS_DATA } from '../__fixtures__/parsedData.js'; const hydraSchemaAnalyzer = schemaAnalyzer(); const dataProvider: ApiPlatformAdminDataProvider = { diff --git a/src/ShowGuesser.tsx b/src/show/ShowGuesser.tsx similarity index 71% rename from src/ShowGuesser.tsx rename to src/show/ShowGuesser.tsx index bcb99594..0e7abc21 100644 --- a/src/ShowGuesser.tsx +++ b/src/show/ShowGuesser.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Show, SimpleShowLayout, @@ -10,30 +9,30 @@ import { import { useParams } from 'react-router-dom'; import type { Field, Resource } from '@api-platform/api-doc-parser'; -import FieldGuesser from './FieldGuesser.js'; -import Introspecter from './Introspecter.js'; -import useMercureSubscription from './useMercureSubscription.js'; -import useDisplayOverrideCode from './useDisplayOverrideCode.js'; +import FieldGuesser from '../field/FieldGuesser.js'; +import Introspecter from '../introspection/Introspecter.js'; +import useMercureSubscription from '../mercure/useMercureSubscription.js'; +import useDisplayOverrideCode from '../useDisplayOverrideCode.js'; import type { IntrospectedShowGuesserProps, ShowGuesserProps, -} from './types.js'; +} from '../types.js'; const getOverrideCode = (schema: Resource, fields: Field[]) => { let code = 'If you want to override at least one field, paste this content in the component of your resource:\n\n'; - code += `const ${schema.title}Show = props => (\n`; - code += ` \n`; + code += `const ${schema.title}Show = () => (\n`; + code += ` \n`; fields.forEach((field) => { - code += ` \n`; + code += ` \n`; }); code += ` \n`; code += `);\n`; code += `\n`; code += `And don't forget update your component:\n`; - code += ``; + code += ``; return code; }; @@ -79,6 +78,9 @@ export const IntrospectedShowGuesser = ({ const ShowGuesser = (props: ShowGuesserProps) => { const resource = useResourceContext(props); + if (!resource) { + throw new Error('ShowGuesser must be used with a resource'); + } return ( { ); }; -/* eslint-disable tree-shaking/no-side-effects-in-initialization */ -ShowGuesser.propTypes = { - children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), - resource: PropTypes.string, -}; -/* eslint-enable tree-shaking/no-side-effects-in-initialization */ - export default ShowGuesser; diff --git a/src/stories/Basic.stories.ts b/src/stories/Basic.stories.ts index 3402a0b8..8fe6c89c 100644 --- a/src/stories/Basic.stories.ts +++ b/src/stories/Basic.stories.ts @@ -18,7 +18,7 @@ type Story = StoryObj; export const Admin: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - await canvas.findByRole('heading', { name: 'Greetings' }); + await canvas.findByText('Greetings'); }, args: { entrypoint: process.env.ENTRYPOINT, diff --git a/src/stories/Custom.stories.tsx b/src/stories/Custom.stories.tsx new file mode 100644 index 00000000..a68b6281 --- /dev/null +++ b/src/stories/Custom.stories.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { HydraAdmin } from '../hydra'; +import ResourceGuesser from '../core/ResourceGuesser'; +import ListGuesser from '../list/ListGuesser'; +import ShowGuesser from '../show/ShowGuesser'; +import FieldGuesser from '../field/FieldGuesser'; +import EditGuesser from '../edit/EditGuesser'; +import InputGuesser from '../input/InputGuesser'; +import CreateGuesser from '../create/CreateGuesser'; + +export default { + title: 'Admin/Custom', + parameters: { + layout: 'fullscreen', + }, +}; + +const GreetingList = () => ( + + + +); + +const GreetingShow = () => ( + + + +); + +const GreetingEdit = () => ( + + + +); + +const GreetingCreate = () => ( + + + +); + +export const Custom = () => ( + + + +); diff --git a/src/types.ts b/src/types.ts index c8143b11..650c55bb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -460,13 +460,13 @@ export type FieldProps = | ArrayFieldProps | (ReferenceArrayFieldProps & Pick) | EnumFieldProps - | ReferenceFieldProps; + | Omit; export type EnumFieldProps = TextFieldProps & { transformEnum?: (value: string | number) => string | number; }; -export type IntrospectedFieldGuesserProps = FieldProps & +export type IntrospectedFieldGuesserProps = Partial & IntrospectedGuesserProps; export type FieldGuesserProps = Omit< diff --git a/src/useOnSubmit.test.tsx b/src/useOnSubmit.test.tsx index 34ac313e..3820cafd 100644 --- a/src/useOnSubmit.test.tsx +++ b/src/useOnSubmit.test.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import { jest } from '@jest/globals'; -import { QueryClient, QueryClientProvider } from 'react-query'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, waitFor } from '@testing-library/react'; import type { CreateResult, RaRecord, UpdateResult } from 'react-admin'; import { DataProviderContext, testDataProvider } from 'react-admin'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; + import useOnSubmit from './useOnSubmit.js'; import schemaAnalyzer from './hydra/schemaAnalyzer.js'; import { API_FIELDS_DATA } from './__fixtures__/parsedData.js'; diff --git a/src/useOnSubmit.ts b/src/useOnSubmit.ts index a4dacc9e..dbc2a8ef 100644 --- a/src/useOnSubmit.ts +++ b/src/useOnSubmit.ts @@ -3,6 +3,7 @@ import { useCreate, useNotify, useRedirect, useUpdate } from 'react-admin'; import type { HttpError, RaRecord } from 'react-admin'; import { useParams } from 'react-router-dom'; import lodashIsPlainObject from 'lodash.isplainobject'; + import getIdentifierValue from './getIdentifierValue.js'; import type { SubmissionErrors, UseOnSubmitProps } from './types.js'; @@ -108,7 +109,7 @@ const useOnSubmit = ({ }); }); failure( - mutateError as string | Error, + mutateError as Error, { data: values, ...(isCreate ? {} : { id, previousData: values }), diff --git a/yarn.lock b/yarn.lock index 4fe55993..45226366 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1063,7 +1063,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.8", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== @@ -1762,14 +1762,14 @@ resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.21.tgz#15ffc56cef7009479229b55126176f988afba96b" integrity sha512-dp9lXBaJZzJYeJfQY3Ow4Rb49QaCEdkl2KKYscdQHQm6bMJ+l4XPY3Cd9PCeeJTsHPIDJ60lzXbeRgs6sx/rpw== -"@mui/icons-material@^5.0.1": +"@mui/icons-material@^5.15.20": version "5.15.21" resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.21.tgz#1e29e1bdb90916be5b66c95c45951f441821f34a" integrity sha512-yqkq1MbdkmX5ZHyvZTBuAaA6RkvoqkoAgwBSx9Oh0L0jAfj9T/Ih/NhMNjkl8PWVSonjfDUkKroBnjRyo/1M9Q== dependencies: "@babel/runtime" "^7.23.9" -"@mui/material@^5.0.2": +"@mui/material@^5.15.20": version "5.15.21" resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.21.tgz#b2c8d756af570a61cb4975acf0e71dafb110b001" integrity sha512-nTyCcgduKwHqiuQ/B03EQUa+utSMzn2sQp0QAibsnYe4tvc3zkMbO0amKpl48vhABIY3IvT6w9615BFIgMt0YA== @@ -2883,6 +2883,18 @@ dependencies: "@swc/counter" "^0.1.3" +"@tanstack/query-core@5.49.1": + version "5.49.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.49.1.tgz#09167842123eddaf47465376f3c835cf59b9f1e9" + integrity sha512-JnC9ndmD1KKS01Rt/ovRUB1tmwO7zkyXAyIxN9mznuJrcNtOrkmOnQqdJF2ib9oHzc2VxHomnEG7xyfo54Npkw== + +"@tanstack/react-query@^5.8.4": + version "5.49.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.49.2.tgz#d9c08f8eb62890f5274608f8954ab1709912ef3c" + integrity sha512-6rfwXDK9BvmHISbNFuGd+wY3P44lyW7lWiA9vIFGT/T0P9aHD1VkjTvcM4SDAIbAQ9ygEZZoLt7dlU1o3NjMVA== + dependencies: + "@tanstack/query-core" "5.49.1" + "@testing-library/dom@10.1.0": version "10.1.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.1.0.tgz#2d073e49771ad614da999ca48f199919e5176fb6" @@ -4293,7 +4305,7 @@ better-opn@^3.0.2: dependencies: open "^8.0.4" -big-integer@^1.6.16, big-integer@^1.6.44: +big-integer@^1.6.44: version "1.6.52" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== @@ -4378,20 +4390,6 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -broadcast-channel@^3.4.1: - version "3.7.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" - integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== - dependencies: - "@babel/runtime" "^7.7.2" - detect-node "^2.1.0" - js-sha3 "0.8.0" - microseconds "0.2.0" - nano-time "1.0.0" - oblivious-set "1.0.0" - rimraf "3.0.2" - unload "2.2.0" - browser-assert@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/browser-assert/-/browser-assert-1.2.1.tgz#9aaa5a2a8c74685c2ae05bfe46efd606f068c200" @@ -4738,12 +4736,7 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -clsx@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== - -clsx@^2.1.0: +clsx@^2.1.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -5103,12 +5096,10 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" -date-fns@^2.19.0: - version "2.30.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" - integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== - dependencies: - "@babel/runtime" "^7.21.0" +date-fns@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf" + integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== debug@2.6.9: version "2.6.9" @@ -5294,11 +5285,6 @@ detect-node-es@^1.1.0: resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== -detect-node@^2.0.4, detect-node@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - detect-package-manager@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/detect-package-manager/-/detect-package-manager-2.0.1.tgz#6b182e3ae5e1826752bfef1de9a7b828cffa50d8" @@ -6084,10 +6070,10 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== events@^3.2.0: version "3.3.0" @@ -6271,12 +6257,12 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-selector@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.5.0.tgz#21c7126dc9728b31a2742d91cab20d55e67e4fb4" - integrity sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA== +file-selector@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc" + integrity sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw== dependencies: - tslib "^2.0.3" + tslib "^2.4.0" file-system-cache@2.3.0: version "2.3.0" @@ -6899,13 +6885,6 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -history@^5.0.0, history@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" - integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== - dependencies: - "@babel/runtime" "^7.7.6" - hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -7096,10 +7075,10 @@ inflection@^1.13.0: resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.13.4.tgz#65aa696c4e2da6225b148d7a154c449366633a32" integrity sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw== -inflection@~1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" - integrity sha512-lRy4DxuIFWXlJU7ed8UiTJOSTqStqYdEb4CEbtXfNbkdj3nH1L+reUWiE10VWcJS2yR7tge8Z74pJjtBjNwj0w== +inflection@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-3.0.0.tgz#6a956fa90d72a27d22e6b32ec1064877593ee23b" + integrity sha512-1zEJU1l19SgJlmwqsEyFTbScw/tkMHFenUo//Y0i+XEP83gDFdMvPizAD/WGcE+l1ku12PcTVHQhO6g5E0UCMw== inflight@^1.0.4: version "1.0.6" @@ -8050,11 +8029,6 @@ joi@^17.11.0: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" -js-sha3@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -8476,14 +8450,6 @@ markdown-to-jsx@7.3.2: resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.3.2.tgz#f286b4d112dad3028acc1e77dfe1f653b347e131" integrity sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q== -match-sorter@^6.0.2: - version "6.3.4" - resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.4.tgz#afa779d8e922c81971fbcb4781c7003ace781be7" - integrity sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg== - dependencies: - "@babel/runtime" "^7.23.8" - remove-accents "0.5.0" - mdast-util-find-and-replace@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz#a6fc7b62f0994e973490e45262e4bc07607b04e0" @@ -8950,11 +8916,6 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -microseconds@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" - integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== - mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -9085,13 +9046,6 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nano-time@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" - integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA== - dependencies: - big-integer "^1.6.16" - nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -9356,11 +9310,6 @@ objectorarray@^1.0.5: resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5" integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg== -oblivious-set@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" - integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== - ohash@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.3.tgz#f12c3c50bfe7271ce3fd1097d42568122ccdcf07" @@ -9891,7 +9840,7 @@ prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -9972,7 +9921,7 @@ qs@^6.10.0, qs@^6.11.2: dependencies: side-channel "^1.0.6" -query-string@^7.1.1: +query-string@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== @@ -9992,56 +9941,56 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -ra-core@^4.16.19: - version "4.16.19" - resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-4.16.19.tgz#d402cde87368ddfbd30a540b7e9f8e1c6ff0fec6" - integrity sha512-1qNuLGaGSHYv5esqJcu7n/PeMKTBXA3JXzjQPAPDK9dBsuWC1IXx9MilivANwh8doYbpFfqsX8VJyW1aqIG1Pg== +ra-core@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-5.0.3.tgz#89bc916065d38c70694ab7c182c3c5566259a71b" + integrity sha512-aTDY4CtX/TLPslGqCABPMFQR2KeiTlF1M8xJ/9SJpe7Cpd244AY9Gbtgx0YjEsW+HeI4/OSQXMEj+Sz0l0Xtiw== dependencies: - clsx "^1.1.1" - date-fns "^2.19.0" - eventemitter3 "^4.0.7" - inflection "~1.12.0" + "@tanstack/react-query" "^5.8.4" + clsx "^2.1.1" + date-fns "^3.6.0" + eventemitter3 "^5.0.1" + hotscript "^1.0.12" + inflection "^3.0.0" jsonexport "^3.2.0" lodash "~4.17.5" - prop-types "^15.6.1" - query-string "^7.1.1" - react-is "^17.0.2" - react-query "^3.32.1" + query-string "^7.1.3" + react-error-boundary "^4.0.13" + react-is "^18.2.0" -ra-i18n-polyglot@^4.16.19: - version "4.16.19" - resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-4.16.19.tgz#884100ea7f20ddf044e72c5d78c1c5c19fc0dd12" - integrity sha512-gRnY0fUn61cVtR36Qf5mgULXnQALRrjZ8Hnp7w+U9YvtrzapNTuIyZem3Tp0QL+/jRgxeqlQW7dqPYdV0bC7vg== +ra-i18n-polyglot@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-5.0.3.tgz#35efef0c25e5cbdedb246535ee6b1dec3ea75ae0" + integrity sha512-zzC1+hjpwj/UQjF2poIEFgHSNsGF8cjWRCAgtSe7iNw+T9ztc5uqOHmDKzRzJaUv4QCLypN+QjGG85GRe9M8QQ== dependencies: node-polyglot "^2.2.2" - ra-core "^4.16.19" + ra-core "^5.0.3" -ra-language-english@^4.16.19: - version "4.16.19" - resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-4.16.19.tgz#5e99bbfbab0b99a1ed31d9df9e01e51e08c78a85" - integrity sha512-k8ykj7bJ5UEAebB+SsOAcpyb6gUlsNkJ8N0+lDAezpilBaHahDQaYtNKA+GkoZnU8rIKYMYLttcl3VdCllkG2g== +ra-language-english@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-5.0.3.tgz#ea38e5171023fecfe8b8ef3a3a7ef7a0e50eb9a3" + integrity sha512-0qm+arNNk7aynYzKlH5s20VVPAYWz2jDfhIeRyBcDVRifC96XbkvTTiFtr9k96LtRyJUGocBROxuuqKsEzw3YA== dependencies: - ra-core "^4.16.19" + ra-core "^5.0.3" -ra-ui-materialui@^4.16.19: - version "4.16.19" - resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-4.16.19.tgz#67a7a03a8786e8db3ad6aeebb34535984df6e9c8" - integrity sha512-EyalFIUq9oUQ8N/vAeQFlrBaRTU/pMPAHvmfPHUxL5ajcQaSii6XKhRgT9S+YOec/XAU0DDaTjH9oJm0H3UgNA== +ra-ui-materialui@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-5.0.3.tgz#42561b69cdbb0cfe69f10a8a1cb0921434c0afe2" + integrity sha512-Y3COTPdztPSZekN7Vc3tP3nbj+4Q1wgCc4w4GEv6ET1Sme0JmzOA+ybgwbM8dKryqlMtnjuMGwIMz1gzPSewbQ== dependencies: + "@tanstack/react-query" "^5.8.4" autosuggest-highlight "^3.1.1" - clsx "^1.1.1" + clsx "^2.1.1" css-mediaquery "^0.1.2" dompurify "^2.4.3" hotscript "^1.0.12" - inflection "~1.12.0" + inflection "^3.0.0" jsonexport "^3.2.0" lodash "~4.17.5" - prop-types "^15.7.0" - query-string "^7.1.1" - react-dropzone "^12.0.4" - react-error-boundary "^3.1.4" - react-query "^3.32.1" - react-transition-group "^4.4.1" + query-string "^7.1.3" + react-dropzone "^14.2.3" + react-error-boundary "^4.0.13" + react-transition-group "^4.4.5" ramda@0.29.0: version "0.29.0" @@ -10092,23 +10041,22 @@ rdf-canonize@^3.4.0: dependencies: setimmediate "^1.0.5" -react-admin@^4.4.0: - version "4.16.19" - resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-4.16.19.tgz#c8f047bbc954da218fb0845d7ef139fb93ac56ef" - integrity sha512-aFUPEbHFaDbfT3158moam2bchiQHE5C1f4yljSV7/0FukFkKa0ihS2hosPchqzilPEOS41ve0krYNEnHCPlHxw== +react-admin@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-5.0.3.tgz#2334b46c7b5092e5331fc3559f5315c9a5037249" + integrity sha512-p8uuCgSCXGzad3EojVGPztIVXcCD7/GHnztywnKZwnfK3YSo9jJL8VYUpY/QnTJ6Gedz8eWRM9vFjBuyTKuwnw== dependencies: "@emotion/react" "^11.4.1" "@emotion/styled" "^11.3.0" - "@mui/icons-material" "^5.0.1" - "@mui/material" "^5.0.2" - history "^5.1.0" - ra-core "^4.16.19" - ra-i18n-polyglot "^4.16.19" - ra-language-english "^4.16.19" - ra-ui-materialui "^4.16.19" - react-hook-form "^7.43.9" - react-router "^6.1.0" - react-router-dom "^6.1.0" + "@mui/icons-material" "^5.15.20" + "@mui/material" "^5.15.20" + ra-core "^5.0.3" + ra-i18n-polyglot "^5.0.3" + ra-language-english "^5.0.3" + ra-ui-materialui "^5.0.3" + react-hook-form "^7.52.0" + react-router "^6.22.0" + react-router-dom "^6.22.0" react-colorful@^5.1.2: version "5.6.1" @@ -10151,13 +10099,13 @@ react-docgen@^7.0.0: loose-envify "^1.1.0" scheduler "^0.23.2" -react-dropzone@^12.0.4: - version "12.1.0" - resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-12.1.0.tgz#e097b37e9da6f9e324efc757b7434ebc6f3dc2cb" - integrity sha512-iBYHA1rbopIvtzokEX4QubO6qk5IF/x3BtKGu74rF2JkQDXnwC4uO/lHKpaw4PJIV6iIAYOlwLv2FpiGyqHNog== +react-dropzone@^14.2.3: + version "14.2.3" + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.3.tgz#0acab68308fda2d54d1273a1e626264e13d4e84b" + integrity sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug== dependencies: attr-accept "^2.2.2" - file-selector "^0.5.0" + file-selector "^0.6.0" prop-types "^15.8.1" react-element-to-jsx-string@^15.0.0: @@ -10169,13 +10117,6 @@ react-element-to-jsx-string@^15.0.0: is-plain-object "5.0.0" react-is "18.1.0" -react-error-boundary@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" - integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== - dependencies: - "@babel/runtime" "^7.12.5" - react-error-boundary@^4.0.13: version "4.0.13" resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.13.tgz#80386b7b27b1131c5fbb7368b8c0d983354c7947" @@ -10183,10 +10124,10 @@ react-error-boundary@^4.0.13: dependencies: "@babel/runtime" "^7.12.5" -react-hook-form@^7.43.9: - version "7.52.0" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.0.tgz#e52b33043e283719586b9dd80f6d51b68dd3999c" - integrity sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A== +react-hook-form@^7.52.0: + version "7.52.1" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.1.tgz#ec2c96437b977f8b89ae2d541a70736c66284852" + integrity sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg== react-is@18.1.0: version "18.1.0" @@ -10203,20 +10144,11 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1, react-is@^17.0.2: +react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-query@^3.32.1: - version "3.39.3" - resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35" - integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g== - dependencies: - "@babel/runtime" "^7.5.5" - broadcast-channel "^3.4.1" - match-sorter "^6.0.2" - react-remove-scroll-bar@^2.3.4: version "2.3.6" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" @@ -10236,7 +10168,7 @@ react-remove-scroll@2.5.7: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" -react-router-dom@^6.1.0: +react-router-dom@^6.22.0: version "6.24.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.24.0.tgz#ec49dc38c49bb9bd25b310a8ae849268d3085e1d" integrity sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g== @@ -10244,7 +10176,7 @@ react-router-dom@^6.1.0: "@remix-run/router" "1.17.0" react-router "6.24.0" -react-router@6.24.0, react-router@^6.1.0: +react-router@6.24.0, react-router@^6.22.0: version "6.24.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.24.0.tgz#aa46648f26b6525e07f908ad3e1ad2e68d131155" integrity sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg== @@ -10277,7 +10209,7 @@ react-test-renderer@^18.0.0: react-shallow-renderer "^16.15.0" scheduler "^0.23.2" -react-transition-group@^4.4.1, react-transition-group@^4.4.5: +react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== @@ -10508,11 +10440,6 @@ remark-stringify@^11.0.0: mdast-util-to-markdown "^2.0.0" unified "^11.0.0" -remove-accents@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" - integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== - remove-accents@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.4.tgz#73704abf7dae3764295d475d2b6afac4ea23e4d9" @@ -10615,7 +10542,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -11747,14 +11674,6 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unload@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" - integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== - dependencies: - "@babel/runtime" "^7.6.2" - detect-node "^2.0.4" - unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"