@strapi/admin 4.12.7 → 4.13.0-alpha.1

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 (180) hide show
  1. package/admin/src/components/NpsSurvey/hooks/useNpsSurveySettings.js +17 -0
  2. package/admin/src/components/NpsSurvey/index.js +365 -0
  3. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumns.js +2 -0
  4. package/admin/src/content-manager/components/DynamicZone/components/DynamicZoneLabel.js +1 -1
  5. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +7 -2
  6. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +6 -0
  7. package/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js +42 -0
  8. package/admin/src/content-manager/components/Filter/Filter.js +54 -0
  9. package/admin/src/content-manager/components/Filter/index.js +1 -0
  10. package/admin/src/content-manager/components/InputUID/index.js +222 -216
  11. package/admin/src/content-manager/components/RelationInput/RelationInput.js +4 -0
  12. package/admin/src/content-manager/components/RepeatableComponent/index.js +32 -2
  13. package/admin/src/content-manager/components/Wysiwyg/Editor.js +432 -68
  14. package/admin/src/content-manager/components/Wysiwyg/WysiwygStyles.js +0 -7
  15. package/admin/src/content-manager/components/Wysiwyg/index.js +147 -152
  16. package/admin/src/content-manager/constants/attributes.js +3 -0
  17. package/admin/src/content-manager/hooks/useAllowedAttributes.js +43 -0
  18. package/admin/src/content-manager/pages/EditView/Information/index.js +9 -8
  19. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +40 -7
  20. package/admin/src/content-manager/pages/ListSettingsView/index.js +6 -2
  21. package/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js +67 -69
  22. package/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js +80 -0
  23. package/admin/src/content-manager/pages/ListView/index.js +236 -67
  24. package/admin/src/content-manager/utils/getDisplayName.js +33 -0
  25. package/admin/src/content-manager/utils/index.js +1 -0
  26. package/admin/src/pages/Admin/index.js +3 -1
  27. package/admin/src/pages/AuthPage/components/Register/index.js +5 -0
  28. package/admin/src/pages/SettingsPage/components/SettingsNav/index.js +3 -3
  29. package/admin/src/pages/SettingsPage/index.js +16 -26
  30. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/index.js +69 -35
  31. package/admin/src/plugins.js +7 -8
  32. package/admin/src/translations/en.json +10 -1
  33. package/build/{1049.f76cb14b.chunk.js → 1049.ec69f5e0.chunk.js} +1 -1
  34. package/build/1227.9f37e1dc.chunk.js +1 -0
  35. package/build/{1386.879bcd90.chunk.js → 1386.ea73b677.chunk.js} +1 -1
  36. package/build/1504.eff012f7.chunk.js +95 -0
  37. package/build/{2225.c6244756.chunk.js → 2225.649fb7bc.chunk.js} +11 -11
  38. package/build/2237.b832ae6e.chunk.js +114 -0
  39. package/build/2379.1f98a31a.chunk.js +1 -0
  40. package/build/2395.0e5e8ded.chunk.js +26 -0
  41. package/build/2801.8e1aa82a.chunk.js +1 -0
  42. package/build/{3483.03c24f96.chunk.js → 3483.19381b40.chunk.js} +1 -1
  43. package/build/4174.f1f39e40.chunk.js +1 -0
  44. package/build/4546.a5946d22.chunk.js +1 -0
  45. package/build/4724.aea5c8c1.chunk.js +6 -0
  46. package/build/502.7bba43b1.chunk.js +1 -0
  47. package/build/6158.c3c13c20.chunk.js +1 -0
  48. package/build/7464.eb057bec.chunk.js +1 -0
  49. package/build/78.dcc6df5c.chunk.js +1 -0
  50. package/build/8276.be3ed581.chunk.js +26 -0
  51. package/build/{Admin-authenticatedApp.31497f74.chunk.js → Admin-authenticatedApp.36b0fe22.chunk.js} +2 -2
  52. package/build/{Admin_InternalErrorPage.f45f2462.chunk.js → Admin_InternalErrorPage.38155af3.chunk.js} +1 -1
  53. package/build/{Admin_homePage.ac9dfb86.chunk.js → Admin_homePage.6f128523.chunk.js} +1 -1
  54. package/build/{Admin_marketplace.c94239f6.chunk.js → Admin_marketplace.061a6e5a.chunk.js} +1 -1
  55. package/build/{Admin_pluginsPage.bbe79434.chunk.js → Admin_pluginsPage.16f837b8.chunk.js} +1 -1
  56. package/build/{Admin_profilePage.192edc52.chunk.js → Admin_profilePage.678bce24.chunk.js} +2 -2
  57. package/build/Admin_settingsPage.af7309e4.chunk.js +111 -0
  58. package/build/{Upload_ConfigureTheView.345ac1e0.chunk.js → Upload_ConfigureTheView.3fc1c100.chunk.js} +1 -1
  59. package/build/admin-app.d63bd229.chunk.js +36 -0
  60. package/build/{admin-edit-roles-page.6d567273.chunk.js → admin-edit-roles-page.38a6c863.chunk.js} +3 -3
  61. package/build/{admin-edit-users.acfd4128.chunk.js → admin-edit-users.545fc882.chunk.js} +2 -2
  62. package/build/{admin-roles-list.23ddff26.chunk.js → admin-roles-list.1e2e814d.chunk.js} +1 -1
  63. package/build/{admin-users.00e20017.chunk.js → admin-users.b8ea5677.chunk.js} +2 -2
  64. package/build/{api-tokens-create-page.46c2ea84.chunk.js → api-tokens-create-page.e0c15627.chunk.js} +1 -1
  65. package/build/{api-tokens-edit-page.58139df9.chunk.js → api-tokens-edit-page.9f2dce47.chunk.js} +1 -1
  66. package/build/{api-tokens-list-page.0af7d431.chunk.js → api-tokens-list-page.d747051c.chunk.js} +2 -2
  67. package/build/{audit-logs-settings-page.0f73ccf8.chunk.js → audit-logs-settings-page.96f9d608.chunk.js} +1 -1
  68. package/build/content-manager.2d676432.chunk.js +1097 -0
  69. package/build/{content-type-builder-list-view.bf9be456.chunk.js → content-type-builder-list-view.b71cf240.chunk.js} +1 -1
  70. package/build/{content-type-builder.cd999f6e.chunk.js → content-type-builder.e5669749.chunk.js} +2 -2
  71. package/build/email-settings-page.2809f0bf.chunk.js +11 -0
  72. package/build/en-json.e12fd5fc.chunk.js +1 -0
  73. package/build/{i18n-settings-page.47f78016.chunk.js → i18n-settings-page.5f716172.chunk.js} +1 -1
  74. package/build/index.html +1 -1
  75. package/build/main.c6c9e04c.js +2859 -0
  76. package/build/review-workflows-settings-create-view.4a156a19.chunk.js +1 -0
  77. package/build/review-workflows-settings-edit-view.ce984d1f.chunk.js +1 -0
  78. package/build/review-workflows-settings-list-view.419b8deb.chunk.js +56 -0
  79. package/build/{runtime~main.d515c521.js → runtime~main.5a10b789.js} +2 -2
  80. package/build/{sso-settings-page.12b6d8ae.chunk.js → sso-settings-page.45153df5.chunk.js} +1 -1
  81. package/build/{transfer-tokens-create-page.1597e6ab.chunk.js → transfer-tokens-create-page.ebba16d8.chunk.js} +1 -1
  82. package/build/{transfer-tokens-edit-page.8741529f.chunk.js → transfer-tokens-edit-page.d7bb2b3e.chunk.js} +1 -1
  83. package/build/{transfer-tokens-list-page.d6986b03.chunk.js → transfer-tokens-list-page.cfe1736c.chunk.js} +2 -2
  84. package/build/{upload-settings.7f93d4c0.chunk.js → upload-settings.cc5ad813.chunk.js} +1 -1
  85. package/build/{upload.37488080.chunk.js → upload.756efc28.chunk.js} +1 -1
  86. package/build/users-advanced-settings-page.818d84eb.chunk.js +9 -0
  87. package/build/users-email-settings-page.c1967c09.chunk.js +9 -0
  88. package/build/users-providers-settings-page.11893e08.chunk.js +14 -0
  89. package/build/users-roles-settings-page.2b051e6a.chunk.js +55 -0
  90. package/build/{webhook-edit-page.6cb479ff.chunk.js → webhook-edit-page.de45c635.chunk.js} +2 -2
  91. package/build/{webhook-list-page.65e1b5bb.chunk.js → webhook-list-page.ca91df8b.chunk.js} +1 -1
  92. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/AssigneeFilter.js +42 -0
  93. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/StageFilter.js +70 -0
  94. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js +71 -0
  95. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +9 -217
  96. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/AssigneeSelect.js +147 -0
  97. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/index.js +1 -0
  98. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +243 -0
  99. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/index.js +1 -0
  100. package/ee/admin/content-manager/pages/EditView/InformationBox/constants.js +2 -0
  101. package/ee/admin/content-manager/pages/ListSettingsView/constants.js +7 -0
  102. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/ReviewWorkflowsAssigneeEE.js +21 -0
  103. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +44 -17
  104. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
  105. package/ee/admin/hooks/useAuthProviders.js +25 -0
  106. package/ee/admin/hooks/{useLicenseLimitNotification/index.js → useLicenseLimitNotification.js} +2 -4
  107. package/ee/admin/hooks/{useLicenseLimits/useLicenseLimits.js → useLicenseLimits.js} +4 -1
  108. package/ee/admin/pages/AuthPage/components/Login/index.js +8 -4
  109. package/ee/admin/pages/AuthPage/components/Providers/index.js +8 -5
  110. package/ee/admin/pages/HomePage/index.js +1 -1
  111. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +1 -1
  112. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +1 -1
  113. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +1 -1
  114. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/CreateAction/index.js +1 -1
  115. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/index.js +1 -1
  116. package/ee/server/constants/workflows.js +1 -0
  117. package/ee/server/controllers/index.js +1 -0
  118. package/ee/server/controllers/workflows/assignees/index.js +44 -0
  119. package/ee/server/routes/review-workflows.js +17 -0
  120. package/ee/server/services/index.js +1 -0
  121. package/ee/server/services/review-workflows/assignees.js +54 -0
  122. package/ee/server/services/review-workflows/metrics/index.js +5 -0
  123. package/ee/server/services/review-workflows/review-workflows.js +20 -11
  124. package/ee/server/validation/review-workflows.js +8 -0
  125. package/index.js +2 -6
  126. package/package.json +9 -9
  127. package/scripts/build.js +15 -15
  128. package/scripts/create-dev-plugins-file.js +5 -38
  129. package/server/controllers/role.js +2 -0
  130. package/server/controllers/user.js +2 -0
  131. package/server/services/permission/permissions-manager/index.js +3 -1
  132. package/server/services/permission/permissions-manager/sanitize.js +19 -7
  133. package/server/services/permission/permissions-manager/validate.js +218 -0
  134. package/utils/create-cache-dir.js +62 -16
  135. package/utils/create-plugins-exclude-path.js +3 -23
  136. package/utils/get-plugins.js +110 -0
  137. package/utils/index.js +1 -1
  138. package/webpack.config.js +10 -13
  139. package/admin/src/content-manager/components/AttributeFilter/Filters.js +0 -58
  140. package/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js +0 -42
  141. package/admin/src/content-manager/components/AttributeFilter/index.js +0 -40
  142. package/admin/src/content-manager/components/Wysiwyg/EditorStylesContainer.js +0 -344
  143. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/api.js +0 -23
  144. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/prefixAllUrls.js +0 -17
  145. package/admin/src/pages/SettingsPage/utils/createSectionsRoutes.js +0 -11
  146. package/admin/src/pages/SettingsPage/utils/getSectionsToDisplay.js +0 -5
  147. package/admin/src/pages/SettingsPage/utils/index.js +0 -2
  148. package/build/2379.f1641312.chunk.js +0 -1
  149. package/build/2395.46f8d0c1.chunk.js +0 -26
  150. package/build/2801.5cef5ec8.chunk.js +0 -1
  151. package/build/3929.5632f24d.chunk.js +0 -114
  152. package/build/3984.dda474f7.chunk.js +0 -1
  153. package/build/4546.7a3c0d03.chunk.js +0 -1
  154. package/build/502.8ae8ef60.chunk.js +0 -1
  155. package/build/5483.6dd2e776.chunk.js +0 -6
  156. package/build/5542.2415a393.chunk.js +0 -63
  157. package/build/6158.c974fd83.chunk.js +0 -1
  158. package/build/7464.3e64a1d5.chunk.js +0 -1
  159. package/build/8276.10a3f883.chunk.js +0 -26
  160. package/build/Admin_settingsPage.97cb9d41.chunk.js +0 -111
  161. package/build/admin-app.91898385.chunk.js +0 -36
  162. package/build/content-manager.b40f79c0.chunk.js +0 -1099
  163. package/build/email-settings-page.d494d1eb.chunk.js +0 -11
  164. package/build/en-json.08c05fcf.chunk.js +0 -1
  165. package/build/main.9dbe4579.js +0 -2859
  166. package/build/review-workflows-settings-create-view.cb08cfa2.chunk.js +0 -1
  167. package/build/review-workflows-settings-edit-view.3c7cbe63.chunk.js +0 -1
  168. package/build/review-workflows-settings-list-view.1611dc1f.chunk.js +0 -56
  169. package/build/users-advanced-settings-page.f0760eb8.chunk.js +0 -9
  170. package/build/users-email-settings-page.ff4b32f3.chunk.js +0 -9
  171. package/build/users-providers-settings-page.48de0306.chunk.js +0 -14
  172. package/build/users-roles-settings-page.9d9a1eff.chunk.js +0 -30
  173. package/ee/admin/hooks/index.js +0 -4
  174. package/ee/admin/hooks/useAuthProviders/index.js +0 -50
  175. package/ee/admin/hooks/useAuthProviders/reducer.js +0 -26
  176. package/ee/admin/hooks/useLicenseLimits/index.js +0 -1
  177. package/scripts/create-plugins-file.js +0 -92
  178. package/utils/get-plugins-path.js +0 -41
  179. /package/ee/admin/hooks/{useLicenseLimits/__mocks__/index.js → __mocks__/useLicenseLimits.js} +0 -0
  180. /package/server/services/permission/permissions-manager/{query-builers.js → query-builders.js} +0 -0
@@ -0,0 +1,147 @@
1
+ import * as React from 'react';
2
+
3
+ import { Combobox, ComboboxOption, Field, Flex, Loader, Typography } from '@strapi/design-system';
4
+ import {
5
+ useCMEditViewDataManager,
6
+ useAPIErrorHandler,
7
+ useFetchClient,
8
+ useNotification,
9
+ useRBAC,
10
+ } from '@strapi/helper-plugin';
11
+ import { useIntl } from 'react-intl';
12
+ import { useMutation } from 'react-query';
13
+ import { useSelector } from 'react-redux';
14
+
15
+ import { getDisplayName } from '../../../../../../../../admin/src/content-manager/utils';
16
+ import { useAdminUsers } from '../../../../../../../../admin/src/hooks/useAdminUsers';
17
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
18
+ import { ASSIGNEE_ATTRIBUTE_NAME } from '../../constants';
19
+
20
+ export function AssigneeSelect() {
21
+ const {
22
+ initialData,
23
+ layout: { uid },
24
+ isSingleType,
25
+ onChange,
26
+ } = useCMEditViewDataManager();
27
+ const permissions = useSelector(selectAdminPermissions);
28
+ const { formatMessage } = useIntl();
29
+ const { formatAPIError } = useAPIErrorHandler();
30
+ const toggleNotification = useNotification();
31
+ const { put } = useFetchClient();
32
+ const {
33
+ allowedActions: { canReadUsers },
34
+ } = useRBAC({
35
+ readUsers: permissions.settings.users.read,
36
+ });
37
+ const { users, isLoading, isError } = useAdminUsers(
38
+ {},
39
+ {
40
+ enabled: canReadUsers,
41
+ }
42
+ );
43
+
44
+ const currentAssignee = initialData?.[ASSIGNEE_ATTRIBUTE_NAME] ?? null;
45
+
46
+ const handleChange = async ({ value: assigneeId }) => {
47
+ mutation.mutate({
48
+ entityId: initialData.id,
49
+ assigneeId: parseInt(assigneeId, 10),
50
+ uid,
51
+ });
52
+ };
53
+
54
+ const mutation = useMutation(
55
+ async ({ entityId, assigneeId, uid }) => {
56
+ const typeSlug = isSingleType ? 'single-types' : 'collection-types';
57
+
58
+ const {
59
+ data: { data: createdEntity },
60
+ } = await put(`/admin/content-manager/${typeSlug}/${uid}/${entityId}/assignee`, {
61
+ data: { id: assigneeId },
62
+ });
63
+
64
+ // initialData and modifiedData have to stay in sync, otherwise the entity would be flagged
65
+ // as modified, which is what the boolean flag is for
66
+ onChange(
67
+ {
68
+ target: { name: ASSIGNEE_ATTRIBUTE_NAME, value: createdEntity[ASSIGNEE_ATTRIBUTE_NAME] },
69
+ },
70
+ true
71
+ );
72
+
73
+ return createdEntity;
74
+ },
75
+ {
76
+ onSuccess() {
77
+ toggleNotification({
78
+ type: 'success',
79
+ message: {
80
+ id: 'content-manager.reviewWorkflows.assignee.notification.saved',
81
+ defaultMessage: 'Assignee updated',
82
+ },
83
+ });
84
+ },
85
+ }
86
+ );
87
+
88
+ return (
89
+ <Field name={ASSIGNEE_ATTRIBUTE_NAME} id={ASSIGNEE_ATTRIBUTE_NAME}>
90
+ <Flex direction="column" gap={2} alignItems="stretch">
91
+ <Combobox
92
+ clearLabel={formatMessage({
93
+ id: 'content-manager.reviewWorkflows.assignee.clear',
94
+ defaultMessage: 'Clear assignee',
95
+ })}
96
+ error={
97
+ (isError &&
98
+ canReadUsers &&
99
+ formatMessage({
100
+ id: 'content-manager.reviewWorkflows.assignee.error',
101
+ defaultMessage: 'An error occurred while fetching users',
102
+ })) ||
103
+ (mutation.error && formatAPIError(mutation.error))
104
+ }
105
+ disabled={!isLoading && users.length === 0}
106
+ name={ASSIGNEE_ATTRIBUTE_NAME}
107
+ id={ASSIGNEE_ATTRIBUTE_NAME}
108
+ value={currentAssignee ? currentAssignee.id : null}
109
+ onChange={(value) => handleChange({ value })}
110
+ onClear={() => handleChange({ value: null })}
111
+ placeholder={formatMessage({
112
+ id: 'content-manager.reviewWorkflows.assignee.placeholder',
113
+ defaultMessage: 'Select …',
114
+ })}
115
+ label={formatMessage({
116
+ id: 'content-manager.reviewWorkflows.assignee.label',
117
+ defaultMessage: 'Assignee',
118
+ })}
119
+ // eslint-disable-next-line react/no-unstable-nested-components
120
+ customizeContent={() => (
121
+ <Flex as="span" justifyContent="space-between" alignItems="center" width="100%">
122
+ <Typography textColor="neutral800" ellipsis>
123
+ {currentAssignee ? getDisplayName(currentAssignee, formatMessage) : null}
124
+ </Typography>
125
+
126
+ {isLoading || mutation.isLoading ? (
127
+ <Loader small style={{ display: 'flex' }} />
128
+ ) : null}
129
+ </Flex>
130
+ )}
131
+ >
132
+ {users.map((user) => {
133
+ return (
134
+ <ComboboxOption
135
+ key={user.id}
136
+ value={user.id}
137
+ textValue={getDisplayName(user, formatMessage)}
138
+ >
139
+ {getDisplayName(user, formatMessage)}
140
+ </ComboboxOption>
141
+ );
142
+ })}
143
+ </Combobox>
144
+ </Flex>
145
+ </Field>
146
+ );
147
+ }
@@ -0,0 +1,243 @@
1
+ import * as React from 'react';
2
+
3
+ import {
4
+ SingleSelect,
5
+ SingleSelectOption,
6
+ Field,
7
+ FieldError,
8
+ Flex,
9
+ Loader,
10
+ Typography,
11
+ } from '@strapi/design-system';
12
+ import {
13
+ useCMEditViewDataManager,
14
+ useAPIErrorHandler,
15
+ useFetchClient,
16
+ useNotification,
17
+ } from '@strapi/helper-plugin';
18
+ import { useIntl } from 'react-intl';
19
+ import { useMutation } from 'react-query';
20
+
21
+ import { useLicenseLimits } from '../../../../../../hooks/useLicenseLimits';
22
+ import * as LimitsModal from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal';
23
+ import {
24
+ CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME,
25
+ CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME,
26
+ } from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/constants';
27
+ import { useReviewWorkflows } from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows';
28
+ import { getStageColorByHex } from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
29
+ import { STAGE_ATTRIBUTE_NAME } from '../../constants';
30
+
31
+ export function StageSelect() {
32
+ const {
33
+ initialData,
34
+ layout: { uid },
35
+ isSingleType,
36
+ onChange,
37
+ } = useCMEditViewDataManager();
38
+ const { put } = useFetchClient();
39
+ const { formatMessage } = useIntl();
40
+ const { formatAPIError } = useAPIErrorHandler();
41
+ const toggleNotification = useNotification();
42
+ const {
43
+ meta,
44
+ workflows: [workflow],
45
+ isLoading,
46
+ } = useReviewWorkflows({ filters: { contentTypes: uid } });
47
+ const { getFeature } = useLicenseLimits();
48
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
49
+
50
+ const limits = getFeature('review-workflows');
51
+ // it is possible to rely on initialData here, because it always will
52
+ // be updated at the same time when modifiedData is updated, otherwise
53
+ // the entity is flagged as modified
54
+ const activeWorkflowStage = initialData?.[STAGE_ATTRIBUTE_NAME] ?? null;
55
+
56
+ const mutation = useMutation(
57
+ async ({ entityId, stageId, uid }) => {
58
+ const typeSlug = isSingleType ? 'single-types' : 'collection-types';
59
+
60
+ const {
61
+ data: { data: createdEntity },
62
+ } = await put(`/admin/content-manager/${typeSlug}/${uid}/${entityId}/stage`, {
63
+ data: { id: stageId },
64
+ });
65
+
66
+ // initialData and modifiedData have to stay in sync, otherwise the entity would be flagged
67
+ // as modified, which is what the boolean flag is for
68
+ onChange(
69
+ { target: { name: STAGE_ATTRIBUTE_NAME, value: createdEntity[STAGE_ATTRIBUTE_NAME] } },
70
+ true
71
+ );
72
+
73
+ return createdEntity;
74
+ },
75
+ {
76
+ onSuccess() {
77
+ toggleNotification({
78
+ type: 'success',
79
+ message: {
80
+ id: 'content-manager.reviewWorkflows.stage.notification.saved',
81
+ defaultMessage: 'Review stage updated',
82
+ },
83
+ });
84
+ },
85
+ }
86
+ );
87
+
88
+ const handleChange = async ({ value: stageId }) => {
89
+ try {
90
+ /**
91
+ * If the current license has a limit:
92
+ * check if the total count of workflows exceeds that limit and display
93
+ * the limits modal.
94
+ *
95
+ * If the current license does not have a limit (e.g. offline license):
96
+ * do nothing (for now).
97
+ *
98
+ */
99
+
100
+ if (
101
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
102
+ parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10) < meta.workflowCount
103
+ ) {
104
+ setShowLimitModal('workflow');
105
+
106
+ /**
107
+ * If the current license has a limit:
108
+ * check if the total count of stages exceeds that limit and display
109
+ * the limits modal.
110
+ *
111
+ * If the current license does not have a limit (e.g. offline license):
112
+ * do nothing (for now).
113
+ *
114
+ */
115
+ } else if (
116
+ limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
117
+ parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10) <
118
+ workflow.stages.length
119
+ ) {
120
+ setShowLimitModal('stage');
121
+ } else {
122
+ mutation.mutateAsync({
123
+ entityId: initialData.id,
124
+ stageId,
125
+ uid,
126
+ });
127
+ }
128
+ } catch (error) {
129
+ // react-query@v3: the error doesn't have to be handled here
130
+ // see: https://github.com/TanStack/query/issues/121
131
+ }
132
+ };
133
+
134
+ const { themeColorName } = activeWorkflowStage?.color
135
+ ? getStageColorByHex(activeWorkflowStage?.color)
136
+ : {};
137
+
138
+ return (
139
+ <>
140
+ <Field name={STAGE_ATTRIBUTE_NAME} id={STAGE_ATTRIBUTE_NAME}>
141
+ <Flex direction="column" gap={2} alignItems="stretch">
142
+ <SingleSelect
143
+ error={(mutation.error && formatAPIError(mutation.error)) || null}
144
+ name={STAGE_ATTRIBUTE_NAME}
145
+ id={STAGE_ATTRIBUTE_NAME}
146
+ value={activeWorkflowStage?.id}
147
+ onChange={(value) => handleChange({ value })}
148
+ label={formatMessage({
149
+ id: 'content-manager.reviewWorkflows.stage.label',
150
+ defaultMessage: 'Review stage',
151
+ })}
152
+ startIcon={
153
+ <Flex
154
+ as="span"
155
+ height={2}
156
+ background={activeWorkflowStage?.color}
157
+ borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
158
+ hasRadius
159
+ shrink={0}
160
+ width={2}
161
+ marginRight="-3px"
162
+ />
163
+ }
164
+ // eslint-disable-next-line react/no-unstable-nested-components
165
+ customizeContent={() => (
166
+ <Flex as="span" justifyContent="space-between" alignItems="center" width="100%">
167
+ <Typography textColor="neutral800" ellipsis>
168
+ {activeWorkflowStage?.name}
169
+ </Typography>
170
+ {isLoading ? <Loader small style={{ display: 'flex' }} /> : null}
171
+ </Flex>
172
+ )}
173
+ >
174
+ {workflow
175
+ ? workflow.stages.map(({ id, color, name }) => {
176
+ const { themeColorName } = getStageColorByHex(color);
177
+
178
+ return (
179
+ <SingleSelectOption
180
+ key={id}
181
+ startIcon={
182
+ <Flex
183
+ height={2}
184
+ background={color}
185
+ borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
186
+ hasRadius
187
+ shrink={0}
188
+ width={2}
189
+ />
190
+ }
191
+ value={id}
192
+ textValue={name}
193
+ >
194
+ {name}
195
+ </SingleSelectOption>
196
+ );
197
+ })
198
+ : []}
199
+ </SingleSelect>
200
+ <FieldError />
201
+ </Flex>
202
+ </Field>
203
+
204
+ <LimitsModal.Root
205
+ isOpen={showLimitModal === 'workflow'}
206
+ onClose={() => setShowLimitModal(false)}
207
+ >
208
+ <LimitsModal.Title>
209
+ {formatMessage({
210
+ id: 'content-manager.reviewWorkflows.workflows.limit.title',
211
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
212
+ })}
213
+ </LimitsModal.Title>
214
+
215
+ <LimitsModal.Body>
216
+ {formatMessage({
217
+ id: 'content-manager.reviewWorkflows.workflows.limit.body',
218
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
219
+ })}
220
+ </LimitsModal.Body>
221
+ </LimitsModal.Root>
222
+
223
+ <LimitsModal.Root
224
+ isOpen={showLimitModal === 'stage'}
225
+ onClose={() => setShowLimitModal(false)}
226
+ >
227
+ <LimitsModal.Title>
228
+ {formatMessage({
229
+ id: 'content-manager.reviewWorkflows.stages.limit.title',
230
+ defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
231
+ })}
232
+ </LimitsModal.Title>
233
+
234
+ <LimitsModal.Body>
235
+ {formatMessage({
236
+ id: 'content-manager.reviewWorkflows.stages.limit.body',
237
+ defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
238
+ })}
239
+ </LimitsModal.Body>
240
+ </LimitsModal.Root>
241
+ </>
242
+ );
243
+ }
@@ -0,0 +1,2 @@
1
+ export const STAGE_ATTRIBUTE_NAME = 'strapi_stage';
2
+ export const ASSIGNEE_ATTRIBUTE_NAME = 'strapi_assignee';
@@ -0,0 +1,7 @@
1
+ export const REVIEW_WORKFLOW_STAGE_SORT_OPTION_NAME = {
2
+ value: 'strapi_stage[name]',
3
+ label: {
4
+ id: 'settings.defaultSortOrder.reviewWorkflows.label',
5
+ defaultMessage: 'Review Stage',
6
+ },
7
+ };
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+
3
+ import { Typography } from '@strapi/design-system';
4
+ import PropTypes from 'prop-types';
5
+ import { useIntl } from 'react-intl';
6
+
7
+ import { getDisplayName } from '../../../../../../admin/src/content-manager/utils';
8
+
9
+ export function ReviewWorkflowsAssigneeEE({ user }) {
10
+ const { formatMessage } = useIntl();
11
+
12
+ return <Typography textColor="neutral800">{getDisplayName(user, formatMessage)}</Typography>;
13
+ }
14
+
15
+ ReviewWorkflowsAssigneeEE.propTypes = {
16
+ user: PropTypes.shape({
17
+ firstname: PropTypes.string,
18
+ lastname: PropTypes.string,
19
+ username: PropTypes.string,
20
+ }).isRequired,
21
+ };
@@ -1,24 +1,51 @@
1
1
  import getTrad from '../../../../../../admin/src/content-manager/utils/getTrad';
2
+ import {
3
+ ASSIGNEE_ATTRIBUTE_NAME,
4
+ STAGE_ATTRIBUTE_NAME,
5
+ } from '../../EditView/InformationBox/constants';
2
6
 
3
- export const REVIEW_WORKFLOW_COLUMNS_EE = {
4
- key: '__strapi_stage_temp_key__',
5
- name: 'strapi_stage',
6
- fieldSchema: {
7
- type: 'relation',
7
+ export const REVIEW_WORKFLOW_COLUMNS_EE = [
8
+ {
9
+ key: `__${STAGE_ATTRIBUTE_NAME}_temp_key__`,
10
+ name: STAGE_ATTRIBUTE_NAME,
11
+ fieldSchema: {
12
+ type: 'relation',
13
+ },
14
+ metadatas: {
15
+ // formatMessage() will be applied when the column is rendered
16
+ label: {
17
+ id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.stage`),
18
+ defaultMessage: 'Review stage',
19
+ },
20
+ searchable: false,
21
+ sortable: true,
22
+ mainField: {
23
+ name: 'name',
24
+ schema: {
25
+ type: 'string',
26
+ },
27
+ },
28
+ },
8
29
  },
9
- metadatas: {
10
- // formatMessage() will be applied when the column is rendered
11
- label: {
12
- id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.stage`),
13
- defaultMessage: 'Review stage',
30
+ {
31
+ key: `__${ASSIGNEE_ATTRIBUTE_NAME}_temp_key__`,
32
+ name: ASSIGNEE_ATTRIBUTE_NAME,
33
+ fieldSchema: {
34
+ type: 'relation',
14
35
  },
15
- searchable: false,
16
- sortable: true,
17
- mainField: {
18
- name: 'name',
19
- schema: {
20
- type: 'string',
36
+ metadatas: {
37
+ label: {
38
+ id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.assignee`),
39
+ defaultMessage: 'Assignee',
40
+ },
41
+ searchable: false,
42
+ sortable: true,
43
+ mainField: {
44
+ name: 'firstname',
45
+ schema: {
46
+ type: 'string',
47
+ },
21
48
  },
22
49
  },
23
50
  },
24
- };
51
+ ];
@@ -1 +1,2 @@
1
1
  export * from './ReviewWorkflowsStageEE';
2
+ export * from './ReviewWorkflowsAssigneeEE';
@@ -0,0 +1,25 @@
1
+ import * as React from 'react';
2
+
3
+ import { useFetchClient } from '@strapi/helper-plugin';
4
+ import { useQuery } from 'react-query';
5
+
6
+ export const useAuthProviders = (queryOptions = {}) => {
7
+ const { get } = useFetchClient();
8
+
9
+ const { data, isLoading } = useQuery(
10
+ ['ee', 'providers'],
11
+ async () => {
12
+ const { data } = await get('/admin/providers');
13
+
14
+ return data;
15
+ },
16
+ queryOptions
17
+ );
18
+
19
+ // the return value needs to be memoized, because a fresh
20
+ // instantiated array would cause an infinite rendering loop
21
+ // when used as an effect dependency
22
+ const providers = React.useMemo(() => data ?? [], [data]);
23
+
24
+ return { providers, isLoading };
25
+ };
@@ -10,14 +10,14 @@ import isNil from 'lodash/isNil';
10
10
  import { useIntl } from 'react-intl';
11
11
  import { useLocation } from 'react-router-dom';
12
12
 
13
- import { useLicenseLimits } from '../useLicenseLimits';
13
+ import { useLicenseLimits } from './useLicenseLimits';
14
14
 
15
15
  const STORAGE_KEY_PREFIX = 'strapi-notification-seat-limit';
16
16
 
17
17
  const BILLING_STRAPI_CLOUD_URL = 'https://cloud.strapi.io/profile/billing';
18
18
  const BILLING_SELF_HOSTED_URL = 'https://strapi.io/billing/request-seats';
19
19
 
20
- const useLicenseLimitNotification = () => {
20
+ export const useLicenseLimitNotification = () => {
21
21
  const { formatMessage } = useIntl();
22
22
  let { license, isError, isLoading } = useLicenseLimits();
23
23
  const toggleNotification = useNotification();
@@ -97,5 +97,3 @@ const useLicenseLimitNotification = () => {
97
97
  isError,
98
98
  ]);
99
99
  };
100
-
101
- export default useLicenseLimitNotification;
@@ -19,7 +19,10 @@ export function useLicenseLimits({ enabled } = { enabled: true }) {
19
19
  }
20
20
  );
21
21
 
22
- const license = data ?? {};
22
+ // this needs memoization, because a default value of `{}`
23
+ // would lead to infinite rendering loops, when used as
24
+ // effect dependency
25
+ const license = React.useMemo(() => data ?? {}, [data]);
23
26
 
24
27
  const getFeature = React.useCallback(
25
28
  (name) => {
@@ -7,7 +7,7 @@ import styled from 'styled-components';
7
7
 
8
8
  import UnauthenticatedLayout from '../../../../../../admin/src/layouts/UnauthenticatedLayout';
9
9
  import BaseLogin from '../../../../../../admin/src/pages/AuthPage/components/Login/BaseLogin';
10
- import { useAuthProviders } from '../../../../hooks';
10
+ import { useAuthProviders } from '../../../../hooks/useAuthProviders';
11
11
  import SSOProviders from '../Providers/SSOProviders';
12
12
 
13
13
  const DividerFull = styled(Divider)`
@@ -15,11 +15,15 @@ const DividerFull = styled(Divider)`
15
15
  `;
16
16
 
17
17
  export const LoginEE = (loginProps) => {
18
- const ssoEnabled = window.strapi.features.isEnabled(window.strapi.features.SSO);
19
- const { isLoading, data: providers } = useAuthProviders({ ssoEnabled });
20
18
  const { formatMessage } = useIntl();
19
+ const { isLoading, providers } = useAuthProviders({
20
+ enabled: window.strapi.features.isEnabled(window.strapi.features.SSO),
21
+ });
21
22
 
22
- if (!ssoEnabled || (!isLoading && providers.length === 0)) {
23
+ if (
24
+ !window.strapi.features.isEnabled(window.strapi.features.SSO) ||
25
+ (!isLoading && providers.length === 0)
26
+ ) {
23
27
  return (
24
28
  <UnauthenticatedLayout>
25
29
  <BaseLogin {...loginProps} />
@@ -11,7 +11,7 @@ import UnauthenticatedLayout, {
11
11
  Column,
12
12
  LayoutContent,
13
13
  } from '../../../../../../admin/src/layouts/UnauthenticatedLayout';
14
- import { useAuthProviders } from '../../../../hooks';
14
+ import { useAuthProviders } from '../../../../hooks/useAuthProviders';
15
15
 
16
16
  import SSOProviders from './SSOProviders';
17
17
 
@@ -20,17 +20,20 @@ const DividerFull = styled(Divider)`
20
20
  `;
21
21
 
22
22
  const Providers = () => {
23
- const ssoEnabled = window.strapi.features.isEnabled(window.strapi.features.SSO);
24
-
25
23
  const { push } = useHistory();
26
24
  const { formatMessage } = useIntl();
27
- const { isLoading, data: providers } = useAuthProviders({ ssoEnabled });
25
+ const { isLoading, providers } = useAuthProviders({
26
+ enabled: window.strapi.features.isEnabled(window.strapi.features.SSO),
27
+ });
28
28
 
29
29
  const handleClick = () => {
30
30
  push('/auth/login');
31
31
  };
32
32
 
33
- if (!ssoEnabled || (!isLoading && providers.length === 0)) {
33
+ if (
34
+ !window.strapi.features.isEnabled(window.strapi.features.SSO) ||
35
+ (!isLoading && providers.length === 0)
36
+ ) {
34
37
  return <Redirect to="/auth/login" />;
35
38
  }
36
39
 
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  // eslint-disable-next-line import/no-cycle
4
4
  import { HomePageCE } from '../../../../admin/src/pages/HomePage';
5
- import { useLicenseLimitNotification } from '../../hooks';
5
+ import { useLicenseLimitNotification } from '../../hooks/useLicenseLimitNotification';
6
6
 
7
7
  export function HomePageEE() {
8
8
  useLicenseLimitNotification();
@@ -17,7 +17,7 @@ import { useHistory } from 'react-router-dom';
17
17
 
18
18
  import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
19
19
  import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
20
- import { useLicenseLimits } from '../../../../../../hooks';
20
+ import { useLicenseLimits } from '../../../../../../hooks/useLicenseLimits';
21
21
  import { addStage, resetWorkflow } from '../../actions';
22
22
  import * as Layout from '../../components/Layout';
23
23
  import * as LimitsModal from '../../components/LimitsModal';
@@ -19,7 +19,7 @@ import { useParams } from 'react-router-dom';
19
19
  import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
20
20
  import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
21
21
  import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
22
- import { useLicenseLimits } from '../../../../../../hooks';
22
+ import { useLicenseLimits } from '../../../../../../hooks/useLicenseLimits';
23
23
  import { resetWorkflow, setWorkflow } from '../../actions';
24
24
  import * as Layout from '../../components/Layout';
25
25
  import * as LimitsModal from '../../components/LimitsModal';
@@ -35,7 +35,7 @@ import styled from 'styled-components';
35
35
 
36
36
  import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
37
37
  import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
38
- import { useLicenseLimits } from '../../../../../../hooks';
38
+ import { useLicenseLimits } from '../../../../../../hooks/useLicenseLimits';
39
39
  import * as Layout from '../../components/Layout';
40
40
  import * as LimitsModal from '../../components/LimitsModal';
41
41
  import { CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME } from '../../constants';