@strapi/plugin-users-permissions 0.0.0-4fc90398602f

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 (168) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +1 -0
  3. package/admin/src/components/BoundRoute/getMethodColor.js +41 -0
  4. package/admin/src/components/BoundRoute/index.js +72 -0
  5. package/admin/src/components/FormModal/Input/index.js +121 -0
  6. package/admin/src/components/FormModal/index.js +121 -0
  7. package/admin/src/components/Permissions/PermissionRow/CheckboxWrapper.js +30 -0
  8. package/admin/src/components/Permissions/PermissionRow/SubCategory.js +114 -0
  9. package/admin/src/components/Permissions/PermissionRow/index.js +53 -0
  10. package/admin/src/components/Permissions/index.js +56 -0
  11. package/admin/src/components/Permissions/init.js +9 -0
  12. package/admin/src/components/Permissions/reducer.js +27 -0
  13. package/admin/src/components/Policies/index.js +60 -0
  14. package/admin/src/components/UsersPermissions/index.js +94 -0
  15. package/admin/src/components/UsersPermissions/init.js +10 -0
  16. package/admin/src/components/UsersPermissions/reducer.js +60 -0
  17. package/admin/src/contexts/UsersPermissionsContext/index.js +17 -0
  18. package/admin/src/hooks/index.js +5 -0
  19. package/admin/src/hooks/useFetchRole/index.js +64 -0
  20. package/admin/src/hooks/useFetchRole/reducer.js +31 -0
  21. package/admin/src/hooks/useForm/index.js +70 -0
  22. package/admin/src/hooks/useForm/reducer.js +40 -0
  23. package/admin/src/hooks/usePlugins/index.js +65 -0
  24. package/admin/src/hooks/usePlugins/init.js +5 -0
  25. package/admin/src/hooks/usePlugins/reducer.js +34 -0
  26. package/admin/src/hooks/useRolesList/index.js +63 -0
  27. package/admin/src/hooks/useRolesList/init.js +5 -0
  28. package/admin/src/hooks/useRolesList/reducer.js +31 -0
  29. package/admin/src/index.js +123 -0
  30. package/admin/src/pages/AdvancedSettings/index.js +238 -0
  31. package/admin/src/pages/AdvancedSettings/utils/api.js +13 -0
  32. package/admin/src/pages/AdvancedSettings/utils/layout.js +96 -0
  33. package/admin/src/pages/AdvancedSettings/utils/schema.js +19 -0
  34. package/admin/src/pages/EmailTemplates/components/EmailForm.js +173 -0
  35. package/admin/src/pages/EmailTemplates/components/EmailTable.js +121 -0
  36. package/admin/src/pages/EmailTemplates/index.js +162 -0
  37. package/admin/src/pages/EmailTemplates/utils/api.js +13 -0
  38. package/admin/src/pages/EmailTemplates/utils/schema.js +22 -0
  39. package/admin/src/pages/Providers/index.js +274 -0
  40. package/admin/src/pages/Providers/reducer.js +54 -0
  41. package/admin/src/pages/Providers/utils/api.js +21 -0
  42. package/admin/src/pages/Providers/utils/createProvidersArray.js +21 -0
  43. package/admin/src/pages/Providers/utils/forms.js +244 -0
  44. package/admin/src/pages/Roles/CreatePage/index.js +177 -0
  45. package/admin/src/pages/Roles/CreatePage/utils/schema.js +9 -0
  46. package/admin/src/pages/Roles/EditPage/index.js +190 -0
  47. package/admin/src/pages/Roles/EditPage/utils/schema.js +9 -0
  48. package/admin/src/pages/Roles/ListPage/components/TableBody.js +96 -0
  49. package/admin/src/pages/Roles/ListPage/index.js +216 -0
  50. package/admin/src/pages/Roles/ListPage/utils/api.js +28 -0
  51. package/admin/src/pages/Roles/ProtectedCreatePage/index.js +12 -0
  52. package/admin/src/pages/Roles/ProtectedEditPage/index.js +12 -0
  53. package/admin/src/pages/Roles/ProtectedListPage/index.js +15 -0
  54. package/admin/src/pages/Roles/index.js +27 -0
  55. package/admin/src/permissions.js +31 -0
  56. package/admin/src/pluginId.js +5 -0
  57. package/admin/src/translations/ar.json +40 -0
  58. package/admin/src/translations/cs.json +46 -0
  59. package/admin/src/translations/de.json +58 -0
  60. package/admin/src/translations/dk.json +83 -0
  61. package/admin/src/translations/en.json +83 -0
  62. package/admin/src/translations/es.json +83 -0
  63. package/admin/src/translations/fr.json +46 -0
  64. package/admin/src/translations/id.json +58 -0
  65. package/admin/src/translations/it.json +58 -0
  66. package/admin/src/translations/ja.json +44 -0
  67. package/admin/src/translations/ko.json +83 -0
  68. package/admin/src/translations/ms.json +45 -0
  69. package/admin/src/translations/nl.json +44 -0
  70. package/admin/src/translations/pl.json +83 -0
  71. package/admin/src/translations/pt-BR.json +40 -0
  72. package/admin/src/translations/pt.json +44 -0
  73. package/admin/src/translations/ru.json +58 -0
  74. package/admin/src/translations/sk.json +46 -0
  75. package/admin/src/translations/sv.json +58 -0
  76. package/admin/src/translations/th.json +56 -0
  77. package/admin/src/translations/tr.json +44 -0
  78. package/admin/src/translations/uk.json +45 -0
  79. package/admin/src/translations/vi.json +46 -0
  80. package/admin/src/translations/zh-Hans.json +62 -0
  81. package/admin/src/translations/zh.json +44 -0
  82. package/admin/src/utils/axiosInstance.js +36 -0
  83. package/admin/src/utils/cleanPermissions.js +25 -0
  84. package/admin/src/utils/formatPluginName.js +26 -0
  85. package/admin/src/utils/formatPolicies.js +8 -0
  86. package/admin/src/utils/getRequestURL.js +5 -0
  87. package/admin/src/utils/getTrad.js +5 -0
  88. package/admin/src/utils/index.js +5 -0
  89. package/documentation/content-api.yaml +848 -0
  90. package/jest.config.front.js +10 -0
  91. package/package.json +60 -0
  92. package/server/bootstrap/grant-config.js +123 -0
  93. package/server/bootstrap/index.js +133 -0
  94. package/server/bootstrap/users-permissions-actions.js +80 -0
  95. package/server/config.js +23 -0
  96. package/server/content-types/index.js +11 -0
  97. package/server/content-types/permission/index.js +34 -0
  98. package/server/content-types/role/index.js +51 -0
  99. package/server/content-types/user/index.js +72 -0
  100. package/server/content-types/user/schema-config.js +15 -0
  101. package/server/controllers/auth.js +398 -0
  102. package/server/controllers/content-manager-user.js +175 -0
  103. package/server/controllers/index.js +17 -0
  104. package/server/controllers/permissions.js +26 -0
  105. package/server/controllers/role.js +77 -0
  106. package/server/controllers/settings.js +85 -0
  107. package/server/controllers/user.js +198 -0
  108. package/server/controllers/validation/auth.js +57 -0
  109. package/server/controllers/validation/email-template.js +50 -0
  110. package/server/controllers/validation/user.js +26 -0
  111. package/server/graphql/index.js +44 -0
  112. package/server/graphql/mutations/auth/change-password.js +38 -0
  113. package/server/graphql/mutations/auth/email-confirmation.js +39 -0
  114. package/server/graphql/mutations/auth/forgot-password.js +35 -0
  115. package/server/graphql/mutations/auth/login.js +35 -0
  116. package/server/graphql/mutations/auth/register.js +36 -0
  117. package/server/graphql/mutations/auth/reset-password.js +38 -0
  118. package/server/graphql/mutations/crud/role/create-role.js +34 -0
  119. package/server/graphql/mutations/crud/role/delete-role.js +25 -0
  120. package/server/graphql/mutations/crud/role/update-role.js +35 -0
  121. package/server/graphql/mutations/crud/user/create-user.js +45 -0
  122. package/server/graphql/mutations/crud/user/delete-user.js +39 -0
  123. package/server/graphql/mutations/crud/user/update-user.js +46 -0
  124. package/server/graphql/mutations/index.js +43 -0
  125. package/server/graphql/queries/index.js +13 -0
  126. package/server/graphql/queries/me.js +17 -0
  127. package/server/graphql/resolvers-configs.js +42 -0
  128. package/server/graphql/types/create-role-payload.js +11 -0
  129. package/server/graphql/types/delete-role-payload.js +11 -0
  130. package/server/graphql/types/index.js +21 -0
  131. package/server/graphql/types/login-input.js +13 -0
  132. package/server/graphql/types/login-payload.js +12 -0
  133. package/server/graphql/types/me-role.js +14 -0
  134. package/server/graphql/types/me.js +16 -0
  135. package/server/graphql/types/password-payload.js +11 -0
  136. package/server/graphql/types/register-input.js +13 -0
  137. package/server/graphql/types/update-role-payload.js +11 -0
  138. package/server/graphql/utils.js +27 -0
  139. package/server/index.js +21 -0
  140. package/server/middlewares/index.js +7 -0
  141. package/server/middlewares/rateLimit.js +27 -0
  142. package/server/register.js +23 -0
  143. package/server/routes/admin/index.js +10 -0
  144. package/server/routes/admin/permissions.js +20 -0
  145. package/server/routes/admin/role.js +79 -0
  146. package/server/routes/admin/settings.js +95 -0
  147. package/server/routes/content-api/auth.js +82 -0
  148. package/server/routes/content-api/index.js +11 -0
  149. package/server/routes/content-api/permissions.js +9 -0
  150. package/server/routes/content-api/role.js +29 -0
  151. package/server/routes/content-api/user.js +60 -0
  152. package/server/routes/index.js +6 -0
  153. package/server/services/index.js +17 -0
  154. package/server/services/jwt.js +55 -0
  155. package/server/services/providers-registry.js +292 -0
  156. package/server/services/providers.js +115 -0
  157. package/server/services/role.js +177 -0
  158. package/server/services/user.js +140 -0
  159. package/server/services/users-permissions.js +236 -0
  160. package/server/strategies/users-permissions.js +102 -0
  161. package/server/utils/index.d.ts +16 -0
  162. package/server/utils/index.js +12 -0
  163. package/server/utils/sanitize/index.js +9 -0
  164. package/server/utils/sanitize/sanitizers.js +19 -0
  165. package/server/utils/sanitize/visitors/index.js +5 -0
  166. package/server/utils/sanitize/visitors/remove-user-relation-from-role-entities.js +11 -0
  167. package/strapi-admin.js +3 -0
  168. package/strapi-server.js +3 -0
@@ -0,0 +1,121 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useIntl } from 'react-intl';
4
+ import { Table, Thead, Tbody, Tr, Td, Th } from '@strapi/design-system/Table';
5
+ import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
6
+ import { Typography } from '@strapi/design-system/Typography';
7
+ import { IconButton } from '@strapi/design-system/IconButton';
8
+ import { Icon } from '@strapi/design-system/Icon';
9
+ import Pencil from '@strapi/icons/Pencil';
10
+ import Reload from '@strapi/icons/Refresh';
11
+ import { onRowClick, stopPropagation } from '@strapi/helper-plugin';
12
+ import Check from '@strapi/icons/Check';
13
+ import { getTrad } from '../../../utils';
14
+
15
+ const EmailTable = ({ canUpdate, onEditClick }) => {
16
+ const { formatMessage } = useIntl();
17
+
18
+ return (
19
+ <Table colCount={3} rowCount={3}>
20
+ <Thead>
21
+ <Tr>
22
+ <Th width="1%">
23
+ <VisuallyHidden>
24
+ {formatMessage({
25
+ id: getTrad('Email.template.table.icon.label'),
26
+ defaultMessage: 'icon',
27
+ })}
28
+ </VisuallyHidden>
29
+ </Th>
30
+ <Th>
31
+ <Typography variant="sigma" textColor="neutral600">
32
+ {formatMessage({
33
+ id: getTrad('Email.template.table.name.label'),
34
+ defaultMessage: 'name',
35
+ })}
36
+ </Typography>
37
+ </Th>
38
+ <Th width="1%">
39
+ <VisuallyHidden>
40
+ {formatMessage({
41
+ id: getTrad('Email.template.table.action.label'),
42
+ defaultMessage: 'action',
43
+ })}
44
+ </VisuallyHidden>
45
+ </Th>
46
+ </Tr>
47
+ </Thead>
48
+ <Tbody>
49
+ <Tr {...onRowClick({ fn: () => onEditClick('reset_password') })}>
50
+ <Td>
51
+ <Icon>
52
+ <Reload
53
+ aria-label={formatMessage({
54
+ id: 'global.reset-password',
55
+ defaultMessage: 'Reset password',
56
+ })}
57
+ />
58
+ </Icon>
59
+ </Td>
60
+ <Td>
61
+ <Typography>
62
+ {formatMessage({
63
+ id: 'global.reset-password',
64
+ defaultMessage: 'Reset password',
65
+ })}
66
+ </Typography>
67
+ </Td>
68
+ <Td {...stopPropagation}>
69
+ <IconButton
70
+ onClick={() => onEditClick('reset_password')}
71
+ label={formatMessage({
72
+ id: getTrad('Email.template.form.edit.label'),
73
+ defaultMessage: 'Edit a template',
74
+ })}
75
+ noBorder
76
+ icon={canUpdate && <Pencil />}
77
+ />
78
+ </Td>
79
+ </Tr>
80
+ <Tr {...onRowClick({ fn: () => onEditClick('email_confirmation') })}>
81
+ <Td>
82
+ <Icon>
83
+ <Check
84
+ aria-label={formatMessage({
85
+ id: getTrad('Email.template.email_confirmation'),
86
+ defaultMessage: 'Email address confirmation',
87
+ })}
88
+ />
89
+ </Icon>
90
+ </Td>
91
+ <Td>
92
+ <Typography>
93
+ {formatMessage({
94
+ id: getTrad('Email.template.email_confirmation'),
95
+ defaultMessage: 'Email address confirmation',
96
+ })}
97
+ </Typography>
98
+ </Td>
99
+ <Td {...stopPropagation}>
100
+ <IconButton
101
+ onClick={() => onEditClick('email_confirmation')}
102
+ label={formatMessage({
103
+ id: getTrad('Email.template.form.edit.label'),
104
+ defaultMessage: 'Edit a template',
105
+ })}
106
+ noBorder
107
+ icon={canUpdate && <Pencil />}
108
+ />
109
+ </Td>
110
+ </Tr>
111
+ </Tbody>
112
+ </Table>
113
+ );
114
+ };
115
+
116
+ EmailTable.propTypes = {
117
+ canUpdate: PropTypes.bool.isRequired,
118
+ onEditClick: PropTypes.func.isRequired,
119
+ };
120
+
121
+ export default EmailTable;
@@ -0,0 +1,162 @@
1
+ import React, { useMemo, useRef, useState } from 'react';
2
+ import { useQuery, useMutation, useQueryClient } from 'react-query';
3
+ import { useIntl } from 'react-intl';
4
+ import {
5
+ SettingsPageTitle,
6
+ useTracking,
7
+ useNotification,
8
+ useOverlayBlocker,
9
+ CheckPagePermissions,
10
+ useRBAC,
11
+ useFocusWhenNavigate,
12
+ LoadingIndicatorPage,
13
+ } from '@strapi/helper-plugin';
14
+ import { useNotifyAT } from '@strapi/design-system/LiveRegions';
15
+ import { Main } from '@strapi/design-system/Main';
16
+ import { ContentLayout, HeaderLayout } from '@strapi/design-system/Layout';
17
+ import pluginPermissions from '../../permissions';
18
+ import { getTrad } from '../../utils';
19
+ import { fetchData, putEmailTemplate } from './utils/api';
20
+ import EmailTable from './components/EmailTable';
21
+ import EmailForm from './components/EmailForm';
22
+
23
+ const ProtectedEmailTemplatesPage = () => (
24
+ <CheckPagePermissions permissions={pluginPermissions.readEmailTemplates}>
25
+ <EmailTemplatesPage />
26
+ </CheckPagePermissions>
27
+ );
28
+
29
+ const EmailTemplatesPage = () => {
30
+ const { formatMessage } = useIntl();
31
+ const { trackUsage } = useTracking();
32
+ const { notifyStatus } = useNotifyAT();
33
+ const toggleNotification = useNotification();
34
+ const { lockApp, unlockApp } = useOverlayBlocker();
35
+ const trackUsageRef = useRef(trackUsage);
36
+ const queryClient = useQueryClient();
37
+ useFocusWhenNavigate();
38
+
39
+ const [isModalOpen, setIsModalOpen] = useState(false);
40
+ const [templateToEdit, setTemplateToEdit] = useState(null);
41
+
42
+ const updatePermissions = useMemo(() => {
43
+ return { update: pluginPermissions.updateEmailTemplates };
44
+ }, []);
45
+
46
+ const {
47
+ isLoading: isLoadingForPermissions,
48
+ allowedActions: { canUpdate },
49
+ } = useRBAC(updatePermissions);
50
+
51
+ const { status: isLoadingData, data } = useQuery('email-templates', () => fetchData(), {
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() {
61
+ toggleNotification({
62
+ type: 'warning',
63
+ message: { id: 'notification.error', defaultMessage: 'An error occured' },
64
+ });
65
+ },
66
+ });
67
+
68
+ const isLoading = isLoadingForPermissions || isLoadingData !== 'success';
69
+
70
+ const handleToggle = () => {
71
+ setIsModalOpen((prev) => !prev);
72
+ };
73
+
74
+ const handleEditClick = (template) => {
75
+ setTemplateToEdit(template);
76
+ handleToggle();
77
+ };
78
+
79
+ const submitMutation = useMutation((body) => putEmailTemplate({ 'email-templates': body }), {
80
+ async onSuccess() {
81
+ await queryClient.invalidateQueries('email-templates');
82
+
83
+ toggleNotification({
84
+ type: 'success',
85
+ message: { id: 'notification.success.saved', defaultMessage: 'Saved' },
86
+ });
87
+
88
+ trackUsageRef.current('didEditEmailTemplates');
89
+
90
+ unlockApp();
91
+ handleToggle();
92
+ },
93
+ onError() {
94
+ toggleNotification({
95
+ type: 'warning',
96
+ message: { id: 'notification.error', defaultMessage: 'An error occured' },
97
+ });
98
+ unlockApp();
99
+ },
100
+ refetchActive: true,
101
+ });
102
+ const { isLoading: isSubmittingForm } = submitMutation;
103
+
104
+ const handleSubmit = (body) => {
105
+ lockApp();
106
+ trackUsageRef.current('willEditEmailTemplates');
107
+
108
+ const editedTemplates = { ...data, [templateToEdit]: body };
109
+ submitMutation.mutate(editedTemplates);
110
+ };
111
+
112
+ if (isLoading) {
113
+ return (
114
+ <Main aria-busy="true">
115
+ <SettingsPageTitle
116
+ name={formatMessage({
117
+ id: getTrad('HeaderNav.link.emailTemplates'),
118
+ defaultMessage: 'Email templates',
119
+ })}
120
+ />
121
+ <HeaderLayout
122
+ title={formatMessage({
123
+ id: getTrad('HeaderNav.link.emailTemplates'),
124
+ defaultMessage: 'Email templates',
125
+ })}
126
+ />
127
+ <ContentLayout>
128
+ <LoadingIndicatorPage />
129
+ </ContentLayout>
130
+ </Main>
131
+ );
132
+ }
133
+
134
+ return (
135
+ <Main aria-busy={isSubmittingForm}>
136
+ <SettingsPageTitle
137
+ name={formatMessage({
138
+ id: getTrad('HeaderNav.link.emailTemplates'),
139
+ defaultMessage: 'Email templates',
140
+ })}
141
+ />
142
+ <HeaderLayout
143
+ title={formatMessage({
144
+ id: getTrad('HeaderNav.link.emailTemplates'),
145
+ defaultMessage: 'Email templates',
146
+ })}
147
+ />
148
+ <ContentLayout>
149
+ <EmailTable onEditClick={handleEditClick} canUpdate={canUpdate} />
150
+ {isModalOpen && (
151
+ <EmailForm
152
+ template={data[templateToEdit]}
153
+ onToggle={handleToggle}
154
+ onSubmit={handleSubmit}
155
+ />
156
+ )}
157
+ </ContentLayout>
158
+ </Main>
159
+ );
160
+ };
161
+
162
+ export default ProtectedEmailTemplatesPage;
@@ -0,0 +1,13 @@
1
+ import { axiosInstance, getRequestURL } from '../../../utils';
2
+
3
+ const fetchData = async () => {
4
+ const { data } = await axiosInstance.get(getRequestURL('email-templates'));
5
+
6
+ return data;
7
+ };
8
+
9
+ const putEmailTemplate = (body) => {
10
+ return axiosInstance.put(getRequestURL('email-templates'), body);
11
+ };
12
+
13
+ export { fetchData, putEmailTemplate };
@@ -0,0 +1,22 @@
1
+ import * as yup from 'yup';
2
+ import { translatedErrors } from '@strapi/helper-plugin';
3
+
4
+ const schema = yup.object().shape({
5
+ options: yup
6
+ .object()
7
+ .shape({
8
+ from: yup
9
+ .object()
10
+ .shape({
11
+ name: yup.string().required(translatedErrors.required),
12
+ email: yup.string().email(translatedErrors.email).required(translatedErrors.required),
13
+ })
14
+ .required(),
15
+ response_email: yup.string().email(translatedErrors.email),
16
+ object: yup.string().required(translatedErrors.required),
17
+ message: yup.string().required(translatedErrors.required),
18
+ })
19
+ .required(translatedErrors.required),
20
+ });
21
+
22
+ export default schema;
@@ -0,0 +1,274 @@
1
+ import React, { useMemo, useRef, useState } from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import {
4
+ SettingsPageTitle,
5
+ LoadingIndicatorPage,
6
+ useTracking,
7
+ useNotification,
8
+ useOverlayBlocker,
9
+ CheckPagePermissions,
10
+ useRBAC,
11
+ useFocusWhenNavigate,
12
+ onRowClick,
13
+ stopPropagation,
14
+ } from '@strapi/helper-plugin';
15
+ import has from 'lodash/has';
16
+ import upperFirst from 'lodash/upperFirst';
17
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
18
+ import { HeaderLayout, Layout, ContentLayout } from '@strapi/design-system/Layout';
19
+ import { Main } from '@strapi/design-system/Main';
20
+ import { useNotifyAT } from '@strapi/design-system/LiveRegions';
21
+ import { Table, Thead, Tr, Th, Tbody, Td } from '@strapi/design-system/Table';
22
+ import { Typography } from '@strapi/design-system/Typography';
23
+ import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
24
+ import { IconButton } from '@strapi/design-system/IconButton';
25
+ import Pencil from '@strapi/icons/Pencil';
26
+ import { useQuery, useMutation, useQueryClient } from 'react-query';
27
+ import forms from './utils/forms';
28
+ import { fetchData, putProvider } from './utils/api';
29
+ import createProvidersArray from './utils/createProvidersArray';
30
+ import { getTrad } from '../../utils';
31
+ import pluginPermissions from '../../permissions';
32
+ import FormModal from '../../components/FormModal';
33
+
34
+ export const ProvidersPage = () => {
35
+ const { formatMessage } = useIntl();
36
+ useFocusWhenNavigate();
37
+ const { notifyStatus } = useNotifyAT();
38
+ const queryClient = useQueryClient();
39
+ const { trackUsage } = useTracking();
40
+ const trackUsageRef = useRef(trackUsage);
41
+ const [isOpen, setIsOpen] = useState(false);
42
+ const [isSubmiting, setIsSubmiting] = useState(false);
43
+ const [providerToEditName, setProviderToEditName] = useState(null);
44
+ const toggleNotification = useNotification();
45
+ const { lockApp, unlockApp } = useOverlayBlocker();
46
+
47
+ const updatePermissions = useMemo(() => {
48
+ return { update: pluginPermissions.updateProviders };
49
+ }, []);
50
+
51
+ const {
52
+ isLoading: isLoadingForPermissions,
53
+ allowedActions: { canUpdate },
54
+ } = useRBAC(updatePermissions);
55
+
56
+ const {
57
+ isLoading: isLoadingForData,
58
+ data: modifiedData,
59
+ isFetching,
60
+ } = useQuery('get-providers', () => fetchData(toggleNotification), {
61
+ onSuccess() {
62
+ notifyStatus(
63
+ formatMessage({
64
+ id: getTrad('Providers.data.loaded'),
65
+ defaultMessage: 'Providers have been loaded',
66
+ })
67
+ );
68
+ },
69
+ initialData: {},
70
+ });
71
+
72
+ const isLoading = isLoadingForData || isFetching;
73
+
74
+ const submitMutation = useMutation(putProvider, {
75
+ async onSuccess() {
76
+ await queryClient.invalidateQueries('get-providers');
77
+ toggleNotification({
78
+ type: 'info',
79
+ message: { id: getTrad('notification.success.submit') },
80
+ });
81
+
82
+ trackUsageRef.current('didEditAuthenticationProvider');
83
+ setIsSubmiting(false);
84
+ handleToggleModal();
85
+ unlockApp();
86
+ },
87
+ onError() {
88
+ toggleNotification({
89
+ type: 'warning',
90
+ message: { id: 'notification.error' },
91
+ });
92
+ unlockApp();
93
+ setIsSubmiting(false);
94
+ },
95
+ refetchActive: false,
96
+ });
97
+
98
+ const providers = useMemo(() => createProvidersArray(modifiedData), [modifiedData]);
99
+
100
+ const rowCount = providers.length;
101
+
102
+ const isProviderWithSubdomain = useMemo(() => {
103
+ if (!providerToEditName) {
104
+ return false;
105
+ }
106
+
107
+ const providerToEdit = providers.find((obj) => obj.name === providerToEditName);
108
+
109
+ return has(providerToEdit, 'subdomain');
110
+ }, [providers, providerToEditName]);
111
+
112
+ const pageTitle = formatMessage({
113
+ id: getTrad('HeaderNav.link.providers'),
114
+ defaultMessage: 'Providers',
115
+ });
116
+
117
+ const layoutToRender = useMemo(() => {
118
+ if (providerToEditName === 'email') {
119
+ return forms.email;
120
+ }
121
+
122
+ if (isProviderWithSubdomain) {
123
+ return forms.providersWithSubdomain;
124
+ }
125
+
126
+ return forms.providers;
127
+ }, [providerToEditName, isProviderWithSubdomain]);
128
+
129
+ const handleToggleModal = () => {
130
+ setIsOpen((prev) => !prev);
131
+ };
132
+
133
+ const handleClickEdit = (provider) => {
134
+ if (canUpdate) {
135
+ setProviderToEditName(provider.name);
136
+ handleToggleModal();
137
+ }
138
+ };
139
+
140
+ const handleSubmit = async (values) => {
141
+ setIsSubmiting(true);
142
+
143
+ lockApp();
144
+
145
+ trackUsageRef.current('willEditAuthenticationProvider');
146
+
147
+ const body = { ...modifiedData, [providerToEditName]: values };
148
+
149
+ submitMutation.mutate({ providers: body });
150
+ };
151
+
152
+ return (
153
+ <Layout>
154
+ <SettingsPageTitle name={pageTitle} />
155
+ <Main>
156
+ <HeaderLayout
157
+ title={formatMessage({
158
+ id: getTrad('HeaderNav.link.providers'),
159
+ defaultMessage: 'Providers',
160
+ })}
161
+ />
162
+ {isLoading || isLoadingForPermissions ? (
163
+ <LoadingIndicatorPage />
164
+ ) : (
165
+ <ContentLayout>
166
+ <Table colCount={4} rowCount={rowCount + 1}>
167
+ <Thead>
168
+ <Tr>
169
+ <Th>
170
+ <Typography variant="sigma" textColor="neutral600">
171
+ <VisuallyHidden>
172
+ {formatMessage({ id: getTrad('Providers.image'), defaultMessage: 'Image' })}
173
+ </VisuallyHidden>
174
+ </Typography>
175
+ </Th>
176
+ <Th>
177
+ <Typography variant="sigma" textColor="neutral600">
178
+ {formatMessage({ id: 'global.name', defaultMessage: 'Name' })}
179
+ </Typography>
180
+ </Th>
181
+ <Th>
182
+ <Typography variant="sigma" textColor="neutral600">
183
+ {formatMessage({ id: getTrad('Providers.status'), defaultMessage: 'Status' })}
184
+ </Typography>
185
+ </Th>
186
+ <Th>
187
+ <Typography variant="sigma">
188
+ <VisuallyHidden>
189
+ {formatMessage({
190
+ id: 'global.settings',
191
+ defaultMessage: 'Settings',
192
+ })}
193
+ </VisuallyHidden>
194
+ </Typography>
195
+ </Th>
196
+ </Tr>
197
+ </Thead>
198
+ <Tbody>
199
+ {providers.map((provider) => (
200
+ <Tr
201
+ key={provider.name}
202
+ {...onRowClick({
203
+ fn: () => handleClickEdit(provider),
204
+ condition: canUpdate,
205
+ })}
206
+ >
207
+ <Td width="">
208
+ <FontAwesomeIcon icon={provider.icon} />
209
+ </Td>
210
+ <Td width="45%">
211
+ <Typography fontWeight="semiBold" textColor="neutral800">
212
+ {provider.name}
213
+ </Typography>
214
+ </Td>
215
+ <Td width="65%">
216
+ <Typography
217
+ textColor={provider.enabled ? 'success600' : 'danger600'}
218
+ data-testid={`enable-${provider.name}`}
219
+ >
220
+ {provider.enabled
221
+ ? formatMessage({
222
+ id: 'global.enabled',
223
+ defaultMessage: 'Enabled',
224
+ })
225
+ : formatMessage({
226
+ id: 'global.disabled',
227
+ defaultMessage: 'Disabled',
228
+ })}
229
+ </Typography>
230
+ </Td>
231
+ <Td {...stopPropagation}>
232
+ {canUpdate && (
233
+ <IconButton
234
+ onClick={() => handleClickEdit(provider)}
235
+ noBorder
236
+ icon={<Pencil />}
237
+ label="Edit"
238
+ />
239
+ )}
240
+ </Td>
241
+ </Tr>
242
+ ))}
243
+ </Tbody>
244
+ </Table>
245
+ </ContentLayout>
246
+ )}
247
+ </Main>
248
+ <FormModal
249
+ initialData={modifiedData[providerToEditName]}
250
+ isOpen={isOpen}
251
+ isSubmiting={isSubmiting}
252
+ layout={layoutToRender}
253
+ headerBreadcrumbs={[
254
+ formatMessage({
255
+ id: getTrad('PopUpForm.header.edit.providers'),
256
+ defaultMessage: 'Edit Provider',
257
+ }),
258
+ upperFirst(providerToEditName),
259
+ ]}
260
+ onToggle={handleToggleModal}
261
+ onSubmit={handleSubmit}
262
+ providerToEditName={providerToEditName}
263
+ />
264
+ </Layout>
265
+ );
266
+ };
267
+
268
+ const ProtectedProvidersPage = () => (
269
+ <CheckPagePermissions permissions={pluginPermissions.readProviders}>
270
+ <ProvidersPage />
271
+ </CheckPagePermissions>
272
+ );
273
+
274
+ export default ProtectedProvidersPage;
@@ -0,0 +1,54 @@
1
+ import produce from 'immer';
2
+ import { set } from 'lodash';
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,21 @@
1
+ import { getRequestURL, axiosInstance } from '../../../utils';
2
+
3
+ // eslint-disable-next-line import/prefer-default-export
4
+ export const fetchData = async (toggleNotification) => {
5
+ try {
6
+ const { data } = await axiosInstance.get(getRequestURL('providers'));
7
+
8
+ return data;
9
+ } catch (err) {
10
+ toggleNotification({
11
+ type: 'warning',
12
+ message: { id: 'notification.error' },
13
+ });
14
+
15
+ throw new Error('error');
16
+ }
17
+ };
18
+
19
+ export const putProvider = (body) => {
20
+ return axiosInstance.put(getRequestURL('providers'), body);
21
+ };
@@ -0,0 +1,21 @@
1
+ import { sortBy } from 'lodash';
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;