@strapi/review-workflows 5.8.1 → 5.10.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 (77) hide show
  1. package/dist/admin/chunks/Layout-27uslS7w.mjs +265 -0
  2. package/dist/admin/chunks/Layout-27uslS7w.mjs.map +1 -0
  3. package/dist/admin/chunks/Layout-B4l91S3p.js +290 -0
  4. package/dist/admin/chunks/Layout-B4l91S3p.js.map +1 -0
  5. package/dist/admin/chunks/en-BixG7IBu.mjs +14 -0
  6. package/dist/admin/chunks/en-BixG7IBu.mjs.map +1 -0
  7. package/dist/admin/chunks/en-C6SESe-Y.js +16 -0
  8. package/dist/admin/chunks/en-C6SESe-Y.js.map +1 -0
  9. package/dist/admin/chunks/id-DnRGfGvc.js +1442 -0
  10. package/dist/admin/chunks/id-DnRGfGvc.js.map +1 -0
  11. package/dist/admin/chunks/id-LAMc9ydb.mjs +1420 -0
  12. package/dist/admin/chunks/id-LAMc9ydb.mjs.map +1 -0
  13. package/dist/admin/chunks/index-BiGArvbK.mjs +271 -0
  14. package/dist/admin/chunks/index-BiGArvbK.mjs.map +1 -0
  15. package/dist/admin/chunks/index-C5IeISWp.js +960 -0
  16. package/dist/admin/chunks/index-C5IeISWp.js.map +1 -0
  17. package/dist/admin/chunks/index-CSYOmjZt.mjs +926 -0
  18. package/dist/admin/chunks/index-CSYOmjZt.mjs.map +1 -0
  19. package/dist/admin/chunks/index-DZpcSUvc.js +293 -0
  20. package/dist/admin/chunks/index-DZpcSUvc.js.map +1 -0
  21. package/dist/admin/chunks/purchase-review-workflows-CEP8kvxZ.mjs +53 -0
  22. package/dist/admin/chunks/purchase-review-workflows-CEP8kvxZ.mjs.map +1 -0
  23. package/dist/admin/chunks/purchase-review-workflows-Drlxm8yP.js +55 -0
  24. package/dist/admin/chunks/purchase-review-workflows-Drlxm8yP.js.map +1 -0
  25. package/dist/admin/chunks/router-CrN8UbpP.js +30 -0
  26. package/dist/admin/chunks/router-CrN8UbpP.js.map +1 -0
  27. package/dist/admin/chunks/router-Z3AR7XZ7.mjs +28 -0
  28. package/dist/admin/chunks/router-Z3AR7XZ7.mjs.map +1 -0
  29. package/dist/admin/index.js +17 -2
  30. package/dist/admin/index.js.map +1 -1
  31. package/dist/admin/index.mjs +12 -4
  32. package/dist/admin/index.mjs.map +1 -1
  33. package/dist/server/index.js +2109 -7839
  34. package/dist/server/index.js.map +1 -1
  35. package/dist/server/index.mjs +2114 -7847
  36. package/dist/server/index.mjs.map +1 -1
  37. package/dist/server/src/services/document-service-middleware.d.ts +1 -1
  38. package/dist/server/src/services/document-service-middleware.d.ts.map +1 -1
  39. package/dist/shared/contracts/review-workflows.d.ts +0 -1
  40. package/package.json +14 -10
  41. package/dist/_chunks/Layout-C3IORH2n.js +0 -250
  42. package/dist/_chunks/Layout-C3IORH2n.js.map +0 -1
  43. package/dist/_chunks/Layout-DNKR5bym.mjs +0 -233
  44. package/dist/_chunks/Layout-DNKR5bym.mjs.map +0 -1
  45. package/dist/_chunks/en-CYgjfSep.js +0 -15
  46. package/dist/_chunks/en-CYgjfSep.js.map +0 -1
  47. package/dist/_chunks/en-D9dxziEb.mjs +0 -15
  48. package/dist/_chunks/en-D9dxziEb.mjs.map +0 -1
  49. package/dist/_chunks/id-C9Ku9Br9.mjs +0 -1274
  50. package/dist/_chunks/id-C9Ku9Br9.mjs.map +0 -1
  51. package/dist/_chunks/id-oOE1bYls.js +0 -1293
  52. package/dist/_chunks/id-oOE1bYls.js.map +0 -1
  53. package/dist/_chunks/index-ByXbOW-R.mjs +0 -815
  54. package/dist/_chunks/index-ByXbOW-R.mjs.map +0 -1
  55. package/dist/_chunks/index-CmHHjN95.js +0 -231
  56. package/dist/_chunks/index-CmHHjN95.js.map +0 -1
  57. package/dist/_chunks/index-CyhaJuJG.mjs +0 -213
  58. package/dist/_chunks/index-CyhaJuJG.mjs.map +0 -1
  59. package/dist/_chunks/index-DMT27jNE.js +0 -832
  60. package/dist/_chunks/index-DMT27jNE.js.map +0 -1
  61. package/dist/_chunks/purchase-review-workflows-BxoDFxQ5.js +0 -52
  62. package/dist/_chunks/purchase-review-workflows-BxoDFxQ5.js.map +0 -1
  63. package/dist/_chunks/purchase-review-workflows-DyFV_H0I.mjs +0 -52
  64. package/dist/_chunks/purchase-review-workflows-DyFV_H0I.mjs.map +0 -1
  65. package/dist/_chunks/router-BPl2HZMq.mjs +0 -24
  66. package/dist/_chunks/router-BPl2HZMq.mjs.map +0 -1
  67. package/dist/_chunks/router-vDfGt9bq.js +0 -24
  68. package/dist/_chunks/router-vDfGt9bq.js.map +0 -1
  69. /package/dist/admin/src/routes/content-manager/{[model] → model}/components/AssigneeFilter.d.ts +0 -0
  70. /package/dist/admin/src/routes/content-manager/{[model] → model}/components/StageFilter.d.ts +0 -0
  71. /package/dist/admin/src/routes/content-manager/{[model] → model}/components/TableColumns.d.ts +0 -0
  72. /package/dist/admin/src/routes/content-manager/{[model] → model}/configure/constants.d.ts +0 -0
  73. /package/dist/admin/src/routes/content-manager/{[model] → model}/constants.d.ts +0 -0
  74. /package/dist/admin/src/routes/content-manager/{[model]/[id] → model/id}/components/AssigneeSelect.d.ts +0 -0
  75. /package/dist/admin/src/routes/content-manager/{[model]/[id] → model/id}/components/Panel.d.ts +0 -0
  76. /package/dist/admin/src/routes/content-manager/{[model]/[id] → model/id}/components/StageSelect.d.ts +0 -0
  77. /package/dist/admin/src/routes/content-manager/{[model]/[id] → model/id}/components/constants.d.ts +0 -0
@@ -0,0 +1,1420 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { useTracking, useForm, useField, InputRenderer as InputRenderer$1, useNotification, ConfirmDialog, Page, useAPIErrorHandler, useRBAC, Form, BackButton } from '@strapi/admin/strapi-admin';
4
+ import { useLicenseLimits } from '@strapi/admin/strapi-admin/ee';
5
+ import { Box, Typography, Flex, Accordion, MenuItem, Menu, MultiSelectOption, useComposedRefs, VisuallyHidden, IconButton, Grid, Field, SingleSelect, SingleSelectOption, TextInput, MultiSelect, MultiSelectGroup, Dialog, useCollator, Button } from '@strapi/design-system';
6
+ import { PlusCircle, More, Drag, EyeStriked, Duplicate, Check } from '@strapi/icons';
7
+ import { generateNKeysBetween } from 'fractional-indexing';
8
+ import { useIntl } from 'react-intl';
9
+ import { useParams, useNavigate } from 'react-router-dom';
10
+ import * as yup from 'yup';
11
+ import { r as reviewWorkflowsApi, A as AVAILABLE_COLORS, g as getStageColorByHex, u as useGetContentTypesQuery, a as useTypedSelector, C as CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME, b as CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME, L as LimitsModal, i as isBaseQueryError } from './index-CSYOmjZt.mjs';
12
+ import { D as DRAG_DROP_TYPES, u as useReviewWorkflows, a as DragLayerRendered, H as Header, R as Root } from './Layout-27uslS7w.mjs';
13
+ import { getEmptyImage } from 'react-dnd-html5-backend';
14
+ import { styled } from 'styled-components';
15
+ import { useDrop, useDrag } from 'react-dnd';
16
+ import '@strapi/content-manager/strapi-admin';
17
+ import 'react-redux';
18
+
19
+ const adminApi = reviewWorkflowsApi.injectEndpoints({
20
+ endpoints (builder) {
21
+ return {
22
+ getAdminRoles: builder.query({
23
+ query: ()=>({
24
+ url: `/admin/roles`,
25
+ method: 'GET'
26
+ }),
27
+ transformResponse: (res)=>{
28
+ return res.data;
29
+ }
30
+ })
31
+ };
32
+ }
33
+ });
34
+ const { useGetAdminRolesQuery } = adminApi;
35
+
36
+ /**
37
+ * Utility hook designed to implement keyboard accessibile drag and drop by
38
+ * returning an onKeyDown handler to be passed to the drag icon button.
39
+ *
40
+ * @internal - You should use `useDragAndDrop` instead.
41
+ */ const useKeyboardDragAndDrop = (active, index, { onCancel, onDropItem, onGrabItem, onMoveItem })=>{
42
+ const [isSelected, setIsSelected] = React.useState(false);
43
+ const handleMove = (movement)=>{
44
+ if (!isSelected) {
45
+ return;
46
+ }
47
+ if (typeof index === 'number' && onMoveItem) {
48
+ if (movement === 'UP') {
49
+ onMoveItem(index - 1, index);
50
+ } else if (movement === 'DOWN') {
51
+ onMoveItem(index + 1, index);
52
+ }
53
+ }
54
+ };
55
+ const handleDragClick = ()=>{
56
+ if (isSelected) {
57
+ if (onDropItem) {
58
+ onDropItem(index);
59
+ }
60
+ setIsSelected(false);
61
+ } else {
62
+ if (onGrabItem) {
63
+ onGrabItem(index);
64
+ }
65
+ setIsSelected(true);
66
+ }
67
+ };
68
+ const handleCancel = ()=>{
69
+ if (isSelected) {
70
+ setIsSelected(false);
71
+ if (onCancel) {
72
+ onCancel(index);
73
+ }
74
+ }
75
+ };
76
+ const handleKeyDown = (e)=>{
77
+ if (!active) {
78
+ return;
79
+ }
80
+ if (e.key === 'Tab' && !isSelected) {
81
+ return;
82
+ }
83
+ e.preventDefault();
84
+ switch(e.key){
85
+ case ' ':
86
+ case 'Enter':
87
+ handleDragClick();
88
+ break;
89
+ case 'Escape':
90
+ handleCancel();
91
+ break;
92
+ case 'ArrowDown':
93
+ case 'ArrowRight':
94
+ handleMove('DOWN');
95
+ break;
96
+ case 'ArrowUp':
97
+ case 'ArrowLeft':
98
+ handleMove('UP');
99
+ break;
100
+ }
101
+ };
102
+ return handleKeyDown;
103
+ };
104
+
105
+ const DIRECTIONS = {
106
+ UPWARD: 'upward',
107
+ DOWNWARD: 'downward'
108
+ };
109
+ const DROP_SENSITIVITY = {
110
+ REGULAR: 'regular',
111
+ IMMEDIATE: 'immediate'
112
+ };
113
+ /**
114
+ * A utility hook abstracting the general drag and drop hooks from react-dnd.
115
+ * Centralising the same behaviours and by default offering keyboard support.
116
+ */ const useDragAndDrop = (active, { type = 'STRAPI_DND', index, item, onStart, onEnd, onGrabItem, onDropItem, onCancel, onMoveItem, dropSensitivity = DROP_SENSITIVITY.REGULAR })=>{
117
+ const objectRef = React.useRef(null);
118
+ const [{ handlerId, isOver }, dropRef] = useDrop({
119
+ accept: type,
120
+ collect (monitor) {
121
+ return {
122
+ handlerId: monitor.getHandlerId(),
123
+ isOver: monitor.isOver({
124
+ shallow: true
125
+ })
126
+ };
127
+ },
128
+ drop (item) {
129
+ const draggedIndex = item.index;
130
+ const newIndex = index;
131
+ if (isOver && onDropItem) {
132
+ onDropItem(draggedIndex, newIndex);
133
+ }
134
+ },
135
+ hover (item, monitor) {
136
+ if (!objectRef.current || !onMoveItem) {
137
+ return;
138
+ }
139
+ const dragIndex = item.index;
140
+ const newIndex = index;
141
+ const hoverBoundingRect = objectRef.current?.getBoundingClientRect();
142
+ const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
143
+ const clientOffset = monitor.getClientOffset();
144
+ if (!clientOffset) return;
145
+ const hoverClientY = clientOffset && clientOffset.y - hoverBoundingRect.top;
146
+ if (typeof dragIndex === 'number' && typeof newIndex === 'number') {
147
+ if (dragIndex === newIndex) {
148
+ // Don't replace items with themselves
149
+ return;
150
+ }
151
+ if (dropSensitivity === DROP_SENSITIVITY.REGULAR) {
152
+ // Dragging downwards
153
+ if (dragIndex < newIndex && hoverClientY < hoverMiddleY) {
154
+ return;
155
+ }
156
+ // Dragging upwards
157
+ if (dragIndex > newIndex && hoverClientY > hoverMiddleY) {
158
+ return;
159
+ }
160
+ }
161
+ // Time to actually perform the action
162
+ onMoveItem(newIndex, dragIndex);
163
+ item.index = newIndex;
164
+ } else {
165
+ // Using numbers as indices doesn't work for nested list items with path like [1, 1, 0]
166
+ if (Array.isArray(dragIndex) && Array.isArray(newIndex)) {
167
+ // Indices comparison to find item position in nested list
168
+ const minLength = Math.min(dragIndex.length, newIndex.length);
169
+ let areEqual = true;
170
+ let isLessThan = false;
171
+ let isGreaterThan = false;
172
+ for(let i = 0; i < minLength; i++){
173
+ if (dragIndex[i] < newIndex[i]) {
174
+ isLessThan = true;
175
+ areEqual = false;
176
+ break;
177
+ } else if (dragIndex[i] > newIndex[i]) {
178
+ isGreaterThan = true;
179
+ areEqual = false;
180
+ break;
181
+ }
182
+ }
183
+ // Don't replace items with themselves
184
+ if (areEqual && dragIndex.length === newIndex.length) {
185
+ return;
186
+ }
187
+ if (dropSensitivity === DROP_SENSITIVITY.REGULAR) {
188
+ // Dragging downwards
189
+ if (isLessThan && !isGreaterThan && hoverClientY < hoverMiddleY) {
190
+ return;
191
+ }
192
+ // Dragging upwards
193
+ if (isGreaterThan && !isLessThan && hoverClientY > hoverMiddleY) {
194
+ return;
195
+ }
196
+ }
197
+ }
198
+ onMoveItem(newIndex, dragIndex);
199
+ item.index = newIndex;
200
+ }
201
+ }
202
+ });
203
+ const getDragDirection = (monitor)=>{
204
+ if (monitor && monitor.isDragging() && !monitor.didDrop() && monitor.getInitialClientOffset() && monitor.getClientOffset()) {
205
+ const deltaY = monitor.getInitialClientOffset().y - monitor.getClientOffset().y;
206
+ if (deltaY > 0) return DIRECTIONS.UPWARD;
207
+ if (deltaY < 0) return DIRECTIONS.DOWNWARD;
208
+ return null;
209
+ }
210
+ return null;
211
+ };
212
+ const [{ isDragging, direction }, dragRef, dragPreviewRef] = useDrag({
213
+ type,
214
+ item () {
215
+ if (onStart) {
216
+ onStart();
217
+ }
218
+ /**
219
+ * This will be attached and it helps define the preview sizes
220
+ * when a component is flexy e.g. Relations
221
+ */ const { width } = objectRef.current?.getBoundingClientRect() ?? {};
222
+ return {
223
+ index,
224
+ width,
225
+ ...item
226
+ };
227
+ },
228
+ end () {
229
+ if (onEnd) {
230
+ onEnd();
231
+ }
232
+ },
233
+ canDrag: active,
234
+ /**
235
+ * This is useful when the item is in a virtualized list.
236
+ * However, if we don't have an ID then we want the libraries
237
+ * defaults to take care of this.
238
+ */ isDragging: item?.id ? (monitor)=>{
239
+ return item.id === monitor.getItem().id;
240
+ } : undefined,
241
+ collect: (monitor)=>({
242
+ isDragging: monitor.isDragging(),
243
+ initialOffset: monitor.getInitialClientOffset(),
244
+ currentOffset: monitor.getClientOffset(),
245
+ direction: getDragDirection(monitor)
246
+ })
247
+ });
248
+ const handleKeyDown = useKeyboardDragAndDrop(active, index, {
249
+ onGrabItem,
250
+ onDropItem,
251
+ onCancel,
252
+ onMoveItem
253
+ });
254
+ return [
255
+ {
256
+ handlerId,
257
+ isDragging,
258
+ handleKeyDown,
259
+ isOverDropTarget: isOver,
260
+ direction
261
+ },
262
+ objectRef,
263
+ dropRef,
264
+ dragRef,
265
+ dragPreviewRef
266
+ ];
267
+ };
268
+
269
+ const AddStage = ({ children, ...props })=>{
270
+ return /*#__PURE__*/ jsx(StyledButton, {
271
+ tag: "button",
272
+ background: "neutral0",
273
+ borderColor: "neutral150",
274
+ paddingBottom: 3,
275
+ paddingLeft: 4,
276
+ paddingRight: 4,
277
+ paddingTop: 3,
278
+ shadow: "filterShadow",
279
+ ...props,
280
+ children: /*#__PURE__*/ jsx(Typography, {
281
+ variant: "pi",
282
+ fontWeight: "bold",
283
+ children: /*#__PURE__*/ jsxs(Flex, {
284
+ tag: "span",
285
+ gap: 2,
286
+ children: [
287
+ /*#__PURE__*/ jsx(PlusCircle, {
288
+ width: "2.4rem",
289
+ height: "2.4rem",
290
+ "aria-hidden": true
291
+ }),
292
+ children
293
+ ]
294
+ })
295
+ })
296
+ });
297
+ };
298
+ const StyledButton = styled(Box)`
299
+ border-radius: 26px;
300
+ color: ${({ theme })=>theme.colors.neutral500};
301
+
302
+ &:hover {
303
+ color: ${({ theme })=>theme.colors.primary600};
304
+ }
305
+
306
+ &:active {
307
+ color: ${({ theme })=>theme.colors.primary600};
308
+ }
309
+ `;
310
+
311
+ const Stages = ({ canDelete = true, canUpdate = true, isCreating })=>{
312
+ const { formatMessage } = useIntl();
313
+ const { trackUsage } = useTracking();
314
+ const addFieldRow = useForm('Stages', (state)=>state.addFieldRow);
315
+ const { value: stages = [] } = useField('stages');
316
+ return /*#__PURE__*/ jsxs(Flex, {
317
+ direction: "column",
318
+ gap: 6,
319
+ width: "100%",
320
+ children: [
321
+ /*#__PURE__*/ jsxs(Box, {
322
+ position: "relative",
323
+ width: "100%",
324
+ children: [
325
+ /*#__PURE__*/ jsx(Background, {
326
+ background: "neutral200",
327
+ height: "100%",
328
+ left: "50%",
329
+ position: "absolute",
330
+ top: "0",
331
+ width: 2
332
+ }),
333
+ /*#__PURE__*/ jsx(Flex, {
334
+ direction: "column",
335
+ alignItems: "stretch",
336
+ gap: 6,
337
+ position: "relative",
338
+ tag: "ol",
339
+ children: stages.map((stage, index)=>{
340
+ return /*#__PURE__*/ jsx(Box, {
341
+ tag: "li",
342
+ children: /*#__PURE__*/ jsx(Stage, {
343
+ index: index,
344
+ canDelete: stages.length > 1 && canDelete,
345
+ canReorder: stages.length > 1,
346
+ canUpdate: canUpdate,
347
+ stagesCount: stages.length,
348
+ defaultOpen: !stage.id,
349
+ ...stage
350
+ })
351
+ }, stage.__temp_key__);
352
+ })
353
+ })
354
+ ]
355
+ }),
356
+ canUpdate && /*#__PURE__*/ jsx(AddStage, {
357
+ type: "button",
358
+ onClick: ()=>{
359
+ addFieldRow('stages', {
360
+ name: ''
361
+ });
362
+ trackUsage('willCreateStage');
363
+ },
364
+ children: formatMessage({
365
+ id: 'Settings.review-workflows.stage.add',
366
+ defaultMessage: 'Add new stage'
367
+ })
368
+ })
369
+ ]
370
+ });
371
+ };
372
+ const Background = styled(Box)`
373
+ transform: translateX(-50%);
374
+ `;
375
+ const Stage = ({ index, canDelete = false, canReorder = false, canUpdate = false, stagesCount, name, permissions, color, defaultOpen })=>{
376
+ const [liveText, setLiveText] = React.useState();
377
+ const { formatMessage } = useIntl();
378
+ const { trackUsage } = useTracking();
379
+ const stageErrors = useForm('Stages', (state)=>state.errors.stages);
380
+ const error = stageErrors?.[index];
381
+ const addFieldRow = useForm('Stage', (state)=>state.addFieldRow);
382
+ const moveFieldRow = useForm('Stage', (state)=>state.moveFieldRow);
383
+ const removeFieldRow = useForm('Stage', (state)=>state.removeFieldRow);
384
+ const getItemPos = (index)=>`${index + 1} of ${stagesCount}`;
385
+ const handleGrabStage = (index)=>{
386
+ setLiveText(formatMessage({
387
+ id: 'dnd.grab-item',
388
+ defaultMessage: `{item}, grabbed. Current position in list: {position}. Press up and down arrow to change position, Spacebar to drop, Escape to cancel.`
389
+ }, {
390
+ item: name,
391
+ position: getItemPos(index)
392
+ }));
393
+ };
394
+ const handleDropStage = (index)=>{
395
+ setLiveText(formatMessage({
396
+ id: 'dnd.drop-item',
397
+ defaultMessage: `{item}, dropped. Final position in list: {position}.`
398
+ }, {
399
+ item: name,
400
+ position: getItemPos(index)
401
+ }));
402
+ };
403
+ const handleCancelDragStage = ()=>{
404
+ setLiveText(formatMessage({
405
+ id: 'dnd.cancel-item',
406
+ defaultMessage: '{item}, dropped. Re-order cancelled.'
407
+ }, {
408
+ item: name
409
+ }));
410
+ };
411
+ const handleMoveStage = (newIndex, oldIndex)=>{
412
+ setLiveText(formatMessage({
413
+ id: 'dnd.reorder',
414
+ defaultMessage: '{item}, moved. New position in list: {position}.'
415
+ }, {
416
+ item: name,
417
+ position: getItemPos(newIndex)
418
+ }));
419
+ moveFieldRow('stages', oldIndex, newIndex);
420
+ };
421
+ const [{ handlerId, isDragging, handleKeyDown }, stageRef, dropRef, dragRef, dragPreviewRef] = useDragAndDrop(canReorder, {
422
+ index,
423
+ item: {
424
+ index,
425
+ name
426
+ },
427
+ onGrabItem: handleGrabStage,
428
+ onDropItem: handleDropStage,
429
+ onMoveItem: handleMoveStage,
430
+ onCancel: handleCancelDragStage,
431
+ type: DRAG_DROP_TYPES.STAGE
432
+ });
433
+ // @ts-expect-error – the stageRef is incorrectly typed.
434
+ const composedRef = useComposedRefs(stageRef, dropRef);
435
+ React.useEffect(()=>{
436
+ dragPreviewRef(getEmptyImage(), {
437
+ captureDraggingState: false
438
+ });
439
+ }, [
440
+ dragPreviewRef,
441
+ index
442
+ ]);
443
+ const handleCloneClick = ()=>{
444
+ addFieldRow('stages', {
445
+ name,
446
+ color,
447
+ permissions
448
+ });
449
+ };
450
+ const id = React.useId();
451
+ return /*#__PURE__*/ jsxs(Box, {
452
+ ref: composedRef,
453
+ shadow: "tableShadow",
454
+ children: [
455
+ liveText && /*#__PURE__*/ jsx(VisuallyHidden, {
456
+ "aria-live": "assertive",
457
+ children: liveText
458
+ }),
459
+ isDragging ? /*#__PURE__*/ jsx(Box, {
460
+ background: "primary100",
461
+ borderStyle: "dashed",
462
+ borderColor: "primary600",
463
+ borderWidth: "1px",
464
+ display: "block",
465
+ hasRadius: true,
466
+ padding: 6
467
+ }) : /*#__PURE__*/ jsx(AccordionRoot, {
468
+ onValueChange: (value)=>{
469
+ if (value) {
470
+ trackUsage('willEditStage');
471
+ }
472
+ },
473
+ defaultValue: defaultOpen ? id : undefined,
474
+ $error: Object.values(error ?? {}).length > 0,
475
+ children: /*#__PURE__*/ jsxs(Accordion.Item, {
476
+ value: id,
477
+ children: [
478
+ /*#__PURE__*/ jsxs(Accordion.Header, {
479
+ children: [
480
+ /*#__PURE__*/ jsx(Accordion.Trigger, {
481
+ children: name
482
+ }),
483
+ /*#__PURE__*/ jsx(Accordion.Actions, {
484
+ children: canDelete || canUpdate ? /*#__PURE__*/ jsxs(Fragment, {
485
+ children: [
486
+ /*#__PURE__*/ jsxs(Menu.Root, {
487
+ children: [
488
+ /*#__PURE__*/ jsxs(ContextMenuTrigger, {
489
+ size: "S",
490
+ endIcon: null,
491
+ paddingLeft: 2,
492
+ paddingRight: 2,
493
+ children: [
494
+ /*#__PURE__*/ jsx(More, {
495
+ "aria-hidden": true,
496
+ focusable: false
497
+ }),
498
+ /*#__PURE__*/ jsx(VisuallyHidden, {
499
+ tag: "span",
500
+ children: formatMessage({
501
+ id: '[tbdb].components.DynamicZone.more-actions',
502
+ defaultMessage: 'More actions'
503
+ })
504
+ })
505
+ ]
506
+ }),
507
+ /*#__PURE__*/ jsx(Menu.Content, {
508
+ popoverPlacement: "bottom-end",
509
+ zIndex: 2,
510
+ children: /*#__PURE__*/ jsxs(Menu.SubRoot, {
511
+ children: [
512
+ canUpdate && /*#__PURE__*/ jsx(MenuItem, {
513
+ onClick: handleCloneClick,
514
+ children: formatMessage({
515
+ id: 'Settings.review-workflows.stage.delete',
516
+ defaultMessage: 'Duplicate stage'
517
+ })
518
+ }),
519
+ canDelete && /*#__PURE__*/ jsx(DeleteMenuItem, {
520
+ onClick: ()=>removeFieldRow('stages', index),
521
+ children: formatMessage({
522
+ id: 'Settings.review-workflows.stage.delete',
523
+ defaultMessage: 'Delete'
524
+ })
525
+ })
526
+ ]
527
+ })
528
+ })
529
+ ]
530
+ }),
531
+ canUpdate && /*#__PURE__*/ jsx(IconButton, {
532
+ background: "transparent",
533
+ hasRadius: true,
534
+ variant: "ghost",
535
+ "data-handler-id": handlerId,
536
+ ref: dragRef,
537
+ label: formatMessage({
538
+ id: 'Settings.review-workflows.stage.drag',
539
+ defaultMessage: 'Drag'
540
+ }),
541
+ onClick: (e)=>e.stopPropagation(),
542
+ onKeyDown: handleKeyDown,
543
+ children: /*#__PURE__*/ jsx(Drag, {})
544
+ })
545
+ ]
546
+ }) : null
547
+ })
548
+ ]
549
+ }),
550
+ /*#__PURE__*/ jsx(Accordion.Content, {
551
+ children: /*#__PURE__*/ jsx(Grid.Root, {
552
+ gap: 4,
553
+ padding: 6,
554
+ children: [
555
+ {
556
+ disabled: !canUpdate,
557
+ label: formatMessage({
558
+ id: 'Settings.review-workflows.stage.name.label',
559
+ defaultMessage: 'Stage name'
560
+ }),
561
+ name: `stages.${index}.name`,
562
+ required: true,
563
+ size: 6,
564
+ type: 'string'
565
+ },
566
+ {
567
+ disabled: !canUpdate,
568
+ label: formatMessage({
569
+ id: 'content-manager.reviewWorkflows.stage.color',
570
+ defaultMessage: 'Color'
571
+ }),
572
+ name: `stages.${index}.color`,
573
+ required: true,
574
+ size: 6,
575
+ type: 'color'
576
+ },
577
+ {
578
+ disabled: !canUpdate,
579
+ label: formatMessage({
580
+ id: 'Settings.review-workflows.stage.permissions.label',
581
+ defaultMessage: 'Roles that can change this stage'
582
+ }),
583
+ name: `stages.${index}.permissions`,
584
+ placeholder: formatMessage({
585
+ id: 'Settings.review-workflows.stage.permissions.placeholder',
586
+ defaultMessage: 'Select a role'
587
+ }),
588
+ required: true,
589
+ size: 6,
590
+ type: 'permissions'
591
+ }
592
+ ].map(({ size, ...field })=>/*#__PURE__*/ jsx(Grid.Item, {
593
+ col: size,
594
+ direction: "column",
595
+ alignItems: "stretch",
596
+ children: /*#__PURE__*/ jsx(InputRenderer, {
597
+ ...field
598
+ })
599
+ }, field.name))
600
+ })
601
+ })
602
+ ]
603
+ })
604
+ })
605
+ ]
606
+ });
607
+ };
608
+ const AccordionRoot = styled(Accordion.Root)`
609
+ border: 1px solid
610
+ ${({ theme, $error })=>$error ? theme.colors.danger600 : theme.colors.neutral200};
611
+ `;
612
+ const DeleteMenuItem = styled(MenuItem)`
613
+ color: ${({ theme })=>theme.colors.danger600};
614
+ `;
615
+ // Removing the font-size from the child-span aligns the
616
+ // more icon vertically
617
+ const ContextMenuTrigger = styled(Menu.Trigger)`
618
+ :hover,
619
+ :focus {
620
+ background-color: ${({ theme })=>theme.colors.neutral100};
621
+ }
622
+
623
+ > span {
624
+ font-size: 0;
625
+ }
626
+ `;
627
+ const InputRenderer = (props)=>{
628
+ switch(props.type){
629
+ case 'color':
630
+ return /*#__PURE__*/ jsx(ColorSelector, {
631
+ ...props
632
+ });
633
+ case 'permissions':
634
+ return /*#__PURE__*/ jsx(PermissionsField, {
635
+ ...props
636
+ });
637
+ default:
638
+ return /*#__PURE__*/ jsx(InputRenderer$1, {
639
+ ...props
640
+ });
641
+ }
642
+ };
643
+ const ColorSelector = ({ disabled, label, name, required })=>{
644
+ const { formatMessage } = useIntl();
645
+ const { value, error, onChange } = useField(name);
646
+ const colorOptions = AVAILABLE_COLORS.map(({ hex, name })=>({
647
+ value: hex,
648
+ label: formatMessage({
649
+ id: 'Settings.review-workflows.stage.color.name',
650
+ defaultMessage: '{name}'
651
+ }, {
652
+ name
653
+ }),
654
+ color: hex
655
+ }));
656
+ const { themeColorName } = getStageColorByHex(value) ?? {};
657
+ return /*#__PURE__*/ jsxs(Field.Root, {
658
+ error: error,
659
+ name: name,
660
+ required: required,
661
+ children: [
662
+ /*#__PURE__*/ jsx(Field.Label, {
663
+ children: label
664
+ }),
665
+ /*#__PURE__*/ jsx(SingleSelect, {
666
+ disabled: disabled,
667
+ onChange: (v)=>{
668
+ onChange(name, v.toString());
669
+ },
670
+ value: value?.toUpperCase(),
671
+ startIcon: /*#__PURE__*/ jsx(Flex, {
672
+ tag: "span",
673
+ height: 2,
674
+ background: value,
675
+ borderColor: themeColorName === 'neutral0' ? 'neutral150' : 'transparent',
676
+ hasRadius: true,
677
+ shrink: 0,
678
+ width: 2
679
+ }),
680
+ children: colorOptions.map(({ value, label, color })=>{
681
+ const { themeColorName } = getStageColorByHex(color) || {};
682
+ return /*#__PURE__*/ jsx(SingleSelectOption, {
683
+ value: value,
684
+ startIcon: /*#__PURE__*/ jsx(Flex, {
685
+ tag: "span",
686
+ height: 2,
687
+ background: color,
688
+ borderColor: themeColorName === 'neutral0' ? 'neutral150' : 'transparent',
689
+ hasRadius: true,
690
+ shrink: 0,
691
+ width: 2
692
+ }),
693
+ children: label
694
+ }, value);
695
+ })
696
+ }),
697
+ /*#__PURE__*/ jsx(Field.Error, {})
698
+ ]
699
+ });
700
+ };
701
+ const PermissionsField = ({ disabled, name, placeholder, required })=>{
702
+ const { formatMessage } = useIntl();
703
+ const { toggleNotification } = useNotification();
704
+ const [isApplyAllConfirmationOpen, setIsApplyAllConfirmationOpen] = React.useState(false);
705
+ const { value = [], error, onChange } = useField(name);
706
+ const allStages = useForm('PermissionsField', (state)=>state.values.stages);
707
+ const onFormValueChange = useForm('PermissionsField', (state)=>state.onChange);
708
+ const rolesErrorCount = React.useRef(0);
709
+ const { data: roles = [], isLoading, error: getRolesError } = useGetAdminRolesQuery();
710
+ // Super admins always have permissions to do everything and therefore
711
+ // there is no point for this role to show up in the role combobox
712
+ const filteredRoles = roles?.filter((role)=>role.code !== 'strapi-super-admin') ?? [];
713
+ React.useEffect(()=>{
714
+ if (!isLoading && getRolesError && 'status' in getRolesError && getRolesError.status == 403 && rolesErrorCount.current === 0) {
715
+ rolesErrorCount.current = 1;
716
+ toggleNotification({
717
+ blockTransition: true,
718
+ type: 'danger',
719
+ message: formatMessage({
720
+ id: 'review-workflows.stage.permissions.noPermissions.description',
721
+ defaultMessage: 'You don’t have the permission to see roles. Contact your administrator.'
722
+ })
723
+ });
724
+ }
725
+ }, [
726
+ formatMessage,
727
+ isLoading,
728
+ roles,
729
+ toggleNotification,
730
+ getRolesError
731
+ ]);
732
+ if (!isLoading && filteredRoles.length === 0) {
733
+ return /*#__PURE__*/ jsxs(Field.Root, {
734
+ name: name,
735
+ hint: formatMessage({
736
+ id: 'Settings.review-workflows.stage.permissions.noPermissions.description',
737
+ defaultMessage: 'You don’t have the permission to see roles'
738
+ }),
739
+ required: required,
740
+ children: [
741
+ /*#__PURE__*/ jsx(Field.Label, {
742
+ children: formatMessage({
743
+ id: 'Settings.review-workflows.stage.permissions.label',
744
+ defaultMessage: 'Roles that can change this stage'
745
+ })
746
+ }),
747
+ /*#__PURE__*/ jsx(TextInput, {
748
+ disabled: true,
749
+ placeholder: formatMessage({
750
+ id: 'components.NotAllowedInput.text',
751
+ defaultMessage: 'No permissions to see this field'
752
+ }),
753
+ startAction: /*#__PURE__*/ jsx(EyeStriked, {
754
+ fill: "neutral600"
755
+ }),
756
+ type: "text",
757
+ value: ""
758
+ }),
759
+ /*#__PURE__*/ jsx(Field.Hint, {})
760
+ ]
761
+ });
762
+ }
763
+ return /*#__PURE__*/ jsx(Fragment, {
764
+ children: /*#__PURE__*/ jsxs(Flex, {
765
+ alignItems: "flex-end",
766
+ gap: 3,
767
+ children: [
768
+ /*#__PURE__*/ jsx(PermissionWrapper, {
769
+ grow: 1,
770
+ children: /*#__PURE__*/ jsxs(Field.Root, {
771
+ error: error,
772
+ name: name,
773
+ required: true,
774
+ children: [
775
+ /*#__PURE__*/ jsx(Field.Label, {
776
+ children: formatMessage({
777
+ id: 'Settings.review-workflows.stage.permissions.label',
778
+ defaultMessage: 'Roles that can change this stage'
779
+ })
780
+ }),
781
+ /*#__PURE__*/ jsx(MultiSelect, {
782
+ disabled: disabled,
783
+ onChange: (values)=>{
784
+ // Because the select components expects strings for values, but
785
+ // the yup schema validates we are sending full permission objects to the API,
786
+ // we must coerce the string value back to an object
787
+ const permissions = values.map((value)=>({
788
+ role: parseInt(value, 10),
789
+ action: 'admin::review-workflows.stage.transition'
790
+ }));
791
+ onChange(name, permissions);
792
+ },
793
+ placeholder: placeholder,
794
+ // The Select component expects strings for values
795
+ value: value.map((permission)=>`${permission.role}`),
796
+ withTags: true,
797
+ children: /*#__PURE__*/ jsx(MultiSelectGroup, {
798
+ label: formatMessage({
799
+ id: 'Settings.review-workflows.stage.permissions.allRoles.label',
800
+ defaultMessage: 'All roles'
801
+ }),
802
+ values: filteredRoles.map((r)=>`${r.id}`),
803
+ children: filteredRoles.map((role)=>{
804
+ return /*#__PURE__*/ jsx(NestedOption$1, {
805
+ value: `${role.id}`,
806
+ children: role.name
807
+ }, role.id);
808
+ })
809
+ })
810
+ }),
811
+ /*#__PURE__*/ jsx(Field.Error, {})
812
+ ]
813
+ })
814
+ }),
815
+ /*#__PURE__*/ jsxs(Dialog.Root, {
816
+ open: isApplyAllConfirmationOpen,
817
+ onOpenChange: setIsApplyAllConfirmationOpen,
818
+ children: [
819
+ /*#__PURE__*/ jsx(Dialog.Trigger, {
820
+ children: /*#__PURE__*/ jsx(IconButton, {
821
+ disabled: disabled,
822
+ label: formatMessage({
823
+ id: 'Settings.review-workflows.stage.permissions.apply.label',
824
+ defaultMessage: 'Apply to all stages'
825
+ }),
826
+ size: "L",
827
+ children: /*#__PURE__*/ jsx(Duplicate, {})
828
+ })
829
+ }),
830
+ /*#__PURE__*/ jsx(ConfirmDialog, {
831
+ onConfirm: ()=>{
832
+ onFormValueChange('stages', allStages.map((stage)=>({
833
+ ...stage,
834
+ permissions: value
835
+ })));
836
+ setIsApplyAllConfirmationOpen(false);
837
+ toggleNotification({
838
+ type: 'success',
839
+ message: formatMessage({
840
+ id: 'Settings.review-workflows.page.edit.confirm.stages.permissions.copy.success',
841
+ defaultMessage: 'Applied roles to all other stages of the workflow'
842
+ })
843
+ });
844
+ },
845
+ variant: "default",
846
+ children: formatMessage({
847
+ id: 'Settings.review-workflows.page.edit.confirm.stages.permissions.copy',
848
+ defaultMessage: 'Roles that can change that stage will be applied to all the other stages.'
849
+ })
850
+ })
851
+ ]
852
+ })
853
+ ]
854
+ })
855
+ });
856
+ };
857
+ const NestedOption$1 = styled(MultiSelectOption)`
858
+ padding-left: ${({ theme })=>theme.spaces[7]};
859
+ `;
860
+ // Grow the size of the permission Select
861
+ const PermissionWrapper = styled(Flex)`
862
+ > * {
863
+ flex-grow: 1;
864
+ }
865
+ `;
866
+
867
+ const WorkflowAttributes = ({ canUpdate = true })=>{
868
+ const { formatMessage } = useIntl();
869
+ return /*#__PURE__*/ jsxs(Grid.Root, {
870
+ background: "neutral0",
871
+ hasRadius: true,
872
+ gap: 4,
873
+ padding: 6,
874
+ shadow: "tableShadow",
875
+ children: [
876
+ /*#__PURE__*/ jsx(Grid.Item, {
877
+ col: 6,
878
+ direction: "column",
879
+ alignItems: "stretch",
880
+ children: /*#__PURE__*/ jsx(InputRenderer$1, {
881
+ disabled: !canUpdate,
882
+ label: formatMessage({
883
+ id: 'Settings.review-workflows.workflow.name.label',
884
+ defaultMessage: 'Workflow Name'
885
+ }),
886
+ name: "name",
887
+ required: true,
888
+ type: "string"
889
+ })
890
+ }),
891
+ /*#__PURE__*/ jsx(Grid.Item, {
892
+ col: 6,
893
+ direction: "column",
894
+ alignItems: "stretch",
895
+ children: /*#__PURE__*/ jsx(ContentTypesSelector, {
896
+ disabled: !canUpdate
897
+ })
898
+ }),
899
+ /*#__PURE__*/ jsx(Grid.Item, {
900
+ col: 6,
901
+ direction: "column",
902
+ alignItems: "stretch",
903
+ children: /*#__PURE__*/ jsx(StageSelector, {
904
+ disabled: !canUpdate
905
+ })
906
+ })
907
+ ]
908
+ });
909
+ };
910
+ const ContentTypesSelector = ({ disabled })=>{
911
+ const { formatMessage, locale } = useIntl();
912
+ const { data: contentTypes, isLoading } = useGetContentTypesQuery();
913
+ const { workflows } = useReviewWorkflows();
914
+ const currentWorkflow = useForm('ContentTypesSelector', (state)=>state.values);
915
+ const { error, value, onChange } = useField('contentTypes');
916
+ const formatter = useCollator(locale, {
917
+ sensitivity: 'base'
918
+ });
919
+ const isDisabled = disabled || isLoading || !contentTypes || contentTypes.collectionType.length === 0 && contentTypes.singleType.length === 0;
920
+ const collectionTypes = (contentTypes?.collectionType ?? []).toSorted((a, b)=>formatter.compare(a.info.displayName, b.info.displayName)).map((contentType)=>({
921
+ label: contentType.info.displayName,
922
+ value: contentType.uid
923
+ }));
924
+ const singleTypes = (contentTypes?.singleType ?? []).map((contentType)=>({
925
+ label: contentType.info.displayName,
926
+ value: contentType.uid
927
+ }));
928
+ return /*#__PURE__*/ jsxs(Field.Root, {
929
+ error: error,
930
+ name: 'contentTypes',
931
+ children: [
932
+ /*#__PURE__*/ jsx(Field.Label, {
933
+ children: formatMessage({
934
+ id: 'Settings.review-workflows.workflow.contentTypes.label',
935
+ defaultMessage: 'Associated to'
936
+ })
937
+ }),
938
+ /*#__PURE__*/ jsx(MultiSelect, {
939
+ customizeContent: (value)=>formatMessage({
940
+ id: 'Settings.review-workflows.workflow.contentTypes.displayValue',
941
+ defaultMessage: '{count} {count, plural, one {content type} other {content types}} selected'
942
+ }, {
943
+ count: value?.length
944
+ }),
945
+ disabled: isDisabled,
946
+ onChange: (values)=>{
947
+ onChange('contentTypes', values);
948
+ },
949
+ value: value,
950
+ placeholder: formatMessage({
951
+ id: 'Settings.review-workflows.workflow.contentTypes.placeholder',
952
+ defaultMessage: 'Select'
953
+ }),
954
+ children: [
955
+ ...collectionTypes.length > 0 ? [
956
+ {
957
+ label: formatMessage({
958
+ id: 'Settings.review-workflows.workflow.contentTypes.collectionTypes.label',
959
+ defaultMessage: 'Collection Types'
960
+ }),
961
+ children: collectionTypes
962
+ }
963
+ ] : [],
964
+ ...singleTypes.length > 0 ? [
965
+ {
966
+ label: formatMessage({
967
+ id: 'Settings.review-workflows.workflow.contentTypes.singleTypes.label',
968
+ defaultMessage: 'Single Types'
969
+ }),
970
+ children: singleTypes
971
+ }
972
+ ] : []
973
+ ].map((opt)=>{
974
+ return /*#__PURE__*/ jsx(MultiSelectGroup, {
975
+ label: opt.label,
976
+ values: opt.children.map((child)=>child.value.toString()),
977
+ children: opt.children.map((child)=>{
978
+ const { name: assignedWorkflowName } = workflows?.find((workflow)=>(currentWorkflow && workflow.id !== currentWorkflow.id || !currentWorkflow) && workflow.contentTypes.includes(child.value)) ?? {};
979
+ return /*#__PURE__*/ jsx(NestedOption, {
980
+ value: child.value,
981
+ children: /*#__PURE__*/ jsx(Typography, {
982
+ children: // @ts-expect-error - formatMessage options doesn't expect to be a React component but that's what we need actually for the <i> and <em> components
983
+ formatMessage({
984
+ id: 'Settings.review-workflows.workflow.contentTypes.assigned.notice',
985
+ defaultMessage: '{label} {name, select, undefined {} other {<i>(assigned to <em>{name}</em> workflow)</i>}}'
986
+ }, {
987
+ label: child.label,
988
+ name: assignedWorkflowName,
989
+ em: (...children)=>/*#__PURE__*/ jsx(Typography, {
990
+ tag: "em",
991
+ fontWeight: "bold",
992
+ children: children
993
+ }),
994
+ i: (...children)=>/*#__PURE__*/ jsx(ContentTypeTakeNotice, {
995
+ children: children
996
+ })
997
+ })
998
+ })
999
+ }, child.value);
1000
+ })
1001
+ }, opt.label);
1002
+ })
1003
+ })
1004
+ ]
1005
+ });
1006
+ };
1007
+ const NestedOption = styled(MultiSelectOption)`
1008
+ padding-left: ${({ theme })=>theme.spaces[7]};
1009
+ `;
1010
+ const ContentTypeTakeNotice = styled(Typography)`
1011
+ font-style: italic;
1012
+ `;
1013
+ const StageSelector = ({ disabled })=>{
1014
+ const { value: stages = [] } = useField('stages');
1015
+ const { formatMessage } = useIntl();
1016
+ const { error, value, onChange } = useField('stageRequiredToPublish');
1017
+ // stages with empty names are not valid, so we avoid them from being used to avoid errors
1018
+ const validStages = stages.filter((stage)=>stage.name);
1019
+ return /*#__PURE__*/ jsxs(Field.Root, {
1020
+ error: error,
1021
+ name: "stageRequiredToPublish",
1022
+ hint: formatMessage({
1023
+ id: 'settings.review-workflows.workflow.stageRequiredToPublish.hint',
1024
+ defaultMessage: 'Prevents entries from being published if they are not at the required stage.'
1025
+ }),
1026
+ children: [
1027
+ /*#__PURE__*/ jsx(Field.Label, {
1028
+ children: formatMessage({
1029
+ id: 'settings.review-workflows.workflow.stageRequiredToPublish.label',
1030
+ defaultMessage: 'Required stage for publishing'
1031
+ })
1032
+ }),
1033
+ /*#__PURE__*/ jsxs(SingleSelect, {
1034
+ disabled: disabled,
1035
+ onChange: (value)=>{
1036
+ onChange('stageRequiredToPublish', value);
1037
+ },
1038
+ value: value,
1039
+ children: [
1040
+ /*#__PURE__*/ jsx(SingleSelectOption, {
1041
+ value: '',
1042
+ children: formatMessage({
1043
+ id: 'settings.review-workflows.workflow.stageRequiredToPublish.any',
1044
+ defaultMessage: 'Any stage'
1045
+ })
1046
+ }),
1047
+ validStages.map((stage, i)=>/*#__PURE__*/ jsx(SingleSelectOption, {
1048
+ value: stage.id?.toString() || stage.__temp_key__,
1049
+ children: stage.name
1050
+ }, `requiredToPublishStage-${stage.id || stage.__temp_key__}`))
1051
+ ]
1052
+ }),
1053
+ /*#__PURE__*/ jsx(Field.Hint, {})
1054
+ ]
1055
+ });
1056
+ };
1057
+
1058
+ /* -------------------------------------------------------------------------------------------------
1059
+ * EditPage
1060
+ * -----------------------------------------------------------------------------------------------*/ const WORKFLOW_SCHEMA = yup.object({
1061
+ contentTypes: yup.array().of(yup.string()),
1062
+ name: yup.string().max(255, {
1063
+ id: 'review-workflows.validation.name.max-length',
1064
+ defaultMessage: 'Name can not be longer than 255 characters'
1065
+ }).required().nullable(),
1066
+ stages: yup.array().of(yup.object().shape({
1067
+ name: yup.string().nullable().required({
1068
+ id: 'review-workflows.validation.stage.name',
1069
+ defaultMessage: 'Name is required'
1070
+ }).max(255, {
1071
+ id: 'review-workflows.validation.stage.max-length',
1072
+ defaultMessage: 'Name can not be longer than 255 characters'
1073
+ }).test('unique-name', {
1074
+ id: 'review-workflows.validation.stage.duplicate',
1075
+ defaultMessage: 'Stage name must be unique'
1076
+ }, (stageName, context)=>{
1077
+ // @ts-expect-error it does exist.
1078
+ const { stages } = context.from[1].value;
1079
+ return stages.filter((stage)=>stage.name === stageName).length === 1;
1080
+ }),
1081
+ color: yup.string().nullable().required({
1082
+ id: 'review-workflows.validation.stage.color',
1083
+ defaultMessage: 'Color is required'
1084
+ }).matches(/^#(?:[0-9a-fA-F]{3}){1,2}$/i),
1085
+ permissions: yup.array(yup.object({
1086
+ role: yup.number().strict().typeError({
1087
+ id: 'review-workflows.validation.stage.permissions.role.number',
1088
+ defaultMessage: 'Role must be of type number'
1089
+ }).required(),
1090
+ action: yup.string().required({
1091
+ id: 'review-workflows.validation.stage.permissions.action.required',
1092
+ defaultMessage: 'Action is a required argument'
1093
+ })
1094
+ })).strict()
1095
+ })).min(1),
1096
+ stageRequiredToPublish: yup.string().nullable()
1097
+ });
1098
+ const EditPage = ()=>{
1099
+ const { id = '' } = useParams();
1100
+ const isCreatingWorkflow = id === 'create';
1101
+ const { formatMessage } = useIntl();
1102
+ const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
1103
+ const navigate = useNavigate();
1104
+ const { toggleNotification } = useNotification();
1105
+ const { isLoading: isLoadingWorkflow, meta, workflows, error, update, create } = useReviewWorkflows();
1106
+ const permissions = useTypedSelector((state)=>state.admin_app.permissions['settings']?.['review-workflows']);
1107
+ const { allowedActions: { canDelete, canUpdate, canCreate } } = useRBAC(permissions);
1108
+ const [savePrompts, setSavePrompts] = React.useState({});
1109
+ const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
1110
+ const [showLimitModal, setShowLimitModal] = React.useState(null);
1111
+ const currentWorkflow = workflows?.find((workflow)=>workflow.id === parseInt(id, 10));
1112
+ const contentTypesFromOtherWorkflows = workflows?.filter((workflow)=>workflow.id !== parseInt(id, 10)).flatMap((workflow)=>workflow.contentTypes);
1113
+ const limits = getFeature('review-workflows');
1114
+ const numberOfWorkflows = limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME];
1115
+ const stagesPerWorkflow = limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME];
1116
+ const submitForm = async (data, helpers)=>{
1117
+ try {
1118
+ const { stageRequiredToPublish, ...rest } = data;
1119
+ const stageRequiredToPublishName = stageRequiredToPublish === '' ? null : rest.stages.find((stage)=>stage.id === Number(stageRequiredToPublish) || stage.__temp_key__ === stageRequiredToPublish)?.name;
1120
+ if (!isCreatingWorkflow) {
1121
+ const res = await update(id, {
1122
+ ...rest,
1123
+ // compare permissions of stages and only submit them if at least one has
1124
+ // changed; this enables partial updates e.g. for users who don't have
1125
+ // permissions to see roles
1126
+ stages: rest.stages.map((stage)=>{
1127
+ let hasUpdatedPermissions = true;
1128
+ const serverStage = currentWorkflow?.stages?.find((serverStage)=>serverStage.id === stage?.id);
1129
+ if (serverStage) {
1130
+ hasUpdatedPermissions = serverStage.permissions?.length !== stage.permissions?.length || !serverStage.permissions?.every((serverPermission)=>!!stage.permissions?.find((permission)=>permission.role === serverPermission.role));
1131
+ }
1132
+ return {
1133
+ ...stage,
1134
+ permissions: hasUpdatedPermissions ? stage.permissions : undefined
1135
+ };
1136
+ }),
1137
+ stageRequiredToPublishName
1138
+ });
1139
+ if ('error' in res && isBaseQueryError(res.error) && res.error.name === 'ValidationError') {
1140
+ helpers.setErrors(formatValidationErrors(res.error));
1141
+ }
1142
+ } else {
1143
+ const res = await create({
1144
+ ...rest,
1145
+ stageRequiredToPublishName
1146
+ });
1147
+ if ('error' in res && isBaseQueryError(res.error) && res.error.name === 'ValidationError') {
1148
+ helpers.setErrors(formatValidationErrors(res.error));
1149
+ } else if ('data' in res) {
1150
+ navigate(`../${res.data.id}`, {
1151
+ replace: true
1152
+ });
1153
+ }
1154
+ }
1155
+ } catch (error) {
1156
+ toggleNotification({
1157
+ type: 'danger',
1158
+ message: formatMessage({
1159
+ id: 'notification.error',
1160
+ defaultMessage: 'An error occurred'
1161
+ })
1162
+ });
1163
+ }
1164
+ setSavePrompts({});
1165
+ };
1166
+ const handleConfirmDeleteDialog = (data, helpers)=>async ()=>{
1167
+ await submitForm(data, helpers);
1168
+ };
1169
+ const handleConfirmClose = ()=>{
1170
+ setSavePrompts({});
1171
+ };
1172
+ const handleSubmit = async (data, helpers)=>{
1173
+ const isContentTypeReassignment = data.contentTypes.some((contentType)=>contentTypesFromOtherWorkflows?.includes(contentType));
1174
+ const hasDeletedServerStages = !isCreatingWorkflow && !currentWorkflow?.stages.every((stage)=>data.stages.some((newStage)=>newStage.id === stage.id));
1175
+ if (meta && numberOfWorkflows && meta?.workflowCount > parseInt(numberOfWorkflows, 10)) {
1176
+ /**
1177
+ * If the current license has a limit, check if the total count of workflows
1178
+ * exceeds that limit and display the limits modal instead of sending the
1179
+ * update, because it would throw an API error.
1180
+ */ setShowLimitModal('workflow');
1181
+ /**
1182
+ * If the current license has a limit, check if the total count of stages
1183
+ * exceeds that limit and display the limits modal instead of sending the
1184
+ * update, because it would throw an API error.
1185
+ */ } else if (data.stages && stagesPerWorkflow && data.stages.length > parseInt(stagesPerWorkflow, 10)) {
1186
+ setShowLimitModal('stage');
1187
+ } else if (hasDeletedServerStages || isContentTypeReassignment) {
1188
+ if (hasDeletedServerStages) {
1189
+ setSavePrompts((prev)=>({
1190
+ ...prev,
1191
+ hasDeletedServerStages: true
1192
+ }));
1193
+ }
1194
+ if (isContentTypeReassignment) {
1195
+ setSavePrompts((prev)=>({
1196
+ ...prev,
1197
+ hasReassignedContentTypes: true
1198
+ }));
1199
+ }
1200
+ } else {
1201
+ await submitForm(data, helpers);
1202
+ }
1203
+ };
1204
+ /**
1205
+ * If the current license has a limit:
1206
+ * check if the total count of workflows or stages exceeds that limit and display
1207
+ * the limits modal on page load. It can be closed by the user, but the
1208
+ * API will throw an error in case they try to create a new workflow or update the
1209
+ * stages.
1210
+ *
1211
+ * If the current license does not have a limit (e.g. offline license):
1212
+ * do nothing (for now). In case they are trying to create the 201st workflow/ stage
1213
+ * the API will throw an error.
1214
+ *
1215
+ */ React.useEffect(()=>{
1216
+ if (!isLoadingWorkflow && !isLicenseLoading) {
1217
+ if (meta && numberOfWorkflows && meta?.workflowCount > parseInt(numberOfWorkflows, 10)) {
1218
+ setShowLimitModal('workflow');
1219
+ } else if (currentWorkflow && currentWorkflow.stages && stagesPerWorkflow && currentWorkflow.stages.length > parseInt(stagesPerWorkflow, 10)) {
1220
+ setShowLimitModal('stage');
1221
+ }
1222
+ }
1223
+ }, [
1224
+ currentWorkflow,
1225
+ isLicenseLoading,
1226
+ isLoadingWorkflow,
1227
+ limits,
1228
+ meta,
1229
+ numberOfWorkflows,
1230
+ stagesPerWorkflow
1231
+ ]);
1232
+ const initialValues = React.useMemo(()=>{
1233
+ if (isCreatingWorkflow || !currentWorkflow) {
1234
+ return {
1235
+ name: '',
1236
+ stages: [],
1237
+ contentTypes: [],
1238
+ stageRequiredToPublish: ''
1239
+ };
1240
+ } else {
1241
+ return {
1242
+ name: currentWorkflow.name,
1243
+ stages: addTmpKeysToStages(currentWorkflow.stages),
1244
+ contentTypes: currentWorkflow.contentTypes,
1245
+ stageRequiredToPublish: currentWorkflow.stageRequiredToPublish?.id.toString() ?? ''
1246
+ };
1247
+ }
1248
+ }, [
1249
+ currentWorkflow,
1250
+ isCreatingWorkflow
1251
+ ]);
1252
+ if (isLoadingWorkflow) {
1253
+ return /*#__PURE__*/ jsx(Page.Loading, {});
1254
+ }
1255
+ if (error) {
1256
+ return /*#__PURE__*/ jsx(Page.Error, {});
1257
+ }
1258
+ return /*#__PURE__*/ jsxs(Fragment, {
1259
+ children: [
1260
+ /*#__PURE__*/ jsx(DragLayerRendered, {}),
1261
+ /*#__PURE__*/ jsx(Form, {
1262
+ method: isCreatingWorkflow ? 'POST' : 'PUT',
1263
+ initialValues: initialValues,
1264
+ validationSchema: WORKFLOW_SCHEMA,
1265
+ onSubmit: handleSubmit,
1266
+ children: ({ modified, isSubmitting, values, setErrors })=>/*#__PURE__*/ jsxs(Fragment, {
1267
+ children: [
1268
+ /*#__PURE__*/ jsx(Header, {
1269
+ navigationAction: /*#__PURE__*/ jsx(BackButton, {
1270
+ fallback: ".."
1271
+ }),
1272
+ primaryAction: canUpdate || canCreate ? /*#__PURE__*/ jsx(Button, {
1273
+ startIcon: /*#__PURE__*/ jsx(Check, {}),
1274
+ type: "submit",
1275
+ disabled: !modified || isSubmitting || values.stages.length === 0,
1276
+ // if the confirm dialog is open the loading state is on
1277
+ // the confirm button already
1278
+ loading: !Boolean(Object.keys(savePrompts).length > 0) && isSubmitting,
1279
+ children: formatMessage({
1280
+ id: 'global.save',
1281
+ defaultMessage: 'Save'
1282
+ })
1283
+ }) : null,
1284
+ subtitle: formatMessage({
1285
+ id: 'review-workflows.page.subtitle',
1286
+ defaultMessage: '{count, plural, one {# stage} other {# stages}}'
1287
+ }, {
1288
+ count: currentWorkflow?.stages?.length ?? 0
1289
+ }),
1290
+ title: currentWorkflow?.name || formatMessage({
1291
+ id: 'Settings.review-workflows.create.page.title',
1292
+ defaultMessage: 'Create Review Workflow'
1293
+ })
1294
+ }),
1295
+ /*#__PURE__*/ jsx(Root, {
1296
+ children: /*#__PURE__*/ jsxs(Flex, {
1297
+ alignItems: "stretch",
1298
+ direction: "column",
1299
+ gap: 7,
1300
+ children: [
1301
+ /*#__PURE__*/ jsx(WorkflowAttributes, {
1302
+ canUpdate: canUpdate || canCreate
1303
+ }),
1304
+ /*#__PURE__*/ jsx(Stages, {
1305
+ canDelete: canDelete,
1306
+ canUpdate: canUpdate || canCreate,
1307
+ isCreating: isCreatingWorkflow
1308
+ })
1309
+ ]
1310
+ })
1311
+ }),
1312
+ /*#__PURE__*/ jsx(Dialog.Root, {
1313
+ open: Object.keys(savePrompts).length > 0,
1314
+ onOpenChange: handleConfirmClose,
1315
+ children: /*#__PURE__*/ jsx(ConfirmDialog, {
1316
+ onConfirm: handleConfirmDeleteDialog(values, {
1317
+ setErrors
1318
+ }),
1319
+ children: /*#__PURE__*/ jsxs(Flex, {
1320
+ direction: "column",
1321
+ gap: 5,
1322
+ children: [
1323
+ savePrompts.hasDeletedServerStages && /*#__PURE__*/ jsx(Typography, {
1324
+ textAlign: "center",
1325
+ variant: "omega",
1326
+ children: formatMessage({
1327
+ id: 'review-workflows.page.delete.confirm.stages.body',
1328
+ defaultMessage: 'All entries assigned to deleted stages will be moved to the previous stage.'
1329
+ })
1330
+ }),
1331
+ savePrompts.hasReassignedContentTypes && /*#__PURE__*/ jsx(Typography, {
1332
+ textAlign: "center",
1333
+ variant: "omega",
1334
+ children: formatMessage({
1335
+ id: 'review-workflows.page.delete.confirm.contentType.body',
1336
+ defaultMessage: '{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.'
1337
+ }, {
1338
+ count: contentTypesFromOtherWorkflows?.filter((contentType)=>values.contentTypes.includes(contentType)).length ?? 0
1339
+ })
1340
+ }),
1341
+ /*#__PURE__*/ jsx(Typography, {
1342
+ textAlign: "center",
1343
+ variant: "omega",
1344
+ children: formatMessage({
1345
+ id: 'review-workflows.page.delete.confirm.confirm',
1346
+ defaultMessage: 'Are you sure you want to save?'
1347
+ })
1348
+ })
1349
+ ]
1350
+ })
1351
+ })
1352
+ })
1353
+ ]
1354
+ })
1355
+ }),
1356
+ /*#__PURE__*/ jsxs(LimitsModal.Root, {
1357
+ open: showLimitModal === 'workflow',
1358
+ onOpenChange: ()=>setShowLimitModal(null),
1359
+ children: [
1360
+ /*#__PURE__*/ jsx(LimitsModal.Title, {
1361
+ children: formatMessage({
1362
+ id: 'review-workflows.edit.page.workflows.limit.title',
1363
+ defaultMessage: 'You’ve reached the limit of workflows in your plan'
1364
+ })
1365
+ }),
1366
+ /*#__PURE__*/ jsx(LimitsModal.Body, {
1367
+ children: formatMessage({
1368
+ id: 'review-workflows.edit.page.workflows.limit.body',
1369
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.'
1370
+ })
1371
+ })
1372
+ ]
1373
+ }),
1374
+ /*#__PURE__*/ jsxs(LimitsModal.Root, {
1375
+ open: showLimitModal === 'stage',
1376
+ onOpenChange: ()=>setShowLimitModal(null),
1377
+ children: [
1378
+ /*#__PURE__*/ jsx(LimitsModal.Title, {
1379
+ children: formatMessage({
1380
+ id: 'review-workflows.edit.page.stages.limit.title',
1381
+ defaultMessage: 'You have reached the limit of stages for this workflow in your plan'
1382
+ })
1383
+ }),
1384
+ /*#__PURE__*/ jsx(LimitsModal.Body, {
1385
+ children: formatMessage({
1386
+ id: 'review-workflows.edit.page.stages.limit.body',
1387
+ defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.'
1388
+ })
1389
+ })
1390
+ ]
1391
+ })
1392
+ ]
1393
+ });
1394
+ };
1395
+ const addTmpKeysToStages = (data)=>{
1396
+ const keys = generateNKeysBetween(undefined, undefined, data.length);
1397
+ return data.map((datum, index)=>({
1398
+ ...datum,
1399
+ __temp_key__: keys[index]
1400
+ }));
1401
+ };
1402
+ /* -------------------------------------------------------------------------------------------------
1403
+ * ProtectedEditPage
1404
+ * -----------------------------------------------------------------------------------------------*/ const ProtectedEditPage = ()=>{
1405
+ const permissions = useTypedSelector((state)=>{
1406
+ const { create = [], update = [], read = [] } = state.admin_app.permissions.settings?.['review-workflows'] ?? {};
1407
+ return [
1408
+ ...create,
1409
+ ...update,
1410
+ ...read
1411
+ ];
1412
+ });
1413
+ return /*#__PURE__*/ jsx(Page.Protect, {
1414
+ permissions: permissions,
1415
+ children: /*#__PURE__*/ jsx(EditPage, {})
1416
+ });
1417
+ };
1418
+
1419
+ export { ProtectedEditPage };
1420
+ //# sourceMappingURL=id-LAMc9ydb.mjs.map