Skip to content

Commit

Permalink
chore(helper-plugin)!: remove page decorators (#19662)
Browse files Browse the repository at this point in the history
* chore(helper-plugin): remove page decorators

* chore: fixes
  • Loading branch information
joshuaellis committed Mar 5, 2024
1 parent 04432e8 commit cbe7d7a
Show file tree
Hide file tree
Showing 93 changed files with 1,553 additions and 1,693 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@ be redirect to the homepage.
#### Usage

```tsx
import { CheckPagePermissions } from '@strapi/helper-plugin';
import { Page } from '@strapi/strapi/admin';

const permissions = [{ action: 'plugins::my-plugin.access', subject: null }];

const MyPage = () => {
return (
<CheckPagePermissions permissions={permissions}>
<Page.Protect permissions={permissions}>
<h1>aha you found me</h1>
</CheckPagePermissions>
</Page.Protect>
);
};
```
Expand Down Expand Up @@ -255,9 +255,9 @@ const MyComponent = () => {
return !permissions ? (
<p>Loading...</p>
) : (
<CheckPagePermissions permissions={permissions}>
<Page.Protect permissions={permissions}>
<h1>aha you found me</h1>
</CheckPagePermissions>
</Page.Protect>
);
};
```
Expand Down
12 changes: 4 additions & 8 deletions packages/core/admin/admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,13 @@
import * as React from 'react';

import { SkipToContent } from '@strapi/design-system';
import {
LoadingIndicatorPage,
TrackingProvider,
useAppInfo,
useNotification,
} from '@strapi/helper-plugin';
import { TrackingProvider, useAppInfo, useNotification } from '@strapi/helper-plugin';
import merge from 'lodash/merge';
import { useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Outlet } from 'react-router-dom';

import { Page } from './components/PageHelpers';
import { ADMIN_PERMISSIONS_CE } from './constants';
import { useAuth } from './features/Auth';
import { ConfigurationProvider, ConfigurationProviderProps } from './features/Configuration';
Expand Down Expand Up @@ -105,11 +101,11 @@ export const App = ({ authLogo, menuLogo, showReleaseNotification, showTutorials
);

if (initQuery.isLoading) {
return <LoadingIndicatorPage />;
return <Page.Loading />;
}

return (
<React.Suspense fallback={<LoadingIndicatorPage />}>
<React.Suspense fallback={<Page.Loading />}>
<SkipToContent>
{formatMessage({ id: 'skipToContent', defaultMessage: 'Skip to content' })}
</SkipToContent>
Expand Down
12 changes: 4 additions & 8 deletions packages/core/admin/admin/src/StrapiApp.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import * as React from 'react';

import { darkTheme, lightTheme } from '@strapi/design-system';
import {
LoadingIndicatorPage,
MenuItem,
StrapiAppSetting,
StrapiAppSettingLink,
} from '@strapi/helper-plugin';
import { MenuItem, StrapiAppSetting, StrapiAppSettingLink } from '@strapi/helper-plugin';
import invariant from 'invariant';
import isFunction from 'lodash/isFunction';
import merge from 'lodash/merge';
Expand All @@ -28,6 +23,7 @@ import {
InjectionZoneModule,
InjectionZones,
} from './components/InjectionZone';
import { Page } from './components/PageHelpers';
import { Providers } from './components/Providers';
import { HOOKS } from './constants';
import { routes as cmRoutes } from './content-manager/router';
Expand Down Expand Up @@ -729,7 +725,7 @@ class StrapiApp {
links.map(({ to, Component }) => ({
path: `${to}/*`,
element: (
<React.Suspense fallback={<LoadingIndicatorPage />}>
<React.Suspense fallback={<Page.Loading />}>
<Component />
</React.Suspense>
),
Expand All @@ -741,7 +737,7 @@ class StrapiApp {
...this.menu.map(({ to, Component }) => ({
path: `${to}/*`,
element: (
<React.Suspense fallback={<LoadingIndicatorPage />}>
<React.Suspense fallback={<Page.Loading />}>
<Component />
</React.Suspense>
),
Expand Down
193 changes: 193 additions & 0 deletions packages/core/admin/admin/src/components/PageHelpers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import * as React from 'react';

import {
EmptyStateLayout,
EmptyStateLayoutProps,
Flex,
Icon,
Loader,
Main,
} from '@strapi/design-system';
import { useAPIErrorHandler, useNotification, useRBACProvider } from '@strapi/helper-plugin';
import { EmptyPermissions, ExclamationMarkCircle } from '@strapi/icons';
import { useIntl } from 'react-intl';

import { Permission } from '../../../shared/contracts/shared';
import { useCheckPermissionsQuery } from '../services/auth';

/* -------------------------------------------------------------------------------------------------
* Loading
* -----------------------------------------------------------------------------------------------*/
interface LoadingProps {
/**
* @default 'Loading content.'
*/
children?: React.ReactNode;
}

/**
* @public
* @description A loading component that should be rendered as the page
* whilst you load the content for the aforementioned page.
*/
const Loading = ({ children = 'Loading content.' }: LoadingProps) => {
return (
<Main height="100vh" aria-busy={true}>
<Flex alignItems="center" height="100%" justifyContent="center">
<Loader>{children}</Loader>
</Flex>
</Main>
);
};

/* -------------------------------------------------------------------------------------------------
* Error
* -----------------------------------------------------------------------------------------------*/
interface ErrorProps extends Partial<EmptyStateLayoutProps> {}

/**
* @public
* @description An error component that should be rendered as the page
* when an error occurs.
*/
const Error = (props: ErrorProps) => {
const { formatMessage } = useIntl();

return (
<Main height="100%">
<Flex alignItems="center" height="100%" justifyContent="center">
<EmptyStateLayout
icon={<Icon as={ExclamationMarkCircle} width="10rem" />}
content={formatMessage({
id: 'anErrorOccurred',
defaultMessage: 'Woops! Something went wrong. Please, try again.',
})}
{...props}
/>
</Flex>
</Main>
);
};

/* -------------------------------------------------------------------------------------------------
* NoPermissions
* -----------------------------------------------------------------------------------------------*/
interface NoPermissionsProps extends Partial<EmptyStateLayoutProps> {}

/**
* @public
* @description A component that should be rendered as the page
* when the user does not have the permissions to access the content.
* This component does not check any permissions, it's up to you to decide
* when it should be rendered.
*/
const NoPermissions = (props: NoPermissionsProps) => {
const { formatMessage } = useIntl();

return (
<Main height="100%">
<Flex alignItems="center" height="100%" justifyContent="center">
<EmptyStateLayout
icon={<EmptyPermissions width="10rem" />}
content={formatMessage({
id: 'app.components.EmptyStateLayout.content-permissions',
defaultMessage: "You don't have the permissions to access that content",
})}
{...props}
/>
</Flex>
</Main>
);
};

/* -------------------------------------------------------------------------------------------------
* Protect
* -----------------------------------------------------------------------------------------------*/
export interface ProtectProps {
/**
* The children to render if the user has the required permissions.
* If providing a function, it will be called with an object containing
* the permissions the user has based on the array you passed to the component.
*/
children: React.ReactNode | ((args: { permissions: Permission[] }) => React.ReactNode);
/**
* The permissions the user needs to have to access the content.
*/
permissions?: Array<Omit<Partial<Permission>, 'action'> & Pick<Permission, 'action'>>;
}

/**
* @public
* @description A wrapper component that should be used to protect a page. It will check the permissions
* you pass to it and render the children if the user has the required permissions. If a user does not have ALL
* the required permissions, it will redirect the user to the home page. Whilst these checks happen it will render
* the loading component and should the check fail it will render the error component with a notification.
*/
const Protect = ({ permissions = [], children }: ProtectProps) => {
const { allPermissions } = useRBACProvider();
const toggleNotification = useNotification();
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();

const matchingPermissions = allPermissions.filter(
(permission) =>
permissions.findIndex(
(perm) => perm.action === permission.action && perm.subject === permission.subject
) >= 0
);

const shouldCheckConditions = matchingPermissions.some(
(perm) => Array.isArray(perm.conditions) && perm.conditions.length > 0
);

const {
isLoading,
error,
data = [],
} = useCheckPermissionsQuery(
{
permissions: matchingPermissions.map((perm) => ({
action: perm.action,
subject: perm.subject,
})),
},
{
skip: !shouldCheckConditions,
}
);

React.useEffect(() => {
if (error) {
toggleNotification({
type: 'warning',
message: formatAPIError(error),
});
}
}, [error, formatAPIError, toggleNotification]);

if (isLoading) {
return <Loading />;
}

if (error) {
return <Error />;
}

const canAccess = shouldCheckConditions ? !data.includes(false) : matchingPermissions.length > 0;

if (!canAccess) {
return <NoPermissions />;
}

// @ts-expect-error this error comes from the fact we have permissions defined in the helper-plugin & admin, this will be resolved soon.
return typeof children === 'function' ? children({ permissions: matchingPermissions }) : children;
};

const Page = {
Error,
Loading,
NoPermissions,
Protect,
};

export { Page };
export type { ErrorProps, LoadingProps, NoPermissionsProps };
10 changes: 4 additions & 6 deletions packages/core/admin/admin/src/components/PluginsInitializer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import * as React from 'react';

import {
LoadingIndicatorPage,
useStrapiApp,
type StrapiAppContextValue,
} from '@strapi/helper-plugin';
import { useStrapiApp, type StrapiAppContextValue } from '@strapi/helper-plugin';
import produce from 'immer';
import set from 'lodash/set';

import { Page } from '../components/PageHelpers';

/**
* TODO: this isn't great, and we really should focus on fixing this.
*/
Expand Down Expand Up @@ -65,7 +63,7 @@ const PluginsInitializer = ({ children }: { children: React.ReactNode }) => {
return (
<>
{initializers}
<LoadingIndicatorPage />
<Page.Loading />
</>
);
}
Expand Down
13 changes: 2 additions & 11 deletions packages/core/admin/admin/src/components/RBACProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import * as React from 'react';

import {
LoadingIndicatorPage,
Permission,
RBACContext,
RBACContextValue,
} from '@strapi/helper-plugin';
import { Permission, RBACContext, RBACContextValue } from '@strapi/helper-plugin';
import produce from 'immer';

import { useTypedSelector, useTypedDispatch } from '../core/store/hooks';
Expand All @@ -21,7 +16,7 @@ interface RBACProviderProps {
}

const RBACProvider = ({ children, permissions, refetchPermissions }: RBACProviderProps) => {
const allPermissions = useTypedSelector((state) => state.rbacProvider.allPermissions);
const allPermissions = useTypedSelector((state) => state.rbacProvider.allPermissions) ?? [];

const dispatch = useTypedDispatch();

Expand All @@ -33,10 +28,6 @@ const RBACProvider = ({ children, permissions, refetchPermissions }: RBACProvide
};
}, [permissions, dispatch]);

if (!allPermissions) {
return <LoadingIndicatorPage />;
}

return (
<RBACContext.Provider value={{ allPermissions, refetchPermissions }}>
{children}
Expand Down
Loading

0 comments on commit cbe7d7a

Please sign in to comment.