@strapi/admin 4.10.1 → 4.10.2

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 (127) hide show
  1. package/admin/src/components/AuthenticatedApp/index.js +19 -24
  2. package/admin/src/components/DragLayer/DragLayer.js +53 -0
  3. package/admin/src/components/DragLayer/index.js +1 -0
  4. package/admin/src/components/LeftMenu/index.js +2 -2
  5. package/admin/src/components/Providers/index.js +11 -7
  6. package/admin/src/content-manager/contexts/index.js +0 -1
  7. package/admin/src/content-manager/hooks/index.js +0 -1
  8. package/admin/src/content-manager/hooks/useLazyComponents/index.js +7 -3
  9. package/admin/src/content-manager/hooks/useRelation/useRelation.js +1 -3
  10. package/admin/src/content-manager/pages/{ListSettingsView/components/CardPreview.js → App/components/CardDragPreview.js} +28 -28
  11. package/admin/src/content-manager/pages/App/components/ComponentDragPreview.js +75 -0
  12. package/admin/src/content-manager/{components/DragLayer → pages/App/components}/RelationDragPreview.js +7 -4
  13. package/admin/src/content-manager/pages/App/index.js +39 -2
  14. package/admin/src/content-manager/pages/EditSettingsView/components/ComponentFieldList.js +1 -1
  15. package/admin/src/content-manager/pages/EditSettingsView/components/DisplayedFieldButton.js +1 -1
  16. package/admin/src/content-manager/pages/EditSettingsView/components/DynamicZoneList.js +1 -1
  17. package/admin/src/content-manager/pages/EditSettingsView/components/FormModal.js +1 -1
  18. package/admin/src/content-manager/{components → pages/EditSettingsView/components}/LayoutDndProvider/index.js +3 -4
  19. package/admin/src/content-manager/pages/EditSettingsView/components/LinkToCTB.js +1 -1
  20. package/admin/src/content-manager/pages/EditSettingsView/components/ModalForm.js +1 -1
  21. package/admin/src/content-manager/pages/EditSettingsView/components/RowItemsLayout.js +1 -1
  22. package/admin/src/content-manager/pages/EditSettingsView/hooks/useLayoutDnd.js +6 -0
  23. package/admin/src/content-manager/pages/EditSettingsView/index.js +1 -1
  24. package/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js +6 -6
  25. package/admin/src/hooks/useMenu/index.js +2 -2
  26. package/admin/src/hooks/useReleaseNotification/index.js +2 -2
  27. package/admin/src/hooks/useSettingsMenu/index.js +2 -2
  28. package/admin/src/pages/Admin/Onboarding/index.js +2 -2
  29. package/admin/src/pages/App/index.js +2 -2
  30. package/admin/src/pages/HomePage/SocialLinks.js +2 -2
  31. package/admin/src/pages/MarketplacePage/index.js +2 -2
  32. package/admin/src/pages/ProfilePage/index.js +2 -2
  33. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/index.js +2 -2
  34. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/Permissions/utils/updateValues.js +3 -3
  35. package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +2 -2
  36. package/admin/src/translations/fr.json +1 -1
  37. package/build/{5563.905daa13.chunk.js → 5563.986609ed.chunk.js} +2 -2
  38. package/build/6858.56d4d528.chunk.js +50 -0
  39. package/build/{7259.b7d00cea.chunk.js → 7259.7a48aa2f.chunk.js} +1 -1
  40. package/build/7725.1633e06f.chunk.js +213 -0
  41. package/build/9703.e590889d.chunk.js +1 -0
  42. package/build/Admin-authenticatedApp.a8373103.chunk.js +79 -0
  43. package/build/{Admin_InternalErrorPage.15c6bf07.chunk.js → Admin_InternalErrorPage.96ceaae1.chunk.js} +1 -1
  44. package/build/{Admin_homePage.f9309c6d.chunk.js → Admin_homePage.9f7c0bb1.chunk.js} +5 -5
  45. package/build/{Admin_marketplace.56bc1008.chunk.js → Admin_marketplace.a60cde15.chunk.js} +5 -5
  46. package/build/{Admin_pluginsPage.f6b52ee9.chunk.js → Admin_pluginsPage.9e6fa51c.chunk.js} +1 -1
  47. package/build/{Admin_profilePage.9112cffc.chunk.js → Admin_profilePage.1b337b73.chunk.js} +2 -2
  48. package/build/{Admin_settingsPage.4604a16c.chunk.js → Admin_settingsPage.5e045f42.chunk.js} +2 -2
  49. package/build/{Upload_ConfigureTheView.eaaec495.chunk.js → Upload_ConfigureTheView.4fc648b5.chunk.js} +1 -1
  50. package/build/admin-app.9bfe4ec7.chunk.js +63 -0
  51. package/build/{admin-edit-roles-page.8a4063f7.chunk.js → admin-edit-roles-page.d0c9497b.chunk.js} +24 -24
  52. package/build/{admin-edit-users.7e14d85f.chunk.js → admin-edit-users.ba27c532.chunk.js} +2 -2
  53. package/build/{admin-roles-list.329c1f63.chunk.js → admin-roles-list.c759daa3.chunk.js} +1 -1
  54. package/build/{admin-users.d02de059.chunk.js → admin-users.ad5dd832.chunk.js} +2 -2
  55. package/build/{api-tokens-create-page.97595e12.chunk.js → api-tokens-create-page.973d2816.chunk.js} +1 -1
  56. package/build/{api-tokens-edit-page.cd36e30e.chunk.js → api-tokens-edit-page.29725c5e.chunk.js} +1 -1
  57. package/build/{api-tokens-list-page.6757c7b9.chunk.js → api-tokens-list-page.66c4fbdd.chunk.js} +2 -2
  58. package/build/{audit-logs-settings-page.19d90bda.chunk.js → audit-logs-settings-page.45cb4fb5.chunk.js} +1 -1
  59. package/build/content-manager.d28eb183.chunk.js +1111 -0
  60. package/build/{content-type-builder-list-view.9c2c020c.chunk.js → content-type-builder-list-view.5ff685ec.chunk.js} +1 -1
  61. package/build/{content-type-builder.68af11d2.chunk.js → content-type-builder.4737a30c.chunk.js} +3 -3
  62. package/build/{email-settings-page.1095e1ab.chunk.js → email-settings-page.dc07d518.chunk.js} +1 -1
  63. package/build/{fr-json.5947cf63.chunk.js → fr-json.73494bf5.chunk.js} +1 -1
  64. package/build/{i18n-settings-page.d95b32df.chunk.js → i18n-settings-page.8219dd99.chunk.js} +1 -1
  65. package/build/index.html +1 -1
  66. package/build/main.41970e4c.js +2597 -0
  67. package/build/review-workflows-settings.7c0b4e73.chunk.js +61 -0
  68. package/build/{runtime~main.d4c8d6a2.js → runtime~main.c20330f1.js} +1 -1
  69. package/build/{sso-settings-page.1dd4886e.chunk.js → sso-settings-page.f44d95d8.chunk.js} +1 -1
  70. package/build/{transfer-tokens-create-page.ec2ca215.chunk.js → transfer-tokens-create-page.170acee6.chunk.js} +1 -1
  71. package/build/{transfer-tokens-edit-page.22bf28e5.chunk.js → transfer-tokens-edit-page.6cf23295.chunk.js} +1 -1
  72. package/build/{transfer-tokens-list-page.cf8c77f2.chunk.js → transfer-tokens-list-page.c3fec4c1.chunk.js} +2 -2
  73. package/build/{upload-settings.945fdcfa.chunk.js → upload-settings.dd2d987c.chunk.js} +1 -1
  74. package/build/{upload.a86b1054.chunk.js → upload.c8479232.chunk.js} +1 -1
  75. package/build/{users-advanced-settings-page.5b5a9baa.chunk.js → users-advanced-settings-page.c36cfd59.chunk.js} +1 -1
  76. package/build/{users-email-settings-page.e5506eb4.chunk.js → users-email-settings-page.2716ce8e.chunk.js} +5 -5
  77. package/build/{users-providers-settings-page.e32089c2.chunk.js → users-providers-settings-page.0d6304a5.chunk.js} +1 -1
  78. package/build/{users-roles-settings-page.20656f92.chunk.js → users-roles-settings-page.eeb3a339.chunk.js} +1 -1
  79. package/build/{webhook-edit-page.a3b62049.chunk.js → webhook-edit-page.f4db86f3.chunk.js} +2 -2
  80. package/build/{webhook-list-page.ca38eeef.chunk.js → webhook-list-page.30d73114.chunk.js} +1 -1
  81. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStageEE.js +11 -5
  82. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +7 -3
  83. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +16 -2
  84. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +34 -25
  85. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +11 -0
  86. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/StageDragPreview/StageDragPreview.js +45 -0
  87. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/StageDragPreview/index.js +1 -0
  88. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +287 -63
  89. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/OptionColor/OptionColor.js +27 -0
  90. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/OptionColor/index.js +1 -0
  91. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/SingleValueColor/SingleValueColor.js +31 -0
  92. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/SingleValueColor/index.js +1 -0
  93. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +2 -1
  94. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +26 -0
  95. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +36 -2
  96. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/colors.js +33 -0
  97. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +9 -0
  98. package/ee/server/constants/workflows.js +1 -0
  99. package/ee/server/content-types/workflow-stage/index.js +7 -0
  100. package/ee/server/controllers/authentication/middlewares.js +1 -1
  101. package/ee/server/migrations/review-workflows-stages-color.js +20 -0
  102. package/ee/server/register.js +2 -0
  103. package/ee/server/services/review-workflows/review-workflows.js +1 -1
  104. package/ee/server/services/review-workflows/stages.js +1 -1
  105. package/ee/server/utils/persisted-tables.js +3 -0
  106. package/ee/server/validation/review-workflows.js +7 -1
  107. package/jest.config.front.js +1 -0
  108. package/package.json +10 -10
  109. package/admin/src/components/AutoReloadOverlayBlockerProvider/Blocker.js +0 -95
  110. package/admin/src/components/AutoReloadOverlayBlockerProvider/Overlay.js +0 -42
  111. package/admin/src/components/AutoReloadOverlayBlockerProvider/index.js +0 -99
  112. package/admin/src/components/Notifications/Notification/index.js +0 -159
  113. package/admin/src/components/Notifications/index.js +0 -46
  114. package/admin/src/components/Notifications/reducer.js +0 -47
  115. package/admin/src/components/OverlayBlocker/index.js +0 -67
  116. package/admin/src/content-manager/components/DragLayer/ComponentDragPreview.js +0 -83
  117. package/admin/src/content-manager/components/DragLayer/index.js +0 -85
  118. package/admin/src/content-manager/contexts/LayoutDnd.js +0 -5
  119. package/admin/src/content-manager/hooks/useCallbackRef.js +0 -23
  120. package/admin/src/content-manager/hooks/useLayoutDnd.js +0 -6
  121. package/admin/src/content-manager/pages/ListSettingsView/utils/ellipsisCardTitle.js +0 -7
  122. package/build/8694.6522968d.chunk.js +0 -247
  123. package/build/Admin-authenticatedApp.5562aedc.chunk.js +0 -79
  124. package/build/admin-app.014adc27.chunk.js +0 -110
  125. package/build/content-manager.84f81966.chunk.js +0 -1130
  126. package/build/main.841e0dcb.js +0 -2280
  127. package/build/review-workflows-settings.f7890c40.chunk.js +0 -106
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
1
+ import * as React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { useField } from 'formik';
4
4
  import { useIntl } from 'react-intl';
@@ -7,84 +7,308 @@ import {
7
7
  Accordion,
8
8
  AccordionToggle,
9
9
  AccordionContent,
10
+ Box,
11
+ Field,
12
+ FieldLabel,
13
+ FieldError,
14
+ Flex,
10
15
  Grid,
11
16
  GridItem,
12
17
  IconButton,
13
18
  TextInput,
19
+ VisuallyHidden,
14
20
  } from '@strapi/design-system';
15
- import { useTracking } from '@strapi/helper-plugin';
16
- import { Trash } from '@strapi/icons';
21
+ import { ReactSelect, useTracking } from '@strapi/helper-plugin';
22
+ import { Drag, Trash } from '@strapi/icons';
17
23
 
18
- import { deleteStage, updateStage } from '../../../actions';
24
+ import { deleteStage, updateStagePosition, updateStage } from '../../../actions';
25
+ import { getAvailableStageColors } from '../../../utils/colors';
26
+ import { OptionColor } from './components/OptionColor';
27
+ import { SingleValueColor } from './components/SingleValueColor';
28
+ import { useDragAndDrop } from '../../../../../../../../../admin/src/content-manager/hooks';
29
+ import { composeRefs } from '../../../../../../../../../admin/src/content-manager/utils';
30
+ import { DRAG_DROP_TYPES } from '../../../constants';
19
31
 
20
- function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) {
32
+ const AVAILABLE_COLORS = getAvailableStageColors();
33
+
34
+ function StageDropPreview() {
35
+ return (
36
+ <Box
37
+ background="primary100"
38
+ borderStyle="dashed"
39
+ borderColor="primary600"
40
+ borderWidth="1px"
41
+ display="block"
42
+ hasRadius
43
+ padding={6}
44
+ shadow="tableShadow"
45
+ />
46
+ );
47
+ }
48
+
49
+ export function Stage({
50
+ id,
51
+ index,
52
+ canDelete,
53
+ canReorder,
54
+ isOpen: isOpenDefault = false,
55
+ stagesCount,
56
+ }) {
57
+ /**
58
+ *
59
+ * @param {number} index
60
+ * @returns {string}
61
+ */
62
+ const getItemPos = (index) => `${index + 1} of ${stagesCount}`;
63
+
64
+ /**
65
+ *
66
+ * @param {number} index
67
+ * @returns {void}
68
+ */
69
+ const handleGrabStage = (index) => {
70
+ setLiveText(
71
+ formatMessage(
72
+ {
73
+ id: 'dnd.grab-item',
74
+ defaultMessage: `{item}, grabbed. Current position in list: {position}. Press up and down arrow to change position, Spacebar to drop, Escape to cancel.`,
75
+ },
76
+ {
77
+ item: nameField.value,
78
+ position: getItemPos(index),
79
+ }
80
+ )
81
+ );
82
+ };
83
+
84
+ /**
85
+ *
86
+ * @param {number} index
87
+ * @returns {void}
88
+ */
89
+ const handleDropStage = (index) => {
90
+ setLiveText(
91
+ formatMessage(
92
+ {
93
+ id: 'dnd.drop-item',
94
+ defaultMessage: `{item}, dropped. Final position in list: {position}.`,
95
+ },
96
+ {
97
+ item: nameField.value,
98
+ position: getItemPos(index),
99
+ }
100
+ )
101
+ );
102
+ };
103
+
104
+ /**
105
+ *
106
+ * @param {number} index
107
+ * @returns {void}
108
+ */
109
+ const handleCancelDragStage = () => {
110
+ setLiveText(
111
+ formatMessage(
112
+ {
113
+ id: 'dnd.cancel-item',
114
+ defaultMessage: '{item}, dropped. Re-order cancelled.',
115
+ },
116
+ {
117
+ item: nameField.value,
118
+ }
119
+ )
120
+ );
121
+ };
122
+
123
+ const handleMoveStage = (newIndex, oldIndex) => {
124
+ setLiveText(
125
+ formatMessage(
126
+ {
127
+ id: 'dnd.reorder',
128
+ defaultMessage: '{item}, moved. New position in list: {position}.',
129
+ },
130
+ {
131
+ item: nameField.value,
132
+ position: getItemPos(newIndex),
133
+ }
134
+ )
135
+ );
136
+
137
+ dispatch(updateStagePosition(oldIndex, newIndex));
138
+ };
139
+
140
+ const [liveText, setLiveText] = React.useState(null);
21
141
  const { formatMessage } = useIntl();
22
142
  const { trackUsage } = useTracking();
23
- const [isOpen, setIsOpen] = useState(isOpenDefault);
24
- const fieldIdentifier = `stages.${index}.name`;
25
- const [field, meta] = useField(fieldIdentifier);
26
143
  const dispatch = useDispatch();
144
+ const [isOpen, setIsOpen] = React.useState(isOpenDefault);
145
+ const [nameField, nameMeta] = useField(`stages.${index}.name`);
146
+ const [colorField, colorMeta] = useField(`stages.${index}.color`);
147
+ const [{ handlerId, isDragging, handleKeyDown }, stageRef, dropRef, dragRef] = useDragAndDrop(
148
+ canReorder,
149
+ {
150
+ index,
151
+ item: {
152
+ name: nameField.value,
153
+ },
154
+ onGrabItem: handleGrabStage,
155
+ onDropItem: handleDropStage,
156
+ onMoveItem: handleMoveStage,
157
+ onCancel: handleCancelDragStage,
158
+ type: DRAG_DROP_TYPES.STAGE,
159
+ }
160
+ );
161
+
162
+ const composedRef = composeRefs(stageRef, dropRef);
163
+
164
+ const colorOptions = AVAILABLE_COLORS.map(({ hex, name }) => ({
165
+ value: hex,
166
+ label: formatMessage(
167
+ {
168
+ id: 'Settings.review-workflows.stage.color.name',
169
+ defaultMessage: '{name}',
170
+ },
171
+ { name }
172
+ ),
173
+ color: hex,
174
+ }));
175
+ // TODO: the .toUpperCase() conversion can be removed once the hex code is normalized in
176
+ // the admin API
177
+ const colorValue = colorOptions.find(({ value }) => value === colorField.value.toUpperCase());
27
178
 
28
179
  return (
29
- <Accordion
30
- size="S"
31
- variant="primary"
32
- onToggle={() => {
33
- setIsOpen(!isOpen);
34
-
35
- if (!isOpen) {
36
- trackUsage('willEditStage');
37
- }
38
- }}
39
- expanded={isOpen}
40
- shadow="tableShadow"
41
- >
42
- <AccordionToggle
43
- title={name}
44
- togglePosition="left"
45
- action={
46
- canDelete ? (
47
- <IconButton
48
- background="transparent"
49
- noBorder
50
- onClick={() => dispatch(deleteStage(id))}
51
- label={formatMessage({
52
- id: 'Settings.review-workflows.stage.delete',
53
- defaultMessage: 'Delete stage',
54
- })}
55
- icon={<Trash />}
56
- />
57
- ) : null
58
- }
59
- />
60
- <AccordionContent padding={6} background="neutral0" hasRadius>
61
- <Grid gap={4}>
62
- <GridItem col={6}>
63
- <TextInput
64
- {...field}
65
- id={fieldIdentifier}
66
- value={name}
67
- label={formatMessage({
68
- id: 'Settings.review-workflows.stage.name.label',
69
- defaultMessage: 'Stage name',
70
- })}
71
- error={meta.error ?? false}
72
- onChange={(event) => {
73
- field.onChange(event);
74
- dispatch(updateStage(id, { name: event.target.value }));
75
- }}
76
- />
77
- </GridItem>
78
- </Grid>
79
- </AccordionContent>
80
- </Accordion>
180
+ <Box ref={composedRef}>
181
+ {liveText && <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>}
182
+
183
+ {isDragging ? (
184
+ <StageDropPreview />
185
+ ) : (
186
+ <Accordion
187
+ size="S"
188
+ variant="primary"
189
+ onToggle={() => {
190
+ setIsOpen(!isOpen);
191
+
192
+ if (!isOpen) {
193
+ trackUsage('willEditStage');
194
+ }
195
+ }}
196
+ expanded={isOpen}
197
+ shadow="tableShadow"
198
+ >
199
+ <AccordionToggle
200
+ title={nameField.value}
201
+ togglePosition="left"
202
+ action={
203
+ <>
204
+ {canDelete && (
205
+ <IconButton
206
+ background="transparent"
207
+ icon={<Trash />}
208
+ label={formatMessage({
209
+ id: 'Settings.review-workflows.stage.delete',
210
+ defaultMessage: 'Delete stage',
211
+ })}
212
+ noBorder
213
+ onClick={() => dispatch(deleteStage(id))}
214
+ />
215
+ )}
216
+
217
+ <IconButton
218
+ background="transparent"
219
+ forwardedAs="div"
220
+ role="button"
221
+ noBorder
222
+ tabIndex={0}
223
+ data-handler-id={handlerId}
224
+ ref={dragRef}
225
+ label={formatMessage({
226
+ id: 'Settings.review-workflows.stage.drag',
227
+ defaultMessage: 'Drag',
228
+ })}
229
+ onClick={(e) => e.stopPropagation()}
230
+ onKeyDown={handleKeyDown}
231
+ >
232
+ <Drag />
233
+ </IconButton>
234
+ </>
235
+ }
236
+ />
237
+ <AccordionContent padding={6} background="neutral0" hasRadius>
238
+ <Grid gap={4}>
239
+ <GridItem col={6}>
240
+ <TextInput
241
+ {...nameField}
242
+ id={nameField.name}
243
+ label={formatMessage({
244
+ id: 'Settings.review-workflows.stage.name.label',
245
+ defaultMessage: 'Stage name',
246
+ })}
247
+ error={nameMeta.error ?? false}
248
+ onChange={(event) => {
249
+ nameField.onChange(event);
250
+ dispatch(updateStage(id, { name: event.target.value }));
251
+ }}
252
+ required
253
+ />
254
+ </GridItem>
255
+
256
+ <GridItem col={6}>
257
+ <Field
258
+ error={colorMeta?.error ?? false}
259
+ name={colorField.name}
260
+ id={colorField.name}
261
+ required
262
+ >
263
+ <Flex direction="column" gap={1} alignItems="stretch">
264
+ <FieldLabel>
265
+ {formatMessage({
266
+ id: 'content-manager.reviewWorkflows.stage.color',
267
+ defaultMessage: 'Color',
268
+ })}
269
+ </FieldLabel>
270
+
271
+ <ReactSelect
272
+ components={{ Option: OptionColor, SingleValue: SingleValueColor }}
273
+ error={colorMeta?.error}
274
+ inputId={colorField.name}
275
+ name={colorField.name}
276
+ options={colorOptions}
277
+ onChange={({ value }) => {
278
+ colorField.onChange({ target: { value } });
279
+ dispatch(updateStage(id, { color: value }));
280
+ }}
281
+ // If no color was found in all the valid theme colors it means a user
282
+ // has set a custom value e.g. through the content API. In that case we
283
+ // display the custom color and a "Custom" label.
284
+ value={
285
+ colorValue ?? {
286
+ value: colorField.value,
287
+ label: formatMessage({
288
+ id: 'Settings.review-workflows.stage.color.name.custom',
289
+ defaultMessage: 'Custom',
290
+ }),
291
+ color: colorField.value,
292
+ }
293
+ }
294
+ />
295
+
296
+ <FieldError />
297
+ </Flex>
298
+ </Field>
299
+ </GridItem>
300
+ </Grid>
301
+ </AccordionContent>
302
+ </Accordion>
303
+ )}
304
+ </Box>
81
305
  );
82
306
  }
83
307
 
84
- export { Stage };
85
-
86
308
  Stage.propTypes = PropTypes.shape({
87
309
  id: PropTypes.number.isRequired,
88
- name: PropTypes.string.isRequired,
310
+ color: PropTypes.string.isRequired,
89
311
  canDelete: PropTypes.bool.isRequired,
312
+ canReorder: PropTypes.bool.isRequired,
313
+ stagesCount: PropTypes.number.isRequired,
90
314
  }).isRequired;
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { components } from 'react-select';
4
+ import { Flex, Typography } from '@strapi/design-system';
5
+
6
+ export function OptionColor({ children, ...props }) {
7
+ const { color } = props.data;
8
+
9
+ return (
10
+ <components.Option {...props}>
11
+ <Flex alignItems="center" gap={2}>
12
+ <Flex height={2} background={color} hasRadius shrink={0} width={2} />
13
+
14
+ <Typography textColor="neutral800" ellipsis>
15
+ {children}
16
+ </Typography>
17
+ </Flex>
18
+ </components.Option>
19
+ );
20
+ }
21
+
22
+ OptionColor.propTypes = {
23
+ children: PropTypes.node.isRequired,
24
+ data: PropTypes.shape({
25
+ color: PropTypes.string,
26
+ }).isRequired,
27
+ };
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { components } from 'react-select';
4
+ import { Flex, Typography } from '@strapi/design-system';
5
+
6
+ export function SingleValueColor({ children, ...props }) {
7
+ const { color } = props.data;
8
+
9
+ return (
10
+ <components.SingleValue {...props}>
11
+ <Flex alignItems="center" gap={2}>
12
+ <Flex height={2} background={color} hasRadius shrink={0} width={2} />
13
+
14
+ <Typography textColor="neutral800" ellipsis>
15
+ {children}
16
+ </Typography>
17
+ </Flex>
18
+ </components.SingleValue>
19
+ );
20
+ }
21
+
22
+ SingleValueColor.defaultProps = {
23
+ children: null,
24
+ };
25
+
26
+ SingleValueColor.propTypes = {
27
+ children: PropTypes.node,
28
+ data: PropTypes.shape({
29
+ color: PropTypes.string,
30
+ }).isRequired,
31
+ };
@@ -45,11 +45,12 @@ function Stages({ stages }) {
45
45
  return (
46
46
  <Box key={`stage-${id}`} as="li">
47
47
  <Stage
48
- {...stage}
49
48
  id={id}
50
49
  index={index}
51
50
  canDelete={stages.length > 1}
52
51
  isOpen={!stage.id}
52
+ canReorder={stages.length > 1}
53
+ stagesCount={stages.length}
53
54
  />
54
55
  </Box>
55
56
  );
@@ -1,6 +1,32 @@
1
+ import { lightTheme } from '@strapi/design-system';
2
+
1
3
  export const REDUX_NAMESPACE = 'settings_review-workflows';
2
4
 
3
5
  export const ACTION_SET_WORKFLOWS = `Settings/Review_Workflows/SET_WORKFLOWS`;
4
6
  export const ACTION_DELETE_STAGE = `Settings/Review_Workflows/WORKFLOW_DELETE_STAGE`;
5
7
  export const ACTION_ADD_STAGE = `Settings/Review_Workflows/WORKFLOW_ADD_STAGE`;
6
8
  export const ACTION_UPDATE_STAGE = `Settings/Review_Workflows/WORKFLOW_UPDATE_STAGE`;
9
+ export const ACTION_UPDATE_STAGE_POSITION = `Settings/Review_Workflows/WORKFLOW_UPDATE_STAGE_POSITION`;
10
+
11
+ export const STAGE_COLORS = {
12
+ primary600: 'Blue',
13
+ primary200: 'Lilac',
14
+ alternative600: 'Violet',
15
+ alternative200: 'Lavender',
16
+ success600: 'Green',
17
+ success200: 'Pale Green',
18
+ danger500: 'Cherry',
19
+ danger200: 'Pink',
20
+ warning600: 'Orange',
21
+ warning200: 'Yellow',
22
+ secondary600: 'Teal',
23
+ secondary200: 'Baby Blue',
24
+ neutral400: 'Gray',
25
+ neutral0: 'White',
26
+ };
27
+
28
+ export const STAGE_COLOR_DEFAULT = lightTheme.colors.primary600;
29
+
30
+ export const DRAG_DROP_TYPES = {
31
+ STAGE: 'stage',
32
+ };
@@ -6,6 +6,8 @@ import {
6
6
  ACTION_DELETE_STAGE,
7
7
  ACTION_ADD_STAGE,
8
8
  ACTION_UPDATE_STAGE,
9
+ ACTION_UPDATE_STAGE_POSITION,
10
+ STAGE_COLOR_DEFAULT,
9
11
  } from '../constants';
10
12
 
11
13
  export const initialState = {
@@ -29,8 +31,18 @@ export function reducer(state = initialState, action) {
29
31
 
30
32
  draft.status = status;
31
33
 
32
- if (workflows) {
33
- const defaultWorkflow = workflows[0];
34
+ if (workflows?.length > 0) {
35
+ let defaultWorkflow = workflows[0];
36
+
37
+ // A safety net in case a stage does not have a color assigned;
38
+ // this normallly should not happen
39
+ defaultWorkflow = {
40
+ ...defaultWorkflow,
41
+ stages: defaultWorkflow.stages.map((stage) => ({
42
+ ...stage,
43
+ color: stage?.color ?? STAGE_COLOR_DEFAULT,
44
+ })),
45
+ };
34
46
 
35
47
  draft.serverState.workflows = workflows;
36
48
  draft.serverState.currentWorkflow = defaultWorkflow;
@@ -69,6 +81,7 @@ export function reducer(state = initialState, action) {
69
81
 
70
82
  draft.clientState.currentWorkflow.data.stages.push({
71
83
  ...payload,
84
+ color: payload?.color ?? STAGE_COLOR_DEFAULT,
72
85
  __temp_key__: newTempKey,
73
86
  });
74
87
 
@@ -91,6 +104,27 @@ export function reducer(state = initialState, action) {
91
104
  break;
92
105
  }
93
106
 
107
+ case ACTION_UPDATE_STAGE_POSITION: {
108
+ const {
109
+ currentWorkflow: {
110
+ data: { stages },
111
+ },
112
+ } = state.clientState;
113
+ const { newIndex, oldIndex } = payload;
114
+
115
+ if (newIndex >= 0 && newIndex < stages.length) {
116
+ const stage = stages[oldIndex];
117
+ let newStages = [...stages];
118
+
119
+ newStages.splice(oldIndex, 1);
120
+ newStages.splice(newIndex, 0, stage);
121
+
122
+ draft.clientState.currentWorkflow.data.stages = newStages;
123
+ }
124
+
125
+ break;
126
+ }
127
+
94
128
  default:
95
129
  break;
96
130
  }
@@ -0,0 +1,33 @@
1
+ import { lightTheme } from '@strapi/design-system';
2
+
3
+ import { STAGE_COLORS } from '../constants';
4
+
5
+ export function getStageColorByHex(hex) {
6
+ // there are multiple colors with the same hex code in the design tokens. In order to find
7
+ // the correct one we have to find all matching colors and then check, which ones are usable
8
+ // for stages.
9
+ const themeColors = Object.entries(lightTheme.colors).filter(([, value]) => value === hex);
10
+ const themeColorName = themeColors.reduce((acc, [name]) => {
11
+ if (STAGE_COLORS?.[name]) {
12
+ acc = name;
13
+ }
14
+
15
+ return acc;
16
+ }, null);
17
+
18
+ if (!themeColorName) {
19
+ return null;
20
+ }
21
+
22
+ return {
23
+ themeColorName,
24
+ name: STAGE_COLORS[themeColorName],
25
+ };
26
+ }
27
+
28
+ export function getAvailableStageColors() {
29
+ return Object.entries(STAGE_COLORS).map(([themeColorName, name]) => ({
30
+ hex: lightTheme.colors[themeColorName].toUpperCase(),
31
+ name,
32
+ }));
33
+ }
@@ -19,6 +19,15 @@ export function getWorkflowValidationSchema({ formatMessage }) {
19
19
  defaultMessage: 'Name can not be longer than 255 characters',
20
20
  })
21
21
  ),
22
+ color: yup
23
+ .string()
24
+ .required(
25
+ formatMessage({
26
+ id: 'Settings.review-workflows.validation.stage.color',
27
+ defaultMessage: 'Color is required',
28
+ })
29
+ )
30
+ .matches(/^#(?:[0-9a-fA-F]{3}){1,2}$/i),
22
31
  })
23
32
  ),
24
33
  });
@@ -4,5 +4,6 @@
4
4
  module.exports = {
5
5
  WORKFLOW_MODEL_UID: 'admin::workflow',
6
6
  STAGE_MODEL_UID: 'admin::workflow-stage',
7
+ STAGE_DEFAULT_COLOR: '#4945FF',
7
8
  ENTITY_STAGE_ATTRIBUTE: 'strapi_reviewWorkflows_stage',
8
9
  };
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { STAGE_DEFAULT_COLOR } = require('../../constants/workflows');
4
+
3
5
  module.exports = {
4
6
  schema: {
5
7
  collectionName: 'strapi_workflows_stages',
@@ -24,6 +26,11 @@ module.exports = {
24
26
  type: 'string',
25
27
  configurable: false,
26
28
  },
29
+ color: {
30
+ type: 'string',
31
+ configurable: false,
32
+ default: STAGE_DEFAULT_COLOR,
33
+ },
27
34
  workflow: {
28
35
  type: 'relation',
29
36
  target: 'admin::workflow',
@@ -95,7 +95,7 @@ const redirectWithAuth = (ctx) => {
95
95
  params: { provider },
96
96
  } = ctx;
97
97
  const redirectUrls = utils.getPrefixedRedirectUrls();
98
- const domain = strapi.config.get('server.admin.auth.domain');
98
+ const domain = strapi.config.get('admin.auth.domain');
99
99
  const { user } = ctx.state;
100
100
 
101
101
  const jwt = getService('token').createJwtToken(user);
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { STAGE_DEFAULT_COLOR } = require('../constants/workflows');
4
+
5
+ async function migrateReviewWorkflowStagesColor({ oldContentTypes, contentTypes }) {
6
+ // Look for CT's color attribute
7
+ const hadColor = !!oldContentTypes?.['admin::workflow-stage']?.attributes?.color;
8
+ const hasColor = !!contentTypes['admin::workflow-stage']?.attributes?.color;
9
+
10
+ // Add the default stage color if color attribute was added
11
+ if (!hadColor || hasColor) {
12
+ await strapi.query('admin::workflow-stage').updateMany({
13
+ data: {
14
+ color: STAGE_DEFAULT_COLOR,
15
+ },
16
+ });
17
+ }
18
+ }
19
+
20
+ module.exports = migrateReviewWorkflowStagesColor;
@@ -3,6 +3,7 @@
3
3
  const { features } = require('@strapi/strapi/lib/utils/ee');
4
4
  const executeCERegister = require('../../server/register');
5
5
  const migrateAuditLogsTable = require('./migrations/audit-logs-table');
6
+ const migrateReviewWorkflowStagesColor = require('./migrations/review-workflows-stages-color');
6
7
  const createAuditLogsService = require('./services/audit-logs');
7
8
  const reviewWorkflowsMiddlewares = require('./middlewares/review-workflows');
8
9
  const { getService } = require('./utils');
@@ -17,6 +18,7 @@ module.exports = async ({ strapi }) => {
17
18
  await auditLogsService.register();
18
19
  }
19
20
  if (features.isEnabled('review-workflows')) {
21
+ strapi.hook('strapi::content-types.afterSync').register(migrateReviewWorkflowStagesColor);
20
22
  const reviewWorkflowService = getService('review-workflows');
21
23
 
22
24
  reviewWorkflowsMiddlewares.contentTypeMiddleware(strapi);