@strapi/plugin-users-permissions 0.0.0-next.f426350b859ddae6592e9bfa99e6be94ae22e117 → 0.0.0-next.f4ec69568d980c6fee91ce2ee0f41c138347aa81

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 (218) hide show
  1. package/.eslintignore +1 -2
  2. package/.eslintrc +17 -0
  3. package/LICENSE +18 -3
  4. package/admin/src/components/BoundRoute/{index.js → index.jsx} +2 -2
  5. package/admin/src/components/FormModal/Input/{index.js → index.jsx} +32 -31
  6. package/admin/src/components/FormModal/index.jsx +115 -0
  7. package/admin/src/components/Permissions/PermissionRow/{CheckboxWrapper.js → CheckboxWrapper.jsx} +4 -3
  8. package/admin/src/components/Permissions/PermissionRow/{SubCategory.js → SubCategory.jsx} +13 -22
  9. package/admin/src/components/Permissions/index.jsx +47 -0
  10. package/admin/src/components/Permissions/reducer.js +1 -1
  11. package/admin/src/components/Policies/{index.js → index.jsx} +7 -5
  12. package/admin/src/components/UsersPermissions/{index.js → index.jsx} +15 -7
  13. package/admin/src/components/UsersPermissions/reducer.js +1 -1
  14. package/admin/src/index.js +19 -47
  15. package/admin/src/pages/AdvancedSettings/index.jsx +214 -0
  16. package/admin/src/pages/AdvancedSettings/utils/layout.js +20 -35
  17. package/admin/src/pages/AdvancedSettings/utils/schema.js +5 -2
  18. package/admin/src/pages/EmailTemplates/components/EmailForm.jsx +156 -0
  19. package/admin/src/pages/EmailTemplates/components/{EmailTable.js → EmailTable.jsx} +20 -17
  20. package/admin/src/pages/EmailTemplates/index.jsx +148 -0
  21. package/admin/src/pages/EmailTemplates/utils/schema.js +18 -6
  22. package/admin/src/pages/Providers/index.jsx +262 -0
  23. package/admin/src/pages/Providers/utils/forms.js +23 -11
  24. package/admin/src/pages/Roles/constants.js +3 -3
  25. package/admin/src/{hooks → pages/Roles/hooks}/usePlugins.js +19 -12
  26. package/admin/src/pages/Roles/index.jsx +24 -0
  27. package/admin/src/pages/Roles/pages/CreatePage.jsx +194 -0
  28. package/admin/src/pages/Roles/pages/EditPage.jsx +215 -0
  29. package/admin/src/pages/Roles/pages/ListPage/components/TableBody.jsx +119 -0
  30. package/admin/src/pages/Roles/{ListPage/index.js → pages/ListPage/index.jsx} +93 -65
  31. package/admin/src/translations/en.json +1 -1
  32. package/admin/src/translations/zh-Hans.json +80 -80
  33. package/admin/src/utils/prefixPluginTranslations.js +13 -0
  34. package/dist/_chunks/ar-BguGUqwK.js +44 -0
  35. package/dist/_chunks/ar-BguGUqwK.js.map +1 -0
  36. package/dist/_chunks/ar-CK8BRRXB.mjs +44 -0
  37. package/dist/_chunks/ar-CK8BRRXB.mjs.map +1 -0
  38. package/dist/_chunks/cs-BVigMk0l.mjs +50 -0
  39. package/dist/_chunks/cs-BVigMk0l.mjs.map +1 -0
  40. package/dist/_chunks/cs-BW8-K_GY.js +50 -0
  41. package/dist/_chunks/cs-BW8-K_GY.js.map +1 -0
  42. package/dist/_chunks/de-BKUdRFI4.mjs +62 -0
  43. package/dist/_chunks/de-BKUdRFI4.mjs.map +1 -0
  44. package/dist/_chunks/de-owXpVluI.js +62 -0
  45. package/dist/_chunks/de-owXpVluI.js.map +1 -0
  46. package/dist/_chunks/dk-BQiTK50l.mjs +86 -0
  47. package/dist/_chunks/dk-BQiTK50l.mjs.map +1 -0
  48. package/dist/_chunks/dk-LXAnbuBk.js +86 -0
  49. package/dist/_chunks/dk-LXAnbuBk.js.map +1 -0
  50. package/dist/_chunks/en-DOHtPf-2.mjs +86 -0
  51. package/dist/_chunks/en-DOHtPf-2.mjs.map +1 -0
  52. package/dist/_chunks/en-MHo5mcsU.js +86 -0
  53. package/dist/_chunks/en-MHo5mcsU.js.map +1 -0
  54. package/dist/_chunks/es-BwLCLXAQ.js +86 -0
  55. package/dist/_chunks/es-BwLCLXAQ.js.map +1 -0
  56. package/dist/_chunks/es-DNgOVMjD.mjs +86 -0
  57. package/dist/_chunks/es-DNgOVMjD.mjs.map +1 -0
  58. package/dist/_chunks/fr-DkgRugiU.mjs +50 -0
  59. package/dist/_chunks/fr-DkgRugiU.mjs.map +1 -0
  60. package/dist/_chunks/fr-DkhpSjjm.js +50 -0
  61. package/dist/_chunks/fr-DkhpSjjm.js.map +1 -0
  62. package/dist/_chunks/id-BTemOeTZ.js +62 -0
  63. package/dist/_chunks/id-BTemOeTZ.js.map +1 -0
  64. package/dist/_chunks/id-BdEsvnaF.mjs +62 -0
  65. package/dist/_chunks/id-BdEsvnaF.mjs.map +1 -0
  66. package/dist/_chunks/index-2awRBazk.js +281 -0
  67. package/dist/_chunks/index-2awRBazk.js.map +1 -0
  68. package/dist/_chunks/index-BAHBK68t.js +1172 -0
  69. package/dist/_chunks/index-BAHBK68t.js.map +1 -0
  70. package/dist/_chunks/index-BHbzbu1p.mjs +246 -0
  71. package/dist/_chunks/index-BHbzbu1p.mjs.map +1 -0
  72. package/dist/_chunks/index-Be4qNiZI.js +640 -0
  73. package/dist/_chunks/index-Be4qNiZI.js.map +1 -0
  74. package/dist/_chunks/index-C88EQQJQ-C4oUQUND.js +12026 -0
  75. package/dist/_chunks/index-C88EQQJQ-C4oUQUND.js.map +1 -0
  76. package/dist/_chunks/index-C88EQQJQ-DAZ1lfuF.mjs +12002 -0
  77. package/dist/_chunks/index-C88EQQJQ-DAZ1lfuF.mjs.map +1 -0
  78. package/dist/_chunks/index-CbKOY95_.mjs +344 -0
  79. package/dist/_chunks/index-CbKOY95_.mjs.map +1 -0
  80. package/dist/_chunks/index-CeweK3q9.mjs +617 -0
  81. package/dist/_chunks/index-CeweK3q9.mjs.map +1 -0
  82. package/dist/_chunks/index-QcDREbPt.mjs +262 -0
  83. package/dist/_chunks/index-QcDREbPt.mjs.map +1 -0
  84. package/dist/_chunks/index-YTFP-hNZ.js +245 -0
  85. package/dist/_chunks/index-YTFP-hNZ.js.map +1 -0
  86. package/dist/_chunks/index-YZkqoYZN.js +366 -0
  87. package/dist/_chunks/index-YZkqoYZN.js.map +1 -0
  88. package/dist/_chunks/index-mKh-etKG.mjs +1142 -0
  89. package/dist/_chunks/index-mKh-etKG.mjs.map +1 -0
  90. package/dist/_chunks/it-B-rv0E24.mjs +62 -0
  91. package/dist/_chunks/it-B-rv0E24.mjs.map +1 -0
  92. package/dist/_chunks/it-D1rH6V6_.js +62 -0
  93. package/dist/_chunks/it-D1rH6V6_.js.map +1 -0
  94. package/dist/_chunks/ja-C8K-VBPD.mjs +48 -0
  95. package/dist/_chunks/ja-C8K-VBPD.mjs.map +1 -0
  96. package/dist/_chunks/ja-DqShgTMf.js +48 -0
  97. package/dist/_chunks/ja-DqShgTMf.js.map +1 -0
  98. package/dist/_chunks/ko-B9DGEPWH.js +86 -0
  99. package/dist/_chunks/ko-B9DGEPWH.js.map +1 -0
  100. package/dist/_chunks/ko-Busb0wIY.mjs +86 -0
  101. package/dist/_chunks/ko-Busb0wIY.mjs.map +1 -0
  102. package/dist/_chunks/ms-ByvsQjRt.mjs +49 -0
  103. package/dist/_chunks/ms-ByvsQjRt.mjs.map +1 -0
  104. package/dist/_chunks/ms-CPBU3LWf.js +49 -0
  105. package/dist/_chunks/ms-CPBU3LWf.js.map +1 -0
  106. package/dist/_chunks/nl-5qO8Rpcy.mjs +48 -0
  107. package/dist/_chunks/nl-5qO8Rpcy.mjs.map +1 -0
  108. package/dist/_chunks/nl-CwNB6YoO.js +48 -0
  109. package/dist/_chunks/nl-CwNB6YoO.js.map +1 -0
  110. package/dist/_chunks/pl-BdIzifBE.mjs +86 -0
  111. package/dist/_chunks/pl-BdIzifBE.mjs.map +1 -0
  112. package/dist/_chunks/pl-Do9UD69f.js +86 -0
  113. package/dist/_chunks/pl-Do9UD69f.js.map +1 -0
  114. package/dist/_chunks/pt-BIO24ioG.mjs +48 -0
  115. package/dist/_chunks/pt-BIO24ioG.mjs.map +1 -0
  116. package/dist/_chunks/pt-BR-D7dZhxuP.js +44 -0
  117. package/dist/_chunks/pt-BR-D7dZhxuP.js.map +1 -0
  118. package/dist/_chunks/pt-BR-f0p23AQZ.mjs +44 -0
  119. package/dist/_chunks/pt-BR-f0p23AQZ.mjs.map +1 -0
  120. package/dist/_chunks/pt-fdvyOnUp.js +48 -0
  121. package/dist/_chunks/pt-fdvyOnUp.js.map +1 -0
  122. package/dist/_chunks/ru-C94rjPGA.js +86 -0
  123. package/dist/_chunks/ru-C94rjPGA.js.map +1 -0
  124. package/dist/_chunks/ru-VWy-IB7K.mjs +86 -0
  125. package/dist/_chunks/ru-VWy-IB7K.mjs.map +1 -0
  126. package/dist/_chunks/sk-BABEhykl.js +50 -0
  127. package/dist/_chunks/sk-BABEhykl.js.map +1 -0
  128. package/dist/_chunks/sk-B_LIcepm.mjs +50 -0
  129. package/dist/_chunks/sk-B_LIcepm.mjs.map +1 -0
  130. package/dist/_chunks/sv-ABLKOokl.mjs +86 -0
  131. package/dist/_chunks/sv-ABLKOokl.mjs.map +1 -0
  132. package/dist/_chunks/sv-Be43LhA9.js +86 -0
  133. package/dist/_chunks/sv-Be43LhA9.js.map +1 -0
  134. package/dist/_chunks/th-DKyP7ueR.mjs +60 -0
  135. package/dist/_chunks/th-DKyP7ueR.mjs.map +1 -0
  136. package/dist/_chunks/th-DgVhVLhL.js +60 -0
  137. package/dist/_chunks/th-DgVhVLhL.js.map +1 -0
  138. package/dist/_chunks/tr-B_idhkEs.js +85 -0
  139. package/dist/_chunks/tr-B_idhkEs.js.map +1 -0
  140. package/dist/_chunks/tr-qa1Q5UjC.mjs +85 -0
  141. package/dist/_chunks/tr-qa1Q5UjC.mjs.map +1 -0
  142. package/dist/_chunks/uk-BmRqbeQc.mjs +49 -0
  143. package/dist/_chunks/uk-BmRqbeQc.mjs.map +1 -0
  144. package/dist/_chunks/uk-LHOivnhP.js +49 -0
  145. package/dist/_chunks/uk-LHOivnhP.js.map +1 -0
  146. package/dist/_chunks/vi-CdVRdKDw.js +50 -0
  147. package/dist/_chunks/vi-CdVRdKDw.js.map +1 -0
  148. package/dist/_chunks/vi-HW-EdMea.mjs +50 -0
  149. package/dist/_chunks/vi-HW-EdMea.mjs.map +1 -0
  150. package/dist/_chunks/zh-5hKkVPA4.mjs +86 -0
  151. package/dist/_chunks/zh-5hKkVPA4.mjs.map +1 -0
  152. package/dist/_chunks/zh-Cuq8gMnF.js +86 -0
  153. package/dist/_chunks/zh-Cuq8gMnF.js.map +1 -0
  154. package/dist/_chunks/zh-Hans-BHilK-yc.mjs +86 -0
  155. package/dist/_chunks/zh-Hans-BHilK-yc.mjs.map +1 -0
  156. package/dist/_chunks/zh-Hans-GQDMKtY4.js +86 -0
  157. package/dist/_chunks/zh-Hans-GQDMKtY4.js.map +1 -0
  158. package/dist/admin/index.js +4 -0
  159. package/dist/admin/index.js.map +1 -0
  160. package/dist/admin/index.mjs +5 -0
  161. package/dist/admin/index.mjs.map +1 -0
  162. package/jest.config.front.js +1 -1
  163. package/package.json +46 -27
  164. package/packup.config.ts +22 -0
  165. package/server/bootstrap/index.js +18 -15
  166. package/server/bootstrap/users-permissions-actions.js +6 -0
  167. package/server/config.js +29 -0
  168. package/server/content-types/user/index.js +0 -1
  169. package/server/controllers/auth.js +74 -38
  170. package/server/controllers/content-manager-user.js +28 -30
  171. package/server/controllers/role.js +17 -4
  172. package/server/controllers/user.js +18 -8
  173. package/server/controllers/validation/auth.js +81 -25
  174. package/server/middlewares/rateLimit.js +1 -1
  175. package/server/register.js +1 -1
  176. package/server/services/jwt.js +3 -3
  177. package/server/services/permission.js +3 -7
  178. package/server/services/providers-registry.js +469 -261
  179. package/server/services/providers.js +10 -5
  180. package/server/services/role.js +15 -13
  181. package/server/services/user.js +56 -19
  182. package/server/services/users-permissions.js +15 -13
  183. package/server/utils/index.d.ts +2 -1
  184. package/server/utils/sanitize/sanitizers.js +7 -3
  185. package/server/utils/sanitize/visitors/remove-user-relation-from-role-entities.js +2 -2
  186. package/.eslintrc.js +0 -14
  187. package/admin/src/components/FormModal/index.js +0 -126
  188. package/admin/src/components/Permissions/index.js +0 -57
  189. package/admin/src/hooks/index.js +0 -5
  190. package/admin/src/hooks/useFetchRole/index.js +0 -67
  191. package/admin/src/hooks/useFetchRole/reducer.js +0 -31
  192. package/admin/src/hooks/useForm/index.js +0 -68
  193. package/admin/src/hooks/useForm/reducer.js +0 -40
  194. package/admin/src/hooks/useRolesList/index.js +0 -65
  195. package/admin/src/hooks/useRolesList/init.js +0 -5
  196. package/admin/src/hooks/useRolesList/reducer.js +0 -31
  197. package/admin/src/pages/AdvancedSettings/index.js +0 -242
  198. package/admin/src/pages/AdvancedSettings/utils/api.js +0 -16
  199. package/admin/src/pages/EmailTemplates/components/EmailForm.js +0 -176
  200. package/admin/src/pages/EmailTemplates/index.js +0 -159
  201. package/admin/src/pages/EmailTemplates/utils/api.js +0 -16
  202. package/admin/src/pages/Providers/index.js +0 -271
  203. package/admin/src/pages/Providers/reducer.js +0 -54
  204. package/admin/src/pages/Providers/utils/api.js +0 -24
  205. package/admin/src/pages/Providers/utils/createProvidersArray.js +0 -21
  206. package/admin/src/pages/Roles/CreatePage.js +0 -185
  207. package/admin/src/pages/Roles/EditPage.js +0 -197
  208. package/admin/src/pages/Roles/ListPage/components/TableBody.js +0 -93
  209. package/admin/src/pages/Roles/ListPage/utils/api.js +0 -30
  210. package/admin/src/pages/Roles/ProtectedCreatePage.js +0 -15
  211. package/admin/src/pages/Roles/ProtectedEditPage.js +0 -15
  212. package/admin/src/pages/Roles/ProtectedListPage.js +0 -17
  213. package/admin/src/pages/Roles/index.js +0 -30
  214. package/server/bootstrap/grant-config.js +0 -131
  215. package/strapi-admin.js +0 -3
  216. package/strapi-server.js +0 -3
  217. /package/admin/src/components/Permissions/PermissionRow/{index.js → index.jsx} +0 -0
  218. /package/admin/src/contexts/UsersPermissionsContext/{index.js → index.jsx} +0 -0
@@ -0,0 +1,148 @@
1
+ import * as React from 'react';
2
+
3
+ import { useTracking } from '@strapi/admin/strapi-admin';
4
+ import { useNotifyAT } from '@strapi/design-system';
5
+ import {
6
+ Page,
7
+ useAPIErrorHandler,
8
+ useNotification,
9
+ useFetchClient,
10
+ useRBAC,
11
+ Layouts,
12
+ } from '@strapi/strapi/admin';
13
+ import { useIntl } from 'react-intl';
14
+ import { useMutation, useQuery, useQueryClient } from 'react-query';
15
+
16
+ import { PERMISSIONS } from '../../constants';
17
+ import { getTrad } from '../../utils';
18
+
19
+ import EmailForm from './components/EmailForm';
20
+ import EmailTable from './components/EmailTable';
21
+
22
+ const ProtectedEmailTemplatesPage = () => (
23
+ <Page.Protect permissions={PERMISSIONS.readEmailTemplates}>
24
+ <EmailTemplatesPage />
25
+ </Page.Protect>
26
+ );
27
+ const EmailTemplatesPage = () => {
28
+ const { formatMessage } = useIntl();
29
+ const { trackUsage } = useTracking();
30
+ const { notifyStatus } = useNotifyAT();
31
+ const { toggleNotification } = useNotification();
32
+ const queryClient = useQueryClient();
33
+ const { get, put } = useFetchClient();
34
+ const { formatAPIError } = useAPIErrorHandler();
35
+
36
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
37
+ const [templateToEdit, setTemplateToEdit] = React.useState(null);
38
+
39
+ const {
40
+ isLoading: isLoadingForPermissions,
41
+ allowedActions: { canUpdate },
42
+ } = useRBAC({ update: PERMISSIONS.updateEmailTemplates });
43
+
44
+ const { isLoading: isLoadingData, data } = useQuery(
45
+ ['users-permissions', 'email-templates'],
46
+ async () => {
47
+ const { data } = await get('/users-permissions/email-templates');
48
+
49
+ return data;
50
+ },
51
+ {
52
+ onSuccess() {
53
+ notifyStatus(
54
+ formatMessage({
55
+ id: getTrad('Email.template.data.loaded'),
56
+ defaultMessage: 'Email templates has been loaded',
57
+ })
58
+ );
59
+ },
60
+ onError(error) {
61
+ toggleNotification({
62
+ type: 'danger',
63
+ message: formatAPIError(error),
64
+ });
65
+ },
66
+ }
67
+ );
68
+
69
+ const isLoading = isLoadingForPermissions || isLoadingData;
70
+
71
+ const handleToggle = () => {
72
+ setIsModalOpen((prev) => !prev);
73
+ };
74
+
75
+ const handleEditClick = (template) => {
76
+ setTemplateToEdit(template);
77
+ handleToggle();
78
+ };
79
+
80
+ const submitMutation = useMutation(
81
+ (body) => put('/users-permissions/email-templates', { 'email-templates': body }),
82
+ {
83
+ async onSuccess() {
84
+ await queryClient.invalidateQueries(['users-permissions', 'email-templates']);
85
+
86
+ toggleNotification({
87
+ type: 'success',
88
+ message: formatMessage({ id: 'notification.success.saved', defaultMessage: 'Saved' }),
89
+ });
90
+
91
+ trackUsage('didEditEmailTemplates');
92
+
93
+ handleToggle();
94
+ },
95
+ onError(error) {
96
+ toggleNotification({
97
+ type: 'danger',
98
+ message: formatAPIError(error),
99
+ });
100
+ },
101
+ refetchActive: true,
102
+ }
103
+ );
104
+
105
+ const handleSubmit = (body) => {
106
+ trackUsage('willEditEmailTemplates');
107
+
108
+ const editedTemplates = { ...data, [templateToEdit]: body };
109
+ submitMutation.mutate(editedTemplates);
110
+ };
111
+
112
+ if (isLoading) {
113
+ return <Page.Loading />;
114
+ }
115
+
116
+ return (
117
+ <Page.Main aria-busy={submitMutation.isLoading}>
118
+ <Page.Title>
119
+ {formatMessage(
120
+ { id: 'Settings.PageTitle', defaultMessage: 'Settings - {name}' },
121
+ {
122
+ name: formatMessage({
123
+ id: getTrad('HeaderNav.link.emailTemplates'),
124
+ defaultMessage: 'Email templates',
125
+ }),
126
+ }
127
+ )}
128
+ </Page.Title>
129
+ <Layouts.Header
130
+ title={formatMessage({
131
+ id: getTrad('HeaderNav.link.emailTemplates'),
132
+ defaultMessage: 'Email templates',
133
+ })}
134
+ />
135
+ <Layouts.Content>
136
+ <EmailTable onEditClick={handleEditClick} canUpdate={canUpdate} />
137
+ <EmailForm
138
+ template={data[templateToEdit]}
139
+ onToggle={handleToggle}
140
+ open={isModalOpen}
141
+ onSubmit={handleSubmit}
142
+ />
143
+ </Layouts.Content>
144
+ </Page.Main>
145
+ );
146
+ };
147
+
148
+ export { ProtectedEmailTemplatesPage, EmailTemplatesPage };
@@ -1,4 +1,4 @@
1
- import { translatedErrors } from '@strapi/helper-plugin';
1
+ import { translatedErrors } from '@strapi/strapi/admin';
2
2
  import * as yup from 'yup';
3
3
 
4
4
  const schema = yup.object().shape({
@@ -8,15 +8,27 @@ const schema = yup.object().shape({
8
8
  from: yup
9
9
  .object()
10
10
  .shape({
11
- name: yup.string().required(translatedErrors.required),
12
- email: yup.string().email(translatedErrors.email).required(translatedErrors.required),
11
+ name: yup.string().required({
12
+ id: translatedErrors.required.id,
13
+ defaultMessage: 'This field is required',
14
+ }),
15
+ email: yup.string().email(translatedErrors.email).required({
16
+ id: translatedErrors.required.id,
17
+ defaultMessage: 'This field is required',
18
+ }),
13
19
  })
14
20
  .required(),
15
21
  response_email: yup.string().email(translatedErrors.email),
16
- object: yup.string().required(translatedErrors.required),
17
- message: yup.string().required(translatedErrors.required),
22
+ object: yup.string().required({
23
+ id: translatedErrors.required.id,
24
+ defaultMessage: 'This field is required',
25
+ }),
26
+ message: yup.string().required({
27
+ id: translatedErrors.required.id,
28
+ defaultMessage: 'This field is required',
29
+ }),
18
30
  })
19
- .required(translatedErrors.required),
31
+ .required(translatedErrors.required.id),
20
32
  });
21
33
 
22
34
  export default schema;
@@ -0,0 +1,262 @@
1
+ import * as React from 'react';
2
+
3
+ import { useTracking, Layouts } from '@strapi/admin/strapi-admin';
4
+ import {
5
+ IconButton,
6
+ Table,
7
+ Tbody,
8
+ Td,
9
+ Th,
10
+ Thead,
11
+ Tr,
12
+ Typography,
13
+ VisuallyHidden,
14
+ useCollator,
15
+ } from '@strapi/design-system';
16
+ import { Pencil } from '@strapi/icons';
17
+ import {
18
+ Page,
19
+ useAPIErrorHandler,
20
+ useNotification,
21
+ useFetchClient,
22
+ useRBAC,
23
+ } from '@strapi/strapi/admin';
24
+ import upperFirst from 'lodash/upperFirst';
25
+ import { useIntl } from 'react-intl';
26
+ import { useMutation, useQuery, useQueryClient } from 'react-query';
27
+
28
+ import FormModal from '../../components/FormModal';
29
+ import { PERMISSIONS } from '../../constants';
30
+ import { getTrad } from '../../utils';
31
+
32
+ import forms from './utils/forms';
33
+
34
+ export const ProvidersPage = () => {
35
+ const { formatMessage, locale } = useIntl();
36
+ const queryClient = useQueryClient();
37
+ const { trackUsage } = useTracking();
38
+ const [isOpen, setIsOpen] = React.useState(false);
39
+ const [providerToEditName, setProviderToEditName] = React.useState(null);
40
+ const { toggleNotification } = useNotification();
41
+ const { get, put } = useFetchClient();
42
+ const { formatAPIError } = useAPIErrorHandler();
43
+ const formatter = useCollator(locale, {
44
+ sensitivity: 'base',
45
+ });
46
+
47
+ const {
48
+ isLoading: isLoadingPermissions,
49
+ allowedActions: { canUpdate },
50
+ } = useRBAC({ update: PERMISSIONS.updateProviders });
51
+
52
+ const { isLoading: isLoadingData, data } = useQuery(
53
+ ['users-permissions', 'get-providers'],
54
+ async () => {
55
+ const { data } = await get('/users-permissions/providers');
56
+
57
+ return data;
58
+ },
59
+ {
60
+ initialData: {},
61
+ }
62
+ );
63
+
64
+ const submitMutation = useMutation((body) => put('/users-permissions/providers', body), {
65
+ async onSuccess() {
66
+ await queryClient.invalidateQueries(['users-permissions', 'get-providers']);
67
+
68
+ toggleNotification({
69
+ type: 'success',
70
+ message: formatMessage({ id: getTrad('notification.success.submit') }),
71
+ });
72
+
73
+ trackUsage('didEditAuthenticationProvider');
74
+
75
+ handleToggleModal();
76
+ },
77
+ onError(error) {
78
+ toggleNotification({
79
+ type: 'danger',
80
+ message: formatAPIError(error),
81
+ });
82
+ },
83
+ refetchActive: false,
84
+ });
85
+
86
+ const providers = Object.entries(data)
87
+ .reduce((acc, [name, provider]) => {
88
+ const { icon, enabled, subdomain } = provider;
89
+
90
+ acc.push({
91
+ name,
92
+ icon: icon === 'envelope' ? ['fas', 'envelope'] : ['fab', icon],
93
+ enabled,
94
+ subdomain,
95
+ });
96
+
97
+ return acc;
98
+ }, [])
99
+ .sort((a, b) => formatter.compare(a.name, b.name));
100
+
101
+ const isLoading = isLoadingData || isLoadingPermissions;
102
+
103
+ const isProviderWithSubdomain = React.useMemo(() => {
104
+ if (!providerToEditName) {
105
+ return false;
106
+ }
107
+
108
+ const providerToEdit = providers.find((obj) => obj.name === providerToEditName);
109
+
110
+ return !!providerToEdit?.subdomain;
111
+ }, [providers, providerToEditName]);
112
+
113
+ const layoutToRender = React.useMemo(() => {
114
+ if (providerToEditName === 'email') {
115
+ return forms.email;
116
+ }
117
+
118
+ if (isProviderWithSubdomain) {
119
+ return forms.providersWithSubdomain;
120
+ }
121
+
122
+ return forms.providers;
123
+ }, [providerToEditName, isProviderWithSubdomain]);
124
+
125
+ const handleToggleModal = () => {
126
+ setIsOpen((prev) => !prev);
127
+ };
128
+
129
+ const handleClickEdit = (provider) => {
130
+ if (canUpdate) {
131
+ setProviderToEditName(provider.name);
132
+ handleToggleModal();
133
+ }
134
+ };
135
+
136
+ const handleSubmit = async (values) => {
137
+ trackUsage('willEditAuthenticationProvider');
138
+
139
+ submitMutation.mutate({ providers: { ...data, [providerToEditName]: values } });
140
+ };
141
+
142
+ if (isLoading) {
143
+ return <Page.Loading />;
144
+ }
145
+
146
+ return (
147
+ <Layouts.Root>
148
+ <Page.Title>
149
+ {formatMessage(
150
+ { id: 'Settings.PageTitle', defaultMessage: 'Settings - {name}' },
151
+ {
152
+ name: formatMessage({
153
+ id: getTrad('HeaderNav.link.providers'),
154
+ defaultMessage: 'Providers',
155
+ }),
156
+ }
157
+ )}
158
+ </Page.Title>
159
+ <Page.Main>
160
+ <Layouts.Header
161
+ title={formatMessage({
162
+ id: getTrad('HeaderNav.link.providers'),
163
+ defaultMessage: 'Providers',
164
+ })}
165
+ />
166
+ <Layouts.Content>
167
+ <Table colCount={3} rowCount={providers.length + 1}>
168
+ <Thead>
169
+ <Tr>
170
+ <Th>
171
+ <Typography variant="sigma" textColor="neutral600">
172
+ {formatMessage({ id: 'global.name', defaultMessage: 'Name' })}
173
+ </Typography>
174
+ </Th>
175
+ <Th>
176
+ <Typography variant="sigma" textColor="neutral600">
177
+ {formatMessage({ id: getTrad('Providers.status'), defaultMessage: 'Status' })}
178
+ </Typography>
179
+ </Th>
180
+ <Th>
181
+ <Typography variant="sigma">
182
+ <VisuallyHidden>
183
+ {formatMessage({
184
+ id: 'global.settings',
185
+ defaultMessage: 'Settings',
186
+ })}
187
+ </VisuallyHidden>
188
+ </Typography>
189
+ </Th>
190
+ </Tr>
191
+ </Thead>
192
+ <Tbody>
193
+ {providers.map((provider) => (
194
+ <Tr
195
+ key={provider.name}
196
+ onClick={() => (canUpdate ? handleClickEdit(provider) : undefined)}
197
+ >
198
+ <Td width="45%">
199
+ <Typography fontWeight="semiBold" textColor="neutral800">
200
+ {provider.name}
201
+ </Typography>
202
+ </Td>
203
+ <Td width="65%">
204
+ <Typography
205
+ textColor={provider.enabled ? 'success600' : 'danger600'}
206
+ data-testid={`enable-${provider.name}`}
207
+ >
208
+ {provider.enabled
209
+ ? formatMessage({
210
+ id: 'global.enabled',
211
+ defaultMessage: 'Enabled',
212
+ })
213
+ : formatMessage({
214
+ id: 'global.disabled',
215
+ defaultMessage: 'Disabled',
216
+ })}
217
+ </Typography>
218
+ </Td>
219
+ <Td onClick={(e) => e.stopPropagation()}>
220
+ {canUpdate && (
221
+ <IconButton
222
+ onClick={() => handleClickEdit(provider)}
223
+ variant="ghost"
224
+ label="Edit"
225
+ >
226
+ <Pencil />
227
+ </IconButton>
228
+ )}
229
+ </Td>
230
+ </Tr>
231
+ ))}
232
+ </Tbody>
233
+ </Table>
234
+ </Layouts.Content>
235
+ </Page.Main>
236
+ <FormModal
237
+ initialData={data[providerToEditName]}
238
+ isOpen={isOpen}
239
+ isSubmiting={submitMutation.isLoading}
240
+ layout={layoutToRender}
241
+ headerBreadcrumbs={[
242
+ formatMessage({
243
+ id: getTrad('PopUpForm.header.edit.providers'),
244
+ defaultMessage: 'Edit Provider',
245
+ }),
246
+ upperFirst(providerToEditName),
247
+ ]}
248
+ onToggle={handleToggleModal}
249
+ onSubmit={handleSubmit}
250
+ providerToEditName={providerToEditName}
251
+ />
252
+ </Layouts.Root>
253
+ );
254
+ };
255
+
256
+ const ProtectedProvidersPage = () => (
257
+ <Page.Protect permissions={PERMISSIONS.readProviders}>
258
+ <ProvidersPage />
259
+ </Page.Protect>
260
+ );
261
+
262
+ export default ProtectedProvidersPage;
@@ -1,4 +1,4 @@
1
- import { translatedErrors } from '@strapi/helper-plugin';
1
+ import { translatedErrors } from '@strapi/strapi/admin';
2
2
  import * as yup from 'yup';
3
3
 
4
4
  import { getTrad } from '../../../utils';
@@ -34,6 +34,9 @@ const secretLabel = {
34
34
  defaultMessage: 'Client Secret',
35
35
  };
36
36
 
37
+ const CALLBACK_REGEX = /^$|^[a-z][a-z0-9+.-]*:\/\/[^\s/$.?#](?:[^\s]*[^\s/$.?#])?$/i;
38
+ const SUBDOMAIN_REGEX = /^(([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+)(:\d+)?(\/\S*)?$/i;
39
+
37
40
  const forms = {
38
41
  email: {
39
42
  form: [
@@ -52,7 +55,7 @@ const forms = {
52
55
  ],
53
56
  ],
54
57
  schema: yup.object().shape({
55
- enabled: yup.bool().required(translatedErrors.required),
58
+ enabled: yup.bool().required(translatedErrors.required.id),
56
59
  }),
57
60
  },
58
61
  providers: {
@@ -117,20 +120,23 @@ const forms = {
117
120
  ],
118
121
  ],
119
122
  schema: yup.object().shape({
120
- enabled: yup.bool().required(translatedErrors.required),
123
+ enabled: yup.bool().required(translatedErrors.required.id),
121
124
  key: yup.string().when('enabled', {
122
125
  is: true,
123
- then: yup.string().required(translatedErrors.required),
126
+ then: yup.string().required(translatedErrors.required.id),
124
127
  otherwise: yup.string(),
125
128
  }),
126
129
  secret: yup.string().when('enabled', {
127
130
  is: true,
128
- then: yup.string().required(translatedErrors.required),
131
+ then: yup.string().required(translatedErrors.required.id),
129
132
  otherwise: yup.string(),
130
133
  }),
131
134
  callback: yup.string().when('enabled', {
132
135
  is: true,
133
- then: yup.string().required(translatedErrors.required),
136
+ then: yup
137
+ .string()
138
+ .matches(CALLBACK_REGEX, translatedErrors.regex.id)
139
+ .required(translatedErrors.required.id),
134
140
  otherwise: yup.string(),
135
141
  }),
136
142
  }),
@@ -231,25 +237,31 @@ const forms = {
231
237
  ],
232
238
  ],
233
239
  schema: yup.object().shape({
234
- enabled: yup.bool().required(translatedErrors.required),
240
+ enabled: yup.bool().required(translatedErrors.required.id),
235
241
  key: yup.string().when('enabled', {
236
242
  is: true,
237
- then: yup.string().required(translatedErrors.required),
243
+ then: yup.string().required(translatedErrors.required.id),
238
244
  otherwise: yup.string(),
239
245
  }),
240
246
  secret: yup.string().when('enabled', {
241
247
  is: true,
242
- then: yup.string().required(translatedErrors.required),
248
+ then: yup.string().required(translatedErrors.required.id),
243
249
  otherwise: yup.string(),
244
250
  }),
245
251
  subdomain: yup.string().when('enabled', {
246
252
  is: true,
247
- then: yup.string().required(translatedErrors.required),
253
+ then: yup
254
+ .string()
255
+ .matches(SUBDOMAIN_REGEX, translatedErrors.regex.id)
256
+ .required(translatedErrors.required.id),
248
257
  otherwise: yup.string(),
249
258
  }),
250
259
  callback: yup.string().when('enabled', {
251
260
  is: true,
252
- then: yup.string().required(translatedErrors.required),
261
+ then: yup
262
+ .string()
263
+ .matches(CALLBACK_REGEX, translatedErrors.regex.id)
264
+ .required(translatedErrors.required.id),
253
265
  otherwise: yup.string(),
254
266
  }),
255
267
  }),
@@ -1,7 +1,7 @@
1
- import { translatedErrors } from '@strapi/helper-plugin';
1
+ import { translatedErrors } from '@strapi/strapi/admin';
2
2
  import * as yup from 'yup';
3
3
 
4
4
  export const createRoleSchema = yup.object().shape({
5
- name: yup.string().required(translatedErrors.required),
6
- description: yup.string().required(translatedErrors.required),
5
+ name: yup.string().required(translatedErrors.required.id),
6
+ description: yup.string().required(translatedErrors.required.id),
7
7
  });
@@ -1,13 +1,12 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
- import { useNotification, useFetchClient, useAPIErrorHandler } from '@strapi/helper-plugin';
3
+ import { useAPIErrorHandler, useNotification, useFetchClient } from '@strapi/strapi/admin';
4
4
  import { useQueries } from 'react-query';
5
5
 
6
- import pluginId from '../pluginId';
7
- import { cleanPermissions, getTrad } from '../utils';
6
+ import { cleanPermissions, getTrad } from '../../../utils';
8
7
 
9
8
  export const usePlugins = () => {
10
- const toggleNotification = useNotification();
9
+ const { toggleNotification } = useNotification();
11
10
  const { get } = useFetchClient();
12
11
  const { formatAPIError } = useAPIErrorHandler(getTrad);
13
12
 
@@ -21,19 +20,23 @@ export const usePlugins = () => {
21
20
  { data: routes, isLoading: isLoadingRoutes, error: routesError, refetch: refetchRoutes },
22
21
  ] = useQueries([
23
22
  {
24
- queryKey: [pluginId, 'permissions'],
23
+ queryKey: ['users-permissions', 'permissions'],
25
24
  async queryFn() {
26
- const res = await get(`/${pluginId}/permissions`);
25
+ const {
26
+ data: { permissions },
27
+ } = await get(`/users-permissions/permissions`);
27
28
 
28
- return res.data.permissions;
29
+ return permissions;
29
30
  },
30
31
  },
31
32
  {
32
- queryKey: [pluginId, 'routes'],
33
+ queryKey: ['users-permissions', 'routes'],
33
34
  async queryFn() {
34
- const res = await get(`/${pluginId}/routes`);
35
+ const {
36
+ data: { routes },
37
+ } = await get(`/users-permissions/routes`);
35
38
 
36
- return res.data.routes;
39
+ return routes;
37
40
  },
38
41
  },
39
42
  ]);
@@ -45,7 +48,7 @@ export const usePlugins = () => {
45
48
  useEffect(() => {
46
49
  if (permissionsError) {
47
50
  toggleNotification({
48
- type: 'warning',
51
+ type: 'danger',
49
52
  message: formatAPIError(permissionsError),
50
53
  });
51
54
  }
@@ -54,7 +57,7 @@ export const usePlugins = () => {
54
57
  useEffect(() => {
55
58
  if (routesError) {
56
59
  toggleNotification({
57
- type: 'warning',
60
+ type: 'danger',
58
61
  message: formatAPIError(routesError),
59
62
  });
60
63
  }
@@ -63,8 +66,12 @@ export const usePlugins = () => {
63
66
  const isLoading = isLoadingPermissions || isLoadingRoutes;
64
67
 
65
68
  return {
69
+ // TODO: these return values need to be memoized, otherwise
70
+ // they will create infinite rendering loops when used as
71
+ // effect dependencies
66
72
  permissions: permissions ? cleanPermissions(permissions) : {},
67
73
  routes: routes ?? {},
74
+
68
75
  getData: refetchQueries,
69
76
  isLoading,
70
77
  };
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+
3
+ import { Page } from '@strapi/strapi/admin';
4
+ import { Route, Routes } from 'react-router-dom';
5
+
6
+ import { PERMISSIONS } from '../../constants';
7
+
8
+ import { ProtectedRolesCreatePage } from './pages/CreatePage';
9
+ import { ProtectedRolesEditPage } from './pages/EditPage';
10
+ import { ProtectedRolesListPage } from './pages/ListPage';
11
+
12
+ const Roles = () => {
13
+ return (
14
+ <Page.Protect permissions={PERMISSIONS.accessRoles}>
15
+ <Routes>
16
+ <Route index element={<ProtectedRolesListPage />} />
17
+ <Route path="new" element={<ProtectedRolesCreatePage />} />
18
+ <Route path=":id" element={<ProtectedRolesEditPage />} />
19
+ </Routes>
20
+ </Page.Protect>
21
+ );
22
+ };
23
+
24
+ export default Roles;