@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
@@ -0,0 +1,269 @@
1
+ import * as React from 'react';
2
+
3
+ import { Button, Flex, Loader } from '@strapi/design-system';
4
+ import {
5
+ ConfirmDialog,
6
+ useAPIErrorHandler,
7
+ useFetchClient,
8
+ useNotification,
9
+ } from '@strapi/helper-plugin';
10
+ import { Check } from '@strapi/icons';
11
+ import { useFormik, Form, FormikProvider } from 'formik';
12
+ import { useIntl } from 'react-intl';
13
+ import { useMutation } from 'react-query';
14
+ import { useSelector, useDispatch } from 'react-redux';
15
+ import { useParams } from 'react-router-dom';
16
+
17
+ import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
18
+ import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
19
+ import { useLicenseLimits } from '../../../../../../hooks';
20
+ import { setWorkflow } from '../../actions';
21
+ import * as Layout from '../../components/Layout';
22
+ import * as LimitsModal from '../../components/LimitsModal';
23
+ import { Stages } from '../../components/Stages';
24
+ import { WorkflowAttributes } from '../../components/WorkflowAttributes';
25
+ import { REDUX_NAMESPACE } from '../../constants';
26
+ import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
27
+ import { reducer, initialState } from '../../reducer';
28
+ import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
29
+
30
+ export function ReviewWorkflowsEditView() {
31
+ const { workflowId } = useParams();
32
+ const { formatMessage } = useIntl();
33
+ const dispatch = useDispatch();
34
+ const { put } = useFetchClient();
35
+ const { formatAPIError } = useAPIErrorHandler();
36
+ const toggleNotification = useNotification();
37
+ const {
38
+ isLoading: isWorkflowLoading,
39
+ meta,
40
+ workflows: [workflow],
41
+ status: workflowStatus,
42
+ refetch,
43
+ } = useReviewWorkflows({ id: workflowId });
44
+ const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
45
+ const {
46
+ status,
47
+ clientState: {
48
+ currentWorkflow: {
49
+ data: currentWorkflow,
50
+ isDirty: currentWorkflowIsDirty,
51
+ hasDeletedServerStages: currentWorkflowHasDeletedServerStages,
52
+ },
53
+ },
54
+ } = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
55
+ const [isConfirmDeleteDialogOpen, setIsConfirmDeleteDialogOpen] = React.useState(false);
56
+ const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
57
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
58
+
59
+ const { mutateAsync, isLoading } = useMutation(
60
+ async ({ workflow }) => {
61
+ const {
62
+ data: { data },
63
+ } = await put(`/admin/review-workflows/workflows/${workflow.id}`, {
64
+ data: workflow,
65
+ });
66
+
67
+ return data;
68
+ },
69
+ {
70
+ onSuccess() {
71
+ toggleNotification({
72
+ type: 'success',
73
+ message: { id: 'notification.success.saved', defaultMessage: 'Saved' },
74
+ });
75
+ },
76
+ }
77
+ );
78
+
79
+ const updateWorkflow = async (workflow) => {
80
+ try {
81
+ const res = await mutateAsync({ workflow });
82
+
83
+ return res;
84
+ } catch (error) {
85
+ toggleNotification({
86
+ type: 'warning',
87
+ message: formatAPIError(error),
88
+ });
89
+
90
+ return null;
91
+ }
92
+ };
93
+
94
+ const submitForm = async () => {
95
+ await updateWorkflow(currentWorkflow);
96
+ await refetch();
97
+
98
+ setIsConfirmDeleteDialogOpen(false);
99
+ };
100
+
101
+ const handleConfirmDeleteDialog = async () => {
102
+ await submitForm();
103
+ };
104
+
105
+ const toggleConfirmDeleteDialog = () => {
106
+ setIsConfirmDeleteDialogOpen((prev) => !prev);
107
+ };
108
+
109
+ const formik = useFormik({
110
+ enableReinitialize: true,
111
+ initialValues: currentWorkflow,
112
+ async onSubmit() {
113
+ if (currentWorkflowHasDeletedServerStages) {
114
+ setIsConfirmDeleteDialogOpen(true);
115
+ } else {
116
+ submitForm();
117
+ }
118
+ },
119
+ validationSchema: getWorkflowValidationSchema({ formatMessage }),
120
+ });
121
+
122
+ useInjectReducer(REDUX_NAMESPACE, reducer);
123
+
124
+ const limits = getFeature('review-workflows');
125
+
126
+ React.useEffect(() => {
127
+ dispatch(setWorkflow({ status: workflowStatus, data: workflow }));
128
+ }, [workflowStatus, workflow, dispatch]);
129
+
130
+ /**
131
+ * If the current license has a limit:
132
+ * check if the total count of workflows or stages exceeds that limit and display
133
+ * the limits modal on page load. It can be closed by the user, but the
134
+ * API will throw an error in case they try to create a new workflow or update the
135
+ * stages.
136
+ *
137
+ * If the current license does not have a limit (e.g. offline license):
138
+ * do nothing (for now). In case they are trying to create the 201st workflow/ stage
139
+ * the API will throw an error.
140
+ *
141
+ */
142
+
143
+ React.useEffect(() => {
144
+ if (!isWorkflowLoading && !isLicenseLoading) {
145
+ if (limits?.workflows && meta?.workflowCount >= limits.workflows) {
146
+ setShowLimitModal('workflow');
147
+ } else if (
148
+ limits?.stagesPerWorkflow &&
149
+ currentWorkflow.stages.length >= limits.stagesPerWorkflow
150
+ ) {
151
+ setShowLimitModal('stage');
152
+ }
153
+ }
154
+ }, [
155
+ currentWorkflow.stages.length,
156
+ isLicenseLoading,
157
+ isWorkflowLoading,
158
+ limits.stagesPerWorkflow,
159
+ limits.workflows,
160
+ meta?.workflowCount,
161
+ meta.workflowsTotal,
162
+ ]);
163
+
164
+ // TODO: redirect back to list-view if workflow is not found?
165
+
166
+ return (
167
+ <>
168
+ <Layout.DragLayerRendered />
169
+
170
+ <FormikProvider value={formik}>
171
+ <Form onSubmit={formik.handleSubmit}>
172
+ <Layout.Header
173
+ navigationAction={<Layout.Back href="/settings/review-workflows" />}
174
+ primaryAction={
175
+ <Button
176
+ startIcon={<Check />}
177
+ type="submit"
178
+ size="M"
179
+ disabled={!currentWorkflowIsDirty}
180
+ // if the confirm dialog is open the loading state is on
181
+ // the confirm button already
182
+ loading={!isConfirmDeleteDialogOpen && isLoading}
183
+ >
184
+ {formatMessage({
185
+ id: 'global.save',
186
+ defaultMessage: 'Save',
187
+ })}
188
+ </Button>
189
+ }
190
+ subtitle={formatMessage(
191
+ {
192
+ id: 'Settings.review-workflows.page.subtitle',
193
+ defaultMessage: '{count, plural, one {# stage} other {# stages}}',
194
+ },
195
+ { count: currentWorkflow?.stages?.length ?? 0 }
196
+ )}
197
+ title={currentWorkflow.name}
198
+ />
199
+
200
+ <Layout.Root>
201
+ {isLoadingModels || status === 'loading' ? (
202
+ <Loader>
203
+ {formatMessage({
204
+ id: 'Settings.review-workflows.page.isLoading',
205
+ defaultMessage: 'Workflow is loading',
206
+ })}
207
+ </Loader>
208
+ ) : (
209
+ <Flex alignItems="stretch" direction="column" gap={7}>
210
+ <WorkflowAttributes contentTypes={{ collectionTypes, singleTypes }} />
211
+ <Stages stages={formik.values?.stages} />
212
+ </Flex>
213
+ )}
214
+ </Layout.Root>
215
+ </Form>
216
+ </FormikProvider>
217
+
218
+ <ConfirmDialog
219
+ bodyText={{
220
+ id: 'Settings.review-workflows.page.delete.confirm.body',
221
+ defaultMessage:
222
+ 'All entries assigned to deleted stages will be moved to the previous stage. Are you sure you want to save?',
223
+ }}
224
+ isConfirmButtonLoading={isLoading}
225
+ isOpen={isConfirmDeleteDialogOpen}
226
+ onToggleDialog={toggleConfirmDeleteDialog}
227
+ onConfirm={handleConfirmDeleteDialog}
228
+ />
229
+
230
+ <LimitsModal.Root
231
+ isOpen={showLimitModal === 'workflow'}
232
+ onClose={() => setShowLimitModal(false)}
233
+ >
234
+ <LimitsModal.Title>
235
+ {formatMessage({
236
+ id: 'Settings.review-workflows.edit.page.workflows.limit.title',
237
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
238
+ })}
239
+ </LimitsModal.Title>
240
+
241
+ <LimitsModal.Body>
242
+ {formatMessage({
243
+ id: 'Settings.review-workflows.edit.page.workflows.limit.body',
244
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
245
+ })}
246
+ </LimitsModal.Body>
247
+ </LimitsModal.Root>
248
+
249
+ <LimitsModal.Root
250
+ isOpen={showLimitModal === 'stage'}
251
+ onClose={() => setShowLimitModal(false)}
252
+ >
253
+ <LimitsModal.Title>
254
+ {formatMessage({
255
+ id: 'Settings.review-workflows.edit.page.stages.limit.title',
256
+ defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
257
+ })}
258
+ </LimitsModal.Title>
259
+
260
+ <LimitsModal.Body>
261
+ {formatMessage({
262
+ id: 'Settings.review-workflows.edit.page.stages.limit.body',
263
+ defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
264
+ })}
265
+ </LimitsModal.Body>
266
+ </LimitsModal.Root>
267
+ </>
268
+ );
269
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+
3
+ import { ProtectedPage } from '../../components/ProtectedPage';
4
+
5
+ import { ReviewWorkflowsEditView } from './EditView';
6
+
7
+ export default function () {
8
+ return (
9
+ <ProtectedPage>
10
+ <ReviewWorkflowsEditView />
11
+ </ProtectedPage>
12
+ );
13
+ }
@@ -0,0 +1,382 @@
1
+ import React from 'react';
2
+
3
+ import {
4
+ Flex,
5
+ IconButton,
6
+ Loader,
7
+ Table,
8
+ Thead,
9
+ Tbody,
10
+ Tr,
11
+ Td,
12
+ TFooter,
13
+ Th,
14
+ Typography,
15
+ VisuallyHidden,
16
+ } from '@strapi/design-system';
17
+ import {
18
+ ConfirmDialog,
19
+ Link,
20
+ LinkButton,
21
+ onRowClick,
22
+ pxToRem,
23
+ useAPIErrorHandler,
24
+ useFetchClient,
25
+ useNotification,
26
+ useTracking,
27
+ } from '@strapi/helper-plugin';
28
+ import { Pencil, Plus, Trash } from '@strapi/icons';
29
+ import { useIntl } from 'react-intl';
30
+ import { useMutation } from 'react-query';
31
+ import { useHistory } from 'react-router-dom';
32
+ import styled from 'styled-components';
33
+
34
+ import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
35
+ import { useLicenseLimits } from '../../../../../../hooks';
36
+ import * as Layout from '../../components/Layout';
37
+ import * as LimitsModal from '../../components/LimitsModal';
38
+ import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
39
+
40
+ const ActionLink = styled(Link)`
41
+ align-items: center;
42
+ height: ${pxToRem(32)};
43
+ display: flex;
44
+ justify-content: center;
45
+ padding: ${({ theme }) => `${theme.spaces[2]}}`};
46
+ width: ${pxToRem(32)};
47
+
48
+ svg {
49
+ height: ${pxToRem(12)};
50
+ width: ${pxToRem(12)};
51
+
52
+ path {
53
+ fill: ${({ theme }) => theme.colors.neutral500};
54
+ }
55
+ }
56
+
57
+ &:hover,
58
+ &:focus {
59
+ svg {
60
+ path {
61
+ fill: ${({ theme }) => theme.colors.neutral800};
62
+ }
63
+ }
64
+ }
65
+ `;
66
+
67
+ export function ReviewWorkflowsListView() {
68
+ const { formatMessage } = useIntl();
69
+ const { push } = useHistory();
70
+ const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
71
+ const { meta, workflows, isLoading, refetch } = useReviewWorkflows();
72
+ const [workflowToDelete, setWorkflowToDelete] = React.useState(null);
73
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
74
+ const { del } = useFetchClient();
75
+ const { formatAPIError } = useAPIErrorHandler();
76
+ const toggleNotification = useNotification();
77
+ const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
78
+ const { trackUsage } = useTracking();
79
+
80
+ const limits = getFeature('review-workflows');
81
+
82
+ const { mutateAsync, isLoading: isLoadingMutation } = useMutation(
83
+ async ({ workflowId, stages }) => {
84
+ const {
85
+ data: { data },
86
+ } = await del(`/admin/review-workflows/workflows/${workflowId}`, {
87
+ data: stages,
88
+ });
89
+
90
+ return data;
91
+ },
92
+ {
93
+ onSuccess() {
94
+ toggleNotification({
95
+ type: 'success',
96
+ message: { id: 'notification.success.deleted', defaultMessage: 'Deleted' },
97
+ });
98
+ },
99
+ }
100
+ );
101
+
102
+ const getContentTypeDisplayName = (uid) => {
103
+ const contentType = [...collectionTypes, ...singleTypes].find(
104
+ (contentType) => contentType.uid === uid
105
+ );
106
+
107
+ return contentType.info.displayName;
108
+ };
109
+
110
+ const handleDeleteWorkflow = (workflowId) => {
111
+ setWorkflowToDelete(workflowId);
112
+ };
113
+
114
+ const toggleConfirmDeleteDialog = () => {
115
+ setWorkflowToDelete(null);
116
+ };
117
+
118
+ const handleConfirmDeleteDialog = async () => {
119
+ try {
120
+ const res = await mutateAsync({ workflowId: workflowToDelete });
121
+
122
+ await refetch();
123
+ setWorkflowToDelete(null);
124
+
125
+ return res;
126
+ } catch (error) {
127
+ toggleNotification({
128
+ type: 'warning',
129
+ message: formatAPIError(error),
130
+ });
131
+
132
+ return null;
133
+ }
134
+ };
135
+
136
+ /**
137
+ * If the current license has a limit:
138
+ * check if the total count of workflows or stages exceeds that limit and display
139
+ * the limits modal on page load. It can be closed by the user, but the
140
+ * API will throw an error in case they try to create a new workflow or update the
141
+ * stages.
142
+ *
143
+ * If the current license does not have a limit (e.g. offline license):
144
+ * do nothing (for now). In case they are trying to create the 201st workflow/ stage
145
+ * the API will throw an error.
146
+ *
147
+ */
148
+
149
+ React.useEffect(() => {
150
+ if (!isLoading && !isLicenseLoading) {
151
+ if (limits?.workflows && meta?.workflowCount >= limits.workflows) {
152
+ setShowLimitModal(true);
153
+ }
154
+ }
155
+ }, [
156
+ isLicenseLoading,
157
+ isLoading,
158
+ limits.stagesPerWorkflow,
159
+ limits.workflows,
160
+ meta?.workflowCount,
161
+ meta.workflowsTotal,
162
+ ]);
163
+
164
+ return (
165
+ <>
166
+ <Layout.Header
167
+ primaryAction={
168
+ <LinkButton
169
+ startIcon={<Plus />}
170
+ size="S"
171
+ to="/settings/review-workflows/create"
172
+ onClick={(event) => {
173
+ /**
174
+ * If the current license has a workflow limit:
175
+ * check if the total count of workflows exceeds that limit. If so,
176
+ * prevent the navigation and show the limits overlay.
177
+ *
178
+ * If the current license does not have a limit (e.g. offline license):
179
+ * allow the user to navigate to the create-view. In case they exceed the
180
+ * current hard-limit of 200 they will see an error thrown by the API.
181
+ */
182
+
183
+ if (limits?.workflows && meta?.workflowCount >= limits.workflows) {
184
+ event.preventDefault();
185
+ setShowLimitModal(true);
186
+ } else {
187
+ trackUsage('willCreateWorkflow');
188
+ }
189
+ }}
190
+ >
191
+ {formatMessage({
192
+ id: 'Settings.review-workflows.list.page.create',
193
+ defaultMessage: 'Create new workflow',
194
+ })}
195
+ </LinkButton>
196
+ }
197
+ subtitle={formatMessage({
198
+ id: 'Settings.review-workflows.list.page.subtitle',
199
+ defaultMessage:
200
+ 'Manage content review stages and collaborate during content creation from draft to publication',
201
+ })}
202
+ title={formatMessage({
203
+ id: 'Settings.review-workflows.list.page.title',
204
+ defaultMessage: 'Review Workflows',
205
+ })}
206
+ />
207
+
208
+ <Layout.Root>
209
+ {isLoading || isLoadingModels ? (
210
+ <Loader>
211
+ {formatMessage({
212
+ id: 'Settings.review-workflows.page.list.isLoading',
213
+ defaultMessage: 'Workflows are loading',
214
+ })}
215
+ </Loader>
216
+ ) : (
217
+ <Table
218
+ colCount={3}
219
+ footer={
220
+ // 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
+ */
232
+
233
+ if (limits?.workflows && meta?.workflowCount >= limits.workflows) {
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>
246
+ }
247
+ rowCount={1}
248
+ >
249
+ <Thead>
250
+ <Tr>
251
+ <Th>
252
+ <Typography variant="sigma">
253
+ {formatMessage({
254
+ id: 'Settings.review-workflows.list.page.list.column.name.title',
255
+ defaultMessage: 'Name',
256
+ })}
257
+ </Typography>
258
+ </Th>
259
+ <Th>
260
+ <Typography variant="sigma">
261
+ {formatMessage({
262
+ id: 'Settings.review-workflows.list.page.list.column.stages.title',
263
+ defaultMessage: 'Stages',
264
+ })}
265
+ </Typography>
266
+ </Th>
267
+ <Th>
268
+ <Typography variant="sigma">
269
+ {formatMessage({
270
+ id: 'Settings.review-workflows.list.page.list.column.contentTypes.title',
271
+ defaultMessage: 'Content Types',
272
+ })}
273
+ </Typography>
274
+ </Th>
275
+ <Th>
276
+ <VisuallyHidden>
277
+ {formatMessage({
278
+ id: 'Settings.review-workflows.list.page.list.column.actions.title',
279
+ defaultMessage: 'Actions',
280
+ })}
281
+ </VisuallyHidden>
282
+ </Th>
283
+ </Tr>
284
+ </Thead>
285
+
286
+ <Tbody>
287
+ {workflows.map((workflow) => (
288
+ <Tr
289
+ {...onRowClick({
290
+ fn(event) {
291
+ // Abort row onClick event when the user click on the delete button
292
+ if (event.target.nodeName === 'BUTTON') {
293
+ return;
294
+ }
295
+
296
+ push(`/settings/review-workflows/${workflow.id}`);
297
+ },
298
+ })}
299
+ key={`workflow-${workflow.id}`}
300
+ >
301
+ <Td width={pxToRem(250)}>
302
+ <Typography textColor="neutral800" fontWeight="bold" ellipsis>
303
+ {workflow.name}
304
+ </Typography>
305
+ </Td>
306
+ <Td>
307
+ <Typography textColor="neutral800">{workflow.stages.length}</Typography>
308
+ </Td>
309
+ <Td>
310
+ <Typography textColor="neutral800">
311
+ {(workflow?.contentTypes ?? []).map(getContentTypeDisplayName).join(', ')}
312
+ </Typography>
313
+ </Td>
314
+ <Td>
315
+ <Flex alignItems="center" justifyContent="end">
316
+ <ActionLink
317
+ to={`/settings/review-workflows/${workflow.id}`}
318
+ aria-label={formatMessage(
319
+ {
320
+ id: 'Settings.review-workflows.list.page.list.column.actions.edit.label',
321
+ defaultMessage: 'Edit {name}',
322
+ },
323
+ { name: workflow.name }
324
+ )}
325
+ >
326
+ <Pencil />
327
+ </ActionLink>
328
+
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
+ />
344
+ </Flex>
345
+ </Td>
346
+ </Tr>
347
+ ))}
348
+ </Tbody>
349
+ </Table>
350
+ )}
351
+
352
+ <ConfirmDialog
353
+ bodyText={{
354
+ id: 'Settings.review-workflows.list.page.delete.confirm.body',
355
+ defaultMessage:
356
+ 'If you remove this worfklow, all stage-related information will be removed for this content-type. Are you sure you want to remove it?',
357
+ }}
358
+ isConfirmButtonLoading={isLoadingMutation}
359
+ isOpen={!!workflowToDelete}
360
+ onToggleDialog={toggleConfirmDeleteDialog}
361
+ onConfirm={handleConfirmDeleteDialog}
362
+ />
363
+
364
+ <LimitsModal.Root isOpen={showLimitModal} onClose={() => setShowLimitModal(false)}>
365
+ <LimitsModal.Title>
366
+ {formatMessage({
367
+ id: 'Settings.review-workflows.list.page.workflows.limit.title',
368
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
369
+ })}
370
+ </LimitsModal.Title>
371
+
372
+ <LimitsModal.Body>
373
+ {formatMessage({
374
+ id: 'Settings.review-workflows.list.page.workflows.limit.body',
375
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
376
+ })}
377
+ </LimitsModal.Body>
378
+ </LimitsModal.Root>
379
+ </Layout.Root>
380
+ </>
381
+ );
382
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+
3
+ import { ProtectedPage } from '../../components/ProtectedPage';
4
+
5
+ import { ReviewWorkflowsListView } from './ListView';
6
+
7
+ export default function () {
8
+ return (
9
+ <ProtectedPage>
10
+ <ReviewWorkflowsListView />
11
+ </ProtectedPage>
12
+ );
13
+ }