From 21d61fb22250d073ea43e4499a679b445a13c70d Mon Sep 17 00:00:00 2001 From: "HH: Andrico Karoulla" Date: Tue, 13 Apr 2021 17:16:42 +0100 Subject: [PATCH 1/9] pass through custom empty component to Datagrid --- docs/List.md | 5 +++++ .../src/list/datagrid/Datagrid.spec.tsx | 20 +++++++++++++++++++ .../src/list/datagrid/Datagrid.tsx | 9 ++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/List.md b/docs/List.md index 5c0d6cf0f74..5099687f7d3 100644 --- a/docs/List.md +++ b/docs/List.md @@ -1981,6 +1981,7 @@ Here are all the props accepted by the component: * [`isRowExpandable`](#isrowexpandable) * [`isRowSelectable`](#isrowselectable) * [`optimized`](#performance) +* [`empty`](#empty) Additional props are passed down to [the material-ui `` element](https://material-ui.com/api/table/). @@ -2261,6 +2262,10 @@ const PostList = props => ( export default withStyles(styles)(PostList); ``` +### Empty + +It's possible that a Datagrid will have no records to display. If the Datagrid's parent component handles the loading state, the Datagrid will return `null` and render nothing. +Passing through a component to the `empty` prop will cause the Datagrid to render the `empty` component instead of `null`. ### CSS API The `Datagrid` component accepts the usual `className` prop but you can override many class names injected to the inner components by React-admin thanks to the `classes` property (as most Material UI components, see their [documentation about it](https://material-ui.com/customization/components/#overriding-styles-with-classes)). This property accepts the following keys: diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx index 0e4c514fdef..830142b1c30 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx @@ -47,6 +47,26 @@ describe('', () => { expect(contextValue.onSelect).toHaveBeenCalledTimes(0); }); + it('should display the correct empty component', () => { + const Empty = () =>
No records to show
; + + const emptyData = { + ...contextValue, + data: [], + ids: [], + }; + + const { queryByText } = renderWithRedux( + + } hasBulkActions> + + + + ); + + expect(queryByText('No records to show')).toBeTruthy(); + }); + describe('selecting items with the shift key', () => { it('should call onSelect with the correct ids when the last selection is after the first', () => { const Test = ({ selectedIds = [] }) => ( diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx index a5b5cf127c6..178f49324fc 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx @@ -117,6 +117,7 @@ const Datagrid: FC = React.forwardRef((props, ref) => { children, classes: classesOverride, className, + empty, expand, hasBulkActions = false, hover, @@ -242,9 +243,13 @@ const Datagrid: FC = React.forwardRef((props, ref) => { /** * Once loaded, the data for the list may be empty. Instead of * displaying the table header with zero data rows, - * the datagrid displays nothing in this case. + * the datagrid displays nothing or a custom empty component. */ if (loaded && (ids.length === 0 || total === 0)) { + if (empty) { + return empty; + } + return null; } @@ -353,6 +358,7 @@ Datagrid.propTypes = { order: PropTypes.string.isRequired, }), data: PropTypes.any, + empty: PropTypes.element, // @ts-ignore expand: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]), hasBulkActions: PropTypes.bool, @@ -387,6 +393,7 @@ export interface DatagridProps }>; hasBulkActions?: boolean; hover?: boolean; + empty?: ReactElement; isRowSelectable?: (record: Record) => boolean; isRowExpandable?: (record: Record) => boolean; optimized?: boolean; From 6451c30e44626740fa4a52065e3a4e4d3c3b41db Mon Sep 17 00:00:00 2001 From: "HH: Andrico Karoulla" Date: Wed, 2 Jun 2021 21:36:40 +0100 Subject: [PATCH 2/9] offer option to log out in the check error promise error --- docs/Authentication.md | 20 +++++++ .../src/auth/useLogoutIfAccessDenied.spec.tsx | 53 ++++++++++++++++--- .../src/auth/useLogoutIfAccessDenied.ts | 31 ++++++++++- 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/docs/Authentication.md b/docs/Authentication.md index af92d28116e..6ac404f8106 100644 --- a/docs/Authentication.md +++ b/docs/Authentication.md @@ -186,6 +186,26 @@ export default { }; ``` +It's possible to not log the user out, and to instead redirect them. You can do this by passing `error.logoutUser = false` to the `Promise.reject` along with an `error.redirectTo` url. + + +```js +// in src/authProvider.js +export default { + login: ({ username, password }) => { /* ... */ }, + checkError: (error) => { + const status = error.status; + if (status === 401 || status === 403) { + localStorage.removeItem('auth'); + return Promise.reject({ redirectTo: '/unauthorized', logoutUser: false }); + } + // other error code (404, 500, etc): no need to log out + return Promise.resolve(); + }, + // ... +}; +``` + When `authProvider.checkError()` returns a rejected Promise, react-admin displays a notification to the end user, unless the `error.message` is `false`. That means you can disable the notification on error as follows: ```js diff --git a/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx b/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx index 474ca9853e6..4d92b63229a 100644 --- a/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx +++ b/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { useState, useEffect } from 'react'; import expect from 'expect'; import { render, waitFor } from '@testing-library/react'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; import useLogoutIfAccessDenied from './useLogoutIfAccessDenied'; import AuthContext from './AuthContext'; @@ -58,6 +60,16 @@ const notify = jest.fn(); //@ts-expect-error useNotify.mockImplementation(() => notify); +function renderComponent(children) { + const history = createMemoryHistory(); + const api = render({children}); + + return { + ...api, + history, + }; +} + describe('useLogoutIfAccessDenied', () => { afterEach(() => { //@ts-expect-error @@ -67,11 +79,12 @@ describe('useLogoutIfAccessDenied', () => { }); it('should not logout if passed no error', async () => { - const { queryByText } = render( + const { queryByText } = renderComponent( - + ); + await waitFor(() => { expect(authProvider.logout).toHaveBeenCalledTimes(0); expect(notify).toHaveBeenCalledTimes(0); @@ -80,7 +93,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should not log out if passed an error that does not make the authProvider throw', async () => { - const { queryByText } = render( + const { queryByText } = renderComponent( @@ -93,7 +106,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should logout if passed an error that makes the authProvider throw', async () => { - const { queryByText } = render( + const { queryByText } = renderComponent( @@ -106,7 +119,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should not send multiple notifications if already logged out', async () => { - const { queryByText } = render( + const { queryByText } = renderComponent( @@ -129,7 +142,7 @@ describe('useLogoutIfAccessDenied', () => { index++; // answers immediately first, then after 100ms the second time }), }; - const { queryByText } = render( + const { queryByText } = renderComponent( @@ -143,7 +156,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should logout without showing a notification if disableAuthentication is true', async () => { - const { queryByText } = render( + const { queryByText } = renderComponent( { }); it('should logout without showing a notification if authProvider returns error with message false', async () => { - const { queryByText } = render( + const { queryByText } = renderComponent( { expect(queryByText('logged in')).toBeNull(); }); }); + + it('should not logout the user if logoutUser is set to false', async () => { + const { queryByText, history } = renderComponent( + { + return Promise.reject({ + logoutUser: false, + redirectTo: '/unauthorized', + }); + }, + }} + > + + + ); + await waitFor(() => { + expect(authProvider.logout).toHaveBeenCalledTimes(0); + expect(notify).toHaveBeenCalledTimes(1); + expect(queryByText('logged in')).toBeNull(); + expect(history.location.pathname).toBe('/unauthorized'); + }); + }); }); diff --git a/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts b/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts index c1652812e97..83a39f38104 100644 --- a/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts +++ b/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts @@ -3,6 +3,8 @@ import { useCallback } from 'react'; import useAuthProvider from './useAuthProvider'; import useLogout from './useLogout'; import { useNotify } from '../sideEffect'; +import { useHistory } from 'react-router'; +import { LocationDescriptorObject } from 'history'; let timer; @@ -41,12 +43,15 @@ const useLogoutIfAccessDenied = (): LogoutIfAccessDenied => { const authProvider = useAuthProvider(); const logout = useLogout(); const notify = useNotify(); + const history = useHistory(); const logoutIfAccessDenied = useCallback( (error?: any, disableNotification?: boolean) => authProvider .checkError(error) .then(() => false) .catch(async e => { + const logoutUser = e?.logoutUser ?? true; + //manual debounce if (timer) { // side effects already triggered in this tick, exit @@ -76,11 +81,33 @@ const useLogoutIfAccessDenied = (): LogoutIfAccessDenied => { : error && error.redirectTo ? error.redirectTo : undefined; - logout({}, redirectTo); + + if (logoutUser) { + logout({}, redirectTo); + } else { + const redirectToParts = redirectTo.split('?'); + const newLocation: LocationDescriptorObject = { + pathname: redirectToParts[0], + }; + + if (history.location && history.location.pathname) { + newLocation.state = { + nextPathname: history.location.pathname, + nextSearch: history.location.search, + }; + } + + if (redirectToParts[1]) { + newLocation.search = redirectToParts[1]; + } + history.push(redirectTo); + + return false; + } return true; }), - [authProvider, logout, notify] + [authProvider, logout, notify, history] ); return authProvider ? logoutIfAccessDenied From d3bae3fdd03048c5aa6bd7af2cbbd2039470a061 Mon Sep 17 00:00:00 2001 From: "HH: Andrico Karoulla" Date: Wed, 2 Jun 2021 21:41:42 +0100 Subject: [PATCH 3/9] revert bad change to test --- packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx b/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx index 4d92b63229a..553048422be 100644 --- a/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx +++ b/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx @@ -81,7 +81,7 @@ describe('useLogoutIfAccessDenied', () => { it('should not logout if passed no error', async () => { const { queryByText } = renderComponent( - + ); From cc564c9a8c4da6d5734c2e3f4298f24c0f768bd5 Mon Sep 17 00:00:00 2001 From: andrico Date: Sun, 20 Jun 2021 10:50:52 +0100 Subject: [PATCH 4/9] remove unnecessary example code --- docs/Authentication.md | 45 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/docs/Authentication.md b/docs/Authentication.md index 6ac404f8106..8f4c4337446 100644 --- a/docs/Authentication.md +++ b/docs/Authentication.md @@ -196,7 +196,6 @@ export default { checkError: (error) => { const status = error.status; if (status === 401 || status === 403) { - localStorage.removeItem('auth'); return Promise.reject({ redirectTo: '/unauthorized', logoutUser: false }); } // other error code (404, 500, etc): no need to log out @@ -511,40 +510,40 @@ const authProvider = { React-admin calls the `authProvider` methods with the following params: -| Method | Usage | Parameters format | -| ---------------- | ----------------------------------------------- | ------------------ | -| `login` | Log a user in | `Object` whatever fields the login form contains | -| `checkError` | Check if a dataProvider error is an authentication error | `{ message: string, status: number, body: Object }` the error returned by the `dataProvider` | -| `checkAuth` | Check credentials before moving to a new route | `Object` whatever params passed to `useCheckAuth()` - empty for react-admin default routes | -| `logout` | Log a user out | | -| `getIdentity` | Get the current user identity | | -| `getPermissions` | Get the current user credentials | `Object` whatever params passed to `usePermissions()` - empty for react-admin default routes | +| Method | Usage | Parameters format | +| ---------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `login` | Log a user in | `Object` whatever fields the login form contains | +| `checkError` | Check if a dataProvider error is an authentication error | `{ message: string, status: number, body: Object }` the error returned by the `dataProvider` | +| `checkAuth` | Check credentials before moving to a new route | `Object` whatever params passed to `useCheckAuth()` - empty for react-admin default routes | +| `logout` | Log a user out | | +| `getIdentity` | Get the current user identity | | +| `getPermissions` | Get the current user credentials | `Object` whatever params passed to `usePermissions()` - empty for react-admin default routes | ### Response Format `authProvider` methods must return a Promise. In case of success, the Promise should resolve to the following value: -| Method | Resolve if | Response format | -| ---------------- | --------------------------------- | --------------- | -| `login` | Login credentials were accepted | `void` | -| `checkError` | Error is not an auth error | `void` | -| `checkAuth` | User is authenticated | `void` | -| `logout` | Auth backend acknowledged logout | `string | false | void` route to redirect to after logout, defaults to `/login` | -| `getIdentity` | Auth backend returned identity | `{ id: string | number, fullName?: string, avatar?: string }` | +| Method | Resolve if | Response format | +| ---------------- | --------------------------------- | ---------------------------------------------------------------------------------------------- | +| `login` | Login credentials were accepted | `void` | +| `checkError` | Error is not an auth error | `void` | +| `checkAuth` | User is authenticated | `void` | +| `logout` | Auth backend acknowledged logout | `string | false | void` route to redirect to after logout, defaults to `/login` | +| `getIdentity` | Auth backend returned identity | `{ id: string | number, fullName?: string, avatar?: string }` | | `getPermissions` | Auth backend returned permissions | `Object | Array` free format - the response will be returned when `usePermissions()` is called | ### Error Format When the auth backend returns an error, the Auth Provider should return a rejected Promise, with the following value: -| Method | Reject if | Error format | -| ---------------- | ----------------------------------------- | --------------- | -| `login` | Login credentials weren't accepted | `string | { message?: string }` error message to display | +| Method | Reject if | Error format | +| ---------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `login` | Login credentials weren't accepted | `string | { message?: string }` error message to display | | `checkError` | Error is an auth error | `void | { redirectTo?: string, message?: boolean }` route to redirect to after logout, and whether to disable error notification | -| `checkAuth` | User is not authenticated | `void | { redirectTo?: string, message?: string }` route to redirect to after logout, message to notify the user | -| `logout` | Auth backend failed to log the user out | `void` | -| `getIdentity` | Auth backend failed to return identity | `Object` free format - returned as `error` when `useGetIdentity()` is called | -| `getPermissions` | Auth backend failed to return permissions | `Object` free format - returned as `error` when `usePermissions()` is called | +| `checkAuth` | User is not authenticated | `void | { redirectTo?: string, message?: string }` route to redirect to after logout, message to notify the user | +| `logout` | Auth backend failed to log the user out | `void` | +| `getIdentity` | Auth backend failed to return identity | `Object` free format - returned as `error` when `useGetIdentity()` is called | +| `getPermissions` | Auth backend failed to return permissions | `Object` free format - returned as `error` when `usePermissions()` is called | ## Hooks From 1c555058675703baeac84525c85aa5b60a2e3482 Mon Sep 17 00:00:00 2001 From: "HH: Andrico Karoulla" Date: Tue, 29 Jun 2021 20:46:36 +0100 Subject: [PATCH 5/9] revert changes to markdown formatting --- docs/Authentication.md | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/Authentication.md b/docs/Authentication.md index 8f4c4337446..377bf15f692 100644 --- a/docs/Authentication.md +++ b/docs/Authentication.md @@ -510,40 +510,40 @@ const authProvider = { React-admin calls the `authProvider` methods with the following params: -| Method | Usage | Parameters format | -| ---------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `login` | Log a user in | `Object` whatever fields the login form contains | -| `checkError` | Check if a dataProvider error is an authentication error | `{ message: string, status: number, body: Object }` the error returned by the `dataProvider` | -| `checkAuth` | Check credentials before moving to a new route | `Object` whatever params passed to `useCheckAuth()` - empty for react-admin default routes | -| `logout` | Log a user out | | -| `getIdentity` | Get the current user identity | | -| `getPermissions` | Get the current user credentials | `Object` whatever params passed to `usePermissions()` - empty for react-admin default routes | +| Method | Usage | Parameters format | +| ---------------- | ----------------------------------------------- | ------------------ | +| `login` | Log a user in | `Object` whatever fields the login form contains | +| `checkError` | Check if a dataProvider error is an authentication error | `{ message: string, status: number, body: Object }` the error returned by the `dataProvider` | +| `checkAuth` | Check credentials before moving to a new route | `Object` whatever params passed to `useCheckAuth()` - empty for react-admin default routes | +| `logout` | Log a user out | | +| `getIdentity` | Get the current user identity | | +| `getPermissions` | Get the current user credentials | `Object` whatever params passed to `usePermissions()` - empty for react-admin default routes | ### Response Format `authProvider` methods must return a Promise. In case of success, the Promise should resolve to the following value: -| Method | Resolve if | Response format | -| ---------------- | --------------------------------- | ---------------------------------------------------------------------------------------------- | -| `login` | Login credentials were accepted | `void` | -| `checkError` | Error is not an auth error | `void` | -| `checkAuth` | User is authenticated | `void` | -| `logout` | Auth backend acknowledged logout | `string | false | void` route to redirect to after logout, defaults to `/login` | -| `getIdentity` | Auth backend returned identity | `{ id: string | number, fullName?: string, avatar?: string }` | +| Method | Resolve if | Response format | +| ---------------- | --------------------------------- | --------------- | +| `login` | Login credentials were accepted | `void` | +| `checkError` | Error is not an auth error | `void` | +| `checkAuth` | User is authenticated | `void` | +| `logout` | Auth backend acknowledged logout | `string | false | void` route to redirect to after logout, defaults to `/login` | +| `getIdentity` | Auth backend returned identity | `{ id: string | number, fullName?: string, avatar?: string }` | | `getPermissions` | Auth backend returned permissions | `Object | Array` free format - the response will be returned when `usePermissions()` is called | ### Error Format When the auth backend returns an error, the Auth Provider should return a rejected Promise, with the following value: -| Method | Reject if | Error format | -| ---------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `login` | Login credentials weren't accepted | `string | { message?: string }` error message to display | +| Method | Reject if | Error format | +| ---------------- | ----------------------------------------- | --------------- | +| `login` | Login credentials weren't accepted | `string | { message?: string }` error message to display | | `checkError` | Error is an auth error | `void | { redirectTo?: string, message?: boolean }` route to redirect to after logout, and whether to disable error notification | -| `checkAuth` | User is not authenticated | `void | { redirectTo?: string, message?: string }` route to redirect to after logout, message to notify the user | -| `logout` | Auth backend failed to log the user out | `void` | -| `getIdentity` | Auth backend failed to return identity | `Object` free format - returned as `error` when `useGetIdentity()` is called | -| `getPermissions` | Auth backend failed to return permissions | `Object` free format - returned as `error` when `usePermissions()` is called | +| `checkAuth` | User is not authenticated | `void | { redirectTo?: string, message?: string }` route to redirect to after logout, message to notify the user | +| `logout` | Auth backend failed to log the user out | `void` | +| `getIdentity` | Auth backend failed to return identity | `Object` free format - returned as `error` when `useGetIdentity()` is called | +| `getPermissions` | Auth backend failed to return permissions | `Object` free format - returned as `error` when `usePermissions()` is called | ## Hooks From da68b1a9331692796df0abb5db03e0ed4ca13a55 Mon Sep 17 00:00:00 2001 From: "HH: Andrico Karoulla" Date: Tue, 29 Jun 2021 20:53:51 +0100 Subject: [PATCH 6/9] rename render method in test --- .../src/auth/useLogoutIfAccessDenied.spec.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx b/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx index 553048422be..9a04b224db0 100644 --- a/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx +++ b/packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx @@ -60,7 +60,7 @@ const notify = jest.fn(); //@ts-expect-error useNotify.mockImplementation(() => notify); -function renderComponent(children) { +function renderInRouter(children) { const history = createMemoryHistory(); const api = render({children}); @@ -79,7 +79,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should not logout if passed no error', async () => { - const { queryByText } = renderComponent( + const { queryByText } = renderInRouter( @@ -93,7 +93,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should not log out if passed an error that does not make the authProvider throw', async () => { - const { queryByText } = renderComponent( + const { queryByText } = renderInRouter( @@ -106,7 +106,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should logout if passed an error that makes the authProvider throw', async () => { - const { queryByText } = renderComponent( + const { queryByText } = renderInRouter( @@ -119,7 +119,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should not send multiple notifications if already logged out', async () => { - const { queryByText } = renderComponent( + const { queryByText } = renderInRouter( @@ -142,7 +142,7 @@ describe('useLogoutIfAccessDenied', () => { index++; // answers immediately first, then after 100ms the second time }), }; - const { queryByText } = renderComponent( + const { queryByText } = renderInRouter( @@ -156,7 +156,7 @@ describe('useLogoutIfAccessDenied', () => { }); it('should logout without showing a notification if disableAuthentication is true', async () => { - const { queryByText } = renderComponent( + const { queryByText } = renderInRouter( { }); it('should logout without showing a notification if authProvider returns error with message false', async () => { - const { queryByText } = renderComponent( + const { queryByText } = renderInRouter( { }); it('should not logout the user if logoutUser is set to false', async () => { - const { queryByText, history } = renderComponent( + const { queryByText, history } = renderInRouter( Date: Tue, 29 Jun 2021 22:24:35 +0100 Subject: [PATCH 7/9] remove unnecessary code, add notification when redirected via not being authorized --- .../src/auth/useLogoutIfAccessDenied.ts | 29 +++++++++---------- packages/ra-language-english/src/index.ts | 1 + packages/ra-language-french/src/index.ts | 2 ++ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts b/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts index 83a39f38104..0b42fa69a3e 100644 --- a/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts +++ b/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts @@ -71,7 +71,17 @@ const useLogoutIfAccessDenied = (): LogoutIfAccessDenied => { authProvider .checkAuth({}) .then(() => { - notify('ra.notification.logged_out', 'warning'); + if (logoutUser) { + notify( + 'ra.notification.logged_out', + 'warning' + ); + } else { + notify( + 'ra.notification.not_authorized', + 'warning' + ); + } }) .catch(() => {}); } @@ -85,24 +95,11 @@ const useLogoutIfAccessDenied = (): LogoutIfAccessDenied => { if (logoutUser) { logout({}, redirectTo); } else { - const redirectToParts = redirectTo.split('?'); const newLocation: LocationDescriptorObject = { - pathname: redirectToParts[0], + pathname: redirectTo, }; - if (history.location && history.location.pathname) { - newLocation.state = { - nextPathname: history.location.pathname, - nextSearch: history.location.search, - }; - } - - if (redirectToParts[1]) { - newLocation.search = redirectToParts[1]; - } - history.push(redirectTo); - - return false; + history.push(newLocation); } return true; diff --git a/packages/ra-language-english/src/index.ts b/packages/ra-language-english/src/index.ts index ed4d50a3dc1..62b3003e4c1 100644 --- a/packages/ra-language-english/src/index.ts +++ b/packages/ra-language-english/src/index.ts @@ -138,6 +138,7 @@ const englishMessages: TranslationMessages = { 'Cannot load the translations for the specified language', canceled: 'Action cancelled', logged_out: 'Your session has ended, please reconnect.', + not_authorized: "You're not authorized to access this resource.", }, validation: { required: 'Required', diff --git a/packages/ra-language-french/src/index.ts b/packages/ra-language-french/src/index.ts index b01ed2cd9f5..4cb94d63730 100644 --- a/packages/ra-language-french/src/index.ts +++ b/packages/ra-language-french/src/index.ts @@ -143,6 +143,8 @@ const frenchMessages: TranslationMessages = { 'Erreur de chargement des traductions pour la langue sélectionnée', canceled: 'Action annulée', logged_out: 'Votre session a pris fin, veuillez vous reconnecter.', + not_authorized: + "Vous n'êtes pas autorisé à accéder à cette ressource.", }, validation: { required: 'Ce champ est requis', From 78da7d9ad3cf11d914e9a0f94213881576fd2258 Mon Sep 17 00:00:00 2001 From: "HH: Andrico Karoulla" Date: Thu, 1 Jul 2021 08:47:27 +0100 Subject: [PATCH 8/9] make change to french copy --- packages/ra-language-french/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ra-language-french/src/index.ts b/packages/ra-language-french/src/index.ts index 4cb94d63730..ec1c8a15c87 100644 --- a/packages/ra-language-french/src/index.ts +++ b/packages/ra-language-french/src/index.ts @@ -144,7 +144,7 @@ const frenchMessages: TranslationMessages = { canceled: 'Action annulée', logged_out: 'Votre session a pris fin, veuillez vous reconnecter.', not_authorized: - "Vous n'êtes pas autorisé à accéder à cette ressource.", + "Vous n'êtes pas autorisé(e) à accéder à cette ressource.", }, validation: { required: 'Ce champ est requis', From cc5a6f006bbaae9610a017679d8298df7756dc92 Mon Sep 17 00:00:00 2001 From: "HH: Andrico Karoulla" Date: Mon, 5 Jul 2021 22:42:13 +0100 Subject: [PATCH 9/9] remove unnecessary use of location object --- packages/ra-core/src/auth/useLogoutIfAccessDenied.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts b/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts index 0b42fa69a3e..22b84ae11ea 100644 --- a/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts +++ b/packages/ra-core/src/auth/useLogoutIfAccessDenied.ts @@ -4,7 +4,6 @@ import useAuthProvider from './useAuthProvider'; import useLogout from './useLogout'; import { useNotify } from '../sideEffect'; import { useHistory } from 'react-router'; -import { LocationDescriptorObject } from 'history'; let timer; @@ -95,11 +94,7 @@ const useLogoutIfAccessDenied = (): LogoutIfAccessDenied => { if (logoutUser) { logout({}, redirectTo); } else { - const newLocation: LocationDescriptorObject = { - pathname: redirectTo, - }; - - history.push(newLocation); + history.push(redirectTo); } return true;