From d4dddebe848c3b85f74d07020feb52fce58a53e8 Mon Sep 17 00:00:00 2001 From: Josh <37798644+joshuaellis@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:22:34 +0100 Subject: [PATCH] refactor(admin): convert rest of components to TS (#18473) * refactor(admin): convert rest of components to TS * chore(admin): integrate redux types to admin components * chore: type network requests * test: fix unit tests based on changes * chore: use smaller batch of permissions for RBACProvider test --- packages/core/admin/admin/src/StrapiApp.js | 13 +- .../admin/src/components/AuthenticatedApp.tsx | 184 ++++++++++++++++++ .../src/components/AuthenticatedApp/index.js | 116 ----------- .../components/AuthenticatedApp/utils/api.js | 47 ----- .../utils/checkLatestStrapiVersion.ts | 13 -- .../utils/fetchStrapiLatestRelease.ts | 19 -- .../tests/checkLatestStrapiVersion.test.ts | 20 -- .../src/components/ConfigurationProvider.tsx | 3 +- .../admin/src/components/LanguageProvider.tsx | 1 + .../admin/admin/src/components/Providers.tsx | 125 ++++++++++++ .../admin/src/components/Providers/index.js | 156 --------------- .../admin/src/components/RBACProvider.tsx | 124 ++++++++++++ .../src/components/RBACProvider/actions.js | 10 - .../src/components/RBACProvider/constants.js | 2 - .../src/components/RBACProvider/index.js | 39 ---- .../src/components/RBACProvider/reducer.js | 51 ----- .../src/components/ThemeToggleProvider.tsx | 1 + .../AuthenticatedApp.test.tsx} | 12 +- .../RBACProvider.test.tsx} | 48 +++-- .../content-manager/pages/ListView/index.js | 2 +- .../core/admin/admin/src/contexts/admin.ts | 1 + .../src/core/{store.ts => store/configure.ts} | 63 ++---- .../core/admin/admin/src/core/store/hooks.ts | 15 ++ .../tests/useInjectReducer.test.js | 2 +- .../core/admin/admin/src/pages/App/index.js | 4 +- .../src/components/CheckPagePermissions.tsx | 6 +- .../src/components/CheckPermissions.tsx | 6 +- .../helper-plugin/src/features/AppInfo.tsx | 2 + .../src/features/CustomFields.tsx | 7 + .../helper-plugin/src/features/Library.tsx | 3 +- .../core/helper-plugin/src/features/RBAC.tsx | 18 +- .../helper-plugin/src/features/StrapiApp.tsx | 17 +- .../core/helper-plugin/src/hooks/useRBAC.ts | 8 +- packages/core/helper-plugin/src/utils/auth.ts | 22 +-- .../helper-plugin/src/utils/hasPermissions.ts | 6 +- .../src/utils/tests/hasPermissions.test.ts | 4 +- packages/utils/tsconfig/client.json | 1 + 37 files changed, 576 insertions(+), 595 deletions(-) create mode 100644 packages/core/admin/admin/src/components/AuthenticatedApp.tsx delete mode 100644 packages/core/admin/admin/src/components/AuthenticatedApp/index.js delete mode 100644 packages/core/admin/admin/src/components/AuthenticatedApp/utils/api.js delete mode 100644 packages/core/admin/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.ts delete mode 100644 packages/core/admin/admin/src/components/AuthenticatedApp/utils/fetchStrapiLatestRelease.ts delete mode 100644 packages/core/admin/admin/src/components/AuthenticatedApp/utils/tests/checkLatestStrapiVersion.test.ts create mode 100644 packages/core/admin/admin/src/components/Providers.tsx delete mode 100644 packages/core/admin/admin/src/components/Providers/index.js create mode 100644 packages/core/admin/admin/src/components/RBACProvider.tsx delete mode 100644 packages/core/admin/admin/src/components/RBACProvider/actions.js delete mode 100644 packages/core/admin/admin/src/components/RBACProvider/constants.js delete mode 100644 packages/core/admin/admin/src/components/RBACProvider/index.js delete mode 100644 packages/core/admin/admin/src/components/RBACProvider/reducer.js rename packages/core/admin/admin/src/components/{AuthenticatedApp/tests/index.test.js => tests/AuthenticatedApp.test.tsx} (68%) rename packages/core/admin/admin/src/components/{RBACProvider/tests/reducer.test.js => tests/RBACProvider.test.tsx} (65%) rename packages/core/admin/admin/src/core/{store.ts => store/configure.ts} (55%) create mode 100644 packages/core/admin/admin/src/core/store/hooks.ts diff --git a/packages/core/admin/admin/src/StrapiApp.js b/packages/core/admin/admin/src/StrapiApp.js index 6f63469018..9778c12bdf 100644 --- a/packages/core/admin/admin/src/StrapiApp.js +++ b/packages/core/admin/admin/src/StrapiApp.js @@ -10,23 +10,20 @@ import { BrowserRouter } from 'react-router-dom'; import Logo from './assets/images/logo-strapi-2022.svg'; import { LANGUAGE_LOCAL_STORAGE_KEY } from './components/LanguageProvider'; -import Providers from './components/Providers'; -import { - HOOKS, - INJECTION_ZONES -} from './constants'; +import { Providers } from './components/Providers'; +import { HOOKS, INJECTION_ZONES } from './constants'; import { customFields, Plugin, Reducers } from './core/apis'; -import { configureStore } from './core/store'; +import { configureStore } from './core/store/configure'; import { basename, createHook } from './core/utils'; import favicon from './favicon.png'; import App from './pages/App'; import languageNativeNames from './translations/languageNativeNames'; const { - INJECT_COLUMN_IN_TABLE, + INJECT_COLUMN_IN_TABLE, MUTATE_COLLECTION_TYPES_LINKS, MUTATE_EDIT_VIEW_LAYOUT, - MUTATE_SINGLE_TYPES_LINKS + MUTATE_SINGLE_TYPES_LINKS, } = HOOKS; class StrapiApp { diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp.tsx b/packages/core/admin/admin/src/components/AuthenticatedApp.tsx new file mode 100644 index 0000000000..dde6f92e2a --- /dev/null +++ b/packages/core/admin/admin/src/components/AuthenticatedApp.tsx @@ -0,0 +1,184 @@ +import * as React from 'react'; + +import { + AppInfoContextValue, + AppInfoProvider, + auth, + LoadingIndicatorPage, + useFetchClient, + useGuidedTour, +} from '@strapi/helper-plugin'; +import lodashGet from 'lodash/get'; +import { useQueries } from 'react-query'; +import lt from 'semver/functions/lt'; +import valid from 'semver/functions/valid'; +// TODO: DS add loader + +import packageJSON from '../../../package.json'; +import { UserEntity } from '../../../shared/entities'; +import { useConfiguration } from '../hooks/useConfiguration'; +import { APIResponse, APIResponseUsersLegacy } from '../types/adminAPI'; +// @ts-expect-error - no types yet. +import { getFullName, hashAdminUserEmail } from '../utils'; + +import { NpsSurvey } from './NpsSurvey'; +import { PluginsInitializer } from './PluginsInitializer'; +import { RBACProvider, Permission } from './RBACProvider'; + +const strapiVersion = packageJSON.version; + +const AuthenticatedApp = () => { + const { setGuidedTourVisibility } = useGuidedTour(); + const userInfo = auth.get('userInfo'); + const userName = userInfo + ? lodashGet(userInfo, 'username') || getFullName(userInfo.firstname, userInfo.lastname) + : null; + const [userDisplayName, setUserDisplayName] = React.useState(userName); + const [userId, setUserId] = React.useState(); + const { showReleaseNotification } = useConfiguration(); + const { get } = useFetchClient(); + const [ + { data: appInfos, status }, + { data: tagName, isLoading }, + { data: permissions, status: fetchPermissionsStatus, refetch, isFetching }, + { data: userRoles }, + ] = useQueries([ + { + queryKey: 'app-infos', + async queryFn() { + const { data } = await get< + APIResponse< + Pick< + AppInfoContextValue, + | 'currentEnvironment' + | 'autoReload' + | 'communityEdition' + | 'dependencies' + | 'useYarn' + | 'projectId' + | 'strapiVersion' + | 'nodeVersion' + > + > + >('/admin/information'); + + return data.data; + }, + }, + { + queryKey: 'strapi-release', + async queryFn() { + try { + const res = await fetch('https://api.github.com/repos/strapi/strapi/releases/latest'); + + if (!res.ok) { + throw new Error(); + } + + const response = (await res.json()) as { tag_name: string | null | undefined }; + + if (!response.tag_name) { + throw new Error(); + } + + return response.tag_name; + } catch (err) { + // Don't throw an error + return strapiVersion; + } + }, + enabled: showReleaseNotification, + initialData: strapiVersion, + }, + { + queryKey: 'admin-users-permission', + async queryFn() { + const { data } = await get<{ data: Permission[] }>('/admin/users/me/permissions'); + + return data.data; + }, + initialData: [], + }, + { + queryKey: 'user-roles', + async queryFn() { + const { + data: { + data: { roles }, + }, + } = await get>('/admin/users/me'); + + return roles; + }, + }, + ]); + + const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tagName); + + /** + * TODO: does this actually need to be an effect? + */ + React.useEffect(() => { + if (userRoles) { + const isUserSuperAdmin = userRoles.find(({ code }) => code === 'strapi-super-admin'); + + if (isUserSuperAdmin && appInfos?.autoReload) { + setGuidedTourVisibility(true); + } + } + }, [userRoles, appInfos, setGuidedTourVisibility]); + + React.useEffect(() => { + const getUserId = async () => { + const userId = await hashAdminUserEmail(userInfo); + setUserId(userId); + }; + + getUserId(); + }, [userInfo]); + + // We don't need to wait for the release query to be fetched before rendering the plugins + // however, we need the appInfos and the permissions + const shouldShowNotDependentQueriesLoader = + isFetching || status === 'loading' || fetchPermissionsStatus === 'loading'; + + const shouldShowLoader = isLoading || shouldShowNotDependentQueriesLoader; + + if (shouldShowLoader) { + return ; + } + + // TODO: add error state + if (status === 'error') { + return
error...
; + } + + return ( + + + + + + + ); +}; + +const checkLatestStrapiVersion = ( + currentPackageVersion: string, + latestPublishedVersion: string = '' +): boolean => { + if (!valid(currentPackageVersion) || !valid(latestPublishedVersion)) { + return false; + } + + return lt(currentPackageVersion, latestPublishedVersion); +}; + +export { AuthenticatedApp }; diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/index.js b/packages/core/admin/admin/src/components/AuthenticatedApp/index.js deleted file mode 100644 index bf9d59aac3..0000000000 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/index.js +++ /dev/null @@ -1,116 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import { - AppInfoProvider, - auth, - LoadingIndicatorPage, - useGuidedTour, - useNotification, -} from '@strapi/helper-plugin'; -import get from 'lodash/get'; -import { useQueries } from 'react-query'; -// TODO: DS add loader - -import packageJSON from '../../../../package.json'; -import { useConfiguration } from '../../hooks/useConfiguration'; -import { getFullName, hashAdminUserEmail } from '../../utils'; -import { NpsSurvey } from '../NpsSurvey'; -import { PluginsInitializer } from '../PluginsInitializer'; -import RBACProvider from '../RBACProvider'; - -import { fetchAppInfo, fetchCurrentUserPermissions, fetchUserRoles } from './utils/api'; -import { checkLatestStrapiVersion } from './utils/checkLatestStrapiVersion'; -import { fetchStrapiLatestRelease } from './utils/fetchStrapiLatestRelease'; - -const strapiVersion = packageJSON.version; - -const AuthenticatedApp = () => { - const { setGuidedTourVisibility } = useGuidedTour(); - const toggleNotification = useNotification(); - const userInfo = auth.getUserInfo(); - const userName = get(userInfo, 'username') || getFullName(userInfo.firstname, userInfo.lastname); - const [userDisplayName, setUserDisplayName] = useState(userName); - const [userId, setUserId] = useState(null); - const { showReleaseNotification } = useConfiguration(); - const [ - { data: appInfos, status }, - { data: tagName, isLoading }, - { data: permissions, status: fetchPermissionsStatus, refetch, isFetching }, - { data: userRoles }, - ] = useQueries([ - { queryKey: 'app-infos', queryFn: fetchAppInfo }, - { - queryKey: 'strapi-release', - queryFn: () => fetchStrapiLatestRelease(toggleNotification), - enabled: showReleaseNotification, - initialData: strapiVersion, - }, - { - queryKey: 'admin-users-permission', - queryFn: fetchCurrentUserPermissions, - initialData: [], - }, - { - queryKey: 'user-roles', - queryFn: fetchUserRoles, - }, - ]); - - const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tagName); - - /** - * TODO: does this actually need to be an effect? - */ - useEffect(() => { - if (userRoles) { - const isUserSuperAdmin = userRoles.find(({ code }) => code === 'strapi-super-admin'); - - if (isUserSuperAdmin && appInfos?.autoReload) { - setGuidedTourVisibility(true); - } - } - }, [userRoles, appInfos, setGuidedTourVisibility]); - - useEffect(() => { - const getUserId = async () => { - const userId = await hashAdminUserEmail(userInfo); - setUserId(userId); - }; - - getUserId(); - }, [userInfo]); - - // We don't need to wait for the release query to be fetched before rendering the plugins - // however, we need the appInfos and the permissions - const shouldShowNotDependentQueriesLoader = - isFetching || status === 'loading' || fetchPermissionsStatus === 'loading'; - - const shouldShowLoader = isLoading || shouldShowNotDependentQueriesLoader; - - if (shouldShowLoader) { - return ; - } - - // TODO: add error state - if (status === 'error') { - return
error...
; - } - - return ( - - - - - - - ); -}; - -export default AuthenticatedApp; diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/api.js b/packages/core/admin/admin/src/components/AuthenticatedApp/utils/api.js deleted file mode 100644 index ad5a2883c2..0000000000 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/api.js +++ /dev/null @@ -1,47 +0,0 @@ -import { getFetchClient } from '@strapi/helper-plugin'; - -const { get } = getFetchClient(); - -const fetchAppInfo = async () => { - try { - const { data, headers } = await get('/admin/information'); - - if (!headers['content-type'].includes('application/json')) { - throw new Error('Not found'); - } - - return data.data; - } catch (error) { - throw new Error(error); - } -}; - -const fetchCurrentUserPermissions = async () => { - try { - const { data, headers } = await get('/admin/users/me/permissions'); - - if (!headers['content-type'].includes('application/json')) { - throw new Error('Not found'); - } - - return data.data; - } catch (err) { - throw new Error(err); - } -}; - -const fetchUserRoles = async () => { - try { - const { - data: { - data: { roles }, - }, - } = await get('/admin/users/me'); - - return roles; - } catch (err) { - throw new Error(err); - } -}; - -export { fetchAppInfo, fetchCurrentUserPermissions, fetchUserRoles }; diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.ts b/packages/core/admin/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.ts deleted file mode 100644 index e8f13169c3..0000000000 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.ts +++ /dev/null @@ -1,13 +0,0 @@ -import lt from 'semver/functions/lt'; -import valid from 'semver/functions/valid'; - -export const checkLatestStrapiVersion = ( - currentPackageVersion: string, - latestPublishedVersion: string -): boolean => { - if (!valid(currentPackageVersion) || !valid(latestPublishedVersion)) { - return false; - } - - return lt(currentPackageVersion, latestPublishedVersion); -}; diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/fetchStrapiLatestRelease.ts b/packages/core/admin/admin/src/components/AuthenticatedApp/utils/fetchStrapiLatestRelease.ts deleted file mode 100644 index 3fe8edfa68..0000000000 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/fetchStrapiLatestRelease.ts +++ /dev/null @@ -1,19 +0,0 @@ -import packageJSON from '../../../../../package.json'; - -const strapiVersion = packageJSON.version; - -export const fetchStrapiLatestRelease = async () => { - try { - const res = await fetch('https://api.github.com/repos/strapi/strapi/releases/latest'); - - if (!res.ok) { - throw new Error('Failed to fetch latest Strapi version.'); - } - const { tag_name } = await res.json(); - - return tag_name; - } catch (err) { - // Don't throw an error - return strapiVersion; - } -}; diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/tests/checkLatestStrapiVersion.test.ts b/packages/core/admin/admin/src/components/AuthenticatedApp/utils/tests/checkLatestStrapiVersion.test.ts deleted file mode 100644 index facdc53c8a..0000000000 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/tests/checkLatestStrapiVersion.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { checkLatestStrapiVersion } from '../checkLatestStrapiVersion'; - -describe('ADMIN | utils | checkLatestStrapiVersion', () => { - it('should return true if the current version is lower than the latest published version', () => { - expect(checkLatestStrapiVersion('v3.3.2', 'v3.3.4')).toBeTruthy(); - expect(checkLatestStrapiVersion('3.3.2', 'v3.3.4')).toBeTruthy(); - expect(checkLatestStrapiVersion('v3.3.2', '3.3.4')).toBeTruthy(); - expect(checkLatestStrapiVersion('3.3.2', '3.3.4')).toBeTruthy(); - }); - it('should return false if the current version is equal to the latest published version', () => { - expect(checkLatestStrapiVersion('3.3.4', 'v3.3.4')).toBeFalsy(); - expect(checkLatestStrapiVersion('v3.3.4', '3.3.4')).toBeFalsy(); - expect(checkLatestStrapiVersion('3.3.4', '3.3.4')).toBeFalsy(); - }); - it('should return false if the current version is a beta of the next release', () => { - expect(checkLatestStrapiVersion('3.4.0-beta.1', 'v3.3.4')).toBeFalsy(); - expect(checkLatestStrapiVersion('v3.4.0-beta.1', '3.3.4')).toBeFalsy(); - expect(checkLatestStrapiVersion('3.4.0-beta.1', '3.3.4')).toBeFalsy(); - }); -}); diff --git a/packages/core/admin/admin/src/components/ConfigurationProvider.tsx b/packages/core/admin/admin/src/components/ConfigurationProvider.tsx index 3d1cd15eeb..d0737b81a3 100644 --- a/packages/core/admin/admin/src/components/ConfigurationProvider.tsx +++ b/packages/core/admin/admin/src/components/ConfigurationProvider.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { ConfigurationContext, ConfigurationContextValue } from '../contexts/configuration'; -export interface ConfigurationProviderProps { +interface ConfigurationProviderProps { children: React.ReactNode; authLogo: string; menuLogo: string; @@ -65,3 +65,4 @@ const ConfigurationProvider = ({ }; export { ConfigurationProvider }; +export type { ConfigurationProviderProps }; diff --git a/packages/core/admin/admin/src/components/LanguageProvider.tsx b/packages/core/admin/admin/src/components/LanguageProvider.tsx index d647e9fae0..08edc9852b 100644 --- a/packages/core/admin/admin/src/components/LanguageProvider.tsx +++ b/packages/core/admin/admin/src/components/LanguageProvider.tsx @@ -127,3 +127,4 @@ const reducer = (state = initialState, action: Action) => { }; export { LanguageProvider, useLocales, LANGUAGE_LOCAL_STORAGE_KEY }; +export type { LanguageProviderProps, LocalesContextValue }; diff --git a/packages/core/admin/admin/src/components/Providers.tsx b/packages/core/admin/admin/src/components/Providers.tsx new file mode 100644 index 0000000000..ed082ba14c --- /dev/null +++ b/packages/core/admin/admin/src/components/Providers.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; + +import { + AutoReloadOverlayBlockerProvider, + CustomFieldsProvider, + CustomFieldsProviderProps, + LibraryProvider, + LibraryProviderProps, + NotificationsProvider, + OverlayBlockerProvider, + StrapiAppProvider, + StrapiAppProviderProps, +} from '@strapi/helper-plugin'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { Provider } from 'react-redux'; + +import { AdminContext, AdminContextValue } from '../contexts/admin'; + +import { ConfigurationProvider, ConfigurationProviderProps } from './ConfigurationProvider'; +import { GuidedTourProvider } from './GuidedTour/Provider'; +import { LanguageProvider, LanguageProviderProps } from './LanguageProvider'; +import { Theme } from './Theme'; +import { ThemeToggleProvider, ThemeToggleProviderProps } from './ThemeToggleProvider'; + +import type { Store } from '../core/store/configure'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + }, + }, +}); + +interface ProvidersProps + extends Pick, + Pick, + Pick< + ConfigurationProviderProps, + 'authLogo' | 'menuLogo' | 'showReleaseNotification' | 'showTutorials' + >, + Pick, + Pick, + Pick, + Pick< + StrapiAppProviderProps, + | 'getPlugin' + | 'menu' + | 'plugins' + | 'runHookParallel' + | 'runHookSeries' + | 'runHookWaterfall' + | 'settings' + > { + children: React.ReactNode; + store: Store; +} + +const Providers = ({ + authLogo, + children, + components, + customFields, + fields, + getAdminInjectedComponents, + getPlugin, + localeNames, + menu, + menuLogo, + messages, + plugins, + runHookParallel, + runHookSeries, + runHookWaterfall, + settings, + showReleaseNotification, + showTutorials, + store, + themes, +}: ProvidersProps) => { + return ( + + + + + + + + + + + + + + {children} + + + + + + + + + + + + + + ); +}; + +export { Providers }; diff --git a/packages/core/admin/admin/src/components/Providers/index.js b/packages/core/admin/admin/src/components/Providers/index.js deleted file mode 100644 index 97f89f044c..0000000000 --- a/packages/core/admin/admin/src/components/Providers/index.js +++ /dev/null @@ -1,156 +0,0 @@ -import React from 'react'; - -import { - AutoReloadOverlayBlockerProvider, - CustomFieldsProvider, - LibraryProvider, - NotificationsProvider, - OverlayBlockerProvider, - StrapiAppProvider, -} from '@strapi/helper-plugin'; -import PropTypes from 'prop-types'; -import { QueryClient, QueryClientProvider } from 'react-query'; -import { Provider } from 'react-redux'; - -import { AdminContext } from '../../contexts/admin'; -import { ConfigurationProvider } from '../ConfigurationProvider'; -import { GuidedTourProvider } from '../GuidedTour/Provider'; -import { LanguageProvider } from '../LanguageProvider'; -import { Theme } from '../Theme'; -import { ThemeToggleProvider } from '../ThemeToggleProvider'; - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - }, - }, -}); - -const Providers = ({ - authLogo, - children, - components, - customFields, - fields, - getAdminInjectedComponents, - getPlugin, - localeNames, - menu, - menuLogo, - messages, - plugins, - runHookParallel, - runHookSeries, - runHookWaterfall, - settings, - showReleaseNotification, - showTutorials, - store, - themes, -}) => { - return ( - - - - - - - - - - - - - - {children} - - - - - - - - - - - - - - ); -}; - -Providers.propTypes = { - authLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired, - children: PropTypes.node.isRequired, - components: PropTypes.object.isRequired, - customFields: PropTypes.object.isRequired, - fields: PropTypes.object.isRequired, - getAdminInjectedComponents: PropTypes.func.isRequired, - getPlugin: PropTypes.func.isRequired, - localeNames: PropTypes.objectOf(PropTypes.string).isRequired, - menu: PropTypes.arrayOf( - PropTypes.shape({ - to: PropTypes.string.isRequired, - icon: PropTypes.func.isRequired, - intlLabel: PropTypes.shape({ - id: PropTypes.string.isRequired, - defaultMessage: PropTypes.string.isRequired, - }).isRequired, - permissions: PropTypes.array, - Component: PropTypes.func, - }) - ).isRequired, - menuLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired, - messages: PropTypes.object.isRequired, - plugins: PropTypes.object.isRequired, - runHookParallel: PropTypes.func.isRequired, - runHookWaterfall: PropTypes.func.isRequired, - runHookSeries: PropTypes.func.isRequired, - settings: PropTypes.object.isRequired, - showReleaseNotification: PropTypes.bool.isRequired, - showTutorials: PropTypes.bool.isRequired, - store: PropTypes.object.isRequired, - themes: PropTypes.shape({ - light: PropTypes.shape({ - colors: PropTypes.object.isRequired, - shadows: PropTypes.object.isRequired, - sizes: PropTypes.object.isRequired, - zIndices: PropTypes.array.isRequired, - spaces: PropTypes.array.isRequired, - borderRadius: PropTypes.string.isRequired, - mediaQueries: PropTypes.object.isRequired, - fontSizes: PropTypes.array.isRequired, - lineHeights: PropTypes.array.isRequired, - fontWeights: PropTypes.object.isRequired, - }).isRequired, - dark: PropTypes.shape({ - colors: PropTypes.object.isRequired, - shadows: PropTypes.object.isRequired, - sizes: PropTypes.object.isRequired, - zIndices: PropTypes.array.isRequired, - spaces: PropTypes.array.isRequired, - borderRadius: PropTypes.string.isRequired, - mediaQueries: PropTypes.object.isRequired, - fontSizes: PropTypes.array.isRequired, - lineHeights: PropTypes.array.isRequired, - fontWeights: PropTypes.object.isRequired, - }).isRequired, - custom: PropTypes.object, - }).isRequired, -}; - -export default Providers; diff --git a/packages/core/admin/admin/src/components/RBACProvider.tsx b/packages/core/admin/admin/src/components/RBACProvider.tsx new file mode 100644 index 0000000000..659fed05a2 --- /dev/null +++ b/packages/core/admin/admin/src/components/RBACProvider.tsx @@ -0,0 +1,124 @@ +import * as React from 'react'; + +import { + LoadingIndicatorPage, + Permission, + RBACContext, + RBACContextValue, +} from '@strapi/helper-plugin'; +import produce from 'immer'; + +import { useTypedSelector, useTypedDispatch } from '../core/store/hooks'; + +/* ------------------------------------------------------------------------------------------------- + * RBACProvider + * -----------------------------------------------------------------------------------------------*/ + +interface RBACProviderProps { + children: React.ReactNode; + permissions: Permission[]; + refetchPermissions: RBACContextValue['refetchPermissions']; +} + +const RBACProvider = ({ children, permissions, refetchPermissions }: RBACProviderProps) => { + const allPermissions = useTypedSelector((state) => state.rbacProvider.allPermissions); + + const dispatch = useTypedDispatch(); + + React.useEffect(() => { + dispatch(setPermissionsAction(permissions)); + + return () => { + dispatch(resetStoreAction()); + }; + }, [permissions, dispatch]); + + if (!allPermissions) { + return ; + } + + return ( + + {children} + + ); +}; + +/* ------------------------------------------------------------------------------------------------- + * RBACReducer + * -----------------------------------------------------------------------------------------------*/ + +interface RBACState { + allPermissions: null | Permission[]; + collectionTypesRelatedPermissions: Record>; +} + +const initialState = { + allPermissions: null, + collectionTypesRelatedPermissions: {}, +}; + +const RESET_STORE = 'StrapiAdmin/RBACProvider/RESET_STORE'; +const SET_PERMISSIONS = 'StrapiAdmin/RBACProvider/SET_PERMISSIONS'; + +interface ResetStoreAction { + type: typeof RESET_STORE; +} + +const resetStoreAction = (): ResetStoreAction => ({ type: RESET_STORE }); + +interface SetPermissionsAction { + type: typeof SET_PERMISSIONS; + permissions: Permission[]; +} + +const setPermissionsAction = ( + permissions: SetPermissionsAction['permissions'] +): SetPermissionsAction => ({ + type: SET_PERMISSIONS, + permissions, +}); + +type Actions = ResetStoreAction | SetPermissionsAction; + +const RBACReducer = (state: RBACState = initialState, action: Actions) => + produce(state, (draftState) => { + switch (action.type) { + case SET_PERMISSIONS: { + draftState.allPermissions = action.permissions; + draftState.collectionTypesRelatedPermissions = action.permissions + .filter((perm) => perm.subject) + .reduce>>((acc, current) => { + const { subject, action } = current; + + if (!subject) return acc; + + if (!acc[subject]) { + acc[subject] = {}; + } + + acc[subject] = acc[subject][action] + ? { ...acc[subject], [action]: [...acc[subject][action], current] } + : { ...acc[subject], [action]: [current] }; + + return acc; + }, {}); + break; + } + case RESET_STORE: { + return initialState; + } + default: + return state; + } + }); + +export { RBACProvider, RBACReducer, resetStoreAction, setPermissionsAction }; +export type { + RBACState, + Actions, + RBACProviderProps, + ResetStoreAction, + SetPermissionsAction, + Permission, +}; diff --git a/packages/core/admin/admin/src/components/RBACProvider/actions.js b/packages/core/admin/admin/src/components/RBACProvider/actions.js deleted file mode 100644 index dfaba17b30..0000000000 --- a/packages/core/admin/admin/src/components/RBACProvider/actions.js +++ /dev/null @@ -1,10 +0,0 @@ -import { RESET_STORE, SET_PERMISSIONS } from './constants'; - -const resetStore = () => ({ type: RESET_STORE }); - -const setPermissions = (permissions) => ({ - type: SET_PERMISSIONS, - permissions, -}); - -export { resetStore, setPermissions }; diff --git a/packages/core/admin/admin/src/components/RBACProvider/constants.js b/packages/core/admin/admin/src/components/RBACProvider/constants.js deleted file mode 100644 index e7942f3c3e..0000000000 --- a/packages/core/admin/admin/src/components/RBACProvider/constants.js +++ /dev/null @@ -1,2 +0,0 @@ -export const RESET_STORE = 'StrapiAdmin/RBACProvider/RESET_STORE'; -export const SET_PERMISSIONS = 'StrapiAdmin/RBACProvider/SET_PERMISSIONS'; diff --git a/packages/core/admin/admin/src/components/RBACProvider/index.js b/packages/core/admin/admin/src/components/RBACProvider/index.js deleted file mode 100644 index 38b3fe8794..0000000000 --- a/packages/core/admin/admin/src/components/RBACProvider/index.js +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useEffect } from 'react'; - -import { LoadingIndicatorPage, RBACProviderContext } from '@strapi/helper-plugin'; -import PropTypes from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; - -import { resetStore, setPermissions } from './actions'; - -const RBACProvider = ({ children, permissions, refetchPermissions }) => { - const { allPermissions } = useSelector((state) => state.rbacProvider); - - const dispatch = useDispatch(); - - useEffect(() => { - dispatch(setPermissions(permissions)); - - return () => { - dispatch(resetStore()); - }; - }, [permissions, dispatch]); - - if (!allPermissions) { - return ; - } - - return ( - - {children} - - ); -}; - -RBACProvider.propTypes = { - children: PropTypes.node.isRequired, - permissions: PropTypes.array.isRequired, - refetchPermissions: PropTypes.func.isRequired, -}; - -export default RBACProvider; diff --git a/packages/core/admin/admin/src/components/RBACProvider/reducer.js b/packages/core/admin/admin/src/components/RBACProvider/reducer.js deleted file mode 100644 index 1730c78751..0000000000 --- a/packages/core/admin/admin/src/components/RBACProvider/reducer.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * RBACProvider reducer - * The goal of this reducer is to provide - * the plugins with an access to the user's permissions - * in our middleware system - * - */ - -import produce from 'immer'; - -import { RESET_STORE, SET_PERMISSIONS } from './constants'; - -const initialState = { - allPermissions: null, - collectionTypesRelatedPermissions: {}, -}; - -const reducer = (state = initialState, action) => - // eslint-disable-next-line consistent-return - produce(state, (draftState) => { - switch (action.type) { - case SET_PERMISSIONS: { - draftState.allPermissions = action.permissions; - draftState.collectionTypesRelatedPermissions = action.permissions - .filter((perm) => perm.subject) - .reduce((acc, current) => { - const { subject, action } = current; - - if (!acc[subject]) { - acc[subject] = {}; - } - - acc[subject] = acc[subject][action] - ? { ...acc[subject], [action]: [...acc[subject][action], current] } - : { ...acc[subject], [action]: [current] }; - - return acc; - }, {}); - break; - } - case RESET_STORE: { - return initialState; - } - default: - return state; - } - }); - -export default reducer; -export { initialState }; diff --git a/packages/core/admin/admin/src/components/ThemeToggleProvider.tsx b/packages/core/admin/admin/src/components/ThemeToggleProvider.tsx index 54d024bbec..d937259c86 100644 --- a/packages/core/admin/admin/src/components/ThemeToggleProvider.tsx +++ b/packages/core/admin/admin/src/components/ThemeToggleProvider.tsx @@ -48,3 +48,4 @@ const ThemeToggleProvider = ({ children, themes }: ThemeToggleProviderProps) => }; export { ThemeToggleProvider }; +export type { ThemeToggleProviderProps }; diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/tests/index.test.js b/packages/core/admin/admin/src/components/tests/AuthenticatedApp.test.tsx similarity index 68% rename from packages/core/admin/admin/src/components/AuthenticatedApp/tests/index.test.js rename to packages/core/admin/admin/src/components/tests/AuthenticatedApp.test.tsx index cf80c1112c..93ac1038f2 100644 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/tests/index.test.js +++ b/packages/core/admin/admin/src/components/tests/AuthenticatedApp.test.tsx @@ -1,8 +1,6 @@ -import React from 'react'; - import { render, waitFor } from '@tests/utils'; -import AuthenticatedApp from '../index'; +import { AuthenticatedApp } from '../AuthenticatedApp'; jest.mock('@strapi/helper-plugin', () => ({ ...jest.requireActual('@strapi/helper-plugin'), @@ -11,14 +9,14 @@ jest.mock('@strapi/helper-plugin', () => ({ */ usePersistentState: jest.fn().mockImplementation(() => [{ enabled: false }, jest.fn()]), auth: { - getUserInfo: () => ({ firstname: 'kai', lastname: 'doe', email: 'testemail@strapi.io' }), + get: () => ({ firstname: 'kai', lastname: 'doe', email: 'testemail@strapi.io' }), }, useGuidedTour: jest.fn(() => ({ setGuidedTourVisibility: jest.fn(), })), })); -jest.mock('../../PluginsInitializer', () => ({ +jest.mock('../PluginsInitializer', () => ({ PluginsInitializer() { return
PluginsInitializer
; }, @@ -34,10 +32,10 @@ describe('AuthenticatedApp', () => { }); it('should not crash', async () => { - const { queryByText } = render(); + const { queryByText, getByText } = render(); await waitFor(() => expect(queryByText(/Loading/)).not.toBeInTheDocument()); - expect(queryByText(/PluginsInitializer/)).toBeInTheDocument(); + expect(getByText(/PluginsInitializer/)).toBeInTheDocument(); }); }); diff --git a/packages/core/admin/admin/src/components/RBACProvider/tests/reducer.test.js b/packages/core/admin/admin/src/components/tests/RBACProvider.test.tsx similarity index 65% rename from packages/core/admin/admin/src/components/RBACProvider/tests/reducer.test.js rename to packages/core/admin/admin/src/components/tests/RBACProvider.test.tsx index bc50e42559..85a3556cb8 100644 --- a/packages/core/admin/admin/src/components/RBACProvider/tests/reducer.test.js +++ b/packages/core/admin/admin/src/components/tests/RBACProvider.test.tsx @@ -1,10 +1,15 @@ import { fixtures } from '@strapi/admin-test-utils'; -import { resetStore, setPermissions } from '../actions'; -import rbacProviderReducer, { initialState } from '../reducer'; +import { + Permission, + RBACReducer, + RBACState, + resetStoreAction, + setPermissionsAction, +} from '../RBACProvider'; -describe('rbacProviderReducer', () => { - let state; +describe('RBACReducer', () => { + let state: RBACState; beforeEach(() => { state = { @@ -16,24 +21,41 @@ describe('rbacProviderReducer', () => { it('returns the initial state', () => { const expected = state; - expect(rbacProviderReducer(undefined, {})).toEqual(expected); + // @ts-expect-error – testing the default case + expect(RBACReducer(undefined, {})).toEqual(expected); }); - describe('resetStore', () => { + describe('resetStoreAction', () => { it('should reset the state to its initial value', () => { - state.allPermissions = true; - state.collectionTypesRelatedPermissions = true; + state.allPermissions = []; + state.collectionTypesRelatedPermissions = { + apple: {}, + }; - expect(rbacProviderReducer(state, resetStore())).toEqual(initialState); + expect(RBACReducer(state, resetStoreAction())).toMatchInlineSnapshot(` + { + "allPermissions": null, + "collectionTypesRelatedPermissions": {}, + } + `); }); }); - describe('setPermissions', () => { + describe('setPermissionsAction', () => { it('should set the allPermissions value correctly', () => { - const permissions = [{ action: 'test', subject: null }]; + const permissions: Permission[] = [ + { + id: 0, + action: 'test', + subject: null, + conditions: [], + properties: {}, + actionParameters: {}, + }, + ]; const expected = { ...state, allPermissions: permissions }; - expect(rbacProviderReducer(state, setPermissions(permissions))).toEqual(expected); + expect(RBACReducer(state, setPermissionsAction(permissions))).toEqual(expected); }); it('should set the collectionTypesRelatedPermissions correctly', () => { @@ -89,7 +111,7 @@ describe('rbacProviderReducer', () => { }; expect( - rbacProviderReducer(state, setPermissions(fixtures.permissions.allPermissions)) + RBACReducer(state, setPermissionsAction(fixtures.permissions.contentManager)) .collectionTypesRelatedPermissions ).toEqual(expected); }); diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/index.js b/packages/core/admin/admin/src/content-manager/pages/ListView/index.js index b463796cb1..df9d1c7ce8 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListView/index.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListView/index.js @@ -44,7 +44,7 @@ import { useDispatch } from 'react-redux'; import { useHistory, useLocation, Link as ReactRouterLink } from 'react-router-dom'; import { HOOKS } from '../../../constants'; -import { useTypedSelector } from '../../../core/store'; +import { useTypedSelector } from '../../../core/store/hooks'; import { useAdminUsers } from '../../../hooks/useAdminUsers'; import { useEnterprise } from '../../../hooks/useEnterprise'; import { InjectionZone } from '../../../shared/components'; diff --git a/packages/core/admin/admin/src/contexts/admin.ts b/packages/core/admin/admin/src/contexts/admin.ts index 33667d2d65..b862b7f13f 100644 --- a/packages/core/admin/admin/src/contexts/admin.ts +++ b/packages/core/admin/admin/src/contexts/admin.ts @@ -16,3 +16,4 @@ const AdminContext = React.createContext({ const useAdmin = () => React.useContext(AdminContext); export { AdminContext, useAdmin }; +export type { AdminContextValue }; diff --git a/packages/core/admin/admin/src/core/store.ts b/packages/core/admin/admin/src/core/store/configure.ts similarity index 55% rename from packages/core/admin/admin/src/core/store.ts rename to packages/core/admin/admin/src/core/store/configure.ts index d680181af8..54e77e6e14 100644 --- a/packages/core/admin/admin/src/core/store.ts +++ b/packages/core/admin/admin/src/core/store/configure.ts @@ -4,42 +4,28 @@ import { Middleware, Reducer, combineReducers, - createSelector, - Selector, } from '@reduxjs/toolkit'; -import { useDispatch, useStore, TypedUseSelectorHook, useSelector } from 'react-redux'; +import { RBACReducer } from '../../components/RBACProvider'; // @ts-expect-error no types, yet. -import rbacProviderReducer from '../components/RBACProvider/reducer'; +import rbacManagerReducer from '../../content-manager/hooks/useSyncRbac/reducer'; // @ts-expect-error no types, yet. -import rbacManagerReducer from '../content-manager/hooks/useSyncRbac/reducer'; +import cmAppReducer from '../../content-manager/pages/App/reducer'; // @ts-expect-error no types, yet. -import cmAppReducer from '../content-manager/pages/App/reducer'; +import editViewLayoutManagerReducer from '../../content-manager/pages/EditViewLayoutManager/reducer'; // @ts-expect-error no types, yet. -import editViewLayoutManagerReducer from '../content-manager/pages/EditViewLayoutManager/reducer'; +import listViewReducer from '../../content-manager/pages/ListView/reducer'; // @ts-expect-error no types, yet. -import listViewReducer from '../content-manager/pages/ListView/reducer'; +import editViewCrudReducer from '../../content-manager/sharedReducers/crudReducer/reducer'; // @ts-expect-error no types, yet. -import editViewCrudReducer from '../content-manager/sharedReducers/crudReducer/reducer'; -// @ts-expect-error no types, yet. -import appReducer from '../pages/App/reducer'; - -const createReducer = ( - appReducers: Record, - asyncReducers: Record -) => { - return combineReducers({ - ...appReducers, - ...asyncReducers, - }); -}; +import appReducer from '../../pages/App/reducer'; /** * @description Static reducers are ones we know, they live in the admin package. */ -const staticReducers: Record = { +const staticReducers = { admin_app: appReducer, - rbacProvider: rbacProviderReducer, + rbacProvider: RBACReducer, 'content-manager_app': cmAppReducer, 'content-manager_listView': listViewReducer, 'content-manager_rbacManager': rbacManagerReducer, @@ -60,8 +46,13 @@ const injectReducerStoreEnhancer: (appReducers: Record) => Stor asyncReducers, injectReducer: (key: string, asyncReducer: Reducer) => { asyncReducers[key] = asyncReducer; - // @ts-expect-error we dynamically add reducers which makes the types uncomfortable. - store.replaceReducer(createReducer(appReducers, asyncReducers)); + store.replaceReducer( + // @ts-expect-error we dynamically add reducers which makes the types uncomfortable. + combineReducers({ + ...appReducers, + ...asyncReducers, + }) + ); }, }; }; @@ -74,10 +65,10 @@ const configureStoreImpl = ( appMiddlewares: Array<() => Middleware> = [], injectedReducers: Record = {} ) => { - const coreReducers = { ...staticReducers, ...injectedReducers }; + const coreReducers = { ...staticReducers, ...injectedReducers } as const; const store = configureStore({ - reducer: createReducer(coreReducers, {}), + reducer: coreReducers, devTools: process.env.NODE_ENV !== 'production', middleware: (getDefaultMiddleware) => [ ...getDefaultMiddleware(), @@ -95,20 +86,6 @@ type Store = ReturnType & { }; type RootState = ReturnType; -type AppDispatch = Store['dispatch']; -const useTypedDispatch: () => AppDispatch = useDispatch; -const useTypedStore = useStore as () => Store; -const useTypedSelector: TypedUseSelectorHook = useSelector; - -const createTypedSelector = (selector: Selector) => - createSelector((state: RootState) => state, selector); - -export { - useTypedDispatch, - useTypedStore, - useTypedSelector, - configureStoreImpl as configureStore, - createTypedSelector, -}; -export type { RootState }; +export { configureStoreImpl as configureStore }; +export type { RootState, Store }; diff --git a/packages/core/admin/admin/src/core/store/hooks.ts b/packages/core/admin/admin/src/core/store/hooks.ts new file mode 100644 index 0000000000..7b2a9d6643 --- /dev/null +++ b/packages/core/admin/admin/src/core/store/hooks.ts @@ -0,0 +1,15 @@ +import { createSelector, Selector } from '@reduxjs/toolkit'; +import { useDispatch, useStore, TypedUseSelectorHook, useSelector } from 'react-redux'; + +import type { RootState, Store } from './configure'; + +type AppDispatch = Store['dispatch']; + +const useTypedDispatch: () => AppDispatch = useDispatch; +const useTypedStore = useStore as () => Store; +const useTypedSelector: TypedUseSelectorHook = useSelector; + +const createTypedSelector = (selector: Selector) => + createSelector((state: RootState) => state, selector); + +export { useTypedDispatch, useTypedStore, useTypedSelector, createTypedSelector }; diff --git a/packages/core/admin/admin/src/hooks/useInjectReducer/tests/useInjectReducer.test.js b/packages/core/admin/admin/src/hooks/useInjectReducer/tests/useInjectReducer.test.js index 19f1d8abb0..3e07be9a87 100644 --- a/packages/core/admin/admin/src/hooks/useInjectReducer/tests/useInjectReducer.test.js +++ b/packages/core/admin/admin/src/hooks/useInjectReducer/tests/useInjectReducer.test.js @@ -3,7 +3,7 @@ import React from 'react'; import { renderHook } from '@testing-library/react'; import { Provider } from 'react-redux'; -import { configureStore } from '../../../core/store'; +import { configureStore } from '../../../core/store/configure'; import { useInjectReducer } from '../useInjectReducer'; const store = configureStore(); diff --git a/packages/core/admin/admin/src/pages/App/index.js b/packages/core/admin/admin/src/pages/App/index.js index f95020d7b4..b2621d6aaa 100644 --- a/packages/core/admin/admin/src/pages/App/index.js +++ b/packages/core/admin/admin/src/pages/App/index.js @@ -33,7 +33,9 @@ import UseCasePage from '../UseCasePage'; import { ROUTES_CE, SET_ADMIN_PERMISSIONS } from './constants'; const AuthenticatedApp = lazy(() => - import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp') + import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp').then( + (mod) => ({ default: mod.AuthenticatedApp }) + ) ); function App() { diff --git a/packages/core/helper-plugin/src/components/CheckPagePermissions.tsx b/packages/core/helper-plugin/src/components/CheckPagePermissions.tsx index 4c0f7ea417..a81d0e3b39 100644 --- a/packages/core/helper-plugin/src/components/CheckPagePermissions.tsx +++ b/packages/core/helper-plugin/src/components/CheckPagePermissions.tsx @@ -3,15 +3,11 @@ import * as React from 'react'; import { Redirect } from 'react-router-dom'; import { useNotification } from '../features/Notifications'; -import { useRBACProvider } from '../features/RBAC'; +import { Permission, useRBACProvider } from '../features/RBAC'; import { hasPermissions } from '../utils/hasPermissions'; import { LoadingIndicatorPage } from './LoadingIndicatorPage'; -import type { domain } from '@strapi/permissions'; - -type Permission = domain.permission.Permission; - export interface CheckPagePermissionsProps { children: React.ReactNode; permissions?: Permission[]; diff --git a/packages/core/helper-plugin/src/components/CheckPermissions.tsx b/packages/core/helper-plugin/src/components/CheckPermissions.tsx index f2d294237d..226e5fe004 100644 --- a/packages/core/helper-plugin/src/components/CheckPermissions.tsx +++ b/packages/core/helper-plugin/src/components/CheckPermissions.tsx @@ -1,13 +1,9 @@ import * as React from 'react'; import { useNotification } from '../features/Notifications'; -import { useRBACProvider } from '../features/RBAC'; +import { useRBACProvider, Permission } from '../features/RBAC'; import { hasPermissions } from '../utils/hasPermissions'; -import type { domain } from '@strapi/permissions'; - -type Permission = domain.permission.Permission; - // NOTE: this component is very similar to the CheckPagePermissions // except that it does not handle redirections nor loading state diff --git a/packages/core/helper-plugin/src/features/AppInfo.tsx b/packages/core/helper-plugin/src/features/AppInfo.tsx index b7c022c06b..e265b8e6a1 100644 --- a/packages/core/helper-plugin/src/features/AppInfo.tsx +++ b/packages/core/helper-plugin/src/features/AppInfo.tsx @@ -115,3 +115,5 @@ export { useAppInfo, useAppInfos, }; + +export type { AppInfoContextValue, AppInfoProviderProps }; diff --git a/packages/core/helper-plugin/src/features/CustomFields.tsx b/packages/core/helper-plugin/src/features/CustomFields.tsx index 0fd98a03da..10f1bf2864 100644 --- a/packages/core/helper-plugin/src/features/CustomFields.tsx +++ b/packages/core/helper-plugin/src/features/CustomFields.tsx @@ -128,3 +128,10 @@ const CustomFieldsProvider = ({ children, customFields }: CustomFieldsProviderPr const useCustomFields = () => React.useContext(CustomFieldsContext); export { CustomFieldsContext, CustomFieldsProvider, useCustomFields }; +export type { + CustomFieldsProviderProps, + CustomField, + CustomFieldComponents, + CustomFieldOption, + CustomFieldOptions, +}; diff --git a/packages/core/helper-plugin/src/features/Library.tsx b/packages/core/helper-plugin/src/features/Library.tsx index 0a9c86ab8d..3a47b325b4 100644 --- a/packages/core/helper-plugin/src/features/Library.tsx +++ b/packages/core/helper-plugin/src/features/Library.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; * Context * -----------------------------------------------------------------------------------------------*/ -export interface LibraryContextValue { +interface LibraryContextValue { fields?: Record; components?: Record; } @@ -32,3 +32,4 @@ const LibraryProvider = ({ children, fields, components }: LibraryProviderProps) const useLibrary = () => React.useContext(LibraryContext); export { LibraryContext, LibraryProvider, useLibrary }; +export type { LibraryContextValue, LibraryProviderProps }; diff --git a/packages/core/helper-plugin/src/features/RBAC.tsx b/packages/core/helper-plugin/src/features/RBAC.tsx index 69a7e419aa..f67c386bde 100644 --- a/packages/core/helper-plugin/src/features/RBAC.tsx +++ b/packages/core/helper-plugin/src/features/RBAC.tsx @@ -1,9 +1,23 @@ import * as React from 'react'; -import type { domain } from '@strapi/permissions'; +import type { Entity } from '@strapi/types'; import type { QueryObserverBaseResult } from 'react-query'; -type Permission = domain.permission.Permission; +/** + * This is duplicated from the `@strapi/admin` package. + */ +type Permission = { + id?: Entity.ID; + action: string; + subject: string | null; + actionParameters?: object; + properties?: { + fields?: string[]; + locales?: string[]; + [key: string]: unknown; + }; + conditions?: string[]; +}; /* ------------------------------------------------------------------------------------------------- * Context diff --git a/packages/core/helper-plugin/src/features/StrapiApp.tsx b/packages/core/helper-plugin/src/features/StrapiApp.tsx index b7ac96dd82..5383b15fc9 100644 --- a/packages/core/helper-plugin/src/features/StrapiApp.tsx +++ b/packages/core/helper-plugin/src/features/StrapiApp.tsx @@ -4,10 +4,7 @@ import { LinkProps } from 'react-router-dom'; import { TranslationMessage } from '../types'; -import type { domain } from '@strapi/permissions'; - -type Permission = domain.permission.Permission; - +import type { Permission } from './RBAC'; interface MenuItem extends Pick { to: string; icon: React.ElementType; @@ -62,7 +59,7 @@ type RunHookWaterfall = ( store: Store ) => unknown | Promise; -export interface StrapiAppContextValue { +interface StrapiAppContextValue { menu: MenuItem[]; plugins: Record; settings: Record; @@ -124,3 +121,13 @@ const StrapiAppProvider = ({ const useStrapiApp = () => React.useContext(StrapiAppContext); export { StrapiAppContext, StrapiAppProvider, useStrapiApp }; +export type { + StrapiAppProviderProps, + StrapiAppContextValue, + MenuItem, + Plugin, + StrapiAppSettingLink, + StrapiAppSetting, + RunHookSeries, + RunHookWaterfall, +}; diff --git a/packages/core/helper-plugin/src/hooks/useRBAC.ts b/packages/core/helper-plugin/src/hooks/useRBAC.ts index e29dd1150d..e0dbf35dfb 100644 --- a/packages/core/helper-plugin/src/hooks/useRBAC.ts +++ b/packages/core/helper-plugin/src/hooks/useRBAC.ts @@ -2,15 +2,12 @@ import { useCallback, useMemo, useState } from 'react'; import { useQueries } from 'react-query'; -import { useRBACProvider } from '../features/RBAC'; +import { useRBACProvider, Permission } from '../features/RBAC'; import { useFetchClient } from './useFetchClient'; -import type { domain } from '@strapi/permissions'; import type { AxiosResponse } from 'axios'; -type Permission = domain.permission.Permission; - type AllowedActions = Record; export const useRBAC = ( @@ -71,8 +68,7 @@ export const useRBAC = ( data: { data }, } = await post< { data: { data: boolean[] } }, - AxiosResponse<{ data: { data: boolean[] } }>, - { permissions: Permission[] } + AxiosResponse<{ data: { data: boolean[] } }> >('/admin/permissions/check', { permissions: matchingPermissions.map(({ action, subject }) => ({ action, diff --git a/packages/core/helper-plugin/src/utils/auth.ts b/packages/core/helper-plugin/src/utils/auth.ts index a173ce8638..12097e0630 100644 --- a/packages/core/helper-plugin/src/utils/auth.ts +++ b/packages/core/helper-plugin/src/utils/auth.ts @@ -98,26 +98,16 @@ const auth = { sessionStorage.clear(); }, - get(key: T): StorageItems[T] | string | null { - const localStorageItem = localStorage.getItem(key); - if (localStorageItem) { + get(key: T): StorageItems[T] | null { + const item = localStorage.getItem(key) ?? sessionStorage.getItem(key); + if (item) { try { - const parsedItem = JSON.parse(localStorageItem); + const parsedItem = JSON.parse(item); return parsedItem; } catch (error) { // Failed to parse return the string value - return localStorageItem; - } - } - - const sessionStorageItem = sessionStorage.getItem(key); - if (sessionStorageItem) { - try { - const parsedItem = JSON.parse(sessionStorageItem); - return parsedItem; - } catch (error) { - // Failed to parse return the string value - return sessionStorageItem; + // @ts-expect-error - this is fine + return item; } } diff --git a/packages/core/helper-plugin/src/utils/hasPermissions.ts b/packages/core/helper-plugin/src/utils/hasPermissions.ts index ffd0f0300d..53cfb3bd71 100644 --- a/packages/core/helper-plugin/src/utils/hasPermissions.ts +++ b/packages/core/helper-plugin/src/utils/hasPermissions.ts @@ -1,10 +1,8 @@ import { getFetchClient } from './getFetchClient'; -import type { domain } from '@strapi/permissions'; +import type { Permission } from '../features/RBAC'; import type { GenericAbortSignal } from 'axios'; -type Permission = domain.permission.Permission; - const findMatchingPermissions = (userPermissions: Permission[], permissions: Permission[]) => userPermissions.reduce((acc, curr) => { const associatedPermission = permissions.find( @@ -24,7 +22,7 @@ const formatPermissionsForRequest = (permissions: Permission[]) => return {}; } - const returnedPermission: Permission = { + const returnedPermission: Partial = { action: permission.action, }; diff --git a/packages/core/helper-plugin/src/utils/tests/hasPermissions.test.ts b/packages/core/helper-plugin/src/utils/tests/hasPermissions.test.ts index 8c0a37c922..93131c6473 100644 --- a/packages/core/helper-plugin/src/utils/tests/hasPermissions.test.ts +++ b/packages/core/helper-plugin/src/utils/tests/hasPermissions.test.ts @@ -5,9 +5,7 @@ import { shouldCheckPermissions, } from '../hasPermissions'; -import type { domain } from '@strapi/permissions'; - -type Permission = domain.permission.Permission; +import type { Permission } from '../../features/RBAC'; const hasPermissionsTestData: Record> = { userPermissions: { diff --git a/packages/utils/tsconfig/client.json b/packages/utils/tsconfig/client.json index cea9f75090..9a79528013 100644 --- a/packages/utils/tsconfig/client.json +++ b/packages/utils/tsconfig/client.json @@ -14,6 +14,7 @@ "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "noEmit": true, + "noImplicitAny": true, "jsx": "react-jsx" } }