Init content type permissions list

Signed-off-by: HichamELBSI <elabbassih@gmail.com>
This commit is contained in:
HichamELBSI 2020-06-01 10:53:36 +02:00 committed by Alexandre Bodin
parent 57e21e1a04
commit b786f9b43d
31 changed files with 559 additions and 74 deletions

View File

@ -0,0 +1,67 @@
import React from 'react';
import { PropTypes } from 'prop-types';
import { useIntl } from 'react-intl';
import SizedInput from '../../../src/components/SizedInput';
import { ButtonWithNumber } from '../../../src/components/Roles';
import FormCard from '../../../src/components/FormBloc';
const RoleForm = ({ values, errors, onChange, onBlur, isLoading }) => {
const { formatMessage } = useIntl();
const actions = [
<ButtonWithNumber number={0} onClick={() => console.log('Open user modal')} key="user-button">
{formatMessage({
id: 'Settings.roles.form.button.users-with-role',
})}
</ButtonWithNumber>,
];
return (
<FormCard
actions={actions}
isLoading={isLoading}
title={formatMessage({
id: 'Settings.roles.form.title',
})}
subtitle={formatMessage({
id: 'Settings.roles.form.description',
})}
>
<SizedInput
label="Name"
name="name"
type="text"
error={errors.name ? { id: errors.name } : null}
onBlur={onBlur}
value={values.name}
onChange={onChange}
/>
<SizedInput
label="Description"
name="description"
type="textarea"
onBlur={onBlur}
value={values.description}
onChange={onChange}
// Override the default height of the textarea
style={{ height: 115 }}
/>
</FormCard>
);
};
RoleForm.defaultProps = {
isLoading: false,
values: { name: '', description: '' },
};
RoleForm.propTypes = {
values: PropTypes.object,
errors: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func.isRequired,
isLoading: PropTypes.bool,
};
export default RoleForm;

View File

@ -6,18 +6,10 @@ import { useIntl } from 'react-intl';
import { request } from 'strapi-helper-plugin';
import { useHistory } from 'react-router-dom';
import { roleTabsLabel } from '../../../../src/utils';
import BaselineAlignement from '../../../../src/components/BaselineAlignement';
import ContainerFluid from '../../../../src/components/ContainerFluid';
import FormCard from '../../../../src/components/FormBloc';
import {
ButtonWithNumber,
CollectionTypesPermissions,
Tabs,
SingleTypesPermissions,
PluginsPermissions,
SettingsPermissions,
} from '../../../../src/components/Roles';
import { ButtonWithNumber, Permissions } from '../../../../src/components/Roles';
import SizedInput from '../../../../src/components/SizedInput';
import schema from './utils/schema';
@ -126,12 +118,7 @@ const CreatePage = () => {
</FormCard>
<Padded top size="md">
<Tabs tabsLabel={roleTabsLabel}>
<CollectionTypesPermissions />
<SingleTypesPermissions />
<PluginsPermissions />
<SettingsPermissions />
</Tabs>
<Permissions />
</Padded>
</ContainerFluid>
</form>

View File

@ -12,9 +12,10 @@ import {
request,
} from 'strapi-helper-plugin';
import { useIntl } from 'react-intl';
import useSettingsHeaderSearchContext from '../../../../src/hooks/useSettingsHeaderSearchContext';
import { EmptyRole, RoleListWrapper } from '../../../../src/components/Roles';
import useRolesList from '../../../../src/hooks/useRolesList';
import { useRolesList } from '../../../../src/hooks';
import RoleRow from './RoleRow';
import BaselineAlignment from './BaselineAlignment';
import reducer, { initialState } from './reducer';

View File

@ -1,12 +0,0 @@
import React from 'react';
import { Padded, Flex } from '@buffetjs/core';
const CollectionTypesPermissions = () => (
<Padded top left right bottom size="lg">
<Flex justifyContent="center" alignItems="center">
COLLECTION TYPES PERMISSIONS COMMING SOON
</Flex>
</Padded>
);
export default CollectionTypesPermissions;

View File

@ -0,0 +1,10 @@
import styled from 'styled-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const Chevron = styled(FontAwesomeIcon)`
display: none;
padding-left: 0.5rem;
font-size: 1.6rem;
`;
export default Chevron;

View File

@ -0,0 +1,16 @@
import styled from 'styled-components';
import PropTypes from 'prop-types';
const PermissionName = styled.div`
display: flex;
align-items: center;
width: ${({ width }) => width};
`;
PermissionName.defaultProps = {
width: '20rem',
};
PermissionName.propTypes = {
width: PropTypes.string,
};
export default PermissionName;

View File

@ -0,0 +1,38 @@
import styled from 'styled-components';
import { Text } from '@buffetjs/core';
import Chevron from './Chevron';
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;
${({ isActive, theme }) =>
isActive &&
`
border: 1px solid ${theme.main.colors.darkBlue};
background-color: ${theme.main.colors.lightBlue};
color: ${theme.main.colors.mediumBlue};
${Text} {
color: ${theme.main.colors.mediumBlue};
}
${Chevron} {
display: block;
}
`}
&:hover {
border: 1px solid ${({ theme }) => theme.main.colors.darkBlue};
background-color: ${({ theme }) => theme.main.colors.lightBlue};
color: ${({ theme }) => theme.main.colors.mediumBlue};
${Text} {
color: ${({ theme }) => theme.main.colors.mediumBlue};
}
${Chevron} {
display: block;
}
}
`;
export default StyledRow;

View File

@ -0,0 +1,67 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Checkbox, Flex, Text, Padded } from '@buffetjs/core';
import PermissionCheckbox from '../PermissionCheckbox';
import PermissionName from './PermissionName';
import Chevron from './Chevron';
import StyledRow from './StyledRow';
// No need to create an other file for this style. It will be used only in this file.
const CollapseLabel = styled(Flex)`
cursor: pointer;
`;
const ContentTypeRow = ({
openContentTypeAttributes,
openedContentTypeAttributes,
contentType,
index,
}) => {
const isActive = openedContentTypeAttributes === contentType.name;
const handleToggleAttributes = () => {
openContentTypeAttributes(contentType.name);
};
return (
<StyledRow isActive={isActive} isGrey={index % 2 === 0}>
<Flex>
<Padded left size="sm" />
<PermissionName>
<Checkbox someChecked />
<CollapseLabel alignItems="center" onClick={handleToggleAttributes}>
<Text
color="grey"
fontWeight="bold"
fontSize="xs"
textTransform="uppercase"
lineHeight="20px"
>
{contentType.name}
</Text>
<Chevron icon={isActive ? 'chevron-up' : 'chevron-down'} />
</CollapseLabel>
</PermissionName>
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
</Flex>
</StyledRow>
);
};
ContentTypeRow.defaultProps = {
openedContentTypeAttributes: null,
};
ContentTypeRow.propTypes = {
contentType: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
openContentTypeAttributes: PropTypes.func.isRequired,
openedContentTypeAttributes: PropTypes.string,
};
export default ContentTypeRow;

View File

@ -0,0 +1,8 @@
import styled from 'styled-components';
import { Checkbox } from '@buffetjs/core';
const PermissionCheckbox = styled(Checkbox)`
width: 10rem;
`;
export default PermissionCheckbox;

View File

@ -0,0 +1,9 @@
import styled from 'styled-components';
const PermissionsHeaderWrapper = styled.div`
padding-left: 211px;
padding-bottom: 25px;
padding-top: 26px;
`;
export default PermissionsHeaderWrapper;

View File

@ -0,0 +1,21 @@
import React from 'react';
import { Flex } from '@buffetjs/core';
import PermissionCheckbox from '../PermissionCheckbox';
import Wrapper from './Wrapper';
const PermissionsHeader = () => {
return (
<Wrapper>
<Flex>
<PermissionCheckbox message="Create" />
<PermissionCheckbox message="Read" />
<PermissionCheckbox message="Update" />
<PermissionCheckbox message="Delete" />
<PermissionCheckbox message="Publish" />
</Flex>
</Wrapper>
);
};
export default PermissionsHeader;

View File

@ -0,0 +1,7 @@
import styled from 'styled-components';
const Wrapper = styled.div`
background-color: ${({ theme }) => theme.main.colors.white};
`;
export default Wrapper;

View File

@ -0,0 +1,44 @@
import React, { useReducer } from 'react';
import PropTypes from 'prop-types';
import { Padded } from '@buffetjs/core';
import ContentTypeRow from './ContentTypesRow';
import Wrapper from './Wrapper';
import PermissionsHeader from './PermissionsHeader';
import reducer, { initialState } from './reducer';
const ContentTypesPermissions = ({ contentTypes }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const openContentTypeAttributes = contentTypeToOpen => {
dispatch({
type: 'OPEN_CONTENT_TYPE_ATTRIBUTES',
contentTypeToOpen,
});
};
return (
<Wrapper>
<Padded left right bottom size="md">
<PermissionsHeader />
{contentTypes &&
contentTypes.map((contentType, index) => (
<ContentTypeRow
key={contentType.uid}
openedContentTypeAttributes={state.collapseContentTypeAttribute}
openContentTypeAttributes={openContentTypeAttributes}
index={index}
contentType={contentType}
/>
))}
</Padded>
</Wrapper>
);
};
ContentTypesPermissions.propTypes = {
contentTypes: PropTypes.array.isRequired,
};
export default ContentTypesPermissions;

View File

@ -0,0 +1,24 @@
/* eslint-disable consistent-return */
import produce from 'immer';
export const initialState = {
collapseContentTypeAttribute: null,
};
const reducer = (state, action) =>
produce(state, draftState => {
switch (action.type) {
case 'OPEN_CONTENT_TYPE_ATTRIBUTES': {
if (state.collapseContentTypeAttribute === action.contentTypeToOpen) {
draftState.collapseContentTypeAttribute = null;
} else {
draftState.collapseContentTypeAttribute = action.contentTypeToOpen;
}
break;
}
default:
return draftState;
}
});
export default reducer;

View File

@ -0,0 +1,45 @@
const permissions = {
sections: {
contentTypes: [
{
name: 'Create',
action: 'plugins::content-type.create', // same with read, update and delete
subjects: ['plugins::users-permissions.user'], // on which content type it will be applied
},
],
plugins: [
{
name: 'Read', // Label checkbox
plugin: 'plugin::content-type-builder', // Retrieve banner info
subCategory: 'Category name', // if null, then the front uses plugin's name by default
action: 'plugins::content-type-builder.read', // Mapping
},
],
settings: [
{
name: 'Create', // Label checkbox
category: 'Webhook', // Banner info
subCategory: 'category name', // Divider title
action: 'plugins::content-type-builder.create',
},
],
},
conditions: [{}], // To be defined
};
const rolePermissions = [
{
action: 'plugins::content-manager.create',
subject: 'plugins::users-permissions.user',
fields: ['email', 'firstname', 'lastname', 'roles'], // or ["*"] or ["**"]
conditions: [],
},
{
action: 'plugins::content-manager.anAction',
subject: null,
fields: null,
conditions: [],
},
];
export { permissions, rolePermissions };

View File

@ -0,0 +1,46 @@
import React, { useReducer, useEffect } from 'react';
import { request } from 'strapi-helper-plugin';
import Tabs from '../Tabs';
import ContentTypes from './ContentTypes';
import PluginsPermissions from './Plugins';
import SettingsPermissions from './Settings';
import reducer, { initialState } from './reducer';
import { roleTabsLabel } from '../../../utils';
const Permissions = () => {
const [{ collectionTypes, singleTypes }, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
fetchContentTypes();
}, []);
const fetchContentTypes = async () => {
try {
const { data } = await request('/content-manager/content-types', {
method: 'GET',
});
dispatch({
type: 'GET_CONTENT_TYPES_SUCCEDED',
data,
});
} catch (err) {
dispatch({
type: 'GET_CONTENT_TYPES_ERROR',
});
strapi.notification.error('notification.error');
}
};
return (
<Tabs tabsLabel={roleTabsLabel}>
<ContentTypes contentTypes={collectionTypes} />
<ContentTypes contentTypes={singleTypes} />
<PluginsPermissions />
<SettingsPermissions />
</Tabs>
);
};
export default Permissions;

View File

@ -0,0 +1,33 @@
/* eslint-disable consistent-return */
import produce from 'immer';
export const initialState = {
collectionTypes: [],
singleTypes: [],
isLoading: true,
};
const reducer = (state, action) =>
produce(state, draftState => {
switch (action.type) {
case 'GET_CONTENT_TYPES_SUCCEDED': {
const getContentTypeByKind = kind =>
action.data.filter(
contentType => contentType.isDisplayed && contentType.schema.kind === kind
);
draftState.isLoading = false;
draftState.collectionTypes = getContentTypeByKind('collectionType');
draftState.singleTypes = getContentTypeByKind('singleType');
break;
}
case 'GET_CONTENT_TYPES_ERROR': {
draftState.isLoading = false;
break;
}
default:
return draftState;
}
});
export default reducer;

View File

@ -0,0 +1,92 @@
import reducer from '../reducer';
describe('ADMIN | COMPONENTS | ROLES | PERMISSIONS | reducer', () => {
describe('DEFAULT_ACTION', () => {
it('should return the initialState', () => {
const state = {
test: true,
};
expect(reducer(state, {})).toEqual(state);
});
});
describe('GET_DATA_ERROR', () => {
it('should set isLoading to false is an error occured', () => {
const action = {
type: 'GET_CONTENT_TYPES_ERROR',
};
const initialState = {
collectionTypes: [],
singleTypes: [],
isLoading: true,
};
const expected = {
collectionTypes: [],
singleTypes: [],
isLoading: false,
};
expect(reducer(initialState, action)).toEqual(expected);
});
});
describe('GET_CONTENT_TYPES_SUCCEDED', () => {
it('should return the state with the collectionTypes and singleTypes', () => {
const action = {
type: 'GET_CONTENT_TYPES_SUCCEDED',
data: [
{
uid: 'app.homepage',
isDisplayed: true,
schema: {
kind: 'singleType',
},
},
{
uid: 'permissions.role',
isDisplayed: false,
schema: {
kind: 'collectionType',
},
},
{
uid: 'app.category',
isDisplayed: true,
schema: {
kind: 'collectionType',
},
},
],
};
const initialState = {
collectionTypes: [],
singleTypes: [],
isLoading: true,
};
const expected = {
collectionTypes: [
{
uid: 'app.category',
isDisplayed: true,
schema: {
kind: 'collectionType',
},
},
],
singleTypes: [
{
uid: 'app.homepage',
isDisplayed: true,
schema: {
kind: 'singleType',
},
},
],
isLoading: false,
};
expect(reducer(initialState, action)).toEqual(expected);
});
});
});

View File

@ -1,12 +0,0 @@
import React from 'react';
import { Padded, Flex } from '@buffetjs/core';
const SingleTypesPermissions = () => (
<Padded top left right bottom size="lg">
<Flex justifyContent="center" alignItems="center">
SINGLE TYPES PERMISSIONS COMMING SOON
</Flex>
</Padded>
);
export default SingleTypesPermissions;

View File

@ -4,7 +4,4 @@ export { default as RoleDescription } from './RoleDescription';
export { default as RoleListWrapper } from './RoleListWrapper';
export { default as RoleRow } from './RoleRow';
export { default as Tabs } from './Tabs';
export { default as SingleTypesPermissions } from './SingleTypesPermissions';
export { default as CollectionTypesPermissions } from './CollectionTypesPermissions';
export { default as PluginsPermissions } from './PluginsPermissions';
export { default as SettingsPermissions } from './SettingsPermissions';
export { default as Permissions } from './Permissions';

View File

@ -0,0 +1,9 @@
export { default as Header } from './Header';
export { default as MagicLink } from './MagicLink';
export { default as Filter } from './Filter';
export { default as Footer } from './Footer';
export { default as List } from './List';
export { default as FilterPicker } from './FilterPicker';
export { default as SortPicker } from './SortPicker';
export { default as SelectRoles } from './SelectRoles';
export { default as ModalCreateBody } from './ModalCreateBody';

View File

@ -2,7 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { LoadingIndicatorPage, useGlobalContext, request } from 'strapi-helper-plugin';
import { Header } from '@buffetjs/custom';
import useFetchPluginsFromMarketPlace from '../../hooks/useFetchPluginsFromMarketPlace';
import { useFetchPluginsFromMarketPlace } from '../../hooks';
import PageTitle from '../../components/PageTitle';
import PluginCard from './PluginCard';
import Wrapper from './Wrapper';

View File

@ -2,12 +2,13 @@ import React from 'react';
import { BackHeader, auth } from 'strapi-helper-plugin';
import { useHistory } from 'react-router-dom';
import { get } from 'lodash';
import BaselineAlignement from '../../components/BaselineAlignement';
import ContainerFluid from '../../components/ContainerFluid';
import FormBloc from '../../components/FormBloc';
import SizedInput from '../../components/SizedInput';
import Header from '../../components/Users/Header';
import useUsersForm from '../../hooks/useUsersForm';
import { Header } from '../../components/Users';
import { useUsersForm } from '../../hooks';
import { form, schema } from './utils';
const ProfilePage = () => {

View File

@ -6,17 +6,10 @@ import { Padded } from '@buffetjs/core';
import { Formik } from 'formik';
import { useIntl } from 'react-intl';
import { roleTabsLabel } from '../../../utils';
import RoleForm from '../../../components/Roles/RoleForm';
import BaselineAlignement from '../../../components/BaselineAlignement';
import ContainerFluid from '../../../components/ContainerFluid';
import {
CollectionTypesPermissions,
PluginsPermissions,
SettingsPermissions,
SingleTypesPermissions,
Tabs,
} from '../../../components/Roles';
import { Permissions } from '../../../components/Roles';
import { useFetchRole, useFetchPermissionsLayout } from '../../../hooks';
import schema from './utils/schema';
@ -113,12 +106,7 @@ const EditPage = () => {
onBlur={handleBlur}
/>
<Padded top size="md">
<Tabs isLoading={isLayoutLoading} tabsLabel={roleTabsLabel}>
<CollectionTypesPermissions />
<SingleTypesPermissions />
<PluginsPermissions />
<SettingsPermissions />
</Tabs>
<Permissions />
</Padded>
</ContainerFluid>
</form>

View File

@ -7,9 +7,8 @@ import { useHistory } from 'react-router-dom';
import { useGlobalContext, useQuery } from 'strapi-helper-plugin';
import { EmptyRole, RoleListWrapper, RoleRow } from '../../../components/Roles';
import { useRolesList, useSettingsHeaderSearchContext } from '../../../hooks';
import BaselineAlignment from './BaselineAlignment';
import useRolesList from '../../../hooks/useRolesList';
import useSettingsHeaderSearchContext from '../../../hooks/useSettingsHeaderSearchContext';
const RoleListPage = () => {
const { formatMessage } = useIntl();

View File

@ -5,14 +5,13 @@ import { get, isEmpty } from 'lodash';
import { useGlobalContext, auth } from 'strapi-helper-plugin';
import { Col } from 'reactstrap';
import { Padded } from '@buffetjs/core';
import BaselineAlignement from '../../../components/BaselineAlignement';
import ContainerFluid from '../../../components/ContainerFluid';
import FormBloc from '../../../components/FormBloc';
import SizedInput from '../../../components/SizedInput';
import Header from '../../../components/Users/Header';
import MagicLink from '../../../components/Users/MagicLink';
import SelectRoles from '../../../components/Users/SelectRoles';
import useUsersForm from '../../../hooks/useUsersForm';
import { Header, MagicLink, SelectRoles } from '../../../components/Users';
import { useUsersForm } from '../../../hooks';
import { editValidation } from '../../../validations/users';
import form from './utils/form';

View File

@ -2,13 +2,10 @@ import React, { useEffect, useMemo, useReducer, useRef, useState } from 'react';
import { useQuery, request } from 'strapi-helper-plugin';
import { useHistory, useLocation } from 'react-router-dom';
import { Flex, Padded } from '@buffetjs/core';
import BaselineAlignement from '../../../components/BaselineAlignement';
import useSettingsHeaderSearchContext from '../../../hooks/useSettingsHeaderSearchContext';
import Footer from '../../../components/Users/Footer';
import List from '../../../components/Users/List';
import Filter from '../../../components/Users/Filter';
import FilterPicker from '../../../components/Users/FilterPicker';
import SortPicker from '../../../components/Users/SortPicker';
import { useSettingsHeaderSearchContext } from '../../../hooks';
import { Footer, List, Filter, FilterPicker, SortPicker } from '../../../components/Users';
import Header from './Header';
import ModalForm from './ModalForm';
import getFilters from './utils/getFilters';

View File

@ -1,4 +1,4 @@
import ModalCreateBody from '../../../components/Users/ModalCreateBody';
import { ModalCreateBody } from '../../../components/Users';
const stepper = {
create: {

View File

@ -1,3 +1,6 @@
export { default as useFetchRole } from './useFetchRole';
export { default as useRolesList } from './useRolesList';
export { default as useFetchPermissionsLayout } from './useFetchPermissionsLayout';
export { default as useFetchPluginsFromMarketPlace } from './useFetchPluginsFromMarketPlace';
export { default as useSettingsHeaderSearchContext } from './useSettingsHeaderSearchContext';
export { default as useUsersForm } from './useUsersForm';