mirror of
https://github.com/strapi/strapi.git
synced 2025-10-26 07:30:17 +00:00
Merge branch 'v4/ds-migration' into webhooks-listview-ds
This commit is contained in:
commit
f79f6eda59
@ -118,5 +118,6 @@ module.exports = {
|
|||||||
'react/state-in-constructor': 0,
|
'react/state-in-constructor': 0,
|
||||||
'react/static-property-placement': 0,
|
'react/static-property-placement': 0,
|
||||||
'react/display-name': 0,
|
'react/display-name': 0,
|
||||||
|
'react/jsx-wrap-multilines': 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,48 +1,66 @@
|
|||||||
import React from 'react';
|
import { Box, Row, Td, Text, Tr, IconButton, BaseCheckbox } from '@strapi/parts';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CustomRow } from '@buffetjs/styles';
|
import React from 'react';
|
||||||
import { IconLinks, Text } from '@buffetjs/core';
|
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
import RoleDescription from './RoleDescription';
|
import RoleDescription from './RoleDescription';
|
||||||
|
|
||||||
const RoleRow = ({ role, onClick, links, prefix }) => {
|
const RoleRow = ({ onToggle, id, name, description, usersCount, isChecked, icons }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const number = role.usersCount;
|
|
||||||
const text = formatMessage(
|
const usersCountText = formatMessage(
|
||||||
{ id: `Roles.RoleRow.user-count.${number > 1 ? 'plural' : 'singular'}` },
|
{ id: `Roles.RoleRow.user-count.${usersCount > 1 ? 'plural' : 'singular'}` },
|
||||||
{ number }
|
{ number: usersCount }
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomRow onClick={onClick}>
|
<Tr>
|
||||||
{prefix && <td style={{ width: 55 }}>{prefix}</td>}
|
{Boolean(onToggle) && (
|
||||||
<td>
|
<Td>
|
||||||
<Text fontWeight="semiBold">{role.name}</Text>
|
<BaseCheckbox
|
||||||
</td>
|
name="role-checkbox"
|
||||||
<td>
|
onValueChange={() => onToggle(id)}
|
||||||
<RoleDescription>{role.description}</RoleDescription>
|
value={isChecked}
|
||||||
</td>
|
aria-label={formatMessage({ id: `Roles.RoleRow.select-all` }, { name })}
|
||||||
<td>
|
/>
|
||||||
<Text>{text}</Text>
|
</Td>
|
||||||
</td>
|
)}
|
||||||
<td>
|
<Td>
|
||||||
<IconLinks links={links} />
|
<Text textColor="neutral800">{name}</Text>
|
||||||
</td>
|
</Td>
|
||||||
</CustomRow>
|
<Td>
|
||||||
|
<RoleDescription textColor="neutral800">{description}</RoleDescription>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Text textColor="neutral800">{usersCountText}</Text>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Row>
|
||||||
|
{icons.map((icon, i) =>
|
||||||
|
icon ? (
|
||||||
|
<Box key={icon.label} paddingLeft={i === 0 ? 0 : 1}>
|
||||||
|
<IconButton onClick={icon.onClick} label={icon.label} noBorder icon={icon.icon} />
|
||||||
|
</Box>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
RoleRow.defaultProps = {
|
RoleRow.defaultProps = {
|
||||||
onClick: null,
|
onToggle: undefined,
|
||||||
prefix: null,
|
isChecked: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
RoleRow.propTypes = {
|
RoleRow.propTypes = {
|
||||||
links: PropTypes.array.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
onClick: PropTypes.func,
|
name: PropTypes.string.isRequired,
|
||||||
prefix: PropTypes.node,
|
description: PropTypes.string.isRequired,
|
||||||
role: PropTypes.object.isRequired,
|
usersCount: PropTypes.number.isRequired,
|
||||||
|
icons: PropTypes.array.isRequired,
|
||||||
|
onToggle: PropTypes.func,
|
||||||
|
isChecked: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RoleRow;
|
export default RoleRow;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useReducer } from 'react';
|
import { useEffect, useReducer, useCallback } from 'react';
|
||||||
import { request, useNotification } from '@strapi/helper-plugin';
|
import { request, useNotification } from '@strapi/helper-plugin';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import init from './init';
|
import init from './init';
|
||||||
@ -17,7 +17,7 @@ const useRolesList = (shouldFetchData = true) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [shouldFetchData]);
|
}, [shouldFetchData]);
|
||||||
|
|
||||||
const fetchRolesList = async () => {
|
const fetchRolesList = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'GET_DATA',
|
type: 'GET_DATA',
|
||||||
@ -43,7 +43,7 @@ const useRolesList = (shouldFetchData = true) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}, [toggleNotification]);
|
||||||
|
|
||||||
return { roles, isLoading, getData: fetchRolesList };
|
return { roles, isLoading, getData: fetchRolesList };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -81,7 +81,6 @@ const Admin = () => {
|
|||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<AppLayout
|
<AppLayout
|
||||||
sideNav={
|
sideNav={
|
||||||
// eslint-disable-next-line react/jsx-wrap-multilines
|
|
||||||
<LeftMenu
|
<LeftMenu
|
||||||
generalSectionLinks={generalSectionLinks}
|
generalSectionLinks={generalSectionLinks}
|
||||||
pluginsSectionLinks={pluginsSectionLinks}
|
pluginsSectionLinks={pluginsSectionLinks}
|
||||||
|
|||||||
@ -1,148 +1,193 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import { useQuery, useTracking } from '@strapi/helper-plugin';
|
||||||
import { List, Header } from '@buffetjs/custom';
|
import { AddIcon, DeleteIcon, EditIcon, Duplicate } from '@strapi/icons';
|
||||||
import { Button } from '@buffetjs/core';
|
import {
|
||||||
import { Duplicate, Pencil, Plus } from '@buffetjs/icons';
|
Button,
|
||||||
|
ContentLayout,
|
||||||
|
HeaderLayout,
|
||||||
|
Table,
|
||||||
|
TableLabel,
|
||||||
|
Tbody,
|
||||||
|
TFooter,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tr,
|
||||||
|
VisuallyHidden,
|
||||||
|
Main,
|
||||||
|
} from '@strapi/parts';
|
||||||
import matchSorter from 'match-sorter';
|
import matchSorter from 'match-sorter';
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { EmptyRole, RoleRow } from '../../../components/Roles';
|
||||||
import { ListButton, useTracking, useQuery, useRBAC } from '@strapi/helper-plugin';
|
|
||||||
import adminPermissions from '../../../permissions';
|
|
||||||
import PageTitle from '../../../components/SettingsPageTitle';
|
import PageTitle from '../../../components/SettingsPageTitle';
|
||||||
import { EmptyRole, RoleListWrapper, RoleRow } from '../../../components/Roles';
|
|
||||||
import { useRolesList, useSettingsHeaderSearchContext } from '../../../hooks';
|
|
||||||
import UpgradePlanModal from '../../../components/UpgradePlanModal';
|
import UpgradePlanModal from '../../../components/UpgradePlanModal';
|
||||||
import BaselineAlignment from './BaselineAlignment';
|
import { useRolesList } from '../../../hooks';
|
||||||
|
|
||||||
const RoleListPage = () => {
|
const useSortedRoles = () => {
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const { push } = useHistory();
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const { trackUsage } = useTracking();
|
|
||||||
const { roles, isLoading } = useRolesList();
|
const { roles, isLoading } = useRolesList();
|
||||||
const { toggleHeaderSearch } = useSettingsHeaderSearchContext();
|
|
||||||
const {
|
|
||||||
allowedActions: { canUpdate },
|
|
||||||
} = useRBAC(adminPermissions.settings.roles);
|
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
const _q = decodeURIComponent(query.get('_q') || '');
|
const _q = decodeURIComponent(query.get('_q') || '');
|
||||||
const results = matchSorter(roles, _q, { keys: ['name', 'description'] });
|
const sortedRoles = matchSorter(roles, _q, { keys: ['name', 'description'] });
|
||||||
|
|
||||||
useEffect(() => {
|
return { isLoading, sortedRoles };
|
||||||
toggleHeaderSearch({ id: 'Settings.permissions.menu.link.roles.label' });
|
};
|
||||||
|
|
||||||
return () => {
|
const useRoleActions = () => {
|
||||||
toggleHeaderSearch();
|
const { formatMessage } = useIntl();
|
||||||
};
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const { trackUsage } = useTracking();
|
||||||
}, []);
|
const { push } = useHistory();
|
||||||
|
|
||||||
const handleGoTo = useCallback(
|
const handleGoTo = useCallback(
|
||||||
id => {
|
id => {
|
||||||
push(`/settings/roles/${id}`);
|
push(`/settings/roles/${id}`);
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
[push]
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleToggle = useCallback(e => {
|
const handleToggle = useCallback(() => {
|
||||||
e.preventDefault();
|
setIsModalOpen(prev => !prev);
|
||||||
e.stopPropagation();
|
|
||||||
setIsOpen(prev => !prev);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggleModalForCreatingRole = useCallback(e => {
|
const handleToggleModalForCreatingRole = useCallback(() => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
trackUsage('didShowRBACUpgradeModal');
|
trackUsage('didShowRBACUpgradeModal');
|
||||||
|
setIsModalOpen(true);
|
||||||
|
}, [trackUsage]);
|
||||||
|
|
||||||
setIsOpen(true);
|
const getIcons = useCallback(
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
role => [
|
||||||
}, []);
|
{
|
||||||
|
onClick: handleToggle,
|
||||||
|
label: formatMessage({ id: 'app.utils.duplicate', defaultMessage: 'Duplicate' }),
|
||||||
|
icon: <Duplicate />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onClick: () => handleGoTo(role.id),
|
||||||
|
label: formatMessage({ id: 'app.utils.edit', defaultMessage: 'Edit' }),
|
||||||
|
icon: <EditIcon />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onClick: handleToggle,
|
||||||
|
label: formatMessage({ id: 'app.utils.delete', defaultMessage: 'Delete' }),
|
||||||
|
icon: <DeleteIcon />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[formatMessage, handleToggle, handleGoTo]
|
||||||
|
);
|
||||||
|
|
||||||
const headerActions = [
|
return {
|
||||||
{
|
isModalOpen,
|
||||||
label: formatMessage({
|
handleToggleModalForCreatingRole,
|
||||||
id: 'Settings.roles.list.button.add',
|
handleToggle,
|
||||||
defaultMessage: 'Add new role',
|
getIcons,
|
||||||
}),
|
};
|
||||||
onClick: handleToggleModalForCreatingRole,
|
};
|
||||||
color: 'primary',
|
|
||||||
type: 'button',
|
|
||||||
icon: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const resultsCount = results.length;
|
const RoleListPage = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const { sortedRoles, isLoading } = useSortedRoles();
|
||||||
|
const {
|
||||||
|
isModalOpen,
|
||||||
|
handleToggle,
|
||||||
|
handleToggleModalForCreatingRole,
|
||||||
|
getIcons,
|
||||||
|
} = useRoleActions();
|
||||||
|
|
||||||
|
const rowCount = sortedRoles.length + 1;
|
||||||
|
const colCount = 5;
|
||||||
|
|
||||||
|
// ! TODO - Add the search input
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Main labelledBy="title">
|
||||||
<PageTitle name="Roles" />
|
<PageTitle name="Roles" />
|
||||||
<Header
|
<HeaderLayout
|
||||||
icon
|
id="title"
|
||||||
title={{
|
primaryAction={
|
||||||
label: formatMessage({
|
<Button onClick={handleToggleModalForCreatingRole} startIcon={<AddIcon />}>
|
||||||
id: 'Settings.roles.title',
|
{formatMessage({
|
||||||
defaultMessage: 'roles',
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
content={formatMessage({
|
|
||||||
id: 'Settings.roles.list.description',
|
|
||||||
defaultMessage: 'List of roles',
|
|
||||||
})}
|
|
||||||
// Show a loader in the header while requesting data
|
|
||||||
isLoading={isLoading}
|
|
||||||
actions={headerActions}
|
|
||||||
/>
|
|
||||||
<BaselineAlignment />
|
|
||||||
<RoleListWrapper>
|
|
||||||
<List
|
|
||||||
title={formatMessage(
|
|
||||||
{
|
|
||||||
id: `Settings.roles.list.title${results.length > 1 ? '.plural' : '.singular'}`,
|
|
||||||
},
|
|
||||||
{ number: resultsCount }
|
|
||||||
)}
|
|
||||||
items={results}
|
|
||||||
isLoading={isLoading}
|
|
||||||
customRowComponent={role => (
|
|
||||||
<RoleRow
|
|
||||||
onClick={() => handleGoTo(role.id)}
|
|
||||||
canUpdate={canUpdate}
|
|
||||||
links={[
|
|
||||||
{
|
|
||||||
icon: <Duplicate fill="#0e1622" />,
|
|
||||||
onClick: handleToggle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: canUpdate ? <Pencil fill="#0e1622" /> : null,
|
|
||||||
onClick: () => {
|
|
||||||
handleGoTo(role.id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <FontAwesomeIcon icon="trash-alt" />,
|
|
||||||
onClick: handleToggle,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
role={role}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{!resultsCount && !isLoading && <EmptyRole />}
|
|
||||||
<ListButton>
|
|
||||||
<Button
|
|
||||||
onClick={handleToggleModalForCreatingRole}
|
|
||||||
icon={<Plus fill="#007eff" width="11px" height="11px" />}
|
|
||||||
label={formatMessage({
|
|
||||||
id: 'Settings.roles.list.button.add',
|
id: 'Settings.roles.list.button.add',
|
||||||
defaultMessage: 'Add new role',
|
defaultMessage: 'Add new role',
|
||||||
})}
|
})}
|
||||||
/>
|
</Button>
|
||||||
</ListButton>
|
}
|
||||||
</RoleListWrapper>
|
title={formatMessage({
|
||||||
<UpgradePlanModal isOpen={isOpen} onToggle={handleToggle} />
|
id: 'Settings.roles.title',
|
||||||
</>
|
defaultMessage: 'roles',
|
||||||
|
})}
|
||||||
|
subtitle={formatMessage({
|
||||||
|
id: 'Settings.roles.list.description',
|
||||||
|
defaultMessage: 'List of roles',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<ContentLayout>
|
||||||
|
<Table
|
||||||
|
colCount={colCount}
|
||||||
|
rowCount={rowCount}
|
||||||
|
footer={
|
||||||
|
<TFooter onClick={handleToggleModalForCreatingRole} icon={<AddIcon />}>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.roles.list.button.add',
|
||||||
|
defaultMessage: 'Add new role',
|
||||||
|
})}
|
||||||
|
</TFooter>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>
|
||||||
|
<TableLabel>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.roles.list.header.name',
|
||||||
|
defaultMessage: 'Name',
|
||||||
|
})}
|
||||||
|
</TableLabel>
|
||||||
|
</Th>
|
||||||
|
<Th>
|
||||||
|
<TableLabel>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.roles.list.header.description',
|
||||||
|
defaultMessage: 'Description',
|
||||||
|
})}
|
||||||
|
</TableLabel>
|
||||||
|
</Th>
|
||||||
|
<Th>
|
||||||
|
<TableLabel>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.roles.list.header.users',
|
||||||
|
defaultMessage: 'Users',
|
||||||
|
})}
|
||||||
|
</TableLabel>
|
||||||
|
</Th>
|
||||||
|
<Th>
|
||||||
|
<VisuallyHidden>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.roles.list.header.actions',
|
||||||
|
defaultMessage: 'Actions',
|
||||||
|
})}
|
||||||
|
</VisuallyHidden>
|
||||||
|
</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{sortedRoles?.map(role => (
|
||||||
|
<RoleRow
|
||||||
|
key={role.id}
|
||||||
|
id={role.id}
|
||||||
|
name={role.name}
|
||||||
|
description={role.description}
|
||||||
|
usersCount={role.usersCount}
|
||||||
|
icons={getIcons(role)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
{!rowCount && !isLoading && <EmptyRole />}
|
||||||
|
</ContentLayout>
|
||||||
|
<UpgradePlanModal isOpen={isModalOpen} onToggle={handleToggle} />
|
||||||
|
</Main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,7 @@
|
|||||||
"Roles.ListPage.notification.delete-not-allowed": "A role cannot be deleted if associated with users",
|
"Roles.ListPage.notification.delete-not-allowed": "A role cannot be deleted if associated with users",
|
||||||
"Roles.RoleRow.user-count.plural": "{number} users",
|
"Roles.RoleRow.user-count.plural": "{number} users",
|
||||||
"Roles.RoleRow.user-count.singular": "{number} user",
|
"Roles.RoleRow.user-count.singular": "{number} user",
|
||||||
|
"Roles.RoleRow.select-all": "Select {name} for bulk actions",
|
||||||
"Roles.components.List.empty.withSearch": "There is no role corresponding to the search ({search})...",
|
"Roles.components.List.empty.withSearch": "There is no role corresponding to the search ({search})...",
|
||||||
"Settings.PageTitle": "Settings - {name}",
|
"Settings.PageTitle": "Settings - {name}",
|
||||||
"Settings.application.description": "See your project's details",
|
"Settings.application.description": "See your project's details",
|
||||||
@ -137,6 +138,10 @@
|
|||||||
"Settings.roles.list.description": "List of roles",
|
"Settings.roles.list.description": "List of roles",
|
||||||
"Settings.roles.list.title.plural": "{number} roles",
|
"Settings.roles.list.title.plural": "{number} roles",
|
||||||
"Settings.roles.list.title.singular": "{number} role",
|
"Settings.roles.list.title.singular": "{number} role",
|
||||||
|
"Settings.roles.list.header.name": "Name",
|
||||||
|
"Settings.roles.list.header.description": "Description",
|
||||||
|
"Settings.roles.list.header.users": "Users",
|
||||||
|
"Settings.roles.list.header.actions": "Actions",
|
||||||
"Settings.roles.title": "Roles",
|
"Settings.roles.title": "Roles",
|
||||||
"Settings.roles.title.singular": "role",
|
"Settings.roles.title.singular": "role",
|
||||||
"Settings.sso.description": "Configure the settings for the Single Sign-On feature.",
|
"Settings.sso.description": "Configure the settings for the Single Sign-On feature.",
|
||||||
@ -301,6 +306,8 @@
|
|||||||
"app.utils.add-filter": "Add filter",
|
"app.utils.add-filter": "Add filter",
|
||||||
"app.utils.defaultMessage": " ",
|
"app.utils.defaultMessage": " ",
|
||||||
"app.utils.delete": "Delete",
|
"app.utils.delete": "Delete",
|
||||||
|
"app.utils.duplicate": "Duplicate",
|
||||||
|
"app.utils.edit": "Edit",
|
||||||
"app.utils.errors.file-too-big.message": "The file is too big",
|
"app.utils.errors.file-too-big.message": "The file is too big",
|
||||||
"app.utils.filters": "Filters",
|
"app.utils.filters": "Filters",
|
||||||
"app.utils.placeholder.defaultMessage": " ",
|
"app.utils.placeholder.defaultMessage": " ",
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import moment from 'moment';
|
|||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { get, isEmpty } from 'lodash';
|
import { get, isEmpty } from 'lodash';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { HeaderLayout, Button } from '@strapi/parts';
|
||||||
|
import { AddIcon, EditIcon } from '@strapi/icons';
|
||||||
import {
|
import {
|
||||||
BaselineAlignment,
|
BaselineAlignment,
|
||||||
CheckPagePermissions,
|
CheckPagePermissions,
|
||||||
@ -17,7 +19,6 @@ import { useHistory, useRouteMatch } from 'react-router-dom';
|
|||||||
import adminPermissions from '../../../../../admin/src/permissions';
|
import adminPermissions from '../../../../../admin/src/permissions';
|
||||||
import { useFetchPermissionsLayout, useFetchRole } from '../../../../../admin/src/hooks';
|
import { useFetchPermissionsLayout, useFetchRole } from '../../../../../admin/src/hooks';
|
||||||
import PageTitle from '../../../../../admin/src/components/SettingsPageTitle';
|
import PageTitle from '../../../../../admin/src/components/SettingsPageTitle';
|
||||||
import ContainerFluid from '../../../../../admin/src/components/ContainerFluid';
|
|
||||||
import FormCard from '../../../../../admin/src/components/FormBloc';
|
import FormCard from '../../../../../admin/src/components/FormBloc';
|
||||||
import { ButtonWithNumber } from '../../../../../admin/src/components/Roles';
|
import { ButtonWithNumber } from '../../../../../admin/src/components/Roles';
|
||||||
import SizedInput from '../../../../../admin/src/components/SizedInput';
|
import SizedInput from '../../../../../admin/src/components/SizedInput';
|
||||||
@ -142,7 +143,18 @@ const CreatePage = () => {
|
|||||||
>
|
>
|
||||||
{({ handleSubmit, values, errors, handleReset, handleChange, handleBlur }) => (
|
{({ handleSubmit, values, errors, handleReset, handleChange, handleBlur }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<ContainerFluid padding="0">
|
<>
|
||||||
|
<HeaderLayout
|
||||||
|
primaryAction={<Button startIcon={<AddIcon />}>Add an entry</Button>}
|
||||||
|
secondaryAction={
|
||||||
|
<Button variant="tertiary" startIcon={<EditIcon />}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
title="Other CT"
|
||||||
|
subtitle="36 entries found"
|
||||||
|
as="h1"
|
||||||
|
/>
|
||||||
<Header
|
<Header
|
||||||
title={{
|
title={{
|
||||||
label: formatMessage({
|
label: formatMessage({
|
||||||
@ -202,7 +214,7 @@ const CreatePage = () => {
|
|||||||
/>
|
/>
|
||||||
</Padded>
|
</Padded>
|
||||||
)}
|
)}
|
||||||
</ContainerFluid>
|
</>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
import React, { useCallback } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import { useNotification } from '@strapi/helper-plugin';
|
|
||||||
import { Pencil, Duplicate } from '@buffetjs/icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { RoleRow as RoleRowBase } from '../../../../../admin/src/components/Roles';
|
|
||||||
import Checkbox from './CustomCheckbox';
|
|
||||||
|
|
||||||
const RoleRow = ({
|
|
||||||
canCreate,
|
|
||||||
canDelete,
|
|
||||||
canUpdate,
|
|
||||||
role,
|
|
||||||
onRoleToggle,
|
|
||||||
onRoleDuplicate,
|
|
||||||
onRoleRemove,
|
|
||||||
selectedRoles,
|
|
||||||
}) => {
|
|
||||||
const { push } = useHistory();
|
|
||||||
const toggleNotification = useNotification();
|
|
||||||
|
|
||||||
const handleRoleSelection = e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
onRoleToggle(role.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickDelete = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (role.usersCount) {
|
|
||||||
toggleNotification({
|
|
||||||
type: 'info',
|
|
||||||
message: { id: 'Roles.ListPage.notification.delete-not-allowed' },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
onRoleRemove(role.id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGoTo = useCallback(() => {
|
|
||||||
push(`/settings/roles/${role.id}`);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [role.id]);
|
|
||||||
|
|
||||||
const prefix = canDelete ? (
|
|
||||||
<Checkbox
|
|
||||||
value={selectedRoles.findIndex(selectedRoleId => selectedRoleId === role.id) !== -1}
|
|
||||||
onClick={handleRoleSelection}
|
|
||||||
name="role-checkbox"
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RoleRowBase
|
|
||||||
onClick={handleGoTo}
|
|
||||||
selectedRoles={selectedRoles}
|
|
||||||
prefix={prefix}
|
|
||||||
role={role}
|
|
||||||
links={[
|
|
||||||
{
|
|
||||||
icon: canCreate ? <Duplicate fill="#0e1622" /> : null,
|
|
||||||
onClick: e => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
onRoleDuplicate(role.id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: canUpdate ? <Pencil fill="#0e1622" /> : null,
|
|
||||||
onClick: handleGoTo,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: canDelete ? <FontAwesomeIcon icon="trash-alt" /> : null,
|
|
||||||
onClick: handleClickDelete,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
RoleRow.defaultProps = {
|
|
||||||
selectedRoles: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
RoleRow.propTypes = {
|
|
||||||
canCreate: PropTypes.bool.isRequired,
|
|
||||||
canDelete: PropTypes.bool.isRequired,
|
|
||||||
canUpdate: PropTypes.bool.isRequired,
|
|
||||||
onRoleToggle: PropTypes.func.isRequired,
|
|
||||||
onRoleDuplicate: PropTypes.func.isRequired,
|
|
||||||
onRoleRemove: PropTypes.func.isRequired,
|
|
||||||
role: PropTypes.object.isRequired,
|
|
||||||
selectedRoles: PropTypes.arrayOf(PropTypes.number),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RoleRow;
|
|
||||||
@ -1,68 +1,76 @@
|
|||||||
import React, { useEffect, useReducer, useRef, useState } from 'react';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import { Button } from '@buffetjs/core';
|
|
||||||
import { List, Header } from '@buffetjs/custom';
|
|
||||||
import { Plus } from '@buffetjs/icons';
|
|
||||||
import matchSorter from 'match-sorter';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
useQuery,
|
LoadingIndicatorPage,
|
||||||
ListButton,
|
|
||||||
PopUpWarning,
|
PopUpWarning,
|
||||||
request,
|
request,
|
||||||
useRBAC,
|
|
||||||
useNotification,
|
useNotification,
|
||||||
LoadingIndicatorPage,
|
useQuery,
|
||||||
|
useRBAC,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
|
import { AddIcon, DeleteIcon, Duplicate, EditIcon } from '@strapi/icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ContentLayout,
|
||||||
|
HeaderLayout,
|
||||||
|
Table,
|
||||||
|
Tbody,
|
||||||
|
TFooter,
|
||||||
|
Thead,
|
||||||
|
Th,
|
||||||
|
Tr,
|
||||||
|
TableLabel,
|
||||||
|
VisuallyHidden,
|
||||||
|
BaseCheckbox,
|
||||||
|
Main,
|
||||||
|
} from '@strapi/parts';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import matchSorter from 'match-sorter';
|
||||||
|
import React, { useCallback, useEffect, useReducer, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import adminPermissions from '../../../../../admin/src/permissions';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { EmptyRole, RoleRow as BaseRoleRow } from '../../../../../admin/src/components/Roles';
|
||||||
import PageTitle from '../../../../../admin/src/components/SettingsPageTitle';
|
import PageTitle from '../../../../../admin/src/components/SettingsPageTitle';
|
||||||
import useSettingsHeaderSearchContext from '../../../../../admin/src/hooks/useSettingsHeaderSearchContext';
|
|
||||||
import { EmptyRole, RoleListWrapper } from '../../../../../admin/src/components/Roles';
|
|
||||||
import { useRolesList } from '../../../../../admin/src/hooks';
|
import { useRolesList } from '../../../../../admin/src/hooks';
|
||||||
import RoleRow from './RoleRow';
|
import adminPermissions from '../../../../../admin/src/permissions';
|
||||||
import BaselineAlignment from './BaselineAlignment';
|
|
||||||
import reducer, { initialState } from './reducer';
|
import reducer, { initialState } from './reducer';
|
||||||
|
|
||||||
const RoleListPage = () => {
|
const useSortedRoles = () => {
|
||||||
const toggleNotification = useNotification();
|
|
||||||
const [isWarningDeleteAllOpened, setIsWarningDeleteAllOpenend] = useState(false);
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const { push } = useHistory();
|
|
||||||
const [{ selectedRoles, showModalConfirmButtonLoading, shouldRefetchData }, dispath] = useReducer(
|
|
||||||
reducer,
|
|
||||||
initialState
|
|
||||||
);
|
|
||||||
const {
|
const {
|
||||||
isLoading: isLoadingForPermissions,
|
isLoading: isLoadingForPermissions,
|
||||||
allowedActions: { canCreate, canDelete, canRead, canUpdate },
|
allowedActions: { canCreate, canDelete, canRead, canUpdate },
|
||||||
} = useRBAC(adminPermissions.settings.roles);
|
} = useRBAC(adminPermissions.settings.roles);
|
||||||
const { getData, roles, isLoading } = useRolesList(false);
|
const { getData, roles, isLoading } = useRolesList(false);
|
||||||
const getDataRef = useRef(getData);
|
|
||||||
const { toggleHeaderSearch } = useSettingsHeaderSearchContext();
|
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
const _q = decodeURIComponent(query.get('_q') || '');
|
const _q = decodeURIComponent(query.get('_q') || '');
|
||||||
const results = matchSorter(roles, _q, { keys: ['name', 'description'] });
|
const sortedRoles = matchSorter(roles, _q, { keys: ['name', 'description'] });
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Show the search bar only if the user is allowed to read
|
|
||||||
if (canRead) {
|
|
||||||
toggleHeaderSearch({ id: 'Settings.permissions.menu.link.roles.label' });
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (canRead) {
|
|
||||||
toggleHeaderSearch();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [canRead]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoadingForPermissions && canRead) {
|
if (!isLoadingForPermissions && canRead) {
|
||||||
getDataRef.current();
|
getData();
|
||||||
}
|
}
|
||||||
}, [isLoadingForPermissions, canRead]);
|
}, [isLoadingForPermissions, canRead, getData]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoadingForPermissions,
|
||||||
|
canCreate,
|
||||||
|
canDelete,
|
||||||
|
canRead,
|
||||||
|
canUpdate,
|
||||||
|
isLoading,
|
||||||
|
getData,
|
||||||
|
sortedRoles,
|
||||||
|
roles,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useRoleActions = ({ getData, canCreate, canDelete, canUpdate, roles, sortedRoles }) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const toggleNotification = useNotification();
|
||||||
|
const [isWarningDeleteAllOpened, setIsWarningDeleteAllOpenend] = useState(false);
|
||||||
|
const { push } = useHistory();
|
||||||
|
const [
|
||||||
|
{ selectedRoles, showModalConfirmButtonLoading, shouldRefetchData },
|
||||||
|
dispatch,
|
||||||
|
] = useReducer(reducer, initialState);
|
||||||
|
|
||||||
const handleClosedModal = () => {
|
const handleClosedModal = () => {
|
||||||
if (shouldRefetchData) {
|
if (shouldRefetchData) {
|
||||||
@ -70,14 +78,14 @@ const RoleListPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Empty the selected ids when the modal closes
|
// Empty the selected ids when the modal closes
|
||||||
dispath({
|
dispatch({
|
||||||
type: 'RESET_DATA_TO_DELETE',
|
type: 'RESET_DATA_TO_DELETE',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmDeleteData = async () => {
|
const handleConfirmDeleteData = async () => {
|
||||||
try {
|
try {
|
||||||
dispath({
|
dispatch({
|
||||||
type: 'ON_REMOVE_ROLES',
|
type: 'ON_REMOVE_ROLES',
|
||||||
});
|
});
|
||||||
const filteredRoles = selectedRoles.filter(currentId => {
|
const filteredRoles = selectedRoles.filter(currentId => {
|
||||||
@ -103,7 +111,7 @@ const RoleListPage = () => {
|
|||||||
|
|
||||||
// Empty the selectedRolesId and set the shouldRefetchData to true so the
|
// Empty the selectedRolesId and set the shouldRefetchData to true so the
|
||||||
// list is updated when closing the modal
|
// list is updated when closing the modal
|
||||||
dispath({
|
dispatch({
|
||||||
type: 'ON_REMOVE_ROLES_SUCCEEDED',
|
type: 'ON_REMOVE_ROLES_SUCCEEDED',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -128,123 +136,275 @@ const RoleListPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicateRole = id => {
|
const onRoleDuplicate = useCallback(
|
||||||
push(`/settings/roles/duplicate/${id}`);
|
id => {
|
||||||
};
|
push(`/settings/roles/duplicate/${id}`);
|
||||||
|
},
|
||||||
|
[push]
|
||||||
|
);
|
||||||
|
|
||||||
const handleNewRoleClick = () => push('/settings/roles/new');
|
const handleNewRoleClick = () => push('/settings/roles/new');
|
||||||
|
|
||||||
const handleRemoveRole = roleId => {
|
const onRoleRemove = useCallback(roleId => {
|
||||||
dispath({
|
dispatch({
|
||||||
type: 'SET_ROLE_TO_DELETE',
|
type: 'SET_ROLE_TO_DELETE',
|
||||||
id: roleId,
|
id: roleId,
|
||||||
});
|
});
|
||||||
|
|
||||||
handleToggleModal();
|
handleToggleModal();
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleRoleToggle = roleId => {
|
const onRoleToggle = roleId => {
|
||||||
dispath({
|
dispatch({
|
||||||
type: 'ON_SELECTION',
|
type: 'ON_SELECTION',
|
||||||
id: roleId,
|
id: roleId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onAllRolesToggle = () =>
|
||||||
|
dispatch({
|
||||||
|
type: 'TOGGLE_ALL',
|
||||||
|
ids: sortedRoles.map(r => r.id),
|
||||||
|
});
|
||||||
|
|
||||||
const handleToggleModal = () => setIsWarningDeleteAllOpenend(prev => !prev);
|
const handleToggleModal = () => setIsWarningDeleteAllOpenend(prev => !prev);
|
||||||
|
|
||||||
/* eslint-disable indent */
|
const handleGoTo = useCallback(
|
||||||
const headerActions = canCreate
|
id => {
|
||||||
? [
|
push(`/settings/roles/${id}`);
|
||||||
{
|
},
|
||||||
label: formatMessage({
|
[push]
|
||||||
id: 'Settings.roles.list.button.add',
|
);
|
||||||
defaultMessage: 'Add new role',
|
|
||||||
}),
|
|
||||||
onClick: handleNewRoleClick,
|
|
||||||
color: 'primary',
|
|
||||||
type: 'button',
|
|
||||||
icon: true,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [];
|
|
||||||
/* eslint-enable indent */
|
|
||||||
|
|
||||||
const resultsCount = results.length;
|
const handleClickDelete = useCallback(
|
||||||
|
(e, role) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (role.usersCount) {
|
||||||
|
toggleNotification({
|
||||||
|
type: 'info',
|
||||||
|
message: { id: 'Roles.ListPage.notification.delete-not-allowed' },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onRoleRemove(role.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[toggleNotification, onRoleRemove]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClickDuplicate = useCallback(
|
||||||
|
(e, role) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onRoleDuplicate(role.id);
|
||||||
|
},
|
||||||
|
[onRoleDuplicate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getIcons = useCallback(
|
||||||
|
role => [
|
||||||
|
...(canCreate
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
onClick: e => handleClickDuplicate(e, role),
|
||||||
|
label: formatMessage({ id: 'app.utils.duplicate', defaultMessage: 'Duplicate' }),
|
||||||
|
icon: <Duplicate />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(canUpdate
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
onClick: () => handleGoTo(role.id),
|
||||||
|
label: formatMessage({ id: 'app.utils.edit', defaultMessage: 'Edit' }),
|
||||||
|
icon: <EditIcon />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(canDelete
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
onClick: e => handleClickDelete(e, role),
|
||||||
|
label: formatMessage({ id: 'app.utils.delete', defaultMessage: 'Delete' }),
|
||||||
|
icon: <DeleteIcon />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
formatMessage,
|
||||||
|
handleClickDelete,
|
||||||
|
handleClickDuplicate,
|
||||||
|
handleGoTo,
|
||||||
|
canCreate,
|
||||||
|
canUpdate,
|
||||||
|
canDelete,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleClosedModal,
|
||||||
|
handleConfirmDeleteData,
|
||||||
|
handleNewRoleClick,
|
||||||
|
onRoleToggle,
|
||||||
|
onAllRolesToggle,
|
||||||
|
getIcons,
|
||||||
|
selectedRoles,
|
||||||
|
isWarningDeleteAllOpened,
|
||||||
|
showModalConfirmButtonLoading,
|
||||||
|
handleToggleModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const RoleListPage = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const {
|
||||||
|
isLoadingForPermissions,
|
||||||
|
canCreate,
|
||||||
|
canRead,
|
||||||
|
canDelete,
|
||||||
|
canUpdate,
|
||||||
|
isLoading,
|
||||||
|
getData,
|
||||||
|
sortedRoles,
|
||||||
|
roles,
|
||||||
|
} = useSortedRoles();
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleClosedModal,
|
||||||
|
handleConfirmDeleteData,
|
||||||
|
handleNewRoleClick,
|
||||||
|
onRoleToggle,
|
||||||
|
onAllRolesToggle,
|
||||||
|
getIcons,
|
||||||
|
selectedRoles,
|
||||||
|
isWarningDeleteAllOpened,
|
||||||
|
showModalConfirmButtonLoading,
|
||||||
|
handleToggleModal,
|
||||||
|
} = useRoleActions({ getData, canCreate, canDelete, canUpdate, roles, sortedRoles });
|
||||||
|
|
||||||
|
// ! TODO - Show the search bar only if the user is allowed to read - add the search input
|
||||||
|
// canRead
|
||||||
|
|
||||||
|
const rowCount = sortedRoles.length;
|
||||||
|
const colCount = sortedRoles.length ? Object.keys(sortedRoles[0]).length : 0;
|
||||||
|
|
||||||
|
const isAllEntriesIndeterminate = selectedRoles.length
|
||||||
|
? selectedRoles.length !== rowCount
|
||||||
|
: false;
|
||||||
|
const isAllChecked = selectedRoles.length ? selectedRoles.length === rowCount : false;
|
||||||
|
|
||||||
if (isLoadingForPermissions) {
|
if (isLoadingForPermissions) {
|
||||||
return <LoadingIndicatorPage />;
|
return <LoadingIndicatorPage />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Main labelledBy="title">
|
||||||
<PageTitle name="Roles" />
|
<PageTitle name="Roles" />
|
||||||
<Header
|
<HeaderLayout
|
||||||
title={{
|
id="title"
|
||||||
label: formatMessage({
|
primaryAction={
|
||||||
id: 'Settings.roles.title',
|
canCreate ? (
|
||||||
defaultMessage: 'roles',
|
<Button onClick={handleNewRoleClick} startIcon={<AddIcon />}>
|
||||||
}),
|
{formatMessage({
|
||||||
}}
|
id: 'Settings.roles.list.button.add',
|
||||||
content={formatMessage({
|
defaultMessage: 'Add new role',
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
title={formatMessage({
|
||||||
|
id: 'Settings.roles.title',
|
||||||
|
defaultMessage: 'roles',
|
||||||
|
})}
|
||||||
|
subtitle={formatMessage({
|
||||||
id: 'Settings.roles.list.description',
|
id: 'Settings.roles.list.description',
|
||||||
defaultMessage: 'List of roles',
|
defaultMessage: 'List of roles',
|
||||||
})}
|
})}
|
||||||
actions={headerActions}
|
as="h2"
|
||||||
isLoading={isLoading}
|
|
||||||
/>
|
/>
|
||||||
<BaselineAlignment />
|
|
||||||
{canRead && (
|
{canRead && (
|
||||||
<RoleListWrapper>
|
<ContentLayout>
|
||||||
<List
|
<Table
|
||||||
title={formatMessage(
|
colCount={colCount}
|
||||||
{
|
rowCount={rowCount}
|
||||||
id: `Settings.roles.list.title${resultsCount > 1 ? '.plural' : '.singular'}`,
|
footer={
|
||||||
defaultMessage: `{number} ${resultsCount > 1 ? 'roles' : 'role'}`,
|
canCreate ? (
|
||||||
},
|
<TFooter onClick={handleNewRoleClick} icon={<AddIcon />}>
|
||||||
{ number: resultsCount }
|
{formatMessage({
|
||||||
)}
|
id: 'Settings.roles.list.button.add',
|
||||||
isLoading={isLoading}
|
defaultMessage: 'Add new role',
|
||||||
/* eslint-disable indent */
|
})}
|
||||||
button={
|
</TFooter>
|
||||||
canDelete
|
) : null
|
||||||
? {
|
|
||||||
color: 'delete',
|
|
||||||
disabled: selectedRoles.length === 0,
|
|
||||||
label: formatMessage({ id: 'app.utils.delete', defaultMessage: 'Delete' }),
|
|
||||||
onClick: handleToggleModal,
|
|
||||||
type: 'button',
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
/* eslint-enable indent */
|
>
|
||||||
items={results}
|
<Thead>
|
||||||
customRowComponent={role => (
|
<Tr>
|
||||||
<RoleRow
|
{!!onRoleToggle && (
|
||||||
canCreate={canCreate}
|
<Th>
|
||||||
canDelete={canDelete}
|
<BaseCheckbox
|
||||||
canUpdate={canUpdate}
|
aria-label="Select all entries"
|
||||||
selectedRoles={selectedRoles}
|
indeterminate={isAllEntriesIndeterminate}
|
||||||
onRoleDuplicate={handleDuplicateRole}
|
value={isAllChecked}
|
||||||
onRoleRemove={handleRemoveRole}
|
onChange={onAllRolesToggle}
|
||||||
onRoleToggle={handleRoleToggle}
|
/>
|
||||||
role={role}
|
</Th>
|
||||||
/>
|
)}
|
||||||
)}
|
<Th>
|
||||||
/>
|
<TableLabel>
|
||||||
{!resultsCount && !isLoading && <EmptyRole />}
|
{formatMessage({
|
||||||
{canCreate && (
|
id: 'Settings.roles.list.header.name',
|
||||||
<ListButton>
|
defaultMessage: 'Name',
|
||||||
<Button
|
})}
|
||||||
onClick={handleNewRoleClick}
|
</TableLabel>
|
||||||
icon={<Plus fill="#007eff" width="11px" height="11px" />}
|
</Th>
|
||||||
label={formatMessage({
|
<Th>
|
||||||
id: 'Settings.roles.list.button.add',
|
<TableLabel>
|
||||||
defaultMessage: 'Add new role',
|
{formatMessage({
|
||||||
})}
|
id: 'Settings.roles.list.header.description',
|
||||||
/>
|
defaultMessage: 'Description',
|
||||||
</ListButton>
|
})}
|
||||||
)}
|
</TableLabel>
|
||||||
</RoleListWrapper>
|
</Th>
|
||||||
|
<Th>
|
||||||
|
<TableLabel>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.roles.list.header.users',
|
||||||
|
defaultMessage: 'Users',
|
||||||
|
})}
|
||||||
|
</TableLabel>
|
||||||
|
</Th>
|
||||||
|
<Th>
|
||||||
|
<VisuallyHidden>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.roles.list.header.actions',
|
||||||
|
defaultMessage: 'Actions',
|
||||||
|
})}
|
||||||
|
</VisuallyHidden>
|
||||||
|
</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{sortedRoles?.map(role => (
|
||||||
|
<BaseRoleRow
|
||||||
|
key={role.id}
|
||||||
|
id={role.id}
|
||||||
|
onToggle={onRoleToggle}
|
||||||
|
isChecked={
|
||||||
|
selectedRoles.findIndex(selectedRoleId => selectedRoleId === role.id) !== -1
|
||||||
|
}
|
||||||
|
name={role.name}
|
||||||
|
description={role.description}
|
||||||
|
usersCount={role.usersCount}
|
||||||
|
icons={getIcons(role)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
{!rowCount && !isLoading && <EmptyRole />}
|
||||||
|
</ContentLayout>
|
||||||
)}
|
)}
|
||||||
<PopUpWarning
|
<PopUpWarning
|
||||||
isOpen={isWarningDeleteAllOpened}
|
isOpen={isWarningDeleteAllOpened}
|
||||||
@ -253,7 +413,7 @@ const RoleListPage = () => {
|
|||||||
toggleModal={handleToggleModal}
|
toggleModal={handleToggleModal}
|
||||||
isConfirmButtonLoading={showModalConfirmButtonLoading}
|
isConfirmButtonLoading={showModalConfirmButtonLoading}
|
||||||
/>
|
/>
|
||||||
</>
|
</Main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,15 @@ const reducer = (state, action) =>
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'TOGGLE_ALL': {
|
||||||
|
if (state.selectedRoles.length) {
|
||||||
|
draftState.selectedRoles = [];
|
||||||
|
} else {
|
||||||
|
const { ids } = action;
|
||||||
|
draftState.selectedRoles = ids;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'ON_REMOVE_ROLES': {
|
case 'ON_REMOVE_ROLES': {
|
||||||
draftState.showModalConfirmButtonLoading = true;
|
draftState.showModalConfirmButtonLoading = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -106,8 +106,8 @@
|
|||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"react-query": "3.19.0",
|
"react-query": "3.19.0",
|
||||||
"react-redux": "7.2.3",
|
"react-redux": "7.2.3",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "5.2.0",
|
||||||
"react-router-dom": "^5.0.0",
|
"react-router-dom": "5.2.0",
|
||||||
"react-select": "^4.0.2",
|
"react-select": "^4.0.2",
|
||||||
"react-tooltip": "4.2.18",
|
"react-tooltip": "4.2.18",
|
||||||
"react-transition-group": "4.4.1",
|
"react-transition-group": "4.4.1",
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable react/jsx-wrap-multilines */
|
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { useIntl, FormattedMessage } from 'react-intl';
|
import { useIntl, FormattedMessage } from 'react-intl';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const useFocusWhenNavigate = (selector = 'main', dependencies = []) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const mainElement = document.querySelector(selector);
|
||||||
|
|
||||||
|
if (mainElement) {
|
||||||
|
mainElement.focus();
|
||||||
|
window.scrollTo({ top: 0 });
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`[useFocusWhenNavigate] The page does not contain the selector "${selector}" and can't be focused.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, dependencies);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFocusWhenNavigate;
|
||||||
@ -129,6 +129,7 @@ export { default as useAutoReloadOverlayBlocker } from './hooks/useAutoReloadOve
|
|||||||
export { default as useRBACProvider } from './hooks/useRBACProvider';
|
export { default as useRBACProvider } from './hooks/useRBACProvider';
|
||||||
export { default as useRBAC } from './hooks/useRBAC';
|
export { default as useRBAC } from './hooks/useRBAC';
|
||||||
export { default as usePersistentState } from './hooks/usePersistentState';
|
export { default as usePersistentState } from './hooks/usePersistentState';
|
||||||
|
export { default as useFocusWhenNavigate } from './hooks/useFocusWhenNavigate';
|
||||||
|
|
||||||
// Providers
|
// Providers
|
||||||
export { default as LibraryProvider } from './providers/LibraryProvider';
|
export { default as LibraryProvider } from './providers/LibraryProvider';
|
||||||
|
|||||||
@ -0,0 +1,177 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Td,
|
||||||
|
Tbody,
|
||||||
|
Text,
|
||||||
|
TableLabel,
|
||||||
|
VisuallyHidden,
|
||||||
|
Stack,
|
||||||
|
IconButton,
|
||||||
|
} from '@strapi/parts';
|
||||||
|
import EditIcon from '@strapi/icons/EditIcon';
|
||||||
|
import DeleteIcon from '@strapi/icons/DeleteIcon';
|
||||||
|
import DropdownIcon from '@strapi/icons/FilterDropdown';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { getTrad } from '../../utils';
|
||||||
|
|
||||||
|
const ActionIconWrapper = styled.span`
|
||||||
|
svg {
|
||||||
|
transform: ${({ reverse }) => (reverse ? `rotateX(180deg)` : undefined)};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SortingKeys = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
default: 'isDefault',
|
||||||
|
};
|
||||||
|
|
||||||
|
const SortingOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc',
|
||||||
|
};
|
||||||
|
|
||||||
|
const LocaleTable = ({ locales, onDeleteLocale, onEditLocale }) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const [sortingKey, setSortingKey] = useState(SortingKeys.id);
|
||||||
|
const [sortingOrder, setSortingOrder] = useState(SortingOrder.asc);
|
||||||
|
|
||||||
|
const sortedLocales = orderBy([...locales], [sortingKey], [sortingOrder]);
|
||||||
|
|
||||||
|
const handleSorting = key => {
|
||||||
|
if (key === sortingKey) {
|
||||||
|
setSortingOrder(prev => (prev === SortingOrder.asc ? SortingOrder.desc : SortingOrder.asc));
|
||||||
|
} else {
|
||||||
|
setSortingKey(key);
|
||||||
|
setSortingOrder(SortingOrder.asc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isReversedArrow = key => sortingKey === key && sortingOrder === SortingOrder.desc;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table colCount={4} rowCount={sortedLocales.length + 1}>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th
|
||||||
|
action={
|
||||||
|
<IconButton
|
||||||
|
label={formatMessage({ id: getTrad('Settings.locales.list.sort.id') })}
|
||||||
|
icon={
|
||||||
|
<ActionIconWrapper reverse={isReversedArrow(SortingKeys.id)}>
|
||||||
|
<DropdownIcon />
|
||||||
|
</ActionIconWrapper>
|
||||||
|
}
|
||||||
|
noBorder
|
||||||
|
onClick={() => handleSorting(SortingKeys.id)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TableLabel textColor="neutral600">
|
||||||
|
{formatMessage({ id: getTrad('Settings.locales.row.id') })}
|
||||||
|
</TableLabel>
|
||||||
|
</Th>
|
||||||
|
<Th
|
||||||
|
action={
|
||||||
|
<IconButton
|
||||||
|
label={formatMessage({ id: getTrad('Settings.locales.list.sort.displayName') })}
|
||||||
|
icon={
|
||||||
|
<ActionIconWrapper reverse={isReversedArrow(SortingKeys.name)}>
|
||||||
|
<DropdownIcon />
|
||||||
|
</ActionIconWrapper>
|
||||||
|
}
|
||||||
|
noBorder
|
||||||
|
onClick={() => handleSorting(SortingKeys.name)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TableLabel textColor="neutral600">
|
||||||
|
{formatMessage({ id: getTrad('Settings.locales.row.displayName') })}
|
||||||
|
</TableLabel>
|
||||||
|
</Th>
|
||||||
|
<Th
|
||||||
|
action={
|
||||||
|
<IconButton
|
||||||
|
label={formatMessage({ id: getTrad('Settings.locales.list.sort.default') })}
|
||||||
|
icon={
|
||||||
|
<ActionIconWrapper reverse={isReversedArrow(SortingKeys.default)}>
|
||||||
|
<DropdownIcon />
|
||||||
|
</ActionIconWrapper>
|
||||||
|
}
|
||||||
|
noBorder
|
||||||
|
onClick={() => handleSorting(SortingKeys.default)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TableLabel textColor="neutral600">
|
||||||
|
{formatMessage({ id: getTrad('Settings.locales.row.default-locale') })}
|
||||||
|
</TableLabel>
|
||||||
|
</Th>
|
||||||
|
<Th>
|
||||||
|
<VisuallyHidden>Actions</VisuallyHidden>
|
||||||
|
</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{sortedLocales.map(locale => (
|
||||||
|
<Tr key={locale.id}>
|
||||||
|
<Td>
|
||||||
|
<Text textColor="neutral800">{locale.id}</Text>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Text textColor="neutral800">{locale.name}</Text>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Text textColor="neutral800">
|
||||||
|
{locale.isDefault
|
||||||
|
? formatMessage({ id: getTrad('Settings.locales.row.default-locale') })
|
||||||
|
: null}
|
||||||
|
</Text>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Stack horizontal size={1}>
|
||||||
|
{onEditLocale && (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => onEditLocale(locale)}
|
||||||
|
label={formatMessage({ id: getTrad('Settings.list.actions.edit') })}
|
||||||
|
icon={<EditIcon />}
|
||||||
|
noBorder
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{onDeleteLocale && !locale.isDefault && (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => onDeleteLocale(locale)}
|
||||||
|
label={formatMessage({ id: getTrad('Settings.list.actions.delete') })}
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
noBorder
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LocaleTable.defaultProps = {
|
||||||
|
locales: [],
|
||||||
|
onDeleteLocale: undefined,
|
||||||
|
onEditLocale: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
LocaleTable.propTypes = {
|
||||||
|
locales: PropTypes.array,
|
||||||
|
onDeleteLocale: PropTypes.func,
|
||||||
|
onEditLocale: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LocaleTable;
|
||||||
@ -1,89 +1,77 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { EmptyState, ListButton } from '@strapi/helper-plugin';
|
|
||||||
import { List } from '@buffetjs/custom';
|
|
||||||
import { Button } from '@buffetjs/core';
|
|
||||||
import { Plus } from '@buffetjs/icons';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { ContentLayout, EmptyStateLayout, Button, Main, HeaderLayout } from '@strapi/parts';
|
||||||
|
import { useFocusWhenNavigate } from '@strapi/helper-plugin';
|
||||||
|
import AddIcon from '@strapi/icons/AddIcon';
|
||||||
|
import EmptyStateDocument from '@strapi/icons/EmptyStateDocument';
|
||||||
import useLocales from '../../hooks/useLocales';
|
import useLocales from '../../hooks/useLocales';
|
||||||
import LocaleRow from '../LocaleRow';
|
|
||||||
import { getTrad } from '../../utils';
|
import { getTrad } from '../../utils';
|
||||||
import ModalEdit from '../ModalEdit';
|
import ModalEdit from '../ModalEdit';
|
||||||
import ModalDelete from '../ModalDelete';
|
import ModalDelete from '../ModalDelete';
|
||||||
import ModalCreate from '../ModalCreate';
|
import ModalCreate from '../ModalCreate';
|
||||||
|
import LocaleTable from './LocaleTable';
|
||||||
|
|
||||||
const LocaleList = ({ canUpdateLocale, canDeleteLocale, onToggleCreateModal, isCreating }) => {
|
const LocaleList = ({ canUpdateLocale, canDeleteLocale, onToggleCreateModal, isCreating }) => {
|
||||||
const [localeToDelete, setLocaleToDelete] = useState();
|
const [localeToDelete, setLocaleToDelete] = useState();
|
||||||
const [localeToEdit, setLocaleToEdit] = useState();
|
const [localeToEdit, setLocaleToEdit] = useState();
|
||||||
const { locales, isLoading } = useLocales();
|
const { locales } = useLocales();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
useFocusWhenNavigate();
|
||||||
|
|
||||||
// Delete actions
|
// Delete actions
|
||||||
const closeModalToDelete = () => setLocaleToDelete(undefined);
|
const closeModalToDelete = () => setLocaleToDelete(undefined);
|
||||||
const handleDeleteLocale = canDeleteLocale ? setLocaleToDelete : undefined;
|
const handleDeleteLocale = canDeleteLocale ? setLocaleToDelete : undefined;
|
||||||
|
|
||||||
// Edit actions
|
// Edit actions
|
||||||
const closeModalToEdit = () => {
|
const closeModalToEdit = () => setLocaleToEdit(undefined);
|
||||||
setLocaleToEdit(undefined);
|
|
||||||
};
|
|
||||||
const handleEditLocale = canUpdateLocale ? setLocaleToEdit : undefined;
|
const handleEditLocale = canUpdateLocale ? setLocaleToEdit : undefined;
|
||||||
|
|
||||||
if (isLoading || (locales && locales.length > 0)) {
|
|
||||||
const listTitle = isLoading
|
|
||||||
? null
|
|
||||||
: formatMessage(
|
|
||||||
{
|
|
||||||
id: getTrad(
|
|
||||||
`Settings.locales.list.title${locales.length > 1 ? '.plural' : '.singular'}`
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ number: locales.length }
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<List
|
|
||||||
radius="2px"
|
|
||||||
title={listTitle}
|
|
||||||
items={locales}
|
|
||||||
isLoading={isLoading}
|
|
||||||
customRowComponent={locale => (
|
|
||||||
<LocaleRow locale={locale} onDelete={handleDeleteLocale} onEdit={handleEditLocale} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ModalCreate
|
|
||||||
isOpened={isCreating}
|
|
||||||
onClose={onToggleCreateModal}
|
|
||||||
alreadyUsedLocales={locales}
|
|
||||||
/>
|
|
||||||
<ModalDelete localeToDelete={localeToDelete} onClose={closeModalToDelete} />
|
|
||||||
<ModalEdit localeToEdit={localeToEdit} onClose={closeModalToEdit} locales={locales} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Main labelledBy="title" tabIndex={-1}>
|
||||||
<EmptyState
|
<HeaderLayout
|
||||||
title={formatMessage({ id: getTrad('Settings.list.empty.title') })}
|
id="title"
|
||||||
description={formatMessage({ id: getTrad('Settings.list.empty.description') })}
|
primaryAction={
|
||||||
|
<Button startIcon={<AddIcon />} onClick={onToggleCreateModal}>
|
||||||
|
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
title={formatMessage({ id: getTrad('plugin.name') })}
|
||||||
|
subtitle={formatMessage({ id: getTrad('Settings.list.description') })}
|
||||||
/>
|
/>
|
||||||
|
<ContentLayout>
|
||||||
{onToggleCreateModal && (
|
{locales?.length > 0 ? (
|
||||||
<ListButton>
|
<LocaleTable
|
||||||
<Button
|
locales={locales}
|
||||||
label={formatMessage({ id: getTrad('Settings.list.actions.add') })}
|
onDeleteLocale={handleDeleteLocale}
|
||||||
onClick={onToggleCreateModal}
|
onEditLocale={handleEditLocale}
|
||||||
color="primary"
|
|
||||||
type="button"
|
|
||||||
icon={<Plus fill="#007eff" width="11px" height="11px" />}
|
|
||||||
/>
|
/>
|
||||||
</ListButton>
|
) : (
|
||||||
)}
|
<ContentLayout>
|
||||||
|
<EmptyStateLayout
|
||||||
|
icon={<EmptyStateDocument width={undefined} height={undefined} />}
|
||||||
|
content={formatMessage({ id: getTrad('Settings.list.empty.title') })}
|
||||||
|
action={
|
||||||
|
onToggleCreateModal ? (
|
||||||
|
<Button variant="secondary" startIcon={<AddIcon />} onClick={onToggleCreateModal}>
|
||||||
|
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
|
||||||
|
</Button>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ContentLayout>
|
||||||
|
)}
|
||||||
|
</ContentLayout>
|
||||||
|
|
||||||
<ModalCreate isOpened={isCreating} onClose={onToggleCreateModal} />
|
<ModalCreate
|
||||||
</>
|
isOpened={isCreating}
|
||||||
|
onClose={onToggleCreateModal}
|
||||||
|
alreadyUsedLocales={locales}
|
||||||
|
/>
|
||||||
|
<ModalDelete localeToDelete={localeToDelete} onClose={closeModalToDelete} />
|
||||||
|
<ModalEdit localeToEdit={localeToEdit} onClose={closeModalToEdit} locales={locales} />
|
||||||
|
</Main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,77 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { Pencil } from '@buffetjs/icons';
|
|
||||||
import { Text, IconLinks } from '@buffetjs/core';
|
|
||||||
import { CustomRow } from '@buffetjs/styles';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { getTrad } from '../../utils';
|
|
||||||
|
|
||||||
const LocaleSettingsPage = ({ locale, onDelete, onEdit }) => {
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
|
||||||
const links = [];
|
|
||||||
|
|
||||||
if (onEdit) {
|
|
||||||
links.push({
|
|
||||||
icon: (
|
|
||||||
<span aria-label={formatMessage({ id: getTrad('Settings.list.actions.edit') })}>
|
|
||||||
<Pencil fill="#0e1622" />
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
onClick: () => onEdit(locale),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onDelete && !locale.isDefault) {
|
|
||||||
links.push({
|
|
||||||
icon: !locale.isDefault ? (
|
|
||||||
<span aria-label={formatMessage({ id: getTrad('Settings.list.actions.delete') })}>
|
|
||||||
<FontAwesomeIcon icon="trash-alt" />
|
|
||||||
</span>
|
|
||||||
) : null,
|
|
||||||
onClick: e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDelete(locale);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CustomRow onClick={() => onEdit(locale)}>
|
|
||||||
<td>
|
|
||||||
<Text>{locale.code}</Text>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Text fontWeight="regular">{locale.name}</Text>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Text>
|
|
||||||
{locale.isDefault
|
|
||||||
? formatMessage({ id: getTrad('Settings.locales.row.default-locale') })
|
|
||||||
: null}
|
|
||||||
</Text>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<IconLinks links={links} />
|
|
||||||
</td>
|
|
||||||
</CustomRow>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
LocaleSettingsPage.defaultProps = {
|
|
||||||
onDelete: undefined,
|
|
||||||
onEdit: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
LocaleSettingsPage.propTypes = {
|
|
||||||
locale: PropTypes.shape({
|
|
||||||
isDefault: PropTypes.bool,
|
|
||||||
name: PropTypes.string,
|
|
||||||
code: PropTypes.string.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
onDelete: PropTypes.func,
|
|
||||||
onEdit: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LocaleSettingsPage;
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
// eslint-disable-next-line import/prefer-default-export
|
|
||||||
export { default as LocaleRow } from './LocaleRow';
|
|
||||||
@ -1,10 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { BaselineAlignment } from '@strapi/helper-plugin';
|
|
||||||
import { Header } from '@buffetjs/custom';
|
|
||||||
import { Button } from '@buffetjs/core';
|
|
||||||
import { getTrad } from '../../utils';
|
|
||||||
import LocaleList from '../../components/LocaleList';
|
import LocaleList from '../../components/LocaleList';
|
||||||
|
|
||||||
const LocaleSettingsPage = ({
|
const LocaleSettingsPage = ({
|
||||||
@ -13,50 +8,20 @@ const LocaleSettingsPage = ({
|
|||||||
canDeleteLocale,
|
canDeleteLocale,
|
||||||
canUpdateLocale,
|
canUpdateLocale,
|
||||||
}) => {
|
}) => {
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const [isOpenedCreateModal, setIsOpenedCreateModal] = useState(false);
|
const [isOpenedCreateModal, setIsOpenedCreateModal] = useState(false);
|
||||||
|
|
||||||
const handleToggleModalCreate = canCreateLocale
|
const handleToggleModalCreate = canCreateLocale
|
||||||
? () => setIsOpenedCreateModal(s => !s)
|
? () => setIsOpenedCreateModal(s => !s)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const actions = [
|
return canReadLocale ? (
|
||||||
{
|
<LocaleList
|
||||||
label: formatMessage({ id: getTrad('Settings.list.actions.add') }),
|
canUpdateLocale={canUpdateLocale}
|
||||||
onClick: handleToggleModalCreate,
|
canDeleteLocale={canDeleteLocale}
|
||||||
color: 'primary',
|
onToggleCreateModal={handleToggleModalCreate}
|
||||||
type: 'button',
|
isCreating={isOpenedCreateModal}
|
||||||
icon: true,
|
/>
|
||||||
Component: props => (canCreateLocale ? <Button {...props} /> : null),
|
) : null;
|
||||||
style: {
|
|
||||||
paddingLeft: 15,
|
|
||||||
paddingRight: 15,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header
|
|
||||||
title={{
|
|
||||||
label: formatMessage({ id: getTrad('plugin.name') }),
|
|
||||||
}}
|
|
||||||
content={formatMessage({ id: getTrad('Settings.list.description') })}
|
|
||||||
actions={actions}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<BaselineAlignment top size="3px" />
|
|
||||||
|
|
||||||
{canReadLocale ? (
|
|
||||||
<LocaleList
|
|
||||||
canUpdateLocale={canUpdateLocale}
|
|
||||||
canDeleteLocale={canDeleteLocale}
|
|
||||||
onToggleCreateModal={handleToggleModalCreate}
|
|
||||||
isCreating={isOpenedCreateModal}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LocaleSettingsPage.propTypes = {
|
LocaleSettingsPage.propTypes = {
|
||||||
|
|||||||
@ -17,6 +17,9 @@
|
|||||||
"Settings.list.empty.title": "There are no locales.",
|
"Settings.list.empty.title": "There are no locales.",
|
||||||
"Settings.locales.list.title.plural": "{number} Locales",
|
"Settings.locales.list.title.plural": "{number} Locales",
|
||||||
"Settings.locales.list.title.singular": "{number} Locale",
|
"Settings.locales.list.title.singular": "{number} Locale",
|
||||||
|
"Settings.locales.list.sort.id": "Sort by ID",
|
||||||
|
"Settings.locales.list.sort.displayName": "Sort by display name",
|
||||||
|
"Settings.locales.list.sort.default": "Sort by the default locale",
|
||||||
"Settings.locales.modal.advanced": "Advanced settings",
|
"Settings.locales.modal.advanced": "Advanced settings",
|
||||||
"Settings.locales.modal.advanced.setAsDefault": "Set as default locale",
|
"Settings.locales.modal.advanced.setAsDefault": "Set as default locale",
|
||||||
"Settings.locales.modal.advanced.setAsDefault.hint": "One default locale is required, change it by selecting another one",
|
"Settings.locales.modal.advanced.setAsDefault.hint": "One default locale is required, change it by selecting another one",
|
||||||
@ -40,7 +43,9 @@
|
|||||||
"Settings.locales.modal.locales.displayName.error": "The locale display name can only be less than 50 characters.",
|
"Settings.locales.modal.locales.displayName.error": "The locale display name can only be less than 50 characters.",
|
||||||
"Settings.locales.modal.locales.label": "Locales",
|
"Settings.locales.modal.locales.label": "Locales",
|
||||||
"Settings.locales.modal.title": "Configurations",
|
"Settings.locales.modal.title": "Configurations",
|
||||||
"Settings.locales.row.default-locale": "Default locale",
|
"Settings.locales.row.id": "ID",
|
||||||
|
"Settings.locales.row.displayName": "Display name",
|
||||||
|
"Settings.locales.row.default-locale": "Default",
|
||||||
"Settings.permissions.loading": "Loading permissions",
|
"Settings.permissions.loading": "Loading permissions",
|
||||||
"Settings.permissions.read.denied.description": "In order to be able to read this, make sure to get in touch with the administrator of your system.",
|
"Settings.permissions.read.denied.description": "In order to be able to read this, make sure to get in touch with the administrator of your system.",
|
||||||
"Settings.permissions.read.denied.title": "You don't have the permissions to access this content.",
|
"Settings.permissions.read.denied.title": "You don't have the permissions to access this content.",
|
||||||
|
|||||||
@ -17328,7 +17328,7 @@ react-redux@7.2.3:
|
|||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
react-is "^16.13.1"
|
react-is "^16.13.1"
|
||||||
|
|
||||||
react-router-dom@^5.0.0, react-router-dom@^5.2.0:
|
react-router-dom@5.2.0, react-router-dom@^5.0.0, react-router-dom@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
|
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
|
||||||
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
|
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user