Skip to content

Commit

Permalink
Add default Unauthorized component to ra-ui-materialui
Browse files Browse the repository at this point in the history
  • Loading branch information
djhi committed Sep 19, 2024
1 parent 7323384 commit 4d25054
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,8 @@ const MyTitle = () => {

When using an authProvider that supports [the `canAccess` method](./AuthProviderWriting.md#canaccess), react-admin will check whether users can access a resource page and display the `unauthorized` component when they can't.
![Default unauthorized component](./img/unauthorized.png)
You can replace the default "unauthorized" screen by passing a custom component as the `unauthorized` prop:
```tsx
Expand Down
Binary file added docs/img/unauthorized.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions packages/ra-core/src/core/CoreAdminContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
DashboardComponent,
LegacyDataProvider,
LoadingComponent,
UnauthorizedComponent,
} from '../types';
import { LoadingContextProvider } from './LoadingContextProvider';
import { UnauthorizedContextProvider } from './UnauthorizedContextProvider';
Expand Down Expand Up @@ -203,7 +202,7 @@ export interface CoreAdminContextProps {
* </Admin>
* );
*/
unauthorized?: UnauthorizedComponent;
unauthorized?: React.ComponentType;
}

export const CoreAdminContext = (props: CoreAdminContextProps) => {
Expand Down
7 changes: 2 additions & 5 deletions packages/ra-core/src/core/UnauthorizedContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { createContext } from 'react';
import { UnauthorizedComponent } from '../types';
import { ComponentType, createContext } from 'react';

export const UnauthorizedContext = createContext<UnauthorizedComponent>(
() => null
);
export const UnauthorizedContext = createContext<ComponentType>(() => null);
1 change: 0 additions & 1 deletion packages/ra-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,6 @@ export type AdminChildren =

export type TitleComponent = string | ReactElement<any>;
export type CatchAllComponent = ComponentType<{ title?: TitleComponent }>;
export type UnauthorizedComponent = ComponentType<{ title?: TitleComponent }>;

export type LoginComponent = ComponentType<{}> | ReactElement<any>;
export type DashboardComponent = ComponentType<WithPermissionsChildrenParams>;
Expand Down
3 changes: 3 additions & 0 deletions packages/ra-language-english/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const englishMessages: TranslationMessages = {
show: '%{name} %{recordRepresentation}',
empty: 'No %{name} yet.',
invite: 'Do you want to add one?',
unauthorized: 'Unauthorized',
},
input: {
file: {
Expand Down Expand Up @@ -110,6 +111,8 @@ const englishMessages: TranslationMessages = {
yes: 'Yes',
unsaved_changes:
"Some of your changes weren't saved. Are you sure you want to ignore them?",
unauthorized:
"You don't have the right permissions to access this page",
},
navigation: {
clear_filters: 'Clear filters',
Expand Down
2 changes: 2 additions & 0 deletions packages/ra-language-french/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const frenchMessages: TranslationMessages = {
show: '%{name} %{recordRepresentation}',
empty: 'Pas encore de %{name}.',
invite: 'Voulez-vous en créer un ?',
unauthorized: 'Non autorisé',
},
input: {
file: {
Expand Down Expand Up @@ -115,6 +116,7 @@ const frenchMessages: TranslationMessages = {
yes: 'Oui',
unsaved_changes:
"Certains changements n'ont pas été enregistrés. Êtes-vous sûr(e) de vouloir quitter cette page ?",
unauthorized: "Vous n'avez pas les droits d'accès à cette page",
},
navigation: {
clear_filters: 'Effacer les filtres',
Expand Down
4 changes: 3 additions & 1 deletion packages/ra-ui-materialui/src/AdminContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
defaultLightTheme,
defaultDarkTheme,
} from './theme';
import { Unauthorized } from './layout/Unauthorized';

export const AdminContext = (props: AdminContextProps) => {
const {
Expand All @@ -16,10 +17,11 @@ export const AdminContext = (props: AdminContextProps) => {
darkTheme,
defaultTheme,
children,
unauthorized = Unauthorized,
...rest
} = props;
return (
<CoreAdminContext {...rest}>
<CoreAdminContext unauthorized={unauthorized} {...rest}>
<ThemesContext.Provider
value={{
lightTheme: theme || lightTheme,
Expand Down
74 changes: 74 additions & 0 deletions packages/ra-ui-materialui/src/layout/Unauthorized.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import frenchMessages from 'ra-language-french';
import {
I18nContextProvider,
Resource,
testDataProvider,
TestMemoryRouter,
} from 'ra-core';

import { AdminContext } from '../AdminContext';
import { AdminUI } from '../AdminUI';
import { Unauthorized } from './Unauthorized';
import { Link } from 'react-router-dom';

export default {
title: 'ra-ui-materialui/layout/Unauthorized',
};

const i18nProvider = polyglotI18nProvider(
locale => (locale === 'fr' ? frenchMessages : englishMessages),
'en',
[
{ locale: 'en', name: 'English' },
{ locale: 'fr', name: 'Français' },
]
);

export const Basic = () => <Unauthorized />;

export const I18N = () => {
return (
<TestMemoryRouter>
<I18nContextProvider value={i18nProvider}>
<Unauthorized />
</I18nContextProvider>
</TestMemoryRouter>
);
};

const authProvider = {
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkAuth: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () => Promise.resolve(),
canAccess: ({ resource }) => Promise.resolve(resource === 'posts'),
};

export const FullApp = () => (
<TestMemoryRouter>
<AdminContext
dataProvider={testDataProvider()}
authProvider={authProvider}
i18nProvider={i18nProvider}
>
<AdminUI>
<Resource name="users" list={UserList} />
<Resource name="posts" list={PostList} />
</AdminUI>
</AdminContext>
</TestMemoryRouter>
);

const UserList = () => <div style={{ marginTop: 10 }}>User list</div>;
const PostList = () => (
<div style={{ marginTop: 10 }}>
<div>Post list</div>
<div>
<Link to="/users">User list</Link>
</div>
</div>
);
59 changes: 59 additions & 0 deletions packages/ra-ui-materialui/src/layout/Unauthorized.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import { Typography, SxProps } from '@mui/material';
import { useTranslate } from 'ra-core';

export const Unauthorized = (props: UnauthorizedProps) => {
const {
className,
unauthorizedPrimary = 'ra.page.unauthorized',
unauthorizedSecondary = 'ra.message.unauthorized',
...rest
} = props;
const translate = useTranslate();
return (
<Root className={className} {...rest}>
<div className={UnauthorizedClasses.message}>
<Typography variant="h5" mt={3} color="text.secondary">
{translate(unauthorizedPrimary, { _: unauthorizedPrimary })}
</Typography>
<Typography variant="body2">
{translate(unauthorizedSecondary, {
_: unauthorizedSecondary,
})}
</Typography>
</div>
</Root>
);
};

export interface UnauthorizedProps {
className?: string;
unauthorizedPrimary?: string;
unauthorizedSecondary?: string;
sx?: SxProps;
}

const PREFIX = 'RaUnauthorized';

export const UnauthorizedClasses = {
root: `${PREFIX}-root`,
message: `${PREFIX}-message`,
};

const Root = styled('div', {
name: PREFIX,
overridesResolver: (props, styles) => styles.root,
})(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
height: '100%',
[`& .${UnauthorizedClasses.message}`]: {
textAlign: 'center',
fontFamily: 'Roboto, sans-serif',
color: theme.palette.text.disabled,
paddingTop: '1em',
paddingBottom: '1em',
},
}));

0 comments on commit 4d25054

Please sign in to comment.