@strapi/admin 4.11.4 → 4.12.0-beta.0

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 (130) hide show
  1. package/admin/src/constants.js +83 -83
  2. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +8 -5
  3. package/admin/src/content-manager/components/Inputs/index.js +3 -47
  4. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +34 -37
  5. package/admin/src/content-manager/pages/EditSettingsView/components/ModalForm.js +0 -27
  6. package/admin/src/content-manager/pages/ListView/components/TableRows/index.js +93 -14
  7. package/admin/src/content-manager/pages/ListView/index.js +65 -59
  8. package/admin/src/content-manager/pages/ListView/utils/buildValidGetParams.js +30 -0
  9. package/admin/src/content-manager/pages/ListView/utils/index.js +1 -1
  10. package/admin/src/content-manager/utils/mergeMetasWithSchema.js +5 -1
  11. package/admin/src/hooks/index.js +0 -1
  12. package/admin/src/hooks/useAdminUsers/useAdminUsers.js +3 -3
  13. package/admin/src/hooks/useEnterprise/useEnterprise.js +4 -4
  14. package/admin/src/pages/App/index.js +28 -23
  15. package/admin/src/pages/AuthPage/components/Register/index.js +5 -1
  16. package/admin/src/pages/ProfilePage/index.js +6 -1
  17. package/admin/src/pages/SettingsPage/components/Tokens/TokenBox/index.js +1 -1
  18. package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +1 -1
  19. package/admin/src/translations/zh-Hans.json +1 -1
  20. package/build/0cd5f8915b265d5b1856.png +0 -0
  21. package/build/2799.cf9b491f.chunk.js +1 -0
  22. package/build/4485.d3c6dd1d.chunk.js +6 -0
  23. package/build/539.865446c0.chunk.js +1 -0
  24. package/build/{5563.86f9aa9c.chunk.js → 5563.a146acac.chunk.js} +2 -2
  25. package/build/7018.f3dad3c1.chunk.js +1 -0
  26. package/build/7259.0e25ab5d.chunk.js +1 -0
  27. package/build/9465.d8fc1377.chunk.js +112 -0
  28. package/build/9944.29289a16.chunk.js +26 -0
  29. package/build/{Admin-authenticatedApp.cb649fc1.chunk.js → Admin-authenticatedApp.9d3afb79.chunk.js} +2 -2
  30. package/build/{Admin_settingsPage.4069bb8a.chunk.js → Admin_settingsPage.074655f6.chunk.js} +13 -13
  31. package/build/admin-app.3ede71ad.chunk.js +61 -0
  32. package/build/admin-edit-users.78552758.chunk.js +10 -0
  33. package/build/admin-users.c23322fc.chunk.js +11 -0
  34. package/build/audit-logs-settings-page.37fe915c.chunk.js +1 -0
  35. package/build/content-manager.08541eeb.chunk.js +1094 -0
  36. package/build/content-type-builder-translation-en-json.38e20391.chunk.js +1 -0
  37. package/build/content-type-builder.de22f7c9.chunk.js +166 -0
  38. package/build/index.html +1 -1
  39. package/build/main.a8ede50d.js +2927 -0
  40. package/build/review-workflows-settings-create-view.56f61e18.chunk.js +1 -0
  41. package/build/review-workflows-settings-edit-view.912bc9c0.chunk.js +1 -0
  42. package/build/review-workflows-settings-list-view.cf6a08d3.chunk.js +56 -0
  43. package/build/runtime~main.5e9bf4b3.js +2 -0
  44. package/build/{users-roles-settings-page.1f505119.chunk.js → users-roles-settings-page.d286426a.chunk.js} +1 -1
  45. package/build/{zh-Hans-json.4cfef87d.chunk.js → zh-Hans-json.fada6f40.chunk.js} +1 -1
  46. package/ee/admin/constants.js +14 -14
  47. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +84 -30
  48. package/ee/admin/content-manager/{components/DynamicTable/CellContent/ReviewWorkflowsStage → pages/ListView/ReviewWorkflowsColumn}/ReviewWorkflowsStageEE.js +7 -2
  49. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +24 -0
  50. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
  51. package/ee/admin/hooks/useLicenseLimitNotification/index.js +17 -6
  52. package/ee/admin/hooks/useLicenseLimits/index.js +1 -32
  53. package/ee/admin/hooks/useLicenseLimits/useLicenseLimits.js +44 -0
  54. package/ee/admin/pages/SettingsPage/constants.js +25 -1
  55. package/ee/admin/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +6 -4
  56. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js +6 -4
  57. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +19 -4
  58. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Layout/Layout.js +65 -0
  59. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Layout/index.js +1 -0
  60. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/LimitsModal.js +111 -0
  61. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/assets/balloon.png +0 -0
  62. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/index.js +3 -0
  63. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/ProtectedPage.js +21 -0
  64. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/index.js +1 -0
  65. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +4 -4
  66. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +110 -0
  67. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/index.js +1 -0
  68. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +3 -1
  69. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +13 -19
  70. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +246 -0
  71. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/index.js +13 -0
  72. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +269 -0
  73. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/index.js +13 -0
  74. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +382 -0
  75. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/index.js +13 -0
  76. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +53 -23
  77. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +43 -28
  78. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/CreateAction/index.js +9 -2
  79. package/ee/server/config/admin-actions.js +24 -0
  80. package/ee/server/constants/default-stages.json +8 -4
  81. package/ee/server/constants/default-workflow.json +3 -1
  82. package/ee/server/constants/workflows.js +10 -1
  83. package/ee/server/content-types/workflow/index.js +10 -0
  84. package/ee/server/content-types/workflow-stage/index.js +3 -1
  85. package/ee/server/controllers/admin.js +1 -0
  86. package/ee/server/controllers/workflows/index.js +135 -8
  87. package/ee/server/controllers/workflows/stages/index.js +38 -38
  88. package/ee/server/migrations/review-workflows-content-types.js +29 -0
  89. package/ee/server/migrations/review-workflows-deleted-ct-in-workflows.js +39 -0
  90. package/ee/server/migrations/review-workflows-stage-attribute.js +49 -0
  91. package/ee/server/migrations/review-workflows-stages-color.js +2 -2
  92. package/ee/server/migrations/review-workflows-workflow-name.js +21 -0
  93. package/ee/server/register.js +12 -2
  94. package/ee/server/routes/review-workflows.js +44 -10
  95. package/ee/server/services/index.js +1 -0
  96. package/ee/server/services/review-workflows/entity-service-decorator.js +8 -13
  97. package/ee/server/services/review-workflows/review-workflows.js +45 -53
  98. package/ee/server/services/review-workflows/stages.js +84 -46
  99. package/ee/server/services/review-workflows/validation.js +60 -0
  100. package/ee/server/services/review-workflows/workflows/content-types.js +80 -0
  101. package/ee/server/services/review-workflows/workflows/index.js +207 -0
  102. package/ee/server/utils/review-workflows.js +30 -25
  103. package/ee/server/validation/review-workflows.js +49 -10
  104. package/package.json +10 -11
  105. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +0 -2
  106. package/admin/src/content-manager/pages/ListView/utils/buildQueryString.js +0 -36
  107. package/admin/src/content-manager/pages/ListView/utils/createPluginsFilter.js +0 -4
  108. package/admin/src/hooks/useLicenseLimits/index.js +0 -3
  109. package/admin/src/pages/App/utils/index.js +0 -3
  110. package/admin/src/pages/App/utils/unique-identifier.js +0 -12
  111. package/build/1799.44d2e264.chunk.js +0 -33
  112. package/build/5932.6a23b88c.chunk.js +0 -1
  113. package/build/7018.98feed67.chunk.js +0 -1
  114. package/build/7259.fb69d4bf.chunk.js +0 -1
  115. package/build/admin-app.fea867af.chunk.js +0 -61
  116. package/build/admin-edit-users.200551e3.chunk.js +0 -10
  117. package/build/admin-users.3b12dca2.chunk.js +0 -11
  118. package/build/audit-logs-settings-page.f538490f.chunk.js +0 -1
  119. package/build/content-manager.c40f5ff9.chunk.js +0 -1088
  120. package/build/content-type-builder-translation-en-json.f592325b.chunk.js +0 -1
  121. package/build/content-type-builder.bd1bbff1.chunk.js +0 -166
  122. package/build/main.ee36abd9.js +0 -2927
  123. package/build/review-workflows-settings.93808ae0.chunk.js +0 -110
  124. package/build/runtime~main.efd966f6.js +0 -2
  125. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +0 -58
  126. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +0 -3
  127. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ProtectedPage.js +0 -20
  128. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +0 -204
  129. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/index.js +0 -3
  130. package/ee/server/services/review-workflows/workflows.js +0 -25
@@ -11,16 +11,18 @@ import { useIntl } from 'react-intl';
11
11
  import { useMutation } from 'react-query';
12
12
 
13
13
  import Information from '../../../../../../admin/src/content-manager/pages/EditView/Information';
14
+ import useLicenseLimits from '../../../../hooks/useLicenseLimits';
15
+ import * as LimitsModal from '../../../../pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal';
14
16
  import { useReviewWorkflows } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows';
15
17
  import { getStageColorByHex } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
16
18
 
17
- const ATTRIBUTE_NAME = 'strapi_reviewWorkflows_stage';
19
+ const ATTRIBUTE_NAME = 'strapi_stage';
18
20
 
19
21
  export function InformationBoxEE() {
20
22
  const {
21
23
  initialData,
22
24
  isCreatingEntry,
23
- layout: { uid },
25
+ layout: { uid, options },
24
26
  isSingleType,
25
27
  onChange,
26
28
  } = useCMEditViewDataManager();
@@ -29,17 +31,18 @@ export function InformationBoxEE() {
29
31
  // be updated at the same time when modifiedData is updated, otherwise
30
32
  // the entity is flagged as modified
31
33
  const activeWorkflowStage = initialData?.[ATTRIBUTE_NAME] ?? null;
32
- const hasReviewWorkflowsEnabled = Object.prototype.hasOwnProperty.call(
33
- initialData,
34
- ATTRIBUTE_NAME
35
- );
34
+ const hasReviewWorkflowsEnabled = options?.reviewWorkflows ?? false;
36
35
  const { formatMessage } = useIntl();
37
36
  const { formatAPIError } = useAPIErrorHandler();
38
37
  const toggleNotification = useNotification();
38
+ const { getFeature } = useLicenseLimits();
39
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
39
40
 
40
- const { workflows, isLoading: workflowIsLoading } = useReviewWorkflows();
41
- // TODO: this works only as long as we support one workflow
42
- const workflow = workflows?.[0] ?? null;
41
+ const {
42
+ meta,
43
+ workflows: [workflow],
44
+ isLoading: isWorkflowLoading,
45
+ } = useReviewWorkflows({ filters: { contentTypes: uid } });
43
46
 
44
47
  const { error, isLoading, mutateAsync } = useMutation(
45
48
  async ({ entityId, stageId, uid }) => {
@@ -70,29 +73,42 @@ export function InformationBoxEE() {
70
73
  }
71
74
  );
72
75
 
73
- // if entities are created e.g. through lifecycle methods
74
- // they may not have a stage assigned. Updating the entity won't
75
- // set the default stage either which may lead to entities that
76
- // do not have a stage assigned for a while. By displaying an
77
- // error by default we are trying to nudge users into assigning a stage.
78
- const initialStageNullError =
79
- activeWorkflowStage === null &&
80
- !workflowIsLoading &&
81
- !isCreatingEntry &&
82
- formatMessage({
83
- id: 'content-manager.reviewWorkflows.stage.select.placeholder',
84
- defaultMessage: 'Select a stage',
85
- });
86
- const formattedMutationError = error && formatAPIError(error);
87
- const formattedError = formattedMutationError || initialStageNullError || null;
76
+ const limits = getFeature('review-workflows');
77
+ const formattedError = (error && formatAPIError(error)) || null;
88
78
 
89
79
  const handleStageChange = async ({ value: stageId }) => {
90
80
  try {
91
- await mutateAsync({
92
- entityId: initialData.id,
93
- stageId,
94
- uid,
95
- });
81
+ /**
82
+ * If the current license has a limit:
83
+ * check if the total count of workflows exceeds that limit and display
84
+ * the limits modal.
85
+ *
86
+ * If the current license does not have a limit (e.g. offline license):
87
+ * do nothing (for now).
88
+ *
89
+ */
90
+
91
+ if (limits?.workflows && limits.workflows > meta.workflowCount) {
92
+ setShowLimitModal('workflow');
93
+
94
+ /**
95
+ * If the current license has a limit:
96
+ * check if the total count of stages exceeds that limit and display
97
+ * the limits modal.
98
+ *
99
+ * If the current license does not have a limit (e.g. offline license):
100
+ * do nothing (for now).
101
+ *
102
+ */
103
+ } else if (limits?.stagesPerWorkflow && limits.stagesPerWorkflow > workflow.stages.length) {
104
+ setShowLimitModal('stage');
105
+ } else {
106
+ await mutateAsync({
107
+ entityId: initialData.id,
108
+ stageId,
109
+ uid,
110
+ });
111
+ }
96
112
  } catch (error) {
97
113
  // react-query@v3: the error doesn't have to be handled here
98
114
  // see: https://github.com/TanStack/query/issues/121
@@ -136,7 +152,7 @@ export function InformationBoxEE() {
136
152
  <Typography textColor="neutral800" ellipsis>
137
153
  {activeWorkflowStage?.name}
138
154
  </Typography>
139
- {isLoading ? <Loader small style={{ display: 'flex' }} /> : null}
155
+ {isWorkflowLoading || isLoading ? <Loader small style={{ display: 'flex' }} /> : null}
140
156
  </Flex>
141
157
  )}
142
158
  >
@@ -168,6 +184,44 @@ export function InformationBoxEE() {
168
184
  )}
169
185
 
170
186
  <Information.Body />
187
+
188
+ <LimitsModal.Root
189
+ isOpen={showLimitModal === 'workflow'}
190
+ onClose={() => setShowLimitModal(false)}
191
+ >
192
+ <LimitsModal.Title>
193
+ {formatMessage({
194
+ id: 'content-manager.reviewWorkflows.workflows.limit.title',
195
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
196
+ })}
197
+ </LimitsModal.Title>
198
+
199
+ <LimitsModal.Body>
200
+ {formatMessage({
201
+ id: 'content-manager.reviewWorkflows.workflows.limit.body',
202
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
203
+ })}
204
+ </LimitsModal.Body>
205
+ </LimitsModal.Root>
206
+
207
+ <LimitsModal.Root
208
+ isOpen={showLimitModal === 'stage'}
209
+ onClose={() => setShowLimitModal(false)}
210
+ >
211
+ <LimitsModal.Title>
212
+ {formatMessage({
213
+ id: 'content-manager.reviewWorkflows.stages.limit.title',
214
+ defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
215
+ })}
216
+ </LimitsModal.Title>
217
+
218
+ <LimitsModal.Body>
219
+ {formatMessage({
220
+ id: 'content-manager.reviewWorkflows.stages.limit.body',
221
+ defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
222
+ })}
223
+ </LimitsModal.Body>
224
+ </LimitsModal.Root>
171
225
  </Information.Root>
172
226
  );
173
227
  }
@@ -4,7 +4,8 @@ import { Box, Flex, Typography } from '@strapi/design-system';
4
4
  import { pxToRem } from '@strapi/helper-plugin';
5
5
  import PropTypes from 'prop-types';
6
6
 
7
- import { getStageColorByHex } from '../../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
7
+ import { STAGE_COLOR_DEFAULT } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/constants';
8
+ import { getStageColorByHex } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
8
9
 
9
10
  export function ReviewWorkflowsStageEE({ color, name }) {
10
11
  const { themeColorName } = getStageColorByHex(color);
@@ -27,7 +28,11 @@ export function ReviewWorkflowsStageEE({ color, name }) {
27
28
  );
28
29
  }
29
30
 
31
+ ReviewWorkflowsStageEE.defaultProps = {
32
+ color: STAGE_COLOR_DEFAULT,
33
+ };
34
+
30
35
  ReviewWorkflowsStageEE.propTypes = {
31
- color: PropTypes.string.isRequired,
36
+ color: PropTypes.string,
32
37
  name: PropTypes.string.isRequired,
33
38
  };
@@ -0,0 +1,24 @@
1
+ import getTrad from '../../../../../../admin/src/content-manager/utils/getTrad';
2
+
3
+ export const REVIEW_WORKFLOW_COLUMNS_EE = {
4
+ key: '__strapi_reviewWorkflows_stage_temp_key__',
5
+ name: 'strapi_reviewWorkflows_stage',
6
+ fieldSchema: {
7
+ type: 'relation',
8
+ },
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',
14
+ },
15
+ searchable: false,
16
+ sortable: true,
17
+ mainField: {
18
+ name: 'name',
19
+ schema: {
20
+ type: 'string',
21
+ },
22
+ },
23
+ },
24
+ };
@@ -0,0 +1 @@
1
+ export * from './ReviewWorkflowsStageEE';
@@ -19,18 +19,18 @@ const BILLING_SELF_HOSTED_URL = 'https://strapi.io/billing/request-seats';
19
19
 
20
20
  const useLicenseLimitNotification = () => {
21
21
  const { formatMessage } = useIntl();
22
- let { license } = useLicenseLimits();
22
+ let { license, isError, isLoading } = useLicenseLimits();
23
23
  const toggleNotification = useNotification();
24
24
  const { pathname } = useLocation();
25
25
 
26
+ const { enforcementUserCount, permittedSeats, licenseLimitStatus, isHostedOnStrapiCloud } =
27
+ license;
28
+
26
29
  useEffect(() => {
27
- if (!license?.data) {
30
+ if (isError || isLoading) {
28
31
  return;
29
32
  }
30
33
 
31
- const { enforcementUserCount, permittedSeats, licenseLimitStatus, isHostedOnStrapiCloud } =
32
- license?.data ?? {};
33
-
34
34
  const shouldDisplayNotification =
35
35
  !isNil(permittedSeats) &&
36
36
  !window.sessionStorage.getItem(`${STORAGE_KEY_PREFIX}-${pathname}`) &&
@@ -84,7 +84,18 @@ const useLicenseLimitNotification = () => {
84
84
  },
85
85
  });
86
86
  }
87
- }, [toggleNotification, license.data, pathname, formatMessage]);
87
+ }, [
88
+ toggleNotification,
89
+ license,
90
+ pathname,
91
+ formatMessage,
92
+ isLoading,
93
+ permittedSeats,
94
+ licenseLimitStatus,
95
+ enforcementUserCount,
96
+ isHostedOnStrapiCloud,
97
+ isError,
98
+ ]);
88
99
  };
89
100
 
90
101
  export default useLicenseLimitNotification;
@@ -1,34 +1,3 @@
1
- import { useFetchClient, useRBAC } from '@strapi/helper-plugin';
2
- import { useQuery } from 'react-query';
3
- import { useSelector } from 'react-redux';
4
-
5
- import { selectAdminPermissions } from '../../../../admin/src/pages/App/selectors';
6
-
7
- const useLicenseLimits = () => {
8
- const permissions = useSelector(selectAdminPermissions);
9
- const rbac = useRBAC(permissions.settings.users);
10
-
11
- const {
12
- isLoading: isRBACLoading,
13
- allowedActions: { canRead, canCreate, canUpdate, canDelete },
14
- } = rbac;
15
-
16
- const isRBACAllowed = canRead && canCreate && canUpdate && canDelete;
17
-
18
- const { get } = useFetchClient();
19
- const fetchLicenseLimitInfo = async () => {
20
- const {
21
- data: { data },
22
- } = await get('/admin/license-limit-information');
23
-
24
- return data;
25
- };
26
-
27
- const license = useQuery(['ee', 'license-limit-info'], fetchLicenseLimitInfo, {
28
- enabled: !isRBACLoading && isRBACAllowed,
29
- });
30
-
31
- return { license };
32
- };
1
+ import { useLicenseLimits } from './useLicenseLimits';
33
2
 
34
3
  export default useLicenseLimits;
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+
3
+ import { useFetchClient, useRBAC } from '@strapi/helper-plugin';
4
+ import { useQuery } from 'react-query';
5
+ import { useSelector } from 'react-redux';
6
+
7
+ import { selectAdminPermissions } from '../../../../admin/src/pages/App/selectors';
8
+
9
+ export function useLicenseLimits() {
10
+ const permissions = useSelector(selectAdminPermissions);
11
+ const { get } = useFetchClient();
12
+ const {
13
+ isLoading: isRBACLoading,
14
+ allowedActions: { canRead, canCreate, canUpdate, canDelete },
15
+ } = useRBAC(permissions.settings.users);
16
+ const hasPermissions = canRead && canCreate && canUpdate && canDelete;
17
+
18
+ const { data, isError, isLoading } = useQuery(
19
+ ['ee', 'license-limit-info'],
20
+ async () => {
21
+ const {
22
+ data: { data },
23
+ } = await get('/admin/license-limit-information');
24
+
25
+ return data;
26
+ },
27
+ {
28
+ enabled: !isRBACLoading && hasPermissions,
29
+ }
30
+ );
31
+
32
+ const license = data ?? {};
33
+
34
+ const getFeature = React.useCallback(
35
+ (name) => {
36
+ const feature = (license?.features ?? []).find((feature) => feature.name === name);
37
+
38
+ return feature?.options ?? {};
39
+ },
40
+ [license?.features]
41
+ );
42
+
43
+ return { license, getFeature, isError, isLoading };
44
+ }
@@ -20,7 +20,7 @@ export const ROUTES_EE = [
20
20
  {
21
21
  async Component() {
22
22
  const component = await import(
23
- /* webpackChunkName: "review-workflows-settings" */ './pages/ReviewWorkflows'
23
+ /* webpackChunkName: "review-workflows-settings-list-view" */ './pages/ReviewWorkflows/pages/ListView'
24
24
  );
25
25
 
26
26
  return component;
@@ -28,6 +28,30 @@ export const ROUTES_EE = [
28
28
  to: '/settings/review-workflows',
29
29
  exact: true,
30
30
  },
31
+
32
+ {
33
+ async Component() {
34
+ const component = await import(
35
+ /* webpackChunkName: "review-workflows-settings-create-view" */ './pages/ReviewWorkflows/pages/CreateView'
36
+ );
37
+
38
+ return component;
39
+ },
40
+ to: '/settings/review-workflows/create',
41
+ exact: true,
42
+ },
43
+
44
+ {
45
+ async Component() {
46
+ const component = await import(
47
+ /* webpackChunkName: "review-workflows-settings-edit-view" */ './pages/ReviewWorkflows/pages/EditView'
48
+ );
49
+
50
+ return component;
51
+ },
52
+ to: '/settings/review-workflows/:workflowId',
53
+ exact: true,
54
+ },
31
55
  ]
32
56
  : []),
33
57
 
@@ -13,11 +13,13 @@ const BILLING_SELF_HOSTED_URL = 'https://strapi.io/billing/request-seats';
13
13
 
14
14
  const AdminSeatInfo = () => {
15
15
  const { formatMessage } = useIntl();
16
- const { license } = useLicenseLimits();
17
- const { licenseLimitStatus, enforcementUserCount, permittedSeats, isHostedOnStrapiCloud } =
18
- license?.data ?? {};
16
+ const {
17
+ license: { licenseLimitStatus, enforcementUserCount, permittedSeats, isHostedOnStrapiCloud },
18
+ isError,
19
+ isLoading,
20
+ } = useLicenseLimits();
19
21
 
20
- if (!permittedSeats) {
22
+ if (isError || isLoading || !permittedSeats) {
21
23
  return null;
22
24
  }
23
25
 
@@ -1,4 +1,4 @@
1
- import { useFetchClient, useNotification } from '@strapi/helper-plugin';
1
+ import { useFetchClient, useNotification, useQueryParams } from '@strapi/helper-plugin';
2
2
  import { useQuery } from 'react-query';
3
3
  import { useLocation } from 'react-router-dom';
4
4
 
@@ -8,6 +8,7 @@ const useAuditLogsData = ({ canReadAuditLogs, canReadUsers }) => {
8
8
  const { get } = useFetchClient();
9
9
  const { search } = useLocation();
10
10
  const toggleNotification = useNotification();
11
+ const [{ query }] = useQueryParams();
11
12
 
12
13
  const queryOptions = {
13
14
  keepPreviousData: true,
@@ -35,9 +36,10 @@ const useAuditLogsData = ({ canReadAuditLogs, canReadUsers }) => {
35
36
  isError: isAuditLogsError,
36
37
  } = useQuery(
37
38
  ['auditLogs', search],
38
- async ({ queryKey }) => {
39
- const search = queryKey[1];
40
- const { data } = await get(`/admin/audit-logs${search}`);
39
+ async () => {
40
+ const { data } = await get(`/admin/audit-logs`, {
41
+ params: query,
42
+ });
41
43
 
42
44
  return data;
43
45
  },
@@ -1,17 +1,19 @@
1
1
  import {
2
2
  ACTION_ADD_STAGE,
3
3
  ACTION_DELETE_STAGE,
4
- ACTION_SET_WORKFLOWS,
4
+ ACTION_RESET_WORKFLOW,
5
+ ACTION_SET_WORKFLOW,
5
6
  ACTION_UPDATE_STAGE,
6
7
  ACTION_UPDATE_STAGE_POSITION,
8
+ ACTION_UPDATE_WORKFLOW,
7
9
  } from '../constants';
8
10
 
9
- export function setWorkflows({ status, data }) {
11
+ export function setWorkflow({ status, data }) {
10
12
  return {
11
- type: ACTION_SET_WORKFLOWS,
13
+ type: ACTION_SET_WORKFLOW,
12
14
  payload: {
13
15
  status,
14
- workflows: data,
16
+ workflow: data,
15
17
  },
16
18
  };
17
19
  }
@@ -51,3 +53,16 @@ export function updateStagePosition(oldIndex, newIndex) {
51
53
  },
52
54
  };
53
55
  }
56
+
57
+ export function updateWorkflow(payload) {
58
+ return {
59
+ type: ACTION_UPDATE_WORKFLOW,
60
+ payload,
61
+ };
62
+ }
63
+
64
+ export function resetWorkflow() {
65
+ return {
66
+ type: ACTION_RESET_WORKFLOW,
67
+ };
68
+ }
@@ -0,0 +1,65 @@
1
+ /* eslint-disable react/prop-types */
2
+
3
+ import * as React from 'react';
4
+
5
+ import { ContentLayout, HeaderLayout, Layout, Main } from '@strapi/design-system';
6
+ import { Link, SettingsPageTitle } from '@strapi/helper-plugin';
7
+ import { ArrowLeft } from '@strapi/icons';
8
+ import { useIntl } from 'react-intl';
9
+
10
+ import { DragLayer } from '../../../../../../../../admin/src/components/DragLayer';
11
+ import { DRAG_DROP_TYPES } from '../../constants';
12
+ import { StageDragPreview } from '../StageDragPreview';
13
+
14
+ function renderDragLayerItem({ type, item }) {
15
+ switch (type) {
16
+ case DRAG_DROP_TYPES.STAGE:
17
+ return <StageDragPreview {...item} />;
18
+
19
+ default:
20
+ return null;
21
+ }
22
+ }
23
+
24
+ function DragLayerRendered() {
25
+ return <DragLayer renderItem={renderDragLayerItem} />;
26
+ }
27
+
28
+ function Root({ children }) {
29
+ return (
30
+ <Layout>
31
+ <Main tabIndex={-1}>
32
+ <ContentLayout>{children}</ContentLayout>
33
+ </Main>
34
+ </Layout>
35
+ );
36
+ }
37
+
38
+ function Back({ href }) {
39
+ const { formatMessage } = useIntl();
40
+
41
+ return (
42
+ <Link startIcon={<ArrowLeft />} to={href}>
43
+ {formatMessage({
44
+ id: 'global.back',
45
+ defaultMessage: 'Back',
46
+ })}
47
+ </Link>
48
+ );
49
+ }
50
+
51
+ function Header({ title, subtitle, navigationAction, primaryAction }) {
52
+ return (
53
+ <>
54
+ <SettingsPageTitle name={title} />
55
+ <HeaderLayout
56
+ navigationAction={navigationAction}
57
+ primaryAction={primaryAction}
58
+ title={title}
59
+ subtitle={subtitle}
60
+ />
61
+ </>
62
+ );
63
+ }
64
+
65
+ export { Back, DragLayerRendered, Header, Root };
@@ -0,0 +1,111 @@
1
+ import * as React from 'react';
2
+
3
+ import { Box, Flex, IconButton, ModalLayout, ModalBody, Typography } from '@strapi/design-system';
4
+ import { LinkButton } from '@strapi/design-system/v2';
5
+ import { Cross } from '@strapi/icons';
6
+ import PropTypes from 'prop-types';
7
+ import { useIntl } from 'react-intl';
8
+ import styled from 'styled-components';
9
+
10
+ import balloonImageSrc from './assets/balloon.png';
11
+
12
+ const TITLE_ID = 'limits-title';
13
+
14
+ const CTA_LEARN_MORE_HREF = 'https://strapi.io/pricing-cloud';
15
+ const CTA_SALES_HREF = 'https://strapi.io/contact-sales';
16
+
17
+ export function Title({ children }) {
18
+ return (
19
+ <Typography variant="alpha" id={TITLE_ID}>
20
+ {children}
21
+ </Typography>
22
+ );
23
+ }
24
+
25
+ Title.propTypes = {
26
+ children: PropTypes.node.isRequired,
27
+ };
28
+
29
+ export function Body({ children }) {
30
+ return <Typography variant="omega">{children}</Typography>;
31
+ }
32
+
33
+ Body.propTypes = {
34
+ children: PropTypes.node.isRequired,
35
+ };
36
+
37
+ function CallToActions() {
38
+ const { formatMessage } = useIntl();
39
+
40
+ return (
41
+ <Flex gap={2} paddingTop={4}>
42
+ <LinkButton variant="default" isExternal href={CTA_LEARN_MORE_HREF}>
43
+ {formatMessage({
44
+ id: 'Settings.review-workflows.limit.cta.learn',
45
+ defaultMessage: 'Learn more',
46
+ })}
47
+ </LinkButton>
48
+
49
+ <LinkButton variant="tertiary" isExternal href={CTA_SALES_HREF}>
50
+ {formatMessage({
51
+ id: 'Settings.review-workflows.limit.cta.sales',
52
+ defaultMessage: 'Contact Sales',
53
+ })}
54
+ </LinkButton>
55
+ </Flex>
56
+ );
57
+ }
58
+
59
+ const BalloonImage = styled.img`
60
+ // Margin top|right reverse the padding of ModalBody
61
+ margin-right: ${({ theme }) => `-${theme.spaces[7]}`};
62
+ margin-top: ${({ theme }) => `-${theme.spaces[7]}`};
63
+ width: 360px;
64
+ `;
65
+
66
+ export function LimitsModal({ children, isOpen, onClose }) {
67
+ const { formatMessage } = useIntl();
68
+
69
+ if (!isOpen) {
70
+ return null;
71
+ }
72
+
73
+ return (
74
+ <ModalLayout labelledBy={TITLE_ID}>
75
+ <ModalBody>
76
+ <Flex gap={2} paddingLeft={7} position="relative">
77
+ <Flex alignItems="start" direction="column" gap={2} width="60%">
78
+ {children}
79
+
80
+ <CallToActions />
81
+ </Flex>
82
+
83
+ <Flex justifyContent="end" height="100%" width="40%">
84
+ <BalloonImage src={balloonImageSrc} aria-hidden alt="" loading="lazy" />
85
+
86
+ <Box display="flex" position="absolute" right={0} top={0}>
87
+ <IconButton
88
+ icon={<Cross />}
89
+ aria-label={formatMessage({
90
+ id: 'global.close',
91
+ defaultMessage: 'Close',
92
+ })}
93
+ onClick={onClose}
94
+ />
95
+ </Box>
96
+ </Flex>
97
+ </Flex>
98
+ </ModalBody>
99
+ </ModalLayout>
100
+ );
101
+ }
102
+
103
+ LimitsModal.defaultProps = {
104
+ isOpen: false,
105
+ };
106
+
107
+ LimitsModal.propTypes = {
108
+ children: PropTypes.node.isRequired,
109
+ isOpen: PropTypes.bool,
110
+ onClose: PropTypes.func.isRequired,
111
+ };
@@ -0,0 +1,3 @@
1
+ import { Title, Body, LimitsModal as Root } from './LimitsModal';
2
+
3
+ export { Title, Body, Root };
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+
3
+ import { CheckPagePermissions } from '@strapi/helper-plugin';
4
+ import PropTypes from 'prop-types';
5
+ import { useSelector } from 'react-redux';
6
+
7
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
8
+
9
+ export function ProtectedPage({ children }) {
10
+ const permissions = useSelector(selectAdminPermissions);
11
+
12
+ return (
13
+ <CheckPagePermissions permissions={permissions.settings.users.main}>
14
+ {children}
15
+ </CheckPagePermissions>
16
+ );
17
+ }
18
+
19
+ ProtectedPage.propTypes = {
20
+ children: PropTypes.node.isRequired,
21
+ };
@@ -0,0 +1 @@
1
+ export * from './ProtectedPage';