@strapi/admin 4.12.0-beta.3 → 4.12.0-beta.5

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 (68) hide show
  1. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +13 -35
  2. package/admin/src/content-manager/pages/ListView/components/TableRows/index.js +304 -0
  3. package/admin/src/pages/AuthPage/components/Register/index.js +4 -0
  4. package/build/2379.0ca87a89.chunk.js +1 -0
  5. package/build/2395.df7a044a.chunk.js +26 -0
  6. package/build/2801.b1140c9b.chunk.js +1 -0
  7. package/build/{3100.21c343fa.chunk.js → 3100.2ba4df95.chunk.js} +1 -1
  8. package/build/{3483.ddd2d6df.chunk.js → 3483.e2ee2547.chunk.js} +1 -1
  9. package/build/3984.dda474f7.chunk.js +1 -0
  10. package/build/502.8ae8ef60.chunk.js +1 -0
  11. package/build/5483.6dd2e776.chunk.js +6 -0
  12. package/build/7065.ec811562.chunk.js +114 -0
  13. package/build/7464.8a6c1e6c.chunk.js +1 -0
  14. package/build/8276.6c7b8e6e.chunk.js +26 -0
  15. package/build/{9806.91360bb6.chunk.js → 9806.aa25371d.chunk.js} +10 -10
  16. package/build/{Admin-authenticatedApp.36b3826c.chunk.js → Admin-authenticatedApp.6b8dfa45.chunk.js} +1 -1
  17. package/build/{admin-app.1c3f7fd6.chunk.js → admin-app.c2e4e128.chunk.js} +8 -8
  18. package/build/content-manager.8772445b.chunk.js +1103 -0
  19. package/build/index.html +1 -1
  20. package/build/{main.a12c4c0f.js → main.af84ad9c.js} +281 -281
  21. package/build/review-workflows-settings-create-view.05758184.chunk.js +1 -0
  22. package/build/review-workflows-settings-edit-view.c33f7c58.chunk.js +1 -0
  23. package/build/review-workflows-settings-list-view.3ee9190d.chunk.js +56 -0
  24. package/build/{runtime~main.d197f488.js → runtime~main.a65ca6fb.js} +2 -2
  25. package/build/sso-settings-page.7c9b2fd9.chunk.js +1 -0
  26. package/ee/admin/constants.js +3 -0
  27. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +12 -4
  28. package/ee/admin/hooks/index.js +1 -1
  29. package/ee/admin/hooks/useLicenseLimitNotification/index.js +1 -1
  30. package/ee/admin/hooks/useLicenseLimits/__mocks__/index.js +8 -0
  31. package/ee/admin/hooks/useLicenseLimits/index.js +1 -3
  32. package/ee/admin/hooks/useLicenseLimits/useLicenseLimits.js +3 -13
  33. package/ee/admin/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +19 -4
  34. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +7 -0
  35. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +20 -16
  36. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +120 -29
  37. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +3 -0
  38. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +108 -31
  39. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/index.js +8 -3
  40. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +138 -61
  41. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/index.js +8 -3
  42. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +72 -55
  43. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/index.js +8 -3
  44. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +2 -8
  45. package/ee/admin/pages/SettingsPage/pages/SingleSignOn/index.js +2 -8
  46. package/ee/admin/pages/SettingsPage/pages/SingleSignOn/utils/schema.js +8 -5
  47. package/ee/server/services/review-workflows/review-workflows.js +1 -1
  48. package/ee/server/services/review-workflows/validation.js +6 -4
  49. package/ee/server/services/review-workflows/workflows/content-types.js +28 -19
  50. package/ee/server/services/review-workflows/workflows/index.js +14 -2
  51. package/ee/server/validation/authentication.js +20 -9
  52. package/package.json +9 -9
  53. package/build/2379.d33a2e16.chunk.js +0 -1
  54. package/build/2395.b0419a54.chunk.js +0 -26
  55. package/build/2801.18f38baf.chunk.js +0 -1
  56. package/build/3984.ea7b8036.chunk.js +0 -1
  57. package/build/502.ccb38223.chunk.js +0 -1
  58. package/build/5483.ed2c7efa.chunk.js +0 -6
  59. package/build/7464.c6d0565c.chunk.js +0 -1
  60. package/build/8276.23e0763b.chunk.js +0 -26
  61. package/build/8298.fd253c9f.chunk.js +0 -117
  62. package/build/content-manager.b8d593d4.chunk.js +0 -1103
  63. package/build/review-workflows-settings-create-view.dfd87e1f.chunk.js +0 -1
  64. package/build/review-workflows-settings-edit-view.53c00afe.chunk.js +0 -1
  65. package/build/review-workflows-settings-list-view.a34be805.chunk.js +0 -56
  66. package/build/sso-settings-page.ed6f3f15.chunk.js +0 -1
  67. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/ProtectedPage.js +0 -21
  68. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/index.js +0 -1
@@ -1,11 +1,12 @@
1
1
  import * as React from 'react';
2
2
 
3
- import { Button, Flex, Loader } from '@strapi/design-system';
3
+ import { Button, Flex, Loader, Typography } from '@strapi/design-system';
4
4
  import {
5
5
  ConfirmDialog,
6
6
  useAPIErrorHandler,
7
7
  useFetchClient,
8
8
  useNotification,
9
+ useRBAC,
9
10
  } from '@strapi/helper-plugin';
10
11
  import { Check } from '@strapi/icons';
11
12
  import { useFormik, Form, FormikProvider } from 'formik';
@@ -17,19 +18,25 @@ import { useParams } from 'react-router-dom';
17
18
 
18
19
  import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
19
20
  import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
21
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
20
22
  import { useLicenseLimits } from '../../../../../../hooks';
21
- import { setWorkflow } from '../../actions';
23
+ import { resetWorkflow, setWorkflow } from '../../actions';
22
24
  import * as Layout from '../../components/Layout';
23
25
  import * as LimitsModal from '../../components/LimitsModal';
24
26
  import { Stages } from '../../components/Stages';
25
27
  import { WorkflowAttributes } from '../../components/WorkflowAttributes';
26
- import { REDUX_NAMESPACE } from '../../constants';
28
+ import {
29
+ CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME,
30
+ CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME,
31
+ REDUX_NAMESPACE,
32
+ } from '../../constants';
27
33
  import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
28
34
  import { reducer, initialState } from '../../reducer';
29
35
  import { validateWorkflow } from '../../utils/validateWorkflow';
30
36
 
31
37
  export function ReviewWorkflowsEditView() {
32
38
  const { workflowId } = useParams();
39
+ const permissions = useSelector(selectAdminPermissions);
33
40
  const { formatMessage } = useIntl();
34
41
  const dispatch = useDispatch();
35
42
  const { put } = useFetchClient();
@@ -38,10 +45,10 @@ export function ReviewWorkflowsEditView() {
38
45
  const {
39
46
  isLoading: isWorkflowLoading,
40
47
  meta,
41
- workflows: [workflow],
48
+ workflows,
42
49
  status: workflowStatus,
43
50
  refetch,
44
- } = useReviewWorkflows({ id: workflowId });
51
+ } = useReviewWorkflows();
45
52
  const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
46
53
  const {
47
54
  status,
@@ -49,15 +56,23 @@ export function ReviewWorkflowsEditView() {
49
56
  currentWorkflow: {
50
57
  data: currentWorkflow,
51
58
  isDirty: currentWorkflowIsDirty,
52
- hasDeletedServerStages: currentWorkflowHasDeletedServerStages,
59
+ hasDeletedServerStages,
53
60
  },
54
61
  },
55
62
  } = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
56
- const [isConfirmDeleteDialogOpen, setIsConfirmDeleteDialogOpen] = React.useState(false);
63
+ const {
64
+ allowedActions: { canDelete, canUpdate },
65
+ } = useRBAC(permissions.settings['review-workflows']);
66
+ const [savePrompts, setSavePrompts] = React.useState({});
57
67
  const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
58
68
  const [showLimitModal, setShowLimitModal] = React.useState(false);
59
69
  const [initialErrors, setInitialErrors] = React.useState(null);
60
70
 
71
+ const workflow = workflows.find((workflow) => workflow.id === parseInt(workflowId, 10));
72
+ const contentTypesFromOtherWorkflows = workflows
73
+ .filter((workflow) => workflow.id !== parseInt(workflowId, 10))
74
+ .flatMap((workflow) => workflow.contentTypes);
75
+
61
76
  const { mutateAsync, isLoading } = useMutation(
62
77
  async ({ workflow }) => {
63
78
  const {
@@ -87,20 +102,12 @@ export function ReviewWorkflowsEditView() {
87
102
 
88
103
  return res;
89
104
  } catch (error) {
90
- // TODO: the current implementation of `formatAPIError` prints all error messages of all details,
91
- // which get's hairy when we have duplicated-name errors, because the same error message is printed
92
- // several times. What we want instead in these scenarios is to print only the error summary and
93
- // display the individual error messages for each field. This is a workaround, until we change the
94
- // implementation of `formatAPIError`.
105
+ // TODO: this would benefit from a utility to get a formik error
106
+ // representation from an API error
95
107
  if (
96
108
  error.response.data?.error?.name === 'ValidationError' &&
97
109
  error.response.data?.error?.details?.errors?.length > 0
98
110
  ) {
99
- toggleNotification({
100
- type: 'warning',
101
- message: error.response.data.error.message,
102
- });
103
-
104
111
  setInitialErrors(
105
112
  error.response.data?.error?.details?.errors.reduce((acc, error) => {
106
113
  set(acc, error.path, error.message);
@@ -108,13 +115,13 @@ export function ReviewWorkflowsEditView() {
108
115
  return acc;
109
116
  }, {})
110
117
  );
111
- } else {
112
- toggleNotification({
113
- type: 'warning',
114
- message: formatAPIError(error),
115
- });
116
118
  }
117
119
 
120
+ toggleNotification({
121
+ type: 'warning',
122
+ message: formatAPIError(error),
123
+ });
124
+
118
125
  return null;
119
126
  }
120
127
  };
@@ -123,15 +130,15 @@ export function ReviewWorkflowsEditView() {
123
130
  await updateWorkflow(currentWorkflow);
124
131
  await refetch();
125
132
 
126
- setIsConfirmDeleteDialogOpen(false);
133
+ setSavePrompts({});
127
134
  };
128
135
 
129
136
  const handleConfirmDeleteDialog = async () => {
130
137
  await submitForm();
131
138
  };
132
139
 
133
- const toggleConfirmDeleteDialog = () => {
134
- setIsConfirmDeleteDialogOpen((prev) => !prev);
140
+ const handleConfirmClose = () => {
141
+ setSavePrompts({});
135
142
  };
136
143
 
137
144
  const formik = useFormik({
@@ -139,9 +146,14 @@ export function ReviewWorkflowsEditView() {
139
146
  initialErrors,
140
147
  initialValues: currentWorkflow,
141
148
  async onSubmit() {
142
- if (currentWorkflowHasDeletedServerStages) {
143
- setIsConfirmDeleteDialogOpen(true);
144
- } else if (limits?.workflows && meta?.workflowCount > parseInt(limits.workflows, 10)) {
149
+ const isContentTypeReassignment = currentWorkflow.contentTypes.some((contentType) =>
150
+ contentTypesFromOtherWorkflows.includes(contentType)
151
+ );
152
+
153
+ if (
154
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
155
+ meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
156
+ ) {
145
157
  /**
146
158
  * If the current license has a limit, check if the total count of workflows
147
159
  * exceeds that limit and display the limits modal instead of sending the
@@ -155,10 +167,19 @@ export function ReviewWorkflowsEditView() {
155
167
  * update, because it would throw an API error.
156
168
  */
157
169
  } else if (
158
- limits?.stagesPerWorkflow &&
159
- currentWorkflow.stages.length > parseInt(limits.stagesPerWorkflow, 10)
170
+ limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
171
+ currentWorkflow.stages.length >
172
+ parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
160
173
  ) {
161
174
  setShowLimitModal('stage');
175
+ } else if (hasDeletedServerStages || isContentTypeReassignment) {
176
+ if (hasDeletedServerStages) {
177
+ setSavePrompts((prev) => ({ ...prev, hasDeletedServerStages: true }));
178
+ }
179
+
180
+ if (isContentTypeReassignment) {
181
+ setSavePrompts((prev) => ({ ...prev, hasReassignedContentTypes: true }));
182
+ }
162
183
  } else {
163
184
  submitForm();
164
185
  }
@@ -174,6 +195,12 @@ export function ReviewWorkflowsEditView() {
174
195
 
175
196
  React.useEffect(() => {
176
197
  dispatch(setWorkflow({ status: workflowStatus, data: workflow }));
198
+
199
+ // reset the state to the initial state to avoid flashes if a user
200
+ // navigates from an edit-view to a create-view
201
+ return () => {
202
+ dispatch(resetWorkflow());
203
+ };
177
204
  }, [workflowStatus, workflow, dispatch]);
178
205
 
179
206
  /**
@@ -191,11 +218,15 @@ export function ReviewWorkflowsEditView() {
191
218
 
192
219
  React.useEffect(() => {
193
220
  if (!isWorkflowLoading && !isLicenseLoading) {
194
- if (limits?.workflows && meta?.workflowCount > parseInt(limits.workflows, 10)) {
221
+ if (
222
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
223
+ meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
224
+ ) {
195
225
  setShowLimitModal('workflow');
196
226
  } else if (
197
- limits?.stagesPerWorkflow &&
198
- currentWorkflow.stages.length > parseInt(limits.stagesPerWorkflow, 10)
227
+ limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
228
+ currentWorkflow.stages.length >
229
+ parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
199
230
  ) {
200
231
  setShowLimitModal('stage');
201
232
  }
@@ -204,8 +235,7 @@ export function ReviewWorkflowsEditView() {
204
235
  currentWorkflow.stages.length,
205
236
  isLicenseLoading,
206
237
  isWorkflowLoading,
207
- limits.stagesPerWorkflow,
208
- limits.workflows,
238
+ limits,
209
239
  meta?.workflowCount,
210
240
  meta.workflowsTotal,
211
241
  ]);
@@ -225,10 +255,10 @@ export function ReviewWorkflowsEditView() {
225
255
  startIcon={<Check />}
226
256
  type="submit"
227
257
  size="M"
228
- disabled={!currentWorkflowIsDirty}
258
+ disabled={!currentWorkflowIsDirty || !canUpdate}
229
259
  // if the confirm dialog is open the loading state is on
230
260
  // the confirm button already
231
- loading={!isConfirmDeleteDialogOpen && isLoading}
261
+ loading={!Object.keys(savePrompts).length > 0 && isLoading}
232
262
  >
233
263
  {formatMessage({
234
264
  id: 'global.save',
@@ -236,45 +266,92 @@ export function ReviewWorkflowsEditView() {
236
266
  })}
237
267
  </Button>
238
268
  }
239
- subtitle={formatMessage(
240
- {
241
- id: 'Settings.review-workflows.page.subtitle',
242
- defaultMessage: '{count, plural, one {# stage} other {# stages}}',
243
- },
244
- { count: currentWorkflow?.stages?.length ?? 0 }
245
- )}
269
+ subtitle={
270
+ currentWorkflow.stages.length > 0 &&
271
+ formatMessage(
272
+ {
273
+ id: 'Settings.review-workflows.page.subtitle',
274
+ defaultMessage: '{count, plural, one {# stage} other {# stages}}',
275
+ },
276
+ { count: currentWorkflow.stages.length }
277
+ )
278
+ }
246
279
  title={currentWorkflow.name}
247
280
  />
248
281
 
249
282
  <Layout.Root>
250
283
  {isLoadingModels || status === 'loading' ? (
251
- <Loader>
252
- {formatMessage({
253
- id: 'Settings.review-workflows.page.isLoading',
254
- defaultMessage: 'Workflow is loading',
255
- })}
256
- </Loader>
284
+ <Flex justifyContent="center">
285
+ <Loader>
286
+ {formatMessage({
287
+ id: 'Settings.review-workflows.page.isLoading',
288
+ defaultMessage: 'Workflow is loading',
289
+ })}
290
+ </Loader>
291
+ </Flex>
257
292
  ) : (
258
293
  <Flex alignItems="stretch" direction="column" gap={7}>
259
- <WorkflowAttributes contentTypes={{ collectionTypes, singleTypes }} />
260
- <Stages stages={formik.values?.stages} />
294
+ <WorkflowAttributes
295
+ canUpdate={canUpdate}
296
+ contentTypes={{ collectionTypes, singleTypes }}
297
+ currentWorkflow={currentWorkflow}
298
+ workflows={workflows}
299
+ />
300
+ <Stages
301
+ canDelete={canDelete}
302
+ canUpdate={canUpdate}
303
+ stages={formik.values?.stages}
304
+ />
261
305
  </Flex>
262
306
  )}
263
307
  </Layout.Root>
264
308
  </Form>
265
309
  </FormikProvider>
266
310
 
267
- <ConfirmDialog
268
- bodyText={{
269
- id: 'Settings.review-workflows.page.delete.confirm.body',
270
- defaultMessage:
271
- 'All entries assigned to deleted stages will be moved to the previous stage. Are you sure you want to save?',
272
- }}
311
+ <ConfirmDialog.Root
273
312
  isConfirmButtonLoading={isLoading}
274
- isOpen={isConfirmDeleteDialogOpen}
275
- onToggleDialog={toggleConfirmDeleteDialog}
313
+ isOpen={Object.keys(savePrompts).length > 0}
314
+ onToggleDialog={handleConfirmClose}
276
315
  onConfirm={handleConfirmDeleteDialog}
277
- />
316
+ >
317
+ <ConfirmDialog.Body>
318
+ <Flex direction="column" gap={5}>
319
+ {savePrompts.hasDeletedServerStages && (
320
+ <Typography textAlign="center" variant="omega">
321
+ {formatMessage({
322
+ id: 'Settings.review-workflows.page.delete.confirm.stages.body',
323
+ defaultMessage:
324
+ 'All entries assigned to deleted stages will be moved to the previous stage.',
325
+ })}
326
+ </Typography>
327
+ )}
328
+
329
+ {savePrompts.hasReassignedContentTypes && (
330
+ <Typography textAlign="center" variant="omega">
331
+ {formatMessage(
332
+ {
333
+ id: 'Settings.review-workflows.page.delete.confirm.contentType.body',
334
+ defaultMessage:
335
+ '{count} {count, plural, one {content-type} other {content-types}} {count, plural, one {is} other {are}} already mapped to {count, plural, one {another workflow} other {other workflows}}. If you save changes, {count, plural, one {this} other {these}} {count, plural, one {content-type} other {{count} content-types}} will no more be mapped to the {count, plural, one {another workflow} other {other workflows}} and all corresponding information will be removed.',
336
+ },
337
+ {
338
+ count: contentTypesFromOtherWorkflows.filter((contentType) =>
339
+ currentWorkflow.contentTypes.includes(contentType)
340
+ ).length,
341
+ }
342
+ )}
343
+ </Typography>
344
+ )}
345
+
346
+ <Typography textAlign="center" variant="omega">
347
+ {formatMessage({
348
+ id: 'Settings.review-workflows.page.delete.confirm.confirm',
349
+ defaultMessage: 'Are you sure you want to save?',
350
+ })}
351
+ </Typography>
352
+ </Flex>
353
+ </ConfirmDialog.Body>
354
+ </ConfirmDialog.Root>
278
355
 
279
356
  <LimitsModal.Root
280
357
  isOpen={showLimitModal === 'workflow'}
@@ -1,13 +1,18 @@
1
1
  import React from 'react';
2
2
 
3
- import { ProtectedPage } from '../../components/ProtectedPage';
3
+ import { CheckPagePermissions } from '@strapi/helper-plugin';
4
+ import { useSelector } from 'react-redux';
5
+
6
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
4
7
 
5
8
  import { ReviewWorkflowsEditView } from './EditView';
6
9
 
7
10
  export default function () {
11
+ const permissions = useSelector(selectAdminPermissions);
12
+
8
13
  return (
9
- <ProtectedPage>
14
+ <CheckPagePermissions permissions={permissions.settings['review-workflows'].main}>
10
15
  <ReviewWorkflowsEditView />
11
- </ProtectedPage>
16
+ </CheckPagePermissions>
12
17
  );
13
18
  }
@@ -23,18 +23,22 @@ import {
23
23
  useAPIErrorHandler,
24
24
  useFetchClient,
25
25
  useNotification,
26
+ useRBAC,
26
27
  useTracking,
27
28
  } from '@strapi/helper-plugin';
28
29
  import { Pencil, Plus, Trash } from '@strapi/icons';
29
30
  import { useIntl } from 'react-intl';
30
31
  import { useMutation } from 'react-query';
32
+ import { useSelector } from 'react-redux';
31
33
  import { useHistory } from 'react-router-dom';
32
34
  import styled from 'styled-components';
33
35
 
34
36
  import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
37
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
35
38
  import { useLicenseLimits } from '../../../../../../hooks';
36
39
  import * as Layout from '../../components/Layout';
37
40
  import * as LimitsModal from '../../components/LimitsModal';
41
+ import { CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME } from '../../constants';
38
42
  import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
39
43
 
40
44
  const ActionLink = styled(Link)`
@@ -76,6 +80,10 @@ export function ReviewWorkflowsListView() {
76
80
  const toggleNotification = useNotification();
77
81
  const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
78
82
  const { trackUsage } = useTracking();
83
+ const permissions = useSelector(selectAdminPermissions);
84
+ const {
85
+ allowedActions: { canCreate, canDelete },
86
+ } = useRBAC(permissions.settings['review-workflows']);
79
87
 
80
88
  const limits = getFeature('review-workflows');
81
89
 
@@ -148,24 +156,21 @@ export function ReviewWorkflowsListView() {
148
156
 
149
157
  React.useEffect(() => {
150
158
  if (!isLoading && !isLicenseLoading) {
151
- if (limits?.workflows && meta?.workflowCount >= parseInt(limits.workflows, 10)) {
159
+ if (
160
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
161
+ meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
162
+ ) {
152
163
  setShowLimitModal(true);
153
164
  }
154
165
  }
155
- }, [
156
- isLicenseLoading,
157
- isLoading,
158
- limits.stagesPerWorkflow,
159
- limits.workflows,
160
- meta?.workflowCount,
161
- meta.workflowsTotal,
162
- ]);
166
+ }, [isLicenseLoading, isLoading, limits, meta?.workflowCount, meta.workflowsTotal]);
163
167
 
164
168
  return (
165
169
  <>
166
170
  <Layout.Header
167
171
  primaryAction={
168
172
  <LinkButton
173
+ disabled={!canCreate}
169
174
  startIcon={<Plus />}
170
175
  size="S"
171
176
  to="/settings/review-workflows/create"
@@ -180,7 +185,10 @@ export function ReviewWorkflowsListView() {
180
185
  * current hard-limit of 200 they will see an error thrown by the API.
181
186
  */
182
187
 
183
- if (limits?.workflows && meta?.workflowCount >= parseInt(limits.workflows, 10)) {
188
+ if (
189
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
190
+ meta?.workflowCount >= parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
191
+ ) {
184
192
  event.preventDefault();
185
193
  setShowLimitModal(true);
186
194
  } else {
@@ -207,42 +215,50 @@ export function ReviewWorkflowsListView() {
207
215
 
208
216
  <Layout.Root>
209
217
  {isLoading || isLoadingModels ? (
210
- <Loader>
211
- {formatMessage({
212
- id: 'Settings.review-workflows.page.list.isLoading',
213
- defaultMessage: 'Workflows are loading',
214
- })}
215
- </Loader>
218
+ <Flex justifyContent="center">
219
+ <Loader>
220
+ {formatMessage({
221
+ id: 'Settings.review-workflows.page.list.isLoading',
222
+ defaultMessage: 'Workflows are loading',
223
+ })}
224
+ </Loader>
225
+ </Flex>
216
226
  ) : (
217
227
  <Table
218
228
  colCount={3}
219
229
  footer={
220
230
  // TODO: we should be able to use a link here instead of an (inaccessible onClick) handler
221
- <TFooter
222
- icon={<Plus />}
223
- onClick={() => {
224
- /**
225
- * If the current license has a workflow limit:
226
- * check if the total count of workflows exceeds that limit
227
- *
228
- * If the current license does not have a limit (e.g. offline license):
229
- * allow the user to navigate to the create-view. In case they exceed the
230
- * current hard-limit of 200 they will see an error thrown by the API.
231
- */
231
+ canCreate && (
232
+ <TFooter
233
+ icon={<Plus />}
234
+ onClick={() => {
235
+ /**
236
+ * If the current license has a workflow limit:
237
+ * check if the total count of workflows exceeds that limit
238
+ *
239
+ * If the current license does not have a limit (e.g. offline license):
240
+ * allow the user to navigate to the create-view. In case they exceed the
241
+ * current hard-limit of 200 they will see an error thrown by the API.
242
+ */
232
243
 
233
- if (limits?.workflows && meta?.workflowCount >= parseInt(limits.workflows, 10)) {
234
- setShowLimitModal(true);
235
- } else {
236
- push('/settings/review-workflows/create');
237
- trackUsage('willCreateWorkflow');
238
- }
239
- }}
240
- >
241
- {formatMessage({
242
- id: 'Settings.review-workflows.list.page.create',
243
- defaultMessage: 'Create new workflow',
244
- })}
245
- </TFooter>
244
+ if (
245
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
246
+ meta?.workflowCount >=
247
+ parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
248
+ ) {
249
+ setShowLimitModal(true);
250
+ } else {
251
+ push('/settings/review-workflows/create');
252
+ trackUsage('willCreateWorkflow');
253
+ }
254
+ }}
255
+ >
256
+ {formatMessage({
257
+ id: 'Settings.review-workflows.list.page.create',
258
+ defaultMessage: 'Create new workflow',
259
+ })}
260
+ </TFooter>
261
+ )
246
262
  }
247
263
  rowCount={1}
248
264
  >
@@ -326,21 +342,22 @@ export function ReviewWorkflowsListView() {
326
342
  <Pencil />
327
343
  </ActionLink>
328
344
 
329
- <IconButton
330
- aria-label={formatMessage(
331
- {
332
- id: 'Settings.review-workflows.list.page.list.column.actions.delete.label',
333
- defaultMessage: 'Delete {name}',
334
- },
335
- { name: 'Default workflow' }
336
- )}
337
- disabled={workflows.length === 1}
338
- icon={<Trash />}
339
- noBorder
340
- onClick={() => {
341
- handleDeleteWorkflow(workflow.id);
342
- }}
343
- />
345
+ {workflows.length > 1 && canDelete && (
346
+ <IconButton
347
+ aria-label={formatMessage(
348
+ {
349
+ id: 'Settings.review-workflows.list.page.list.column.actions.delete.label',
350
+ defaultMessage: 'Delete {name}',
351
+ },
352
+ { name: 'Default workflow' }
353
+ )}
354
+ icon={<Trash />}
355
+ noBorder
356
+ onClick={() => {
357
+ handleDeleteWorkflow(workflow.id);
358
+ }}
359
+ />
360
+ )}
344
361
  </Flex>
345
362
  </Td>
346
363
  </Tr>
@@ -1,13 +1,18 @@
1
1
  import React from 'react';
2
2
 
3
- import { ProtectedPage } from '../../components/ProtectedPage';
3
+ import { CheckPagePermissions } from '@strapi/helper-plugin';
4
+ import { useSelector } from 'react-redux';
5
+
6
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
4
7
 
5
8
  import { ReviewWorkflowsListView } from './ListView';
6
9
 
7
10
  export default function () {
11
+ const permissions = useSelector(selectAdminPermissions);
12
+
8
13
  return (
9
- <ProtectedPage>
14
+ <CheckPagePermissions permissions={permissions.settings['review-workflows'].main}>
10
15
  <ReviewWorkflowsListView />
11
- </ProtectedPage>
16
+ </CheckPagePermissions>
12
17
  );
13
18
  }
@@ -22,13 +22,7 @@ export const initialState = {
22
22
  data: {
23
23
  name: '',
24
24
  contentTypes: [],
25
- stages: [
26
- {
27
- color: STAGE_COLOR_DEFAULT,
28
- name: '',
29
- __temp_key__: 1,
30
- },
31
- ],
25
+ stages: [],
32
26
  },
33
27
  isDirty: false,
34
28
  hasDeletedServerStages: false,
@@ -79,7 +73,7 @@ export function reducer(state = initialState, action) {
79
73
 
80
74
  if (!currentWorkflow.hasDeletedServerStages) {
81
75
  draft.clientState.currentWorkflow.hasDeletedServerStages = !!(
82
- state.serverState.currentWorkflow?.stages ?? []
76
+ state.serverState.workflow?.stages ?? []
83
77
  ).find((stage) => stage.id === stageId);
84
78
  }
85
79
 
@@ -1,4 +1,4 @@
1
- import React, { useEffect } from 'react';
1
+ import React from 'react';
2
2
 
3
3
  import {
4
4
  Button,
@@ -62,13 +62,7 @@ export const SingleSignOn = () => {
62
62
 
63
63
  const showLoader = isLoadingForPermissions || isLoading;
64
64
 
65
- useEffect(() => {
66
- if (formErrors.defaultRole) {
67
- const selector = `[name="defaultRole"]`;
68
-
69
- document.querySelector(selector).focus();
70
- }
71
- }, [formErrors]);
65
+ // TODO: focus() first error field, but it looks like that requires refactoring from useSettingsForm to Formik
72
66
 
73
67
  const isHeaderButtonDisabled = isEqual(initialData, modifiedData);
74
68
 
@@ -6,11 +6,14 @@ const schema = yup.object().shape({
6
6
  defaultRole: yup.mixed().when('autoRegister', (value, initSchema) => {
7
7
  return value ? initSchema.required(translatedErrors.required) : initSchema.nullable();
8
8
  }),
9
- ssoLockedRoles: yup.array().of(
10
- yup.mixed().when('ssoLockedRoles', (value, initSchema) => {
11
- return value ? initSchema.required(translatedErrors.required) : initSchema.nullable();
12
- })
13
- ),
9
+ ssoLockedRoles: yup
10
+ .array()
11
+ .nullable()
12
+ .of(
13
+ yup.mixed().when('ssoLockedRoles', (value, initSchema) => {
14
+ return value ? initSchema.required(translatedErrors.required) : initSchema.nullable();
15
+ })
16
+ ),
14
17
  });
15
18
 
16
19
  export default schema;
@@ -22,7 +22,7 @@ const MAX_JOIN_TABLE_NAME_SUFFIX =
22
22
  const MAX_CONTENT_TYPE_NAME_LEN = MAX_DB_TABLE_NAME_LEN - MAX_JOIN_TABLE_NAME_SUFFIX;
23
23
 
24
24
  const DEFAULT_OPTIONS = {
25
- workflows: MAX_WORKFLOWS,
25
+ numberOfWorkflows: MAX_WORKFLOWS,
26
26
  stagesPerWorkflow: MAX_STAGES_PER_WORKFLOW,
27
27
  };
28
28