@strapi/review-workflows 0.0.0-experimental.74c69aeafc770d59d5b3d5d37cd249934ef395ba → 0.0.0-experimental.76999222c105ee5da1bc2540e3b5e5f57747300c

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