mirror of
https://github.com/strapi/strapi.git
synced 2025-10-28 00:22:51 +00:00
Merge pull request #10743 from strapi/migration/admin-setting-create-role
Migration/admin setting create role
This commit is contained in:
commit
6b74e05a63
@ -1,8 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
import { Flex } from '@buffetjs/core';
|
||||
import { Row } from '@strapi/parts';
|
||||
|
||||
const CollapseLabel = styled(Flex)`
|
||||
padding-right: 10px;
|
||||
const CollapseLabel = styled(Row)`
|
||||
padding-right: ${({ theme }) => theme.spaces[2]};
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
${({ isCollapsable }) => isCollapsable && 'cursor: pointer;'}
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
/* eslint-disable indent */
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => theme.main.colors.mediumBlue};
|
||||
${({ isRight }) =>
|
||||
isRight &&
|
||||
`
|
||||
position: absolute;
|
||||
right: 5rem;
|
||||
`}
|
||||
${({ hasConditions, disabled, theme }) =>
|
||||
hasConditions &&
|
||||
`
|
||||
&:before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: -15px;
|
||||
font-size: 18px;
|
||||
color: ${disabled ? theme.main.colors.grey : theme.main.colors.mediumBlue};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,32 +1,40 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Settings } from '@strapi/icons';
|
||||
import { Button } from '@strapi/parts';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Flex, Text, Padded } from '@buffetjs/core';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Wrapper from './Wrapper';
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
|
||||
const ConditionsButton = ({ onClick, className, hasConditions, isRight }) => {
|
||||
${({ hasConditions, disabled, theme }) =>
|
||||
hasConditions &&
|
||||
`
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: -10px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: ${20 / 16}rem;;
|
||||
background: ${disabled ? theme.colors.neutral100 : theme.colors.primary600};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const ConditionsButton = ({ onClick, className, hasConditions, variant }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
isRight={isRight}
|
||||
hasConditions={hasConditions}
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Padded right size="smd">
|
||||
<Flex alignItems="center">
|
||||
<Text color="mediumBlue">
|
||||
{formatMessage({ id: 'app.components.LeftMenuLinkContainer.settings' })}
|
||||
</Text>
|
||||
<Padded style={{ height: '18px', lineHeight: 'normal' }} left size="xs">
|
||||
<FontAwesomeIcon style={{ fontSize: '11px' }} icon="cog" />
|
||||
</Padded>
|
||||
</Flex>
|
||||
</Padded>
|
||||
<Wrapper hasConditions={hasConditions} className={className}>
|
||||
<Button variant={variant} startIcon={<Settings />} onClick={onClick}>
|
||||
{formatMessage({
|
||||
id: 'app.components.LeftMenuLinkContainer.settings',
|
||||
defaultMessage: 'Settings',
|
||||
})}
|
||||
</Button>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
@ -34,13 +42,13 @@ const ConditionsButton = ({ onClick, className, hasConditions, isRight }) => {
|
||||
ConditionsButton.defaultProps = {
|
||||
className: null,
|
||||
hasConditions: false,
|
||||
isRight: false,
|
||||
variant: 'secondary',
|
||||
};
|
||||
ConditionsButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
hasConditions: PropTypes.bool,
|
||||
isRight: PropTypes.bool,
|
||||
variant: PropTypes.string,
|
||||
};
|
||||
|
||||
// This is a styled component advanced usage :
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
height: 36px;
|
||||
border-radius: ${({ theme }) => theme.main.sizes.borderRadius};
|
||||
margin-bottom: 18px;
|
||||
background-color: ${({ theme, isGrey }) =>
|
||||
isGrey ? theme.main.colors.content.background : theme.main.colors.white};
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,88 +1,81 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, Padded, Flex } from '@buffetjs/core';
|
||||
import { Row, TableLabel } from '@strapi/parts';
|
||||
import { useIntl } from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import ConditionsSelect from '../ConditionsSelect';
|
||||
import Wrapper from './Wrapper';
|
||||
// import ConditionsSelect from '../ConditionsSelect';
|
||||
import { rowHeight } from '../../Permissions/utils/constants';
|
||||
|
||||
const RowWrapper = styled(Row)`
|
||||
height: ${rowHeight};
|
||||
`;
|
||||
|
||||
const ActionRow = ({
|
||||
arrayOfOptionsGroupedByCategory,
|
||||
isFormDisabled,
|
||||
// arrayOfOptionsGroupedByCategory,
|
||||
// isFormDisabled,
|
||||
isGrey,
|
||||
label,
|
||||
name,
|
||||
onCategoryChange,
|
||||
onChange,
|
||||
value,
|
||||
// name,
|
||||
// onCategoryChange,
|
||||
// onChange,
|
||||
// value,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Wrapper isGrey={isGrey}>
|
||||
<Padded style={{ width: 200 }} top left right bottom size="sm">
|
||||
<Flex>
|
||||
<Text
|
||||
lineHeight="19px"
|
||||
color="grey"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'Settings.permissions.conditions.can',
|
||||
})}
|
||||
|
||||
</Text>
|
||||
<Text
|
||||
title={label}
|
||||
lineHeight="19px"
|
||||
fontWeight="bold"
|
||||
fontSize="xs"
|
||||
textTransform="uppercase"
|
||||
color="mediumBlue"
|
||||
style={{ maxWidth: '60%' }}
|
||||
ellipsis
|
||||
>
|
||||
{formatMessage({
|
||||
id: `Settings.roles.form.permissions.${label.toLowerCase()}`,
|
||||
defaultMessage: label,
|
||||
})}
|
||||
</Text>
|
||||
<Text
|
||||
lineHeight="19px"
|
||||
color="grey"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
|
||||
{formatMessage({
|
||||
id: 'Settings.permissions.conditions.when',
|
||||
})}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Padded>
|
||||
<ConditionsSelect
|
||||
<RowWrapper as="li" background={isGrey ? 'neutral100' : 'neutral0'}>
|
||||
<Row paddingLeft={6} style={{ width: 180 }}>
|
||||
<TableLabel textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: 'Settings.permissions.conditions.can',
|
||||
defaultMessage: 'Can',
|
||||
})}
|
||||
|
||||
</TableLabel>
|
||||
<TableLabel
|
||||
title={label}
|
||||
textColor="primary600"
|
||||
// ! REMOVE THIS WHEN DS IS UPDATED WITH ELLIPSIS PROP
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{formatMessage({
|
||||
id: `Settings.roles.form.permissions.${label.toLowerCase()}`,
|
||||
defaultMessage: label,
|
||||
})}
|
||||
</TableLabel>
|
||||
<TableLabel textColor="neutral600">
|
||||
|
||||
{formatMessage({
|
||||
id: 'Settings.permissions.conditions.when',
|
||||
defaultMessage: 'When',
|
||||
})}
|
||||
</TableLabel>
|
||||
</Row>
|
||||
{/* <ConditionsSelect
|
||||
arrayOfOptionsGroupedByCategory={arrayOfOptionsGroupedByCategory}
|
||||
name={name}
|
||||
isFormDisabled={isFormDisabled}
|
||||
onCategoryChange={onCategoryChange}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
/>
|
||||
</Wrapper>
|
||||
/> */}
|
||||
</RowWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
ActionRow.propTypes = {
|
||||
arrayOfOptionsGroupedByCategory: PropTypes.array.isRequired,
|
||||
isFormDisabled: PropTypes.bool.isRequired,
|
||||
// arrayOfOptionsGroupedByCategory: PropTypes.array.isRequired,
|
||||
// isFormDisabled: PropTypes.bool.isRequired,
|
||||
isGrey: PropTypes.bool.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.object.isRequired,
|
||||
onCategoryChange: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
// name: PropTypes.string.isRequired,
|
||||
// value: PropTypes.object.isRequired,
|
||||
// onCategoryChange: PropTypes.func.isRequired,
|
||||
// onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
export default ActionRow;
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Separator = styled.div`
|
||||
padding-top: 1.4rem;
|
||||
margin-bottom: 2.8rem;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.main.colors.brightGrey};
|
||||
`;
|
||||
|
||||
export default Separator;
|
||||
@ -1,14 +1,24 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Breadcrumbs,
|
||||
Button,
|
||||
Crumb,
|
||||
H2,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalLayout,
|
||||
Stack,
|
||||
Text,
|
||||
Divider,
|
||||
} from '@strapi/parts';
|
||||
import { cloneDeep, get, groupBy, set, upperFirst } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { cloneDeep, get, groupBy, set } from 'lodash';
|
||||
import { Modal, ModalHeader, ModalFooter } from '@strapi/helper-plugin';
|
||||
import { Button, Text, Padded } from '@buffetjs/core';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { usePermissionsDataManager } from '../../../hooks';
|
||||
import createDefaultConditionsForm from './utils/createDefaultConditionsForm';
|
||||
import ActionRow from './ActionRow';
|
||||
import Separator from './Separator';
|
||||
import updateValues from '../Permissions/utils/updateValues';
|
||||
import ActionRow from './ActionRow';
|
||||
import createDefaultConditionsForm from './utils/createDefaultConditionsForm';
|
||||
|
||||
const ConditionsModal = ({
|
||||
actions,
|
||||
@ -79,53 +89,83 @@ const ConditionsModal = ({
|
||||
onToggle();
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<Modal withoverflow="true" onClosed={onClosed} isOpen={isOpen} onToggle={onToggle}>
|
||||
<ModalHeader headerBreadcrumbs={headerBreadCrumbs} />
|
||||
<Padded top left right bottom size="md">
|
||||
<Text fontSize="lg" fontWeight="bold">
|
||||
{formatMessage({
|
||||
id: 'Settings.permissions.conditions.define-conditions',
|
||||
})}
|
||||
</Text>
|
||||
<Separator />
|
||||
{actionsToDisplay.length === 0 && (
|
||||
<Text fontSize="md" color="grey">
|
||||
{formatMessage({ id: 'Settings.permissions.conditions.no-actions' })}
|
||||
</Text>
|
||||
)}
|
||||
{actionsToDisplay.map(({ actionId, label, pathToConditionsObject }, index) => {
|
||||
const name = pathToConditionsObject.join('..');
|
||||
<ModalLayout onClose={onClosed}>
|
||||
<ModalHeader>
|
||||
<Breadcrumbs label={headerBreadCrumbs.join(', ')}>
|
||||
{headerBreadCrumbs.map(label => (
|
||||
<Crumb key={label}>
|
||||
{upperFirst(
|
||||
formatMessage({
|
||||
id: label,
|
||||
defaultMessage: label,
|
||||
})
|
||||
)}
|
||||
</Crumb>
|
||||
))}
|
||||
</Breadcrumbs>
|
||||
</ModalHeader>
|
||||
<Box padding={8}>
|
||||
<Stack size={6}>
|
||||
<H2>
|
||||
{formatMessage({
|
||||
id: 'Settings.permissions.conditions.define-conditions',
|
||||
defaultMessage: 'Define conditions',
|
||||
})}
|
||||
</H2>
|
||||
<Box>
|
||||
<Divider />
|
||||
</Box>
|
||||
<Box>
|
||||
{actionsToDisplay.length === 0 && (
|
||||
<Text>
|
||||
{formatMessage({
|
||||
id: 'Settings.permissions.conditions.no-actions',
|
||||
defaultMessage:
|
||||
'You first need to select actions (create, read, update, ...) before defining conditions on them.',
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
<ul>
|
||||
{actionsToDisplay.map(({ actionId, label, pathToConditionsObject }, index) => {
|
||||
const name = pathToConditionsObject.join('..');
|
||||
|
||||
return (
|
||||
<ActionRow
|
||||
key={actionId}
|
||||
arrayOfOptionsGroupedByCategory={arrayOfOptionsGroupedByCategory}
|
||||
label={label}
|
||||
isFormDisabled={isFormDisabled}
|
||||
isGrey={index % 2 === 0}
|
||||
name={name}
|
||||
onCategoryChange={handleCategoryChange}
|
||||
onChange={handleChange}
|
||||
value={get(state, name, {})}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Padded>
|
||||
<ModalFooter>
|
||||
<section>
|
||||
<Button type="button" color="cancel" onClick={onToggle}>
|
||||
{formatMessage({ id: 'app.components.Button.cancel' })}
|
||||
return (
|
||||
<ActionRow
|
||||
key={actionId}
|
||||
arrayOfOptionsGroupedByCategory={arrayOfOptionsGroupedByCategory}
|
||||
label={label}
|
||||
isFormDisabled={isFormDisabled}
|
||||
isGrey={index % 2 === 0}
|
||||
name={name}
|
||||
onCategoryChange={handleCategoryChange}
|
||||
onChange={handleChange}
|
||||
value={get(state, name, {})}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<Button variant="tertiary" onClick={onToggle}>
|
||||
{formatMessage({ id: 'app.components.Button.cancel', defaultMessage: 'Cancel' })}
|
||||
</Button>
|
||||
|
||||
<Button type="button" color="success" onClick={handleSubmit}>
|
||||
}
|
||||
endActions={
|
||||
<Button onClick={handleSubmit}>
|
||||
{formatMessage({
|
||||
id: 'Settings.permissions.conditions.apply',
|
||||
defaultMessage: 'Apply',
|
||||
})}
|
||||
</Button>
|
||||
</section>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
}
|
||||
/>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
import { Text } from '@buffetjs/core';
|
||||
import ConditionsButton from '../../ConditionsButton';
|
||||
import Chevron from '../../Chevron';
|
||||
|
||||
const activeRowStyle = (theme, isActive) => `
|
||||
border: 1px solid ${theme.main.colors.darkBlue};
|
||||
background-color: ${theme.main.colors.lightBlue};
|
||||
color: ${theme.main.colors.mediumBlue};
|
||||
border-radius: ${isActive ? '2px 2px 0 0' : '2px'};
|
||||
${Text} {
|
||||
color: ${theme.main.colors.mediumBlue};
|
||||
}
|
||||
${Chevron} {
|
||||
display: block;
|
||||
}
|
||||
${ConditionsButton} {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledRow = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
background-color: ${({ isGrey, theme }) =>
|
||||
isGrey ? theme.main.colors.content.background : theme.main.colors.white};
|
||||
border: 1px solid transparent;
|
||||
${ConditionsButton} {
|
||||
display: none;
|
||||
}
|
||||
${({ isActive, theme }) => isActive && activeRowStyle(theme, isActive)}
|
||||
&:hover {
|
||||
${({ theme, isActive }) => activeRowStyle(theme, isActive)}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledRow;
|
||||
@ -1,19 +1,97 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Down, Up } from '@strapi/icons';
|
||||
import { Box, Checkbox, Row, Text } from '@strapi/parts';
|
||||
import IS_DISABLED from 'ee_else_ce/components/Roles/ContentTypeCollapse/Collapse/utils/constants';
|
||||
import { get, omit } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Flex, Padded } from '@buffetjs/core';
|
||||
import IS_DISABLED from 'ee_else_ce/components/Roles/ContentTypeCollapse/Collapse/utils/constants';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
import { usePermissionsDataManager } from '../../../../hooks';
|
||||
import { getCheckboxState } from '../../utils';
|
||||
import CheckboxWithCondition from '../../CheckboxWithCondition';
|
||||
import Chevron from '../../Chevron';
|
||||
import ConditionsButton from '../../ConditionsButton';
|
||||
import ConditionsModal from '../../ConditionsModal';
|
||||
import HiddenAction from '../../HiddenAction';
|
||||
import { cellWidth, rowHeight } from '../../Permissions/utils/constants';
|
||||
import RowLabelWithCheckbox from '../../RowLabelWithCheckbox';
|
||||
import Wrapper from './Wrapper';
|
||||
import { getCheckboxState } from '../../utils';
|
||||
import generateCheckboxesActions from './utils/generateCheckboxesActions';
|
||||
|
||||
const activeRowStyle = (theme, isActive, isClicked) => `
|
||||
${Wrapper} {
|
||||
${isClicked ? `border: 1px solid ${theme.colors.primary600}; border-bottom: none;` : ''}
|
||||
background-color: ${theme.colors.primary100};
|
||||
color: ${theme.colors.primary600};
|
||||
border-radius: ${isActive ? '2px 2px 0 0' : '2px'};
|
||||
}
|
||||
${Text} {
|
||||
color: ${theme.colors.primary600};
|
||||
font-weight: bold;
|
||||
}
|
||||
${Chevron} {
|
||||
display: block;
|
||||
}
|
||||
${ConditionsButton} {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: ${rowHeight};
|
||||
background-color: ${({ isGrey, theme }) =>
|
||||
isGrey ? theme.colors.neutral100 : theme.colors.neutral0};
|
||||
border: 1px solid transparent;
|
||||
`;
|
||||
|
||||
const BoxWrapper = styled.div`
|
||||
display: inline-flex;
|
||||
min-width: 100%;
|
||||
|
||||
${ConditionsButton} {
|
||||
display: none;
|
||||
}
|
||||
${({ isActive, theme }) => isActive && activeRowStyle(theme, isActive, true)}
|
||||
&:hover {
|
||||
${({ theme, isActive }) => activeRowStyle(theme, isActive)}
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
${({ theme, isActive }) => activeRowStyle(theme, isActive)}
|
||||
}
|
||||
`;
|
||||
|
||||
const Cell = styled(Row)`
|
||||
width: ${cellWidth};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Chevron = styled(Box)`
|
||||
display: none;
|
||||
svg {
|
||||
width: 11px;
|
||||
}
|
||||
* {
|
||||
fill: ${({ theme }) => theme.colors.primary600};
|
||||
}
|
||||
`;
|
||||
|
||||
const TinyDot = styled(Box)`
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 37px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 20px;
|
||||
background: ${({ theme }) => theme.colors.primary600};
|
||||
`;
|
||||
|
||||
const AbsoluteBox = styled(Box)`
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
transform: translateY(10px);
|
||||
`;
|
||||
|
||||
const Collapse = ({
|
||||
availableActions,
|
||||
isActive,
|
||||
@ -23,7 +101,8 @@ const Collapse = ({
|
||||
onClickToggle,
|
||||
pathToData,
|
||||
}) => {
|
||||
const [modalState, setModalState] = useState({ isOpen: false, isMounted: false });
|
||||
const [isModalOpen, setModalOpen] = useState(false);
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
modifiedData,
|
||||
onChangeParentCheckbox,
|
||||
@ -31,11 +110,11 @@ const Collapse = ({
|
||||
} = usePermissionsDataManager();
|
||||
|
||||
const handleToggleModalIsOpen = () => {
|
||||
setModalState(prevState => ({ isMounted: true, isOpen: !prevState.isOpen }));
|
||||
setModalOpen(s => !s);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setModalState(prevState => ({ ...prevState, isMounted: false }));
|
||||
setModalOpen(false);
|
||||
};
|
||||
|
||||
// This corresponds to the data related to the CT left checkbox
|
||||
@ -65,9 +144,8 @@ const Collapse = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper isActive={isActive} isGrey={isGrey}>
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<Padded left size="sm" />
|
||||
<BoxWrapper isActive={isActive}>
|
||||
<Wrapper isGrey={isGrey}>
|
||||
<RowLabelWithCheckbox
|
||||
isCollapsable
|
||||
isFormDisabled={isFormDisabled}
|
||||
@ -77,11 +155,12 @@ const Collapse = ({
|
||||
onClick={onClickToggle}
|
||||
someChecked={hasSomeActionsSelected}
|
||||
value={hasAllActionsSelected}
|
||||
isActive={isActive}
|
||||
>
|
||||
<Chevron icon={isActive ? 'chevron-up' : 'chevron-down'} />
|
||||
<Chevron paddingLeft={2}>{isActive ? <Up /> : <Down />}</Chevron>
|
||||
</RowLabelWithCheckbox>
|
||||
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<Row style={{ flex: 1 }}>
|
||||
{checkboxesActions.map(
|
||||
({
|
||||
actionId,
|
||||
@ -91,6 +170,7 @@ const Collapse = ({
|
||||
isDisplayed,
|
||||
isParentCheckbox,
|
||||
checkboxName,
|
||||
label: permissionLabel,
|
||||
}) => {
|
||||
if (!isDisplayed) {
|
||||
return <HiddenAction key={actionId} />;
|
||||
@ -98,48 +178,72 @@ const Collapse = ({
|
||||
|
||||
if (isParentCheckbox) {
|
||||
return (
|
||||
<CheckboxWithCondition
|
||||
key={actionId}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
hasConditions={hasConditions}
|
||||
name={checkboxName}
|
||||
onChange={onChangeParentCheckbox}
|
||||
someChecked={hasSomeActionsSelected}
|
||||
value={hasAllActionsSelected}
|
||||
/>
|
||||
<Cell key={actionId} justifyContent="center" alignItems="center">
|
||||
{hasConditions && <TinyDot />}
|
||||
<Checkbox
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName}
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: `Settings.permissions.select-by-permission`,
|
||||
defaultMessage: 'Select {label} permission',
|
||||
},
|
||||
{ label: `${permissionLabel} ${label}` }
|
||||
)}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChangeParentCheckbox({
|
||||
target: {
|
||||
name: checkboxName,
|
||||
value,
|
||||
},
|
||||
})}
|
||||
indeterminate={hasSomeActionsSelected}
|
||||
value={hasAllActionsSelected}
|
||||
/>
|
||||
</Cell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CheckboxWithCondition
|
||||
key={actionId}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
hasConditions={hasConditions}
|
||||
name={checkboxName}
|
||||
onChange={onChangeSimpleCheckbox}
|
||||
value={hasAllActionsSelected}
|
||||
/>
|
||||
<Cell key={actionId} justifyContent="center" alignItems="center">
|
||||
{hasConditions && <TinyDot />}
|
||||
<Checkbox
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
indeterminate={hasConditions}
|
||||
name={checkboxName}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChangeSimpleCheckbox({
|
||||
target: {
|
||||
name: checkboxName,
|
||||
value,
|
||||
},
|
||||
})}
|
||||
value={hasAllActionsSelected}
|
||||
/>
|
||||
</Cell>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</Flex>
|
||||
<ConditionsButton
|
||||
isRight
|
||||
onClick={handleToggleModalIsOpen}
|
||||
hasConditions={doesConditionButtonHasConditions}
|
||||
/>
|
||||
</Flex>
|
||||
{modalState.isMounted && (
|
||||
</Row>
|
||||
<Box style={{ width: 120 }} />
|
||||
<ConditionsModal
|
||||
headerBreadCrumbs={[label, 'app.components.LeftMenuLinkContainer.settings']}
|
||||
actions={checkboxesActions}
|
||||
isOpen={modalState.isOpen}
|
||||
isOpen={isModalOpen}
|
||||
isFormDisabled={isFormDisabled}
|
||||
onClosed={handleModalClose}
|
||||
onToggle={handleToggleModalIsOpen}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
</Wrapper>
|
||||
<AbsoluteBox>
|
||||
<ConditionsButton
|
||||
onClick={handleToggleModalIsOpen}
|
||||
hasConditions={doesConditionButtonHasConditions}
|
||||
/>
|
||||
</AbsoluteBox>
|
||||
</BoxWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
/* eslint-disable indent */
|
||||
import styled from 'styled-components';
|
||||
import { Flex } from '@buffetjs/core';
|
||||
import { activeStyle } from '../../utils';
|
||||
import Chevron from '../../../Chevron';
|
||||
|
||||
const RowWrapper = styled(Flex)`
|
||||
height: 36px;
|
||||
padding: 1rem 0;
|
||||
flex: 1;
|
||||
${Chevron} {
|
||||
width: 13px;
|
||||
}
|
||||
${({ isCollapsable, theme }) =>
|
||||
isCollapsable &&
|
||||
`
|
||||
${Chevron} {
|
||||
display: block;
|
||||
color: ${theme.main.colors.grey};
|
||||
}
|
||||
&:hover {
|
||||
${activeStyle(theme)}
|
||||
}
|
||||
`}
|
||||
${({ isActive, theme }) => isActive && activeStyle(theme)};
|
||||
`;
|
||||
|
||||
export default RowWrapper;
|
||||
@ -1,19 +1,44 @@
|
||||
import React, { memo, useState, useMemo, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get } from 'lodash';
|
||||
import { Padded, Flex } from '@buffetjs/core';
|
||||
import { Checkbox, Row } from '@strapi/parts';
|
||||
import IS_DISABLED from 'ee_else_ce/components/Roles/ContentTypeCollapse/CollapsePropertyMatrix/ActionRow/utils/constants';
|
||||
import { get } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { usePermissionsDataManager } from '../../../../../hooks';
|
||||
import { getCheckboxState } from '../../../utils';
|
||||
import CheckboxWithCondition from '../../../CheckboxWithCondition';
|
||||
import Chevron from '../../../Chevron';
|
||||
import HiddenAction from '../../../HiddenAction';
|
||||
import { cellWidth, rowHeight } from '../../../Permissions/utils/constants';
|
||||
import RequiredSign from '../../../RequiredSign';
|
||||
import RowLabelWithCheckbox from '../../../RowLabelWithCheckbox';
|
||||
import { getCheckboxState } from '../../../utils';
|
||||
import { activeStyle } from '../../utils';
|
||||
import CarretIcon from '../CarretIcon';
|
||||
import SubActionRow from '../SubActionRow';
|
||||
import Wrapper from './Wrapper';
|
||||
import getRowLabelCheckboxeState from './utils/getRowLabelCheckboxeState';
|
||||
|
||||
const Cell = styled(Row)`
|
||||
width: ${cellWidth};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Wrapper = styled(Row)`
|
||||
height: ${rowHeight};
|
||||
flex: 1;
|
||||
|
||||
${({ isCollapsable, theme }) =>
|
||||
isCollapsable &&
|
||||
`
|
||||
${CarretIcon} {
|
||||
display: block;
|
||||
color: ${theme.colors.neutral100};
|
||||
}
|
||||
&:hover {
|
||||
${activeStyle(theme)}
|
||||
}
|
||||
`}
|
||||
${({ isActive, theme }) => isActive && activeStyle(theme)};
|
||||
`;
|
||||
|
||||
const ActionRow = ({
|
||||
childrenForm,
|
||||
label,
|
||||
@ -23,7 +48,9 @@ const ActionRow = ({
|
||||
pathToData,
|
||||
propertyActions,
|
||||
propertyName,
|
||||
isOdd,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const [rowToOpen, setRowToOpen] = useState(null);
|
||||
const {
|
||||
modifiedData,
|
||||
@ -66,11 +93,14 @@ const ActionRow = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper alignItems="center" isCollapsable={isCollapsable} isActive={isActive}>
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<Padded left size="sm" />
|
||||
<Wrapper
|
||||
alignItems="center"
|
||||
isCollapsable={isCollapsable}
|
||||
isActive={isActive}
|
||||
background={isOdd ? 'neutral100' : 'neutral0'}
|
||||
>
|
||||
<Row>
|
||||
<RowLabelWithCheckbox
|
||||
width="15rem"
|
||||
onChange={handleChangeLeftRowCheckbox}
|
||||
onClick={handleClick}
|
||||
isCollapsable={isCollapsable}
|
||||
@ -78,11 +108,12 @@ const ActionRow = ({
|
||||
label={label}
|
||||
someChecked={hasSomeActionsSelected}
|
||||
value={hasAllActionsSelected}
|
||||
isActive={isActive}
|
||||
>
|
||||
{required && <RequiredSign />}
|
||||
<Chevron icon={isActive ? 'caret-up' : 'caret-down'} />
|
||||
<CarretIcon $isActive={isActive} />
|
||||
</RowLabelWithCheckbox>
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<Row>
|
||||
{propertyActions.map(({ label, isActionRelatedToCurrentProperty, actionId }) => {
|
||||
if (!isActionRelatedToCurrentProperty) {
|
||||
return <HiddenAction key={label} />;
|
||||
@ -100,13 +131,28 @@ const ActionRow = ({
|
||||
const checkboxValue = get(modifiedData, checkboxName, false);
|
||||
|
||||
return (
|
||||
<CheckboxWithCondition
|
||||
key={actionId}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName.join('..')}
|
||||
onChange={onChangeSimpleCheckbox}
|
||||
value={checkboxValue}
|
||||
/>
|
||||
<Cell key={actionId} justifyContent="center" alignItems="center">
|
||||
<Checkbox
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName.join('..')}
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: `Settings.permissions.select-by-permission`,
|
||||
defaultMessage: 'Select {label} permission',
|
||||
},
|
||||
{ label: `${name} ${label}` }
|
||||
)}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChangeSimpleCheckbox({
|
||||
target: {
|
||||
name: checkboxName.join('..'),
|
||||
value,
|
||||
},
|
||||
})}
|
||||
value={checkboxValue}
|
||||
/>
|
||||
</Cell>
|
||||
);
|
||||
}
|
||||
|
||||
@ -115,18 +161,33 @@ const ActionRow = ({
|
||||
const { hasAllActionsSelected, hasSomeActionsSelected } = getCheckboxState(data);
|
||||
|
||||
return (
|
||||
<CheckboxWithCondition
|
||||
key={label}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName.join('..')}
|
||||
onChange={onChangeParentCheckbox}
|
||||
value={hasAllActionsSelected}
|
||||
someChecked={hasSomeActionsSelected}
|
||||
/>
|
||||
<Cell key={label} justifyContent="center" alignItems="center">
|
||||
<Checkbox
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName.join('..')}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChangeParentCheckbox({
|
||||
target: {
|
||||
name: checkboxName.join('..'),
|
||||
value,
|
||||
},
|
||||
})}
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: `Settings.permissions.select-by-permission`,
|
||||
defaultMessage: 'Select {label} permission',
|
||||
},
|
||||
{ label: `${name} ${label}` }
|
||||
)}
|
||||
value={hasAllActionsSelected}
|
||||
indeterminate={hasSomeActionsSelected}
|
||||
/>
|
||||
</Cell>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Row>
|
||||
</Row>
|
||||
</Wrapper>
|
||||
{isActive && (
|
||||
<SubActionRow
|
||||
@ -157,6 +218,7 @@ ActionRow.propTypes = {
|
||||
propertyActions: PropTypes.array.isRequired,
|
||||
propertyName: PropTypes.string.isRequired,
|
||||
required: PropTypes.bool,
|
||||
isOdd: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default memo(ActionRow);
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { Carret } from '@strapi/icons';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CarretIcon = styled(Carret)`
|
||||
display: none;
|
||||
width: ${10 / 16}rem;
|
||||
transform: rotate(${({ $isActive }) => ($isActive ? '180' : '0')}deg);
|
||||
margin-left: ${({ theme }) => theme.spaces[2]};
|
||||
* {
|
||||
fill: ${({ theme }) => theme.colors.primary600};
|
||||
}
|
||||
`;
|
||||
|
||||
export default CarretIcon;
|
||||
@ -1,20 +1,18 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Flex, Text } from '@buffetjs/core';
|
||||
import { Row, TableLabel } from '@strapi/parts';
|
||||
import styled from 'styled-components';
|
||||
// Those styles are very specific.
|
||||
// so it is not a big problem to use custom paddings and widths.
|
||||
const HeaderLabel = styled.div`
|
||||
width: 12rem;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
import { cellWidth, firstRowWidth, rowHeight } from '../../../Permissions/utils/constants';
|
||||
|
||||
const HeaderLabel = styled(Row)`
|
||||
width: ${cellWidth};
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
const PropertyLabelWrapper = styled.div`
|
||||
width: 18rem;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
padding-left: 3.5rem;
|
||||
const PropertyLabelWrapper = styled(Row)`
|
||||
width: ${firstRowWidth};
|
||||
height: ${rowHeight};
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const Header = ({ headers, label }) => {
|
||||
@ -28,9 +26,9 @@ const Header = ({ headers, label }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex>
|
||||
<PropertyLabelWrapper>
|
||||
<Text fontWeight="bold">{translatedLabel}</Text>
|
||||
<Row>
|
||||
<PropertyLabelWrapper alignItems="center" paddingLeft={6}>
|
||||
<TableLabel textColor="neutral500">{translatedLabel}</TableLabel>
|
||||
</PropertyLabelWrapper>
|
||||
{headers.map(header => {
|
||||
if (!header.isActionRelatedToCurrentProperty) {
|
||||
@ -38,17 +36,17 @@ const Header = ({ headers, label }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderLabel key={header.label}>
|
||||
<Text textTransform="capitalize" fontWeight="bold">
|
||||
<HeaderLabel justifyContent="center" key={header.label}>
|
||||
<TableLabel textColor="neutral500">
|
||||
{formatMessage({
|
||||
id: `Settings.roles.form.permissions.${header.label.toLowerCase()}`,
|
||||
defaultMessage: header.label,
|
||||
})}
|
||||
</Text>
|
||||
</TableLabel>
|
||||
</HeaderLabel>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
/* eslint-disable indent */
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding-left: 15px;
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,23 +1,70 @@
|
||||
import React, { memo, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get } from 'lodash';
|
||||
import { Flex, Text } from '@buffetjs/core';
|
||||
import styled from 'styled-components';
|
||||
import { Box, Checkbox, Row, Text } from '@strapi/parts';
|
||||
import IS_DISABLED from 'ee_else_ce/components/Roles/ContentTypeCollapse/CollapsePropertyMatrix/SubActionRow/utils/constants';
|
||||
import { get, upperFirst } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { memo, useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
import { usePermissionsDataManager } from '../../../../../hooks';
|
||||
import { getCheckboxState } from '../../../utils';
|
||||
import CheckboxWithCondition from '../../../CheckboxWithCondition';
|
||||
import Chevron from '../../../Chevron';
|
||||
import CollapseLabel from '../../../CollapseLabel';
|
||||
import Curve from '../../../Curve';
|
||||
import HiddenAction from '../../../HiddenAction';
|
||||
import { cellWidth, rowHeight } from '../../../Permissions/utils/constants';
|
||||
import RequiredSign from '../../../RequiredSign';
|
||||
import { RowStyle, RowWrapper } from './row';
|
||||
import { LeftBorderTimeline, TopTimeline } from './timeline';
|
||||
import Wrapper from './Wrapper';
|
||||
import { getCheckboxState } from '../../../utils';
|
||||
import { activeStyle } from '../../utils';
|
||||
import CarretIcon from '../CarretIcon';
|
||||
|
||||
const SubLevelWrapper = styled.div`
|
||||
padding-bottom: 8px;
|
||||
const Cell = styled(Row)`
|
||||
width: ${cellWidth};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const RowWrapper = styled(Row)`
|
||||
height: ${rowHeight};
|
||||
`;
|
||||
|
||||
const Wrapper = styled(Box)`
|
||||
padding-left: ${31 / 16}rem;
|
||||
`;
|
||||
|
||||
const LeftBorderTimeline = styled(Box)`
|
||||
border-left: ${({ isVisible, theme }) =>
|
||||
isVisible ? `4px solid ${theme.colors.primary200}` : '4px solid transparent'};
|
||||
`;
|
||||
|
||||
const RowStyle = styled(Row)`
|
||||
padding-left: ${({ theme }) => theme.spaces[4]};
|
||||
width: ${({ level }) => 145 - level * 36}px;
|
||||
|
||||
${({ isCollapsable, theme }) =>
|
||||
isCollapsable &&
|
||||
`
|
||||
${CarretIcon} {
|
||||
display: block;
|
||||
color: ${theme.colors.neutral100};
|
||||
}
|
||||
&:hover {
|
||||
${activeStyle(theme)}
|
||||
}
|
||||
`}
|
||||
${({ isActive, theme }) => isActive && activeStyle(theme)};
|
||||
`;
|
||||
|
||||
const TopTimeline = styled.div`
|
||||
padding-top: 8px;
|
||||
margin-top: 8px;
|
||||
width: 4px;
|
||||
background-color: ${({ theme }) => theme.colors.primary200};
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
`;
|
||||
|
||||
// ! REMOVE THIS WHEN DS IS UPDATED WITH ELLIPSIS PROP
|
||||
const StyledText = styled(Text)`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const SubActionRow = ({
|
||||
@ -29,6 +76,7 @@ const SubActionRow = ({
|
||||
parentName,
|
||||
propertyName,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
modifiedData,
|
||||
onChangeParentCheckbox,
|
||||
@ -60,91 +108,117 @@ const SubActionRow = ({
|
||||
{childrenForm.map(({ label, value, required, children: subChildrenForm }, index) => {
|
||||
const isVisible = index + 1 < childrenForm.length;
|
||||
const isArrayType = Array.isArray(subChildrenForm);
|
||||
const isSmall = isArrayType || index + 1 === childrenForm.length;
|
||||
const isActive = rowToOpen === value;
|
||||
|
||||
return (
|
||||
<LeftBorderTimeline key={value} isVisible={isVisible}>
|
||||
<RowWrapper isSmall={isSmall}>
|
||||
<Curve fill="#a5d5ff" />
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<RowWrapper>
|
||||
<Curve color="primary200" />
|
||||
<Row style={{ flex: 1 }}>
|
||||
<RowStyle level={recursiveLevel} isActive={isActive} isCollapsable={isArrayType}>
|
||||
<CollapseLabel
|
||||
alignItems="center"
|
||||
isCollapsable={isArrayType}
|
||||
onClick={() => {
|
||||
if (isArrayType) {
|
||||
handleClickToggleSubLevel(value);
|
||||
}
|
||||
}}
|
||||
{...(isArrayType && {
|
||||
onClick: () => handleClickToggleSubLevel(value),
|
||||
'aria-expanded': isActive,
|
||||
onKeyDown: ({ key }) =>
|
||||
(key === 'Enter' || key === ' ') && handleClickToggleSubLevel(value),
|
||||
tabIndex: 0,
|
||||
role: 'button',
|
||||
})}
|
||||
title={label}
|
||||
>
|
||||
<Text
|
||||
color={isActive ? 'mediumBlue' : 'grey'}
|
||||
ellipsis
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
lineHeight="20px"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<StyledText>{upperFirst(label)}</StyledText>
|
||||
{required && <RequiredSign />}
|
||||
<Chevron icon={isActive ? 'caret-up' : 'caret-down'} />
|
||||
<CarretIcon $isActive={isActive} />
|
||||
</CollapseLabel>
|
||||
</RowStyle>
|
||||
<Flex style={{ flex: 1 }}>
|
||||
{propertyActions.map(({ actionId, label, isActionRelatedToCurrentProperty }) => {
|
||||
if (!isActionRelatedToCurrentProperty) {
|
||||
return <HiddenAction key={actionId} />;
|
||||
}
|
||||
/*
|
||||
* Usually we use a 'dot' in order to know the key path of an object for which we want to change the value.
|
||||
* Since an action and a subject are both separated by '.' or '::' we chose to use the '..' separators
|
||||
*/
|
||||
const checkboxName = [
|
||||
...pathToDataFromActionRow.split('..'),
|
||||
actionId,
|
||||
'properties',
|
||||
propertyName,
|
||||
...parentName.split('..'),
|
||||
value,
|
||||
];
|
||||
<Row style={{ flex: 1 }}>
|
||||
{propertyActions.map(
|
||||
({ actionId, label: propertyLabel, isActionRelatedToCurrentProperty }) => {
|
||||
if (!isActionRelatedToCurrentProperty) {
|
||||
return <HiddenAction key={actionId} />;
|
||||
}
|
||||
/*
|
||||
* Usually we use a 'dot' in order to know the key path of an object for which we want to change the value.
|
||||
* Since an action and a subject are both separated by '.' or '::' we chose to use the '..' separators
|
||||
*/
|
||||
const checkboxName = [
|
||||
...pathToDataFromActionRow.split('..'),
|
||||
actionId,
|
||||
'properties',
|
||||
propertyName,
|
||||
...parentName.split('..'),
|
||||
value,
|
||||
];
|
||||
|
||||
const checkboxValue = get(modifiedData, checkboxName, false);
|
||||
const checkboxValue = get(modifiedData, checkboxName, false);
|
||||
|
||||
if (!subChildrenForm) {
|
||||
return (
|
||||
<Cell key={propertyLabel} justifyContent="center" alignItems="center">
|
||||
<Checkbox
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName.join('..')}
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: `Settings.permissions.select-by-permission`,
|
||||
defaultMessage: 'Select {label} permission',
|
||||
},
|
||||
{ label: `${parentName} ${label} ${propertyLabel}` }
|
||||
)}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChangeSimpleCheckbox({
|
||||
target: {
|
||||
name: checkboxName.join('..'),
|
||||
value,
|
||||
},
|
||||
})}
|
||||
value={checkboxValue}
|
||||
/>
|
||||
</Cell>
|
||||
);
|
||||
}
|
||||
|
||||
const { hasAllActionsSelected, hasSomeActionsSelected } = getCheckboxState(
|
||||
checkboxValue
|
||||
);
|
||||
|
||||
if (!subChildrenForm) {
|
||||
return (
|
||||
<CheckboxWithCondition
|
||||
key={label}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName.join('..')}
|
||||
onChange={onChangeSimpleCheckbox}
|
||||
value={checkboxValue}
|
||||
/>
|
||||
<Cell key={propertyLabel} justifyContent="center" alignItems="center">
|
||||
<Checkbox
|
||||
key={propertyLabel}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName.join('..')}
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: `Settings.permissions.select-by-permission`,
|
||||
defaultMessage: 'Select {label} permission',
|
||||
},
|
||||
{ label: `${parentName} ${label} ${propertyLabel}` }
|
||||
)}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChangeParentCheckbox({
|
||||
target: {
|
||||
name: checkboxName.join('..'),
|
||||
value,
|
||||
},
|
||||
})}
|
||||
value={hasAllActionsSelected}
|
||||
indeterminate={hasSomeActionsSelected}
|
||||
/>
|
||||
</Cell>
|
||||
);
|
||||
}
|
||||
|
||||
const { hasAllActionsSelected, hasSomeActionsSelected } = getCheckboxState(
|
||||
checkboxValue
|
||||
);
|
||||
|
||||
return (
|
||||
<CheckboxWithCondition
|
||||
key={label}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
name={checkboxName.join('..')}
|
||||
onChange={onChangeParentCheckbox}
|
||||
value={hasAllActionsSelected}
|
||||
someChecked={hasSomeActionsSelected}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
</Row>
|
||||
</Row>
|
||||
</RowWrapper>
|
||||
{displayedRecursiveChildren && isActive && (
|
||||
<SubLevelWrapper>
|
||||
<Box paddingBottom={2}>
|
||||
<SubActionRow
|
||||
isFormDisabled={isFormDisabled}
|
||||
parentName={`${parentName}..${value}`}
|
||||
@ -154,7 +228,7 @@ const SubActionRow = ({
|
||||
recursiveLevel={recursiveLevel + 1}
|
||||
childrenForm={displayedRecursiveChildren.children}
|
||||
/>
|
||||
</SubLevelWrapper>
|
||||
</Box>
|
||||
)}
|
||||
</LeftBorderTimeline>
|
||||
);
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
import { Flex } from '@buffetjs/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import Chevron from '../../../Chevron';
|
||||
import { activeStyle } from '../../utils';
|
||||
|
||||
const RowStyle = styled.div`
|
||||
padding-left: ${({ theme }) => theme.main.sizes.paddings.xs};
|
||||
width: ${({ level }) => 128 - level * 18}px;
|
||||
${Chevron} {
|
||||
width: 13px;
|
||||
}
|
||||
${({ isCollapsable, theme }) =>
|
||||
isCollapsable &&
|
||||
`
|
||||
${Chevron} {
|
||||
display: block;
|
||||
color: ${theme.main.colors.grey};
|
||||
}
|
||||
&:hover {
|
||||
${activeStyle(theme)}
|
||||
}
|
||||
`}
|
||||
${({ isActive, theme }) => isActive && activeStyle(theme)}
|
||||
`;
|
||||
|
||||
RowStyle.propTypes = {
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
isCollapsable: PropTypes.bool.isRequired,
|
||||
level: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const RowWrapper = styled(Flex)`
|
||||
height: ${({ isSmall }) => (isSmall ? '28px' : '36px')};
|
||||
`;
|
||||
|
||||
export { RowStyle, RowWrapper };
|
||||
@ -1,15 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const LeftBorderTimeline = styled.div`
|
||||
border-left: ${({ isVisible }) => (isVisible ? '3px solid #a5d5ff' : '3px solid transparent')};
|
||||
`;
|
||||
|
||||
const TopTimeline = styled.div`
|
||||
padding-top: 8px;
|
||||
width: 3px;
|
||||
background-color: #a5d5ff;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
`;
|
||||
|
||||
export { LeftBorderTimeline, TopTimeline };
|
||||
@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding-top: 18px;
|
||||
border: 1px solid ${({ theme }) => theme.main.colors.darkBlue};
|
||||
border-top: none;
|
||||
border-bottom: ${({ isLast, theme }) => {
|
||||
if (isLast) {
|
||||
return `1px solid ${theme.main.colors.darkBlue}`;
|
||||
}
|
||||
|
||||
return `none`;
|
||||
}};
|
||||
border-radius: 0px 0px 2px 2px;
|
||||
`;
|
||||
|
||||
Wrapper.defaultProps = {
|
||||
isLast: true,
|
||||
};
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,10 +1,26 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Padded } from '@buffetjs/core';
|
||||
import styled from 'styled-components';
|
||||
import { Box } from '@strapi/parts';
|
||||
import generateHeadersFromActions from './utils/generateHeadersFromActions';
|
||||
import Header from './Header';
|
||||
import ActionRow from './ActionRow';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
border: 1px solid ${({ theme }) => theme.colors.primary600};
|
||||
border-top: none;
|
||||
border-bottom: ${({ isLast, theme }) => {
|
||||
if (isLast) {
|
||||
return `1px solid ${theme.colors.primary600}`;
|
||||
}
|
||||
|
||||
return `none`;
|
||||
}};
|
||||
`;
|
||||
|
||||
Wrapper.defaultProps = {
|
||||
isLast: true,
|
||||
};
|
||||
|
||||
const CollapsePropertyMatrix = ({
|
||||
availableActions,
|
||||
@ -23,8 +39,8 @@ const CollapsePropertyMatrix = ({
|
||||
return (
|
||||
<Wrapper isLast={isLast}>
|
||||
<Header label={label} headers={propertyActions} />
|
||||
<Padded left size="md">
|
||||
{childrenForm.map(({ children: childrenForm, label, value, required }) => (
|
||||
<Box>
|
||||
{childrenForm.map(({ children: childrenForm, label, value, required }, i) => (
|
||||
<ActionRow
|
||||
childrenForm={childrenForm}
|
||||
key={value}
|
||||
@ -35,9 +51,10 @@ const CollapsePropertyMatrix = ({
|
||||
propertyActions={propertyActions}
|
||||
pathToData={pathToData}
|
||||
propertyName={propertyName}
|
||||
isOdd={i % 2 === 0}
|
||||
/>
|
||||
))}
|
||||
</Padded>
|
||||
</Box>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
/* eslint-disable indent */
|
||||
import styled from 'styled-components';
|
||||
import CollapsePropertyMatrix from './CollapsePropertyMatrix/Wrapper';
|
||||
|
||||
const RowWrapper = styled.div`
|
||||
${({ withMargin }) =>
|
||||
withMargin &&
|
||||
`
|
||||
margin: 9px 0;
|
||||
`}
|
||||
|
||||
${CollapsePropertyMatrix}:last-of-type {
|
||||
padding-bottom: 17px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default RowWrapper;
|
||||
@ -1,9 +1,8 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import Collapse from './Collapse';
|
||||
import CollapsePropertyMatrix from './CollapsePropertyMatrix';
|
||||
import { getAvailableActions } from './utils';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const ContentTypeCollapse = ({
|
||||
allActions,
|
||||
@ -24,10 +23,8 @@ const ContentTypeCollapse = ({
|
||||
return getAvailableActions(allActions, contentTypeName);
|
||||
}, [allActions, contentTypeName]);
|
||||
|
||||
const isOdd = useMemo(() => index % 2 !== 0, [index]);
|
||||
|
||||
return (
|
||||
<Wrapper withMargin={isOdd}>
|
||||
<>
|
||||
<Collapse
|
||||
availableActions={availableActions}
|
||||
isActive={isActive}
|
||||
@ -38,22 +35,21 @@ const ContentTypeCollapse = ({
|
||||
pathToData={pathToData}
|
||||
/>
|
||||
{isActive &&
|
||||
properties.map(({ label, value, children: childrenForm }, i) => {
|
||||
properties.map(({ label: propertyLabel, value, children: childrenForm }, i) => {
|
||||
return (
|
||||
<CollapsePropertyMatrix
|
||||
availableActions={availableActions}
|
||||
childrenForm={childrenForm}
|
||||
isFormDisabled={isFormDisabled}
|
||||
label={label}
|
||||
label={propertyLabel}
|
||||
pathToData={pathToData}
|
||||
propertyName={value}
|
||||
key={value}
|
||||
isLast={i === properties.length - 1}
|
||||
isOdd={isOdd}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { Text } from '@buffetjs/core';
|
||||
import Chevron from '../../Chevron';
|
||||
import { Text } from '@strapi/parts';
|
||||
import CarretIcon from '../CollapsePropertyMatrix/CarretIcon';
|
||||
|
||||
const activeStyle = theme => `
|
||||
color: ${theme.main.colors.mediumBlue};
|
||||
${Text} {
|
||||
color: ${theme.main.colors.mediumBlue};
|
||||
color: ${theme.colors.primary600};
|
||||
font-weight: bold;
|
||||
}
|
||||
${Chevron} {
|
||||
${CarretIcon} {
|
||||
display: block;
|
||||
color: ${theme.main.colors.mediumBlue};
|
||||
color: ${theme.colors.primary600};
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.main.colors.white};
|
||||
overflow: auto;
|
||||
border-bottom-left-radius: ${({ theme }) => theme.main.sizes.borderRadius};
|
||||
border-bottom-right-radius: ${({ theme }) => theme.main.sizes.borderRadius};
|
||||
::-webkit-scrollbar {
|
||||
height: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,23 +1,25 @@
|
||||
import React, { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Padded } from '@buffetjs/core';
|
||||
import { Box } from '@strapi/parts';
|
||||
import styled from 'styled-components';
|
||||
import ContentTypeCollapses from '../ContentTypeCollapses';
|
||||
import GlobalActions from '../GlobalActions';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
overflow-x: auto;
|
||||
`;
|
||||
|
||||
const ContentTypes = ({ isFormDisabled, kind, layout: { actions, subjects } }) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Padded left right bottom size="md">
|
||||
<GlobalActions actions={actions} kind={kind} isFormDisabled={isFormDisabled} />
|
||||
<ContentTypeCollapses
|
||||
actions={actions}
|
||||
isFormDisabled={isFormDisabled}
|
||||
pathToData={kind}
|
||||
subjects={subjects}
|
||||
/>
|
||||
</Padded>
|
||||
</Wrapper>
|
||||
<StyledBox background="neutral0">
|
||||
<GlobalActions actions={actions} kind={kind} isFormDisabled={isFormDisabled} />
|
||||
<ContentTypeCollapses
|
||||
actions={actions}
|
||||
isFormDisabled={isFormDisabled}
|
||||
pathToData={kind}
|
||||
subjects={subjects}
|
||||
/>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,32 +1,52 @@
|
||||
import React, { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import { Box } from '@strapi/parts';
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
transform: translate(-4px, -12px);
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
width: ${4 / 16}rem;
|
||||
height: ${12 / 16}rem;
|
||||
background: ${({ theme }) => theme.colors.primary200};
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const Svg = styled.svg`
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
transform: translate(-0.5px, -1px);
|
||||
|
||||
* {
|
||||
fill: ${({ theme, color }) => theme.colors[color]};
|
||||
}
|
||||
`;
|
||||
|
||||
const Curve = props => (
|
||||
<svg
|
||||
style={{
|
||||
height: '14px',
|
||||
transform: 'translate(-3.2px, -1px)',
|
||||
position: 'relative',
|
||||
}}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 21.08 21"
|
||||
{...props}
|
||||
>
|
||||
<g>
|
||||
<StyledBox>
|
||||
<Svg
|
||||
width="20"
|
||||
height="23"
|
||||
viewBox="0 0 20 23"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2.58 2.5q-1.2 16 16 16"
|
||||
fill="none"
|
||||
stroke={props.fill}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="5"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.02477 14.7513C8.65865 17.0594 11.6046 18.6059 17.5596 18.8856C18.6836 18.9384 19.5976 19.8435 19.5976 20.9688V20.9688C19.5976 22.0941 18.6841 23.0125 17.5599 22.9643C10.9409 22.6805 6.454 20.9387 3.75496 17.1258C0.937988 13.1464 0.486328 7.39309 0.486328 0.593262H4.50974C4.50974 7.54693 5.06394 11.9813 7.02477 14.7513Z"
|
||||
fill="#D9D8FF"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</Svg>
|
||||
</StyledBox>
|
||||
);
|
||||
|
||||
Curve.defaultProps = {
|
||||
fill: '#f3f4f4',
|
||||
fill: 'primary200',
|
||||
};
|
||||
Curve.propTypes = {
|
||||
fill: PropTypes.string,
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding-left: 165px;
|
||||
padding-bottom: 25px;
|
||||
padding-top: 26px;
|
||||
${({ disabled, theme }) =>
|
||||
`
|
||||
input[type='checkbox'] {
|
||||
&:after {
|
||||
color: ${!disabled ? theme.main.colors.mediumBlue : theme.main.colors.grey};
|
||||
}
|
||||
}
|
||||
cursor: initial;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,13 +1,20 @@
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { Checkbox, Stack, TableLabel, Box } from '@strapi/parts';
|
||||
import IS_DISABLED from 'ee_else_ce/components/Roles/GlobalActions/utils/constants';
|
||||
import { get } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Flex } from '@buffetjs/core';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import IS_DISABLED from 'ee_else_ce/components/Roles/GlobalActions/utils/constants';
|
||||
import styled from 'styled-components';
|
||||
import { usePermissionsDataManager } from '../../../hooks';
|
||||
import CheckboxWithCondition from '../CheckboxWithCondition';
|
||||
import { cellWidth, firstRowWidth } from '../Permissions/utils/constants';
|
||||
import { findDisplayedActions, getCheckboxesState } from './utils';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const CenteredStack = styled(Stack)`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: ${cellWidth};
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const GlobalActions = ({ actions, isFormDisabled, kind }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
@ -22,28 +29,43 @@ const GlobalActions = ({ actions, isFormDisabled, kind }) => {
|
||||
}, [modifiedData, displayedActions, kind]);
|
||||
|
||||
return (
|
||||
<Wrapper disabled={isFormDisabled}>
|
||||
<Flex>
|
||||
<Box paddingBottom={4} paddingTop={6} style={{ paddingLeft: firstRowWidth }}>
|
||||
<Stack horizontal>
|
||||
{displayedActions.map(({ label, actionId }) => {
|
||||
return (
|
||||
<CheckboxWithCondition
|
||||
key={actionId}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
message={formatMessage({
|
||||
id: `Settings.roles.form.permissions.${label.toLowerCase()}`,
|
||||
defaultMessage: label,
|
||||
})}
|
||||
onChange={({ target: { value } }) => {
|
||||
onChangeCollectionTypeGlobalActionCheckbox(kind, actionId, value);
|
||||
}}
|
||||
name={actionId}
|
||||
value={get(checkboxesState, [actionId, 'hasAllActionsSelected'], false)}
|
||||
someChecked={get(checkboxesState, [actionId, 'hasSomeActionsSelected'], false)}
|
||||
/>
|
||||
<CenteredStack key={actionId} size={3}>
|
||||
<TableLabel textColor="neutral500">
|
||||
{formatMessage({
|
||||
id: `Settings.roles.form.permissions.${label.toLowerCase()}`,
|
||||
defaultMessage: label,
|
||||
})}
|
||||
</TableLabel>
|
||||
<Checkbox
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
onValueChange={value => {
|
||||
onChangeCollectionTypeGlobalActionCheckbox(kind, actionId, value);
|
||||
}}
|
||||
name={actionId}
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: `Settings.permissions.select-all-by-permission`,
|
||||
defaultMessage: 'Select all {label} permissions',
|
||||
},
|
||||
{
|
||||
label: formatMessage({
|
||||
id: `Settings.roles.form.permissions.${label.toLowerCase()}`,
|
||||
defaultMessage: label,
|
||||
}),
|
||||
}
|
||||
)}
|
||||
value={get(checkboxesState, [actionId, 'hasAllActionsSelected'], false)}
|
||||
indeterminate={get(checkboxesState, [actionId, 'hasSomeActionsSelected'], false)}
|
||||
/>
|
||||
</CenteredStack>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Wrapper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
import { cellWidth } from '../Permissions/utils/constants';
|
||||
|
||||
const HiddenAction = styled.div`
|
||||
min-width: 10rem;
|
||||
max-width: 12rem;
|
||||
flex: 1;
|
||||
width: ${cellWidth};
|
||||
`;
|
||||
|
||||
export default HiddenAction;
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
import React, { forwardRef, memo, useCallback, useImperativeHandle, useReducer } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { difference } from '@strapi/helper-plugin';
|
||||
import { Tab, TabGroup, TabPanel, TabPanels, Tabs } from '@strapi/parts';
|
||||
import { has, isEmpty } from 'lodash';
|
||||
import Tabs from '../Tabs';
|
||||
import PermissionsDataManagerProvider from '../PermissionsDataManagerProvider';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { forwardRef, memo, useCallback, useImperativeHandle, useReducer } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import ContentTypes from '../ContentTypes';
|
||||
import PermissionsDataManagerProvider from '../PermissionsDataManagerProvider';
|
||||
import PluginsAndSettings from '../PluginsAndSettings';
|
||||
import TAB_LABELS from './utils/tabLabels';
|
||||
import formatPermissionsToAPI from './utils/formatPermissionsToAPI';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
import formatPermissionsToAPI from './utils/formatPermissionsToAPI';
|
||||
import TAB_LABELS from './utils/tabLabels';
|
||||
|
||||
const Permissions = forwardRef(({ layout, isFormDisabled, permissions }, ref) => {
|
||||
const [{ initialData, layouts, modifiedData }, dispatch] = useReducer(reducer, initialState, () =>
|
||||
init(layout, permissions)
|
||||
);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
@ -106,28 +108,45 @@ const Permissions = forwardRef(({ layout, isFormDisabled, permissions }, ref) =>
|
||||
onChangeCollectionTypeGlobalActionCheckbox: handleChangeCollectionTypeGlobalActionCheckbox,
|
||||
}}
|
||||
>
|
||||
<Tabs tabsLabel={TAB_LABELS}>
|
||||
<ContentTypes
|
||||
layout={layouts.collectionTypes}
|
||||
kind="collectionTypes"
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
<ContentTypes
|
||||
layout={layouts.singleTypes}
|
||||
kind="singleTypes"
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
<PluginsAndSettings
|
||||
layout={layouts.plugins}
|
||||
kind="plugins"
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
<PluginsAndSettings
|
||||
layout={layouts.settings}
|
||||
kind="settings"
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
</Tabs>
|
||||
<TabGroup id="tabs">
|
||||
<Tabs>
|
||||
{TAB_LABELS.map(tabLabel => (
|
||||
<Tab key={tabLabel.id}>
|
||||
{formatMessage({ id: tabLabel.labelId, defaultMessage: tabLabel.defaultMessage })}
|
||||
</Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
<TabPanels style={{ position: 'relative' }}>
|
||||
<TabPanel>
|
||||
<ContentTypes
|
||||
layout={layouts.collectionTypes}
|
||||
kind="collectionTypes"
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ContentTypes
|
||||
layout={layouts.singleTypes}
|
||||
kind="singleTypes"
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<PluginsAndSettings
|
||||
layout={layouts.plugins}
|
||||
kind="plugins"
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<PluginsAndSettings
|
||||
layout={layouts.settings}
|
||||
kind="settings"
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</PermissionsDataManagerProvider>
|
||||
);
|
||||
});
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export const cellWidth = `${120 / 16}rem`;
|
||||
export const firstRowWidth = `${200 / 16}rem`;
|
||||
export const rowHeight = `${53 / 16}rem`;
|
||||
@ -1,10 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const ListWrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.main.colors.white};
|
||||
border-bottom-left-radius: ${({ theme }) => theme.main.sizes.borderRadius};
|
||||
border-bottom-right-radius: ${({ theme }) => theme.main.sizes.borderRadius};
|
||||
padding: 1.8rem 0;
|
||||
`;
|
||||
|
||||
export default ListWrapper;
|
||||
@ -1,28 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
import { Text } from '@buffetjs/core';
|
||||
|
||||
const activeStyle = theme => `
|
||||
background-color: ${theme.main.colors.lightestBlue};
|
||||
border: 1px solid ${theme.main.colors.darkBlue};
|
||||
${Text} {
|
||||
color: ${theme.main.colors.mediumBlue};
|
||||
}
|
||||
svg {
|
||||
color: ${theme.main.colors.mediumBlue};
|
||||
}
|
||||
`;
|
||||
|
||||
const RowStyle = styled.div`
|
||||
height: 5.4rem;
|
||||
padding: 1rem 3rem;
|
||||
border: 1px solid transparent;
|
||||
background-color: ${({ theme, isWhite }) => theme.main.colors[isWhite ? 'white' : 'lightGrey']};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.main.colors.lightestBlue};
|
||||
${({ theme }) => activeStyle(theme)};
|
||||
}
|
||||
${({ isActive, theme }) => isActive && activeStyle(theme)};
|
||||
`;
|
||||
|
||||
export default RowStyle;
|
||||
@ -1,11 +1,9 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Flex, Text } from '@buffetjs/core';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { PermissionsWrapper, RowContainer } from '@strapi/helper-plugin';
|
||||
import { Accordion, AccordionContent, AccordionToggle, Box } from '@strapi/parts';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import SubCategory from '../SubCategory';
|
||||
import RowStyle from './Wrapper';
|
||||
|
||||
const PermissionRow = ({
|
||||
childrenForm,
|
||||
@ -30,26 +28,18 @@ const PermissionRow = ({
|
||||
}, [name]);
|
||||
|
||||
return (
|
||||
<RowContainer isWhite={isWhite}>
|
||||
<RowStyle isWhite={isWhite} isActive={isOpen} onClick={handleClick}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<div>
|
||||
<Text color="grey" fontWeight="bold" fontSize="xs" textTransform="uppercase">
|
||||
{categoryName}
|
||||
</Text>
|
||||
<Text lineHeight="22px" color="grey">
|
||||
{formatMessage({ id: 'Settings.permissions.category' }, { category: categoryName })}
|
||||
{kind === 'plugins' ? 'plugin' : kind}
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<FontAwesomeIcon style={{ width: '11px' }} color="#9EA7B8" icon="chevron-down" />
|
||||
</div>
|
||||
</Flex>
|
||||
</RowStyle>
|
||||
<Accordion expanded={isOpen} toggle={handleClick} id="acc-1">
|
||||
<AccordionToggle
|
||||
title={upperFirst(categoryName)}
|
||||
description={`${formatMessage(
|
||||
{ id: 'Settings.permissions.category' },
|
||||
{ category: categoryName }
|
||||
)} ${kind === 'plugins' ? 'plugin' : kind}`}
|
||||
variant={isWhite ? 'primary' : 'secondary'}
|
||||
/>
|
||||
|
||||
{isOpen && (
|
||||
<PermissionsWrapper isWhite={isWhite}>
|
||||
<AccordionContent>
|
||||
<Box padding={6}>
|
||||
{childrenForm.map(({ actions, subCategoryName, subCategoryId }) => (
|
||||
<SubCategory
|
||||
key={subCategoryName}
|
||||
@ -60,9 +50,9 @@ const PermissionRow = ({
|
||||
pathToData={[...pathToData, subCategoryId]}
|
||||
/>
|
||||
))}
|
||||
</PermissionsWrapper>
|
||||
)}
|
||||
</RowContainer>
|
||||
</Box>
|
||||
</AccordionContent>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
/* eslint-disable indent */
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CheckboxWrapper = styled.div`
|
||||
min-width: 33%;
|
||||
padding: 0.9rem;
|
||||
height: 3.6rem;
|
||||
position: relative;
|
||||
${({ hasConditions, disabled, theme }) =>
|
||||
hasConditions &&
|
||||
`
|
||||
&:before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0px;
|
||||
color: ${disabled ? theme.main.colors.grey : theme.main.colors.mediumBlue};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export default CheckboxWrapper;
|
||||
@ -1,13 +0,0 @@
|
||||
/* eslint-disable indent */
|
||||
import styled from 'styled-components';
|
||||
|
||||
const ConditionsButtonWrapper = styled.div`
|
||||
padding: 0.9rem;
|
||||
${({ hasConditions }) =>
|
||||
hasConditions &&
|
||||
`
|
||||
padding-left: 22px;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default ConditionsButtonWrapper;
|
||||
@ -1,26 +0,0 @@
|
||||
/* eslint-disable indent */
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding-bottom: 2.6rem;
|
||||
input[type='checkbox'] {
|
||||
&:after {
|
||||
color: ${({ theme }) => theme.main.colors.mediumBlue};
|
||||
}
|
||||
}
|
||||
${({ disabled, theme }) =>
|
||||
disabled &&
|
||||
`
|
||||
label {
|
||||
cursor: default !important;
|
||||
color: ${theme.main.colors.grey};
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
&:after {
|
||||
color: ${theme.main.colors.grey};
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,29 +1,43 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Flex, Padded, Text, Checkbox } from '@buffetjs/core';
|
||||
import { Row, Box, TableLabel, Checkbox, Grid, GridItem } from '@strapi/parts';
|
||||
|
||||
import { useIntl } from 'react-intl';
|
||||
import { BaselineAlignment } from '@strapi/helper-plugin';
|
||||
import { get } from 'lodash';
|
||||
import IS_DISABLED from 'ee_else_ce/components/Roles/PluginsAndSettings/SubCategory/utils/constants';
|
||||
import { usePermissionsDataManager } from '../../../../hooks';
|
||||
import { getCheckboxState, removeConditionKeyFromData } from '../../utils';
|
||||
import ConditionsButton from '../../ConditionsButton';
|
||||
import ConditionsModal from '../../ConditionsModal';
|
||||
import CheckboxWrapper from './CheckboxWrapper';
|
||||
import ConditionsButtonWrapper from './ConditionsButtonWrapper';
|
||||
import Wrapper from './Wrapper';
|
||||
import { formatActions, getConditionsButtonState } from './utils';
|
||||
|
||||
const Border = styled.div`
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
border-top: 1px solid #f6f6f6;
|
||||
padding: 0px 10px;
|
||||
border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
|
||||
`;
|
||||
|
||||
const CheckboxWrapper = styled.div`
|
||||
position: relative;
|
||||
${({ hasConditions, disabled, theme }) =>
|
||||
hasConditions &&
|
||||
`
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: ${-4 / 16}rem;
|
||||
left: ${-8 / 16}rem;
|
||||
width: ${6 / 16}rem;
|
||||
height: ${6 / 16}rem;
|
||||
border-radius: ${20 / 16}rem;
|
||||
background: ${disabled ? theme.colors.neutral100 : theme.colors.primary600};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const SubCategory = ({ categoryName, isFormDisabled, subCategoryName, actions, pathToData }) => {
|
||||
const [modalState, setModalState] = useState({ isOpen: false, isMounted: false });
|
||||
const [isModalOpen, setModalOpen] = useState(false);
|
||||
const {
|
||||
modifiedData,
|
||||
onChangeParentCheckbox,
|
||||
@ -43,84 +57,87 @@ const SubCategory = ({ categoryName, isFormDisabled, subCategoryName, actions, p
|
||||
const { hasAllActionsSelected, hasSomeActionsSelected } = getCheckboxState(dataWithoutCondition);
|
||||
|
||||
const handleToggleModalIsOpen = () => {
|
||||
setModalState(prevState => ({ isMounted: true, isOpen: !prevState.isOpen }));
|
||||
setModalOpen(s => !s);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setModalState(prevState => ({ ...prevState, isMounted: false }));
|
||||
setModalOpen(false);
|
||||
};
|
||||
|
||||
// We need to format the actions so it matches the shape of the ConditionsModal actions props
|
||||
const formattedActions = formatActions(actions, modifiedData, pathToData);
|
||||
const doesButtonHasCondition = getConditionsButtonState(get(modifiedData, [...pathToData], {}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<Padded right size="sm">
|
||||
<Text
|
||||
lineHeight="18px"
|
||||
color="#919bae"
|
||||
fontWeight="bold"
|
||||
fontSize="xs"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
{subCategoryName}
|
||||
</Text>
|
||||
</Padded>
|
||||
<Box>
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<Box paddingRight={4}>
|
||||
<TableLabel textColor="neutral600">{subCategoryName}</TableLabel>
|
||||
</Box>
|
||||
<Border />
|
||||
<Padded left size="sm">
|
||||
<BaselineAlignment top size="1px" />
|
||||
<Box paddingLeft={4}>
|
||||
<Checkbox
|
||||
name={pathToData.join('..')}
|
||||
message={formatMessage({ id: 'app.utils.select-all' })}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
onChange={onChangeParentCheckbox}
|
||||
someChecked={hasSomeActionsSelected}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChangeParentCheckbox({
|
||||
target: {
|
||||
name: pathToData.join('..'),
|
||||
value,
|
||||
},
|
||||
})}
|
||||
indeterminate={hasSomeActionsSelected}
|
||||
value={hasAllActionsSelected}
|
||||
/>
|
||||
</Padded>
|
||||
</Flex>
|
||||
<BaselineAlignment top size="1px" />
|
||||
<Padded top size="xs">
|
||||
<Flex flexWrap="wrap">
|
||||
>
|
||||
{formatMessage({ id: 'app.utils.select-all', defaultMessage: 'Select all' })}
|
||||
</Checkbox>
|
||||
</Box>
|
||||
</Row>
|
||||
<Row paddingTop={6} paddingBottom={6}>
|
||||
<Grid gap={2} style={{ flex: 1 }}>
|
||||
{formattedActions.map(({ checkboxName, value, action, displayName, hasConditions }) => {
|
||||
return (
|
||||
<CheckboxWrapper
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
hasConditions={hasConditions}
|
||||
key={action}
|
||||
>
|
||||
<Checkbox
|
||||
name={checkboxName}
|
||||
<GridItem col={3} key={action}>
|
||||
<CheckboxWrapper
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
message={displayName}
|
||||
onChange={onChangeSimpleCheckbox}
|
||||
value={value}
|
||||
/>
|
||||
</CheckboxWrapper>
|
||||
hasConditions={hasConditions}
|
||||
>
|
||||
<Checkbox
|
||||
name={checkboxName}
|
||||
disabled={isFormDisabled || IS_DISABLED}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChangeSimpleCheckbox({
|
||||
target: {
|
||||
name: checkboxName,
|
||||
value,
|
||||
},
|
||||
})}
|
||||
value={value}
|
||||
>
|
||||
{displayName}
|
||||
</Checkbox>
|
||||
</CheckboxWrapper>
|
||||
</GridItem>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
<ConditionsButtonWrapper disabled={isFormDisabled} hasConditions={doesButtonHasCondition}>
|
||||
<ConditionsButton
|
||||
hasConditions={doesButtonHasCondition}
|
||||
onClick={handleToggleModalIsOpen}
|
||||
/>
|
||||
</ConditionsButtonWrapper>
|
||||
</Padded>
|
||||
</Wrapper>
|
||||
{modalState.isMounted && (
|
||||
<ConditionsModal
|
||||
headerBreadCrumbs={[categoryName, subCategoryName]}
|
||||
actions={formattedActions}
|
||||
isOpen={modalState.isOpen}
|
||||
isFormDisabled={isFormDisabled}
|
||||
onClosed={handleModalClose}
|
||||
onToggle={handleToggleModalIsOpen}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
<ConditionsButton
|
||||
hasConditions={doesButtonHasCondition}
|
||||
onClick={handleToggleModalIsOpen}
|
||||
variant="tertiary"
|
||||
/>
|
||||
</Row>
|
||||
</Box>
|
||||
<ConditionsModal
|
||||
headerBreadCrumbs={[categoryName, subCategoryName]}
|
||||
actions={formattedActions}
|
||||
isOpen={isModalOpen}
|
||||
isFormDisabled={isFormDisabled}
|
||||
onClosed={handleModalClose}
|
||||
onToggle={handleToggleModalIsOpen}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Padded } from '@buffetjs/core';
|
||||
import ListWrapper from './ListWrapper';
|
||||
import { Box } from '@strapi/parts';
|
||||
import PermissionRow from './Row';
|
||||
|
||||
const PluginsAndSettingsPermissions = ({ isFormDisabled, kind, layout }) => {
|
||||
@ -12,25 +11,23 @@ const PluginsAndSettingsPermissions = ({ isFormDisabled, kind, layout }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<ListWrapper>
|
||||
<Padded left right size="md">
|
||||
{layout.map(({ category, categoryId, childrenForm }, index) => {
|
||||
return (
|
||||
<PermissionRow
|
||||
key={category}
|
||||
childrenForm={childrenForm}
|
||||
kind={kind}
|
||||
isFormDisabled={isFormDisabled}
|
||||
isOpen={openedCategory === category}
|
||||
isWhite={index % 2 === 1}
|
||||
name={category}
|
||||
onOpenCategory={handleOpenCategory}
|
||||
pathToData={[kind, categoryId]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Padded>
|
||||
</ListWrapper>
|
||||
<Box padding={6} background="neutral0">
|
||||
{layout.map(({ category, categoryId, childrenForm }, index) => {
|
||||
return (
|
||||
<PermissionRow
|
||||
key={category}
|
||||
childrenForm={childrenForm}
|
||||
kind={kind}
|
||||
isFormDisabled={isFormDisabled}
|
||||
isOpen={openedCategory === category}
|
||||
isWhite={index % 2 === 1}
|
||||
name={category}
|
||||
onOpenCategory={handleOpenCategory}
|
||||
pathToData={[kind, categoryId]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -2,8 +2,8 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Required = styled.span`
|
||||
color: ${({ theme }) => theme.main.colors.red};
|
||||
padding-left: 2px;
|
||||
color: ${({ theme }) => theme.colors.danger700};
|
||||
padding-left: ${({ theme }) => theme.spaces[1]}px;
|
||||
`;
|
||||
|
||||
const RequiredSign = () => <Required>*</Required>;
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: ${({ width }) => width};
|
||||
${({ disabled, theme }) =>
|
||||
disabled &&
|
||||
`
|
||||
input[type='checkbox'] {
|
||||
cursor: not-allowed;
|
||||
&:after {
|
||||
color: ${theme.main.colors.grey};
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
Wrapper.defaultProps = {
|
||||
width: '18rem',
|
||||
};
|
||||
|
||||
Wrapper.propTypes = {
|
||||
width: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,50 +1,72 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Row, Checkbox, Text } from '@strapi/parts';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Checkbox, Text } from '@buffetjs/core';
|
||||
import React, { memo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useIntl } from 'react-intl';
|
||||
import CollapseLabel from '../CollapseLabel';
|
||||
import Wrapper from './Wrapper';
|
||||
import { firstRowWidth } from '../Permissions/utils/constants';
|
||||
|
||||
// ! REMOVE THIS WHEN DS IS UPDATED WITH ELLIPSIS PROP
|
||||
const StyledText = styled(Text)`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const RowLabelWithCheckbox = ({
|
||||
children,
|
||||
isCollapsable,
|
||||
isActive,
|
||||
isFormDisabled,
|
||||
label,
|
||||
onChange,
|
||||
onClick,
|
||||
checkboxName,
|
||||
someChecked,
|
||||
textColor,
|
||||
value,
|
||||
width,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Wrapper width={width} disabled={isFormDisabled}>
|
||||
<Row alignItems="center" paddingLeft={6} style={{ width: firstRowWidth, flexShrink: 0 }}>
|
||||
<Checkbox
|
||||
name={checkboxName}
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: `Settings.permissions.select-all-by-permission`,
|
||||
defaultMessage: 'Select all {label} permissions',
|
||||
},
|
||||
{ label }
|
||||
)}
|
||||
disabled={isFormDisabled}
|
||||
onChange={onChange}
|
||||
someChecked={someChecked}
|
||||
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
|
||||
onValueChange={value =>
|
||||
onChange({
|
||||
target: {
|
||||
name: checkboxName,
|
||||
value,
|
||||
},
|
||||
})}
|
||||
indeterminate={someChecked}
|
||||
value={value}
|
||||
/>
|
||||
<CollapseLabel
|
||||
title={label}
|
||||
alignItems="center"
|
||||
isCollapsable={isCollapsable}
|
||||
onClick={onClick}
|
||||
{...(isCollapsable && {
|
||||
onClick,
|
||||
'aria-expanded': isActive,
|
||||
onKeyDown: ({ key }) => (key === 'Enter' || key === ' ') && onClick(),
|
||||
tabIndex: 0,
|
||||
role: 'button',
|
||||
})}
|
||||
>
|
||||
<Text
|
||||
color={textColor}
|
||||
ellipsis
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
lineHeight="20px"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<StyledText>{upperFirst(label)}</StyledText>
|
||||
{children}
|
||||
</CollapseLabel>
|
||||
</Wrapper>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
@ -55,8 +77,6 @@ RowLabelWithCheckbox.defaultProps = {
|
||||
value: false,
|
||||
someChecked: false,
|
||||
isCollapsable: false,
|
||||
textColor: 'grey',
|
||||
width: '18rem',
|
||||
};
|
||||
|
||||
RowLabelWithCheckbox.propTypes = {
|
||||
@ -68,9 +88,8 @@ RowLabelWithCheckbox.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
someChecked: PropTypes.bool,
|
||||
textColor: PropTypes.string,
|
||||
value: PropTypes.bool,
|
||||
width: PropTypes.string,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default memo(RowLabelWithCheckbox);
|
||||
|
||||
@ -5,7 +5,10 @@ import PageTitle from '../PageTitle';
|
||||
|
||||
const SettingsPageTitle = ({ name }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const text = formatMessage({ id: 'Settings.PageTitle' }, { name });
|
||||
const text = formatMessage(
|
||||
{ id: 'Settings.PageTitle', defaultMessage: 'Settings - {name}' },
|
||||
{ name }
|
||||
);
|
||||
|
||||
return <PageTitle title={text} />;
|
||||
};
|
||||
|
||||
@ -132,6 +132,7 @@ const Login = ({ onSubmit, schema, children }) => {
|
||||
handleChange({ target: { value: checked, name: 'rememberMe' } });
|
||||
}}
|
||||
value={values.rememberMe}
|
||||
aria-label="rememberMe"
|
||||
name="rememberMe"
|
||||
>
|
||||
{formatMessage({
|
||||
|
||||
@ -674,6 +674,7 @@ describe('ADMIN | PAGES | AUTH | BaseLogin', () => {
|
||||
class="c22 c23"
|
||||
>
|
||||
<input
|
||||
aria-label="rememberMe"
|
||||
class="c24"
|
||||
id="checkbox-3"
|
||||
name="rememberMe"
|
||||
|
||||
@ -280,6 +280,7 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => {
|
||||
}}
|
||||
value={values.news}
|
||||
name="news"
|
||||
aria-label="news"
|
||||
>
|
||||
{formatMessage(
|
||||
{
|
||||
|
||||
@ -906,6 +906,7 @@ describe('ADMIN | PAGES | AUTH | Register', () => {
|
||||
class="c4 c33"
|
||||
>
|
||||
<input
|
||||
aria-label="news"
|
||||
class="c34"
|
||||
id="checkbox-7"
|
||||
name="news"
|
||||
|
||||
@ -63,7 +63,10 @@ function SettingsPage() {
|
||||
return <Redirect to="/settings/application-infos" />;
|
||||
}
|
||||
|
||||
const settingTitle = formatMessage({ id: 'app.components.LeftMenuLinkContainer.settings' });
|
||||
const settingTitle = formatMessage({
|
||||
id: 'app.components.LeftMenuLinkContainer.settings',
|
||||
defaultMessage: 'Settings',
|
||||
});
|
||||
|
||||
return (
|
||||
<SettingsSearchHeaderProvider value={{ toggleHeaderSearch }}>
|
||||
|
||||
@ -118,6 +118,8 @@
|
||||
"Settings.permissions.users.listview.header.description.plural": "{number} users found",
|
||||
"Settings.permissions.users.listview.header.description.singular": "{number} user found",
|
||||
"Settings.permissions.users.listview.header.title": "Users",
|
||||
"Settings.permissions.select-all-by-permission": "Select all {label} permissions",
|
||||
"Settings.permissions.select-by-permission": "Select {label} permission",
|
||||
"Settings.profile.form.section.experience.interfaceLanguage": "Interface language",
|
||||
"Settings.profile.form.section.experience.interfaceLanguage.hint": "This will only display your own interface in the chosen language.",
|
||||
"Settings.profile.form.section.experience.title": "Experience",
|
||||
@ -127,7 +129,7 @@
|
||||
"Settings.roles.create.title": "Create a role",
|
||||
"Settings.roles.created": "Role created",
|
||||
"Settings.roles.edit.title": "Edit a role",
|
||||
"Settings.roles.form.button.users-with-role": "Users with this role",
|
||||
"Settings.roles.form.button.users-with-role": "{number, plural, =0 {# users} one {# user} other {# users}} with this role",
|
||||
"Settings.roles.form.created": "Created",
|
||||
"Settings.roles.form.description": "Name and description of the role",
|
||||
"Settings.roles.form.input.description": "Description",
|
||||
|
||||
@ -1,36 +1,54 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Header } from '@buffetjs/custom';
|
||||
import { Padded } from '@buffetjs/core';
|
||||
import moment from 'moment';
|
||||
import { Formik } from 'formik';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { HeaderLayout, Button } from '@strapi/parts';
|
||||
import { AddIcon, EditIcon } from '@strapi/icons';
|
||||
import {
|
||||
BaselineAlignment,
|
||||
CheckPagePermissions,
|
||||
Form,
|
||||
LoadingIndicatorPage,
|
||||
request,
|
||||
useTracking,
|
||||
useNotification,
|
||||
useOverlayBlocker,
|
||||
useTracking,
|
||||
} from '@strapi/helper-plugin';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ContentLayout,
|
||||
Grid,
|
||||
GridItem,
|
||||
HeaderLayout,
|
||||
Main,
|
||||
Row,
|
||||
Stack,
|
||||
Text,
|
||||
Textarea,
|
||||
TextInput,
|
||||
} from '@strapi/parts';
|
||||
import { Formik } from 'formik';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import adminPermissions from '../../../../../admin/src/permissions';
|
||||
import { useFetchPermissionsLayout, useFetchRole } from '../../../../../admin/src/hooks';
|
||||
import PageTitle from '../../../../../admin/src/components/SettingsPageTitle';
|
||||
import FormCard from '../../../../../admin/src/components/FormBloc';
|
||||
import { ButtonWithNumber } from '../../../../../admin/src/components/Roles';
|
||||
import SizedInput from '../../../../../admin/src/components/SizedInput';
|
||||
import styled from 'styled-components';
|
||||
import Permissions from '../../../../../admin/src/components/Roles/Permissions';
|
||||
|
||||
import PageTitle from '../../../../../admin/src/components/SettingsPageTitle';
|
||||
import { useFetchPermissionsLayout, useFetchRole } from '../../../../../admin/src/hooks';
|
||||
import adminPermissions from '../../../../../admin/src/permissions';
|
||||
import schema from './utils/schema';
|
||||
|
||||
const UsersRoleNumber = styled.div`
|
||||
border: 1px solid ${({ theme }) => theme.colors.primary200};
|
||||
background: ${({ theme }) => theme.colors.primary100};
|
||||
padding: ${({ theme }) => `${theme.spaces[2]} ${theme.spaces[4]}`};
|
||||
color: ${({ theme }) => theme.colors.primary600};
|
||||
border-radius: ${({ theme }) => theme.borderRadius};
|
||||
font-size: ${12 / 16}rem;
|
||||
font-width: bold;
|
||||
`;
|
||||
|
||||
const CreatePage = () => {
|
||||
const toggleNotification = useNotification();
|
||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||
const { formatMessage } = useIntl();
|
||||
const [isSubmiting, setIsSubmiting] = useState(false);
|
||||
const [isSubmitting, setIsSubmiting] = useState(false);
|
||||
const { replace } = useHistory();
|
||||
const permissionsRef = useRef();
|
||||
const { trackUsage } = useTracking();
|
||||
@ -39,31 +57,6 @@ const CreatePage = () => {
|
||||
const { isLoading: isLayoutLoading, data: permissionsLayout } = useFetchPermissionsLayout();
|
||||
const { permissions: rolePermissions, isLoading: isRoleLoading } = useFetchRole(id);
|
||||
|
||||
const headerActions = (handleSubmit, handleReset) => [
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'app.components.Button.reset',
|
||||
defaultMessage: 'Reset',
|
||||
}),
|
||||
onClick: () => {
|
||||
handleReset();
|
||||
permissionsRef.current.resetForm();
|
||||
},
|
||||
color: 'cancel',
|
||||
type: 'button',
|
||||
},
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'app.components.Button.save',
|
||||
defaultMessage: 'Save',
|
||||
}),
|
||||
onClick: handleSubmit,
|
||||
color: 'success',
|
||||
type: 'submit',
|
||||
isLoading: isSubmiting,
|
||||
},
|
||||
];
|
||||
|
||||
const handleCreateRoleSubmit = data => {
|
||||
lockApp();
|
||||
setIsSubmiting(true);
|
||||
@ -119,21 +112,12 @@ const CreatePage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const actions = [
|
||||
<ButtonWithNumber number={0} onClick={() => console.log('Open user modal')} key="user-button">
|
||||
{formatMessage({
|
||||
id: 'Settings.roles.form.button.users-with-role',
|
||||
defaultMessage: 'Users with this role',
|
||||
})}
|
||||
</ButtonWithNumber>,
|
||||
];
|
||||
|
||||
const defaultDescription = `${formatMessage({
|
||||
id: 'Settings.roles.form.created',
|
||||
})} ${moment().format('LL')}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Main labelledBy="title">
|
||||
<PageTitle name="Roles" />
|
||||
<Formik
|
||||
initialValues={{ name: '', description: defaultDescription }}
|
||||
@ -141,84 +125,125 @@ const CreatePage = () => {
|
||||
validationSchema={schema}
|
||||
validateOnChange={false}
|
||||
>
|
||||
{({ handleSubmit, values, errors, handleReset, handleChange, handleBlur }) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
{({ handleSubmit, values, errors, handleReset, handleChange }) => (
|
||||
<Form noValidate>
|
||||
<>
|
||||
<HeaderLayout
|
||||
primaryAction={<Button startIcon={<AddIcon />}>Add an entry</Button>}
|
||||
secondaryAction={
|
||||
<Button variant="tertiary" startIcon={<EditIcon />}>
|
||||
Edit
|
||||
</Button>
|
||||
id="title"
|
||||
primaryAction={
|
||||
<Stack horizontal size={2}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
handleReset();
|
||||
permissionsRef.current.resetForm();
|
||||
}}
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'app.components.Button.reset',
|
||||
defaultMessage: 'Reset',
|
||||
})}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} loading={isSubmitting}>
|
||||
{formatMessage({
|
||||
id: 'app.components.Button.save',
|
||||
defaultMessage: 'Save',
|
||||
})}
|
||||
</Button>
|
||||
</Stack>
|
||||
}
|
||||
title="Other CT"
|
||||
subtitle="36 entries found"
|
||||
as="h1"
|
||||
/>
|
||||
<Header
|
||||
title={{
|
||||
label: formatMessage({
|
||||
id: 'Settings.roles.create.title',
|
||||
defaultMessage: 'Create a role',
|
||||
}),
|
||||
}}
|
||||
content={formatMessage({
|
||||
title={formatMessage({
|
||||
id: 'Settings.roles.create.title',
|
||||
defaultMessage: 'Create a role',
|
||||
})}
|
||||
subtitle={formatMessage({
|
||||
id: 'Settings.roles.create.description',
|
||||
defaultMessage: 'Define the rights given to the role',
|
||||
})}
|
||||
actions={headerActions(handleSubmit, handleReset)}
|
||||
isLoading={isLayoutLoading}
|
||||
as="h1"
|
||||
/>
|
||||
<BaselineAlignment top size="3px" />
|
||||
<FormCard
|
||||
actions={actions}
|
||||
title={formatMessage({
|
||||
id: 'Settings.roles.form.title',
|
||||
defaultMessage: 'Details',
|
||||
})}
|
||||
subtitle={formatMessage({
|
||||
id: 'Settings.roles.form.description',
|
||||
defaultMessage: 'Name and description of the role',
|
||||
})}
|
||||
>
|
||||
<SizedInput
|
||||
label="Settings.roles.form.input.name"
|
||||
defaultMessage="Name"
|
||||
name="name"
|
||||
type="text"
|
||||
error={errors.name ? { id: errors.name } : null}
|
||||
onBlur={handleBlur}
|
||||
value={values.name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<SizedInput
|
||||
label="Settings.roles.form.input.description"
|
||||
defaultMessage="Description"
|
||||
name="description"
|
||||
type="textarea"
|
||||
onBlur={handleBlur}
|
||||
value={values.description}
|
||||
onChange={handleChange}
|
||||
// Override the default height of the textarea
|
||||
style={{ height: 115 }}
|
||||
/>
|
||||
</FormCard>
|
||||
{!isLayoutLoading && !isRoleLoading && (
|
||||
<Padded top bottom size="md">
|
||||
<Permissions
|
||||
isFormDisabled={false}
|
||||
ref={permissionsRef}
|
||||
permissions={rolePermissions}
|
||||
layout={permissionsLayout}
|
||||
/>
|
||||
</Padded>
|
||||
)}
|
||||
<ContentLayout>
|
||||
<Stack size={6}>
|
||||
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
|
||||
<Stack size={4}>
|
||||
<Row justifyContent="space-between">
|
||||
<Box>
|
||||
<Box>
|
||||
<Text highlighted>
|
||||
{formatMessage({
|
||||
id: 'Settings.roles.form.title',
|
||||
defaultMessage: 'Details',
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text textColor="neutral600" small>
|
||||
{formatMessage({
|
||||
id: 'Settings.roles.form.description',
|
||||
defaultMessage: 'Name and description of the role',
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<UsersRoleNumber>
|
||||
{formatMessage(
|
||||
{
|
||||
id: 'Settings.roles.form.button.users-with-role',
|
||||
defaultMessage:
|
||||
'{number, plural, =0 {# users} one {# user} other {# users}} with this role',
|
||||
},
|
||||
{ number: 0 }
|
||||
)}
|
||||
</UsersRoleNumber>
|
||||
</Row>
|
||||
<Grid gap={4}>
|
||||
<GridItem col={6}>
|
||||
<TextInput
|
||||
name="name"
|
||||
error={errors.name && formatMessage({ id: errors.name })}
|
||||
label={formatMessage({
|
||||
id: 'Settings.roles.form.input.name',
|
||||
defaultMessage: 'Name',
|
||||
})}
|
||||
onChange={handleChange}
|
||||
value={values.name}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={6}>
|
||||
<Textarea
|
||||
label={formatMessage({
|
||||
id: 'Settings.roles.form.input.description',
|
||||
defaultMessage: 'Description',
|
||||
})}
|
||||
name="description"
|
||||
error={errors.description && formatMessage({ id: errors.description })}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{values.description}
|
||||
</Textarea>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Box>
|
||||
{!isLayoutLoading && !isRoleLoading ? (
|
||||
<Box shadow="filterShadow" hasRadius>
|
||||
<Permissions
|
||||
isFormDisabled={false}
|
||||
ref={permissionsRef}
|
||||
permissions={rolePermissions}
|
||||
layout={permissionsLayout}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<LoadingIndicatorPage />
|
||||
)}
|
||||
</Stack>
|
||||
</ContentLayout>
|
||||
</>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</>
|
||||
</Main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,49 @@
|
||||
/**
|
||||
*
|
||||
* Tests for CreatePage
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Router, Switch, Route } from 'react-router';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import Theme from '../../../../../../admin/src/components/Theme';
|
||||
import { CreatePage } from '../index';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: jest.fn(() => jest.fn()),
|
||||
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn(), unlockApp: jest.fn() })),
|
||||
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
|
||||
}));
|
||||
|
||||
const makeApp = history => (
|
||||
<IntlProvider messages={{ en: {} }} textComponent="span" locale="en">
|
||||
<Theme>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/settings/roles/duplicate/:id">
|
||||
<CreatePage />
|
||||
</Route>
|
||||
<Route path="/settings/roles/new">
|
||||
<CreatePage />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
</Theme>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
describe('<CreatePage />', () => {
|
||||
it('renders and matches the snapshot', () => {
|
||||
const history = createMemoryHistory();
|
||||
const App = makeApp(history);
|
||||
const { container } = render(App);
|
||||
|
||||
history.push('/settings/roles/new');
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@ -3,6 +3,7 @@ import { translatedErrors } from '@strapi/helper-plugin';
|
||||
|
||||
const schema = yup.object().shape({
|
||||
name: yup.string().required(translatedErrors.required),
|
||||
description: yup.string().required(translatedErrors.required),
|
||||
});
|
||||
|
||||
export default schema;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user