@strapi/plugin-users-permissions 4.12.6 → 4.12.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/admin/src/components/Permissions/index.js +4 -2
  2. package/admin/src/hooks/index.js +5 -0
  3. package/admin/src/hooks/useFetchRole/index.js +67 -0
  4. package/admin/src/hooks/useFetchRole/reducer.js +31 -0
  5. package/admin/src/hooks/useForm/index.js +68 -0
  6. package/admin/src/hooks/useForm/reducer.js +40 -0
  7. package/admin/src/{pages/Roles/hooks → hooks}/usePlugins.js +8 -15
  8. package/admin/src/hooks/useRolesList/index.js +65 -0
  9. package/admin/src/hooks/useRolesList/init.js +5 -0
  10. package/admin/src/hooks/useRolesList/reducer.js +31 -0
  11. package/admin/src/index.js +8 -7
  12. package/admin/src/pages/AdvancedSettings/index.js +24 -41
  13. package/admin/src/pages/AdvancedSettings/utils/api.js +16 -0
  14. package/admin/src/pages/EmailTemplates/index.js +47 -62
  15. package/admin/src/pages/EmailTemplates/utils/api.js +16 -0
  16. package/admin/src/pages/Providers/index.js +58 -64
  17. package/admin/src/pages/Providers/reducer.js +54 -0
  18. package/admin/src/pages/Providers/utils/api.js +24 -0
  19. package/admin/src/pages/Providers/utils/createProvidersArray.js +21 -0
  20. package/admin/src/pages/Roles/CreatePage.js +185 -0
  21. package/admin/src/pages/Roles/EditPage.js +197 -0
  22. package/admin/src/pages/Roles/{pages/ListPage → ListPage}/components/TableBody.js +11 -41
  23. package/admin/src/pages/Roles/{pages/ListPage → ListPage}/index.js +15 -19
  24. package/admin/src/pages/Roles/ProtectedCreatePage.js +15 -0
  25. package/admin/src/pages/Roles/ProtectedEditPage.js +15 -0
  26. package/admin/src/pages/Roles/ProtectedListPage.js +17 -0
  27. package/admin/src/pages/Roles/index.js +7 -10
  28. package/jest.config.front.js +1 -1
  29. package/package.json +4 -4
  30. package/admin/src/pages/Roles/pages/CreatePage.js +0 -190
  31. package/admin/src/pages/Roles/pages/EditPage.js +0 -211
  32. /package/admin/src/pages/Roles/{pages/ListPage → ListPage}/utils/api.js +0 -0
@@ -1,12 +1,10 @@
1
- import * as React from 'react';
1
+ import React, { useRef, useState } from 'react';
2
2
 
3
3
  import { ContentLayout, HeaderLayout, Main, useNotifyAT } from '@strapi/design-system';
4
4
  import {
5
5
  CheckPagePermissions,
6
6
  LoadingIndicatorPage,
7
7
  SettingsPageTitle,
8
- useAPIErrorHandler,
9
- useFetchClient,
10
8
  useFocusWhenNavigate,
11
9
  useNotification,
12
10
  useOverlayBlocker,
@@ -21,6 +19,7 @@ import { getTrad } from '../../utils';
21
19
 
22
20
  import EmailForm from './components/EmailForm';
23
21
  import EmailTable from './components/EmailTable';
22
+ import { fetchData, putEmailTemplate } from './utils/api';
24
23
 
25
24
  const ProtectedEmailTemplatesPage = () => (
26
25
  <CheckPagePermissions permissions={PERMISSIONS.readEmailTemplates}>
@@ -34,46 +33,36 @@ const EmailTemplatesPage = () => {
34
33
  const { notifyStatus } = useNotifyAT();
35
34
  const toggleNotification = useNotification();
36
35
  const { lockApp, unlockApp } = useOverlayBlocker();
36
+ const trackUsageRef = useRef(trackUsage);
37
37
  const queryClient = useQueryClient();
38
- const { get, put } = useFetchClient();
39
- const { formatAPIError } = useAPIErrorHandler();
40
-
41
38
  useFocusWhenNavigate();
42
39
 
43
- const [isModalOpen, setIsModalOpen] = React.useState(false);
44
- const [templateToEdit, setTemplateToEdit] = React.useState(null);
40
+ const [isModalOpen, setIsModalOpen] = useState(false);
41
+ const [templateToEdit, setTemplateToEdit] = useState(null);
45
42
 
46
43
  const {
47
44
  isLoading: isLoadingForPermissions,
48
45
  allowedActions: { canUpdate },
49
46
  } = useRBAC({ update: PERMISSIONS.updateEmailTemplates });
50
47
 
51
- const { isLoading: isLoadingData, data } = useQuery(
52
- ['users-permissions', 'email-templates'],
53
- async () => {
54
- const { data } = await get('/users-permissions/email-templates');
55
-
56
- return data;
48
+ const { status: isLoadingData, data } = useQuery('email-templates', () => fetchData(), {
49
+ onSuccess() {
50
+ notifyStatus(
51
+ formatMessage({
52
+ id: getTrad('Email.template.data.loaded'),
53
+ defaultMessage: 'Email templates has been loaded',
54
+ })
55
+ );
57
56
  },
58
- {
59
- onSuccess() {
60
- notifyStatus(
61
- formatMessage({
62
- id: getTrad('Email.template.data.loaded'),
63
- defaultMessage: 'Email templates has been loaded',
64
- })
65
- );
66
- },
67
- onError(error) {
68
- toggleNotification({
69
- type: 'warning',
70
- message: formatAPIError(error),
71
- });
72
- },
73
- }
74
- );
57
+ onError() {
58
+ toggleNotification({
59
+ type: 'warning',
60
+ message: { id: 'notification.error', defaultMessage: 'An error occured' },
61
+ });
62
+ },
63
+ });
75
64
 
76
- const isLoading = isLoadingForPermissions || isLoadingData;
65
+ const isLoading = isLoadingForPermissions || isLoadingData !== 'success';
77
66
 
78
67
  const handleToggle = () => {
79
68
  setIsModalOpen((prev) => !prev);
@@ -84,38 +73,34 @@ const EmailTemplatesPage = () => {
84
73
  handleToggle();
85
74
  };
86
75
 
87
- const submitMutation = useMutation(
88
- (body) => put('/users-permissions/email-templates', { 'email-templates': body }),
89
- {
90
- async onSuccess() {
91
- await queryClient.invalidateQueries(['users-permissions', 'email-templates']);
92
-
93
- toggleNotification({
94
- type: 'success',
95
- message: { id: 'notification.success.saved', defaultMessage: 'Saved' },
96
- });
97
-
98
- trackUsage('didEditEmailTemplates');
99
-
100
- unlockApp();
101
- handleToggle();
102
- },
103
- onError(error) {
104
- toggleNotification({
105
- type: 'warning',
106
- message: formatAPIError(error),
107
- });
108
-
109
- unlockApp();
110
- },
111
- refetchActive: true,
112
- }
113
- );
76
+ const submitMutation = useMutation((body) => putEmailTemplate({ 'email-templates': body }), {
77
+ async onSuccess() {
78
+ await queryClient.invalidateQueries('email-templates');
79
+
80
+ toggleNotification({
81
+ type: 'success',
82
+ message: { id: 'notification.success.saved', defaultMessage: 'Saved' },
83
+ });
84
+
85
+ trackUsageRef.current('didEditEmailTemplates');
86
+
87
+ unlockApp();
88
+ handleToggle();
89
+ },
90
+ onError() {
91
+ toggleNotification({
92
+ type: 'warning',
93
+ message: { id: 'notification.error', defaultMessage: 'An error occured' },
94
+ });
95
+ unlockApp();
96
+ },
97
+ refetchActive: true,
98
+ });
99
+ const { isLoading: isSubmittingForm } = submitMutation;
114
100
 
115
101
  const handleSubmit = (body) => {
116
102
  lockApp();
117
-
118
- trackUsage('willEditEmailTemplates');
103
+ trackUsageRef.current('willEditEmailTemplates');
119
104
 
120
105
  const editedTemplates = { ...data, [templateToEdit]: body };
121
106
  submitMutation.mutate(editedTemplates);
@@ -144,7 +129,7 @@ const EmailTemplatesPage = () => {
144
129
  }
145
130
 
146
131
  return (
147
- <Main aria-busy={submitMutation.isLoading}>
132
+ <Main aria-busy={isSubmittingForm}>
148
133
  <SettingsPageTitle
149
134
  name={formatMessage({
150
135
  id: getTrad('HeaderNav.link.emailTemplates'),
@@ -0,0 +1,16 @@
1
+ import { getFetchClient } from '@strapi/helper-plugin';
2
+
3
+ const fetchData = async () => {
4
+ const { get } = getFetchClient();
5
+ const { data } = await get('/users-permissions/email-templates');
6
+
7
+ return data;
8
+ };
9
+
10
+ const putEmailTemplate = (body) => {
11
+ const { put } = getFetchClient();
12
+
13
+ return put('/users-permissions/email-templates', body);
14
+ };
15
+
16
+ export { fetchData, putEmailTemplate };
@@ -1,4 +1,4 @@
1
- import * as React from 'react';
1
+ import React, { useMemo, useRef, useState } from 'react';
2
2
 
3
3
  import {
4
4
  ContentLayout,
@@ -13,6 +13,7 @@ import {
13
13
  Thead,
14
14
  Tr,
15
15
  Typography,
16
+ useNotifyAT,
16
17
  VisuallyHidden,
17
18
  } from '@strapi/design-system';
18
19
  import {
@@ -21,9 +22,6 @@ import {
21
22
  onRowClick,
22
23
  SettingsPageTitle,
23
24
  stopPropagation,
24
- useAPIErrorHandler,
25
- useCollator,
26
- useFetchClient,
27
25
  useFocusWhenNavigate,
28
26
  useNotification,
29
27
  useOverlayBlocker,
@@ -31,6 +29,7 @@ import {
31
29
  useTracking,
32
30
  } from '@strapi/helper-plugin';
33
31
  import { Pencil } from '@strapi/icons';
32
+ import has from 'lodash/has';
34
33
  import upperFirst from 'lodash/upperFirst';
35
34
  import { useIntl } from 'react-intl';
36
35
  import { useMutation, useQuery, useQueryClient } from 'react-query';
@@ -39,94 +38,90 @@ import FormModal from '../../components/FormModal';
39
38
  import { PERMISSIONS } from '../../constants';
40
39
  import { getTrad } from '../../utils';
41
40
 
41
+ import { fetchData, putProvider } from './utils/api';
42
+ import createProvidersArray from './utils/createProvidersArray';
42
43
  import forms from './utils/forms';
43
44
 
44
45
  export const ProvidersPage = () => {
45
- const { formatMessage, locale } = useIntl();
46
+ const { formatMessage } = useIntl();
47
+ useFocusWhenNavigate();
48
+ const { notifyStatus } = useNotifyAT();
46
49
  const queryClient = useQueryClient();
47
50
  const { trackUsage } = useTracking();
48
- const [isOpen, setIsOpen] = React.useState(false);
49
- const [providerToEditName, setProviderToEditName] = React.useState(null);
51
+ const trackUsageRef = useRef(trackUsage);
52
+ const [isOpen, setIsOpen] = useState(false);
53
+ const [isSubmiting, setIsSubmiting] = useState(false);
54
+ const [providerToEditName, setProviderToEditName] = useState(null);
50
55
  const toggleNotification = useNotification();
51
56
  const { lockApp, unlockApp } = useOverlayBlocker();
52
- const { get, put } = useFetchClient();
53
- const { formatAPIError } = useAPIErrorHandler();
54
- const formatter = useCollator(locale, {
55
- sensitivity: 'base',
56
- });
57
-
58
- useFocusWhenNavigate();
59
57
 
60
58
  const {
61
- isLoading: isLoadingPermissions,
59
+ isLoading: isLoadingForPermissions,
62
60
  allowedActions: { canUpdate },
63
61
  } = useRBAC({ update: PERMISSIONS.updateProviders });
64
62
 
65
- const { isLoading: isLoadingData, data } = useQuery(
66
- ['users-permissions', 'get-providers'],
67
- async () => {
68
- const { data } = await get('/users-permissions/providers');
69
-
70
- return data;
63
+ const {
64
+ isLoading: isLoadingForData,
65
+ data: modifiedData,
66
+ isFetching,
67
+ } = useQuery('get-providers', () => fetchData(toggleNotification), {
68
+ onSuccess() {
69
+ notifyStatus(
70
+ formatMessage({
71
+ id: getTrad('Providers.data.loaded'),
72
+ defaultMessage: 'Providers have been loaded',
73
+ })
74
+ );
71
75
  },
72
- {
73
- initialData: {},
74
- }
75
- );
76
+ initialData: {},
77
+ });
76
78
 
77
- const submitMutation = useMutation((body) => put('/users-permissions/providers', body), {
78
- async onSuccess() {
79
- await queryClient.invalidateQueries(['users-permissions', 'providers']);
79
+ const isLoading = isLoadingForData || isFetching;
80
80
 
81
+ const submitMutation = useMutation(putProvider, {
82
+ async onSuccess() {
83
+ await queryClient.invalidateQueries('get-providers');
81
84
  toggleNotification({
82
- type: 'success',
85
+ type: 'info',
83
86
  message: { id: getTrad('notification.success.submit') },
84
87
  });
85
88
 
86
- trackUsage('didEditAuthenticationProvider');
87
-
89
+ trackUsageRef.current('didEditAuthenticationProvider');
90
+ setIsSubmiting(false);
88
91
  handleToggleModal();
89
92
  unlockApp();
90
93
  },
91
- onError(error) {
94
+ onError() {
92
95
  toggleNotification({
93
96
  type: 'warning',
94
- message: formatAPIError(error),
97
+ message: { id: 'notification.error' },
95
98
  });
96
-
97
99
  unlockApp();
100
+ setIsSubmiting(false);
98
101
  },
99
102
  refetchActive: false,
100
103
  });
101
104
 
102
- const providers = Object.entries(data)
103
- .reduce((acc, [name, provider]) => {
104
- const { icon, enabled, subdomain } = provider;
105
-
106
- acc.push({
107
- name,
108
- icon: icon === 'envelope' ? ['fas', 'envelope'] : ['fab', icon],
109
- enabled,
110
- subdomain,
111
- });
112
-
113
- return acc;
114
- }, [])
115
- .sort((a, b) => formatter.compare(a.name, b.name));
105
+ const providers = useMemo(() => createProvidersArray(modifiedData), [modifiedData]);
116
106
 
117
- const isLoading = isLoadingData || isLoadingPermissions;
107
+ const rowCount = providers.length;
118
108
 
119
- const isProviderWithSubdomain = React.useMemo(() => {
109
+ const isProviderWithSubdomain = useMemo(() => {
120
110
  if (!providerToEditName) {
121
111
  return false;
122
112
  }
123
113
 
124
114
  const providerToEdit = providers.find((obj) => obj.name === providerToEditName);
125
115
 
126
- return !!providerToEdit?.subdomain;
116
+ return has(providerToEdit, 'subdomain');
127
117
  }, [providers, providerToEditName]);
128
118
 
129
- const layoutToRender = React.useMemo(() => {
119
+ const pageTitle = formatMessage({
120
+ id: getTrad('HeaderNav.link.providers'),
121
+ defaultMessage: 'Providers',
122
+ });
123
+
124
+ const layoutToRender = useMemo(() => {
130
125
  if (providerToEditName === 'email') {
131
126
  return forms.email;
132
127
  }
@@ -150,21 +145,20 @@ export const ProvidersPage = () => {
150
145
  };
151
146
 
152
147
  const handleSubmit = async (values) => {
148
+ setIsSubmiting(true);
149
+
153
150
  lockApp();
154
151
 
155
- trackUsage('willEditAuthenticationProvider');
152
+ trackUsageRef.current('willEditAuthenticationProvider');
156
153
 
157
- submitMutation.mutate({ providers: { ...data, [providerToEditName]: values } });
154
+ const body = { ...modifiedData, [providerToEditName]: values };
155
+
156
+ submitMutation.mutate({ providers: body });
158
157
  };
159
158
 
160
159
  return (
161
160
  <Layout>
162
- <SettingsPageTitle
163
- name={formatMessage({
164
- id: getTrad('HeaderNav.link.providers'),
165
- defaultMessage: 'Providers',
166
- })}
167
- />
161
+ <SettingsPageTitle name={pageTitle} />
168
162
  <Main>
169
163
  <HeaderLayout
170
164
  title={formatMessage({
@@ -172,11 +166,11 @@ export const ProvidersPage = () => {
172
166
  defaultMessage: 'Providers',
173
167
  })}
174
168
  />
175
- {isLoading ? (
169
+ {isLoading || isLoadingForPermissions ? (
176
170
  <LoadingIndicatorPage />
177
171
  ) : (
178
172
  <ContentLayout>
179
- <Table colCount={3} rowCount={providers.length + 1}>
173
+ <Table colCount={3} rowCount={rowCount + 1}>
180
174
  <Thead>
181
175
  <Tr>
182
176
  <Th>
@@ -249,9 +243,9 @@ export const ProvidersPage = () => {
249
243
  )}
250
244
  </Main>
251
245
  <FormModal
252
- initialData={data[providerToEditName]}
246
+ initialData={modifiedData[providerToEditName]}
253
247
  isOpen={isOpen}
254
- isSubmiting={submitMutation.isLoading}
248
+ isSubmiting={isSubmiting}
255
249
  layout={layoutToRender}
256
250
  headerBreadcrumbs={[
257
251
  formatMessage({
@@ -0,0 +1,54 @@
1
+ import produce from 'immer';
2
+ import set from 'lodash/set';
3
+
4
+ const initialState = {
5
+ formErrors: {},
6
+ isLoading: true,
7
+ initialData: {},
8
+ modifiedData: {},
9
+ };
10
+
11
+ const reducer = (state, action) =>
12
+ // eslint-disable-next-line consistent-return
13
+ produce(state, (draftState) => {
14
+ switch (action.type) {
15
+ case 'GET_DATA': {
16
+ draftState.isLoading = true;
17
+ draftState.initialData = {};
18
+ draftState.modifiedData = {};
19
+
20
+ break;
21
+ }
22
+
23
+ case 'GET_DATA_SUCCEEDED': {
24
+ draftState.isLoading = false;
25
+ draftState.initialData = action.data;
26
+ draftState.modifiedData = action.data;
27
+
28
+ break;
29
+ }
30
+ case 'GET_DATA_ERROR': {
31
+ draftState.isLoading = true;
32
+ break;
33
+ }
34
+ case 'ON_CHANGE': {
35
+ set(draftState, ['modifiedData', ...action.keys.split('.')], action.value);
36
+ break;
37
+ }
38
+ case 'RESET_FORM': {
39
+ draftState.modifiedData = state.initialData;
40
+ draftState.formErrors = {};
41
+ break;
42
+ }
43
+ case 'SET_ERRORS': {
44
+ draftState.formErrors = action.errors;
45
+ break;
46
+ }
47
+ default: {
48
+ return draftState;
49
+ }
50
+ }
51
+ });
52
+
53
+ export default reducer;
54
+ export { initialState };
@@ -0,0 +1,24 @@
1
+ import { getFetchClient } from '@strapi/helper-plugin';
2
+
3
+ // eslint-disable-next-line import/prefer-default-export
4
+ export const fetchData = async (toggleNotification) => {
5
+ try {
6
+ const { get } = getFetchClient();
7
+ const { data } = await get('/users-permissions/providers');
8
+
9
+ return data;
10
+ } catch (err) {
11
+ toggleNotification({
12
+ type: 'warning',
13
+ message: { id: 'notification.error' },
14
+ });
15
+
16
+ throw new Error('error');
17
+ }
18
+ };
19
+
20
+ export const putProvider = (body) => {
21
+ const { put } = getFetchClient();
22
+
23
+ return put('/users-permissions/providers', body);
24
+ };
@@ -0,0 +1,21 @@
1
+ import sortBy from 'lodash/sortBy';
2
+
3
+ const createProvidersArray = (data) => {
4
+ return sortBy(
5
+ Object.keys(data).reduce((acc, current) => {
6
+ const { icon: iconName, enabled, subdomain } = data[current];
7
+ const icon = iconName === 'envelope' ? ['fas', 'envelope'] : ['fab', iconName];
8
+
9
+ if (subdomain !== undefined) {
10
+ acc.push({ name: current, icon, enabled, subdomain });
11
+ } else {
12
+ acc.push({ name: current, icon, enabled });
13
+ }
14
+
15
+ return acc;
16
+ }, []),
17
+ 'name'
18
+ );
19
+ };
20
+
21
+ export default createProvidersArray;
@@ -0,0 +1,185 @@
1
+ import React, { useRef, useState } from 'react';
2
+
3
+ import {
4
+ Box,
5
+ Button,
6
+ ContentLayout,
7
+ Flex,
8
+ Grid,
9
+ GridItem,
10
+ HeaderLayout,
11
+ Main,
12
+ Textarea,
13
+ TextInput,
14
+ Typography,
15
+ } from '@strapi/design-system';
16
+ import {
17
+ Form,
18
+ SettingsPageTitle,
19
+ useFetchClient,
20
+ useNotification,
21
+ useOverlayBlocker,
22
+ useTracking,
23
+ } from '@strapi/helper-plugin';
24
+ import { Check } from '@strapi/icons';
25
+ import { Formik } from 'formik';
26
+ import { useIntl } from 'react-intl';
27
+ import { useHistory } from 'react-router-dom';
28
+
29
+ import UsersPermissions from '../../components/UsersPermissions';
30
+ import { usePlugins } from '../../hooks';
31
+ import pluginId from '../../pluginId';
32
+ import getTrad from '../../utils/getTrad';
33
+
34
+ import { createRoleSchema } from './constants';
35
+
36
+ const CreatePage = () => {
37
+ const { formatMessage } = useIntl();
38
+ const [isSubmitting, setIsSubmitting] = useState(false);
39
+ const toggleNotification = useNotification();
40
+ const { goBack } = useHistory();
41
+ const { lockApp, unlockApp } = useOverlayBlocker();
42
+ const { isLoading: isLoadingPlugins, permissions, routes } = usePlugins();
43
+ const { trackUsage } = useTracking();
44
+ const permissionsRef = useRef();
45
+ const { post } = useFetchClient();
46
+
47
+ const handleCreateRoleSubmit = async (data) => {
48
+ // Set loading state
49
+ lockApp();
50
+ setIsSubmitting(true);
51
+ try {
52
+ const permissions = permissionsRef.current.getPermissions();
53
+ // Update role in Strapi
54
+ await post(`/${pluginId}/roles`, { ...data, ...permissions, users: [] });
55
+ // Notify success
56
+ trackUsage('didCreateRole');
57
+ toggleNotification({
58
+ type: 'success',
59
+ message: {
60
+ id: getTrad('Settings.roles.created'),
61
+ defaultMessage: 'Role created',
62
+ },
63
+ });
64
+ // Forcing redirecting since we don't have the id in the response
65
+ goBack();
66
+ } catch (err) {
67
+ console.error(err);
68
+ toggleNotification({
69
+ type: 'warning',
70
+ message: {
71
+ id: 'notification.error',
72
+ defaultMessage: 'An error occurred',
73
+ },
74
+ });
75
+ }
76
+ // Unset loading state
77
+ setIsSubmitting(false);
78
+ unlockApp();
79
+ };
80
+
81
+ return (
82
+ <Main>
83
+ <SettingsPageTitle name="Roles" />
84
+ <Formik
85
+ enableReinitialize
86
+ initialValues={{ name: '', description: '' }}
87
+ onSubmit={handleCreateRoleSubmit}
88
+ validationSchema={createRoleSchema}
89
+ >
90
+ {({ handleSubmit, values, handleChange, errors }) => (
91
+ <Form noValidate onSubmit={handleSubmit}>
92
+ <HeaderLayout
93
+ primaryAction={
94
+ !isLoadingPlugins && (
95
+ <Button type="submit" loading={isSubmitting} startIcon={<Check />}>
96
+ {formatMessage({
97
+ id: 'global.save',
98
+ defaultMessage: 'Save',
99
+ })}
100
+ </Button>
101
+ )
102
+ }
103
+ title={formatMessage({
104
+ id: 'Settings.roles.create.title',
105
+ defaultMessage: 'Create a role',
106
+ })}
107
+ subtitle={formatMessage({
108
+ id: 'Settings.roles.create.description',
109
+ defaultMessage: 'Define the rights given to the role',
110
+ })}
111
+ />
112
+ <ContentLayout>
113
+ <Flex direction="column" alignItems="stretch" gap={7}>
114
+ <Box
115
+ background="neutral0"
116
+ hasRadius
117
+ shadow="filterShadow"
118
+ paddingTop={6}
119
+ paddingBottom={6}
120
+ paddingLeft={7}
121
+ paddingRight={7}
122
+ >
123
+ <Flex direction="column" alignItems="stretch" gap={4}>
124
+ <Typography variant="delta" as="h2">
125
+ {formatMessage({
126
+ id: getTrad('EditPage.form.roles'),
127
+ defaultMessage: 'Role details',
128
+ })}
129
+ </Typography>
130
+ <Grid gap={4}>
131
+ <GridItem col={6}>
132
+ <TextInput
133
+ name="name"
134
+ value={values.name || ''}
135
+ onChange={handleChange}
136
+ label={formatMessage({
137
+ id: 'global.name',
138
+ defaultMessage: 'Name',
139
+ })}
140
+ error={
141
+ errors.name
142
+ ? formatMessage({ id: errors.name, defaultMessage: 'Invalid value' })
143
+ : null
144
+ }
145
+ />
146
+ </GridItem>
147
+ <GridItem col={6}>
148
+ <Textarea
149
+ id="description"
150
+ value={values.description || ''}
151
+ onChange={handleChange}
152
+ label={formatMessage({
153
+ id: 'global.description',
154
+ defaultMessage: 'Description',
155
+ })}
156
+ error={
157
+ errors.description
158
+ ? formatMessage({
159
+ id: errors.description,
160
+ defaultMessage: 'Invalid value',
161
+ })
162
+ : null
163
+ }
164
+ />
165
+ </GridItem>
166
+ </Grid>
167
+ </Flex>
168
+ </Box>
169
+ {!isLoadingPlugins && (
170
+ <UsersPermissions
171
+ ref={permissionsRef}
172
+ permissions={permissions}
173
+ routes={routes}
174
+ />
175
+ )}
176
+ </Flex>
177
+ </ContentLayout>
178
+ </Form>
179
+ )}
180
+ </Formik>
181
+ </Main>
182
+ );
183
+ };
184
+
185
+ export default CreatePage;