Merge pull request #10893 from strapi/ds-migration/roles-list-header

[v4] migrate roles page header
This commit is contained in:
cyril lopez 2021-09-07 10:35:53 +02:00 committed by GitHub
commit 13fb791c67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 202 additions and 175 deletions

View File

@ -1,195 +1,62 @@
import React, { useCallback, useMemo, useState } from 'react';
import { List, Header } from '@buffetjs/custom';
import { Helmet } from 'react-helmet';
import React from 'react';
import { Button, HeaderLayout, Layout, Main } from '@strapi/parts';
import { AddIcon } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { useTracking, SettingsPageTitle, CheckPermissions } from '@strapi/helper-plugin';
import { useHistory } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
useRBAC,
PopUpWarning,
request,
useTracking,
useNotification,
useOverlayBlocker,
} from '@strapi/helper-plugin';
import permissions from '../../../permissions';
import { EmptyRole, RoleListWrapper, RoleRow } from '../../../components/Roles';
import { useRolesList } from '../../../hooks';
import BaselineAlignment from './BaselineAlignment';
import pluginId from '../../../pluginId';
import { getTrad } from '../../../utils';
import pluginId from '../../../pluginId';
import permissions from '../../../permissions';
const RoleListPage = () => {
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
const { formatMessage } = useIntl();
const { push } = useHistory();
const toggleNotification = useNotification();
const { lockApp, unlockApp } = useOverlayBlocker();
const [modalToDelete, setModalDelete] = useState();
const [shouldRefetchData, setShouldRefetchData] = useState(false);
const [showModalConfirmButtonLoading, setModalButtonLoading] = useState(false);
const updatePermissions = useMemo(() => {
return {
update: permissions.updateRole,
create: permissions.createRole,
delete: permissions.deleteRole,
read: permissions.readRoles,
};
}, []);
const {
isLoading: isLoadingForPermissions,
allowedActions: { canCreate, canUpdate, canDelete, canRead },
} = useRBAC(updatePermissions);
const shouldFetchData = !isLoadingForPermissions && canRead;
const { roles, getData, isLoading } = useRolesList(shouldFetchData);
const handleGoTo = id => {
if (canUpdate) {
push(`/settings/${pluginId}/roles/${id}`);
}
};
const handleDelete = () => {
lockApp();
setModalButtonLoading(true);
Promise.resolve(
request(`/${pluginId}/roles/${modalToDelete}`, {
method: 'DELETE',
})
)
.then(() => {
setShouldRefetchData(true);
toggleNotification({
type: 'success',
message: { id: getTrad('Settings.roles.deleted') },
});
})
.catch(err => {
console.error(err);
toggleNotification({
type: 'warning',
message: { id: 'notification.error' },
});
})
.finally(() => {
setModalDelete(null);
unlockApp();
});
};
const handleClosedModalDelete = () => {
if (shouldRefetchData) {
getData();
}
setModalButtonLoading(false);
setShouldRefetchData(false);
};
const handleNewRoleClick = () => {
trackUsage('willCreateRole');
push(`/settings/${pluginId}/roles/new`);
};
/* eslint-disable indent */
const headerActions = canCreate
? [
{
label: formatMessage({
id: getTrad('List.button.roles'),
defaultMessage: 'Add new role',
}),
onClick: handleNewRoleClick,
color: 'primary',
type: 'button',
icon: true,
},
]
: [];
/* eslint-enable indent */
const checkCanDeleteRole = useCallback(
role => {
return canDelete && !['public', 'authenticated'].includes(role.type);
},
[canDelete]
);
const getLinks = role => {
const links = [];
if (canUpdate) {
links.push({
icon: <FontAwesomeIcon icon="pencil-alt" />,
onClick: () => handleGoTo(role.id),
});
}
if (checkCanDeleteRole(role)) {
links.push({
icon: <FontAwesomeIcon icon="trash-alt" />,
onClick: e => {
e.preventDefault();
setModalDelete(role.id);
e.stopPropagation();
},
});
}
return links;
};
const pageTitle = formatMessage({
id: getTrad('HeaderNav.link.roles'),
defaultMessage: 'Roles',
});
return (
<>
<Helmet title={formatMessage({ id: getTrad('page.title') })} />
<Header
icon
title={{
label: formatMessage({
id: 'Settings.roles.title',
defaultMessage: 'Roles & Permissions',
}),
}}
content={formatMessage({
id: 'Settings.roles.list.description',
defaultMessage: 'Define the roles and permissions for your users.',
<Layout>
<SettingsPageTitle name={pageTitle} />
<Main
labelledBy={formatMessage({
id: getTrad('HeaderNav.link.roles'),
defaultMessage: 'Roles',
})}
actions={headerActions}
// Show a loader in the header while requesting data
isLoading={isLoading || isLoadingForPermissions}
/>
<BaselineAlignment />
{canRead && (
<RoleListWrapper>
<List
title={formatMessage(
{
id: `Settings.roles.list.title${roles.length > 1 ? '.plural' : '.singular'}`,
},
{ number: roles.length }
)}
items={roles}
isLoading={isLoading || isLoadingForPermissions}
customRowComponent={role => (
<RoleRow onClick={() => handleGoTo(role.id)} links={getLinks(role)} role={role} />
)}
/>
{!roles && !isLoading && !isLoadingForPermissions && <EmptyRole />}
<PopUpWarning
isOpen={Boolean(modalToDelete)}
onConfirm={handleDelete}
onClosed={handleClosedModalDelete}
toggleModal={() => setModalDelete(null)}
isConfirmButtonLoading={showModalConfirmButtonLoading}
/>
</RoleListWrapper>
)}
</>
>
<HeaderLayout
as="h1"
id="roles"
title={formatMessage({
id: 'Settings.roles.title',
defaultMessage: 'Roles',
})}
subtitle={formatMessage({
id: 'Settings.roles.list.description',
defaultMessage: 'List of roles',
})}
primaryAction={
<CheckPermissions permissions={permissions.createRole}>
<Button onClick={handleNewRoleClick} startIcon={<AddIcon />}>
{formatMessage({
id: getTrad('List.button.roles'),
defaultMessage: 'Add new role',
})}
</Button>
</CheckPermissions>
}
/>
</Main>
</Layout>
);
};

View File

@ -0,0 +1,160 @@
import React from 'react';
import { render } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import { ThemeProvider, lightTheme } from '@strapi/parts';
import RoleListPage from '../index';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useNotification: jest.fn(),
CheckPermissions: jest.fn(() => <div />),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: jest.fn(),
}),
}));
const App = (
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{ en: {} }} textComponent="span">
<RoleListPage />
</IntlProvider>
</ThemeProvider>
);
describe('Admin | containers | RoleListPage', () => {
it('renders and matches the snapshot', () => {
const {
container: { firstChild },
} = render(App);
expect(firstChild).toMatchInlineSnapshot(`
.c7 {
font-weight: 600;
font-size: 2rem;
line-height: 1.25;
color: #32324d;
}
.c8 {
font-weight: 400;
font-size: 0.875rem;
line-height: 1.43;
color: #666687;
}
.c9 {
font-size: 1rem;
line-height: 1.5;
}
.c1 {
padding-bottom: 56px;
}
.c4 {
background: #f6f6f9;
padding-top: 56px;
padding-right: 56px;
padding-bottom: 56px;
padding-left: 56px;
}
.c5 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c6 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c0 {
display: grid;
grid-template-columns: 1fr;
}
.c2 {
overflow-x: hidden;
}
.c3 {
outline: none;
}
<div
class="c0"
>
<div
class="c1 c2"
>
<main
aria-labelledby="Roles"
class="c3"
id="main-content"
tabindex="-1"
>
<div
class=""
style="height: 0px;"
>
<div
class="c4"
data-strapi-header="true"
>
<div
class="c5"
>
<div
class="c6"
>
<h1
class="c7"
id="roles"
>
Roles
</h1>
</div>
<div />
</div>
<p
class="c8 c9"
>
List of roles
</p>
</div>
</div>
</main>
</div>
</div>
`);
});
});