@strapi/content-releases 0.0.0-experimental.ee4d311a5e6a131fad03cf07e4696f49fdd9c2e6 → 0.0.0-experimental.f75e3c6d67cc47c64ab37479efdbb7b43be50b78

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 (110) hide show
  1. package/dist/_chunks/App-DUmziQ17.js +1366 -0
  2. package/dist/_chunks/App-DUmziQ17.js.map +1 -0
  3. package/dist/_chunks/App-D_6Y9N2F.mjs +1344 -0
  4. package/dist/_chunks/App-D_6Y9N2F.mjs.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js +52 -0
  6. package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs +52 -0
  8. package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
  9. package/dist/_chunks/{en-MyLPoISH.mjs → en-B9Ur3VsE.mjs} +30 -7
  10. package/dist/_chunks/en-B9Ur3VsE.mjs.map +1 -0
  11. package/dist/_chunks/{en-gYDqKYFd.js → en-DtFJ5ViE.js} +30 -7
  12. package/dist/_chunks/en-DtFJ5ViE.js.map +1 -0
  13. package/dist/_chunks/{index-EIe8S-cw.mjs → index-BomF0-yY.mjs} +352 -221
  14. package/dist/_chunks/index-BomF0-yY.mjs.map +1 -0
  15. package/dist/_chunks/{index-l5iuP0Hb.js → index-C5Hc767q.js} +346 -217
  16. package/dist/_chunks/index-C5Hc767q.js.map +1 -0
  17. package/dist/admin/index.js +1 -15
  18. package/dist/admin/index.js.map +1 -1
  19. package/dist/admin/index.mjs +2 -16
  20. package/dist/admin/index.mjs.map +1 -1
  21. package/dist/admin/src/components/CMReleasesContainer.d.ts +22 -0
  22. package/dist/admin/src/components/RelativeTime.d.ts +28 -0
  23. package/dist/admin/src/components/ReleaseAction.d.ts +3 -0
  24. package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
  25. package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
  26. package/dist/admin/src/components/ReleaseListCell.d.ts +0 -0
  27. package/dist/admin/src/components/ReleaseModal.d.ts +16 -0
  28. package/dist/admin/src/constants.d.ts +58 -0
  29. package/dist/admin/src/index.d.ts +3 -0
  30. package/dist/admin/src/pages/App.d.ts +1 -0
  31. package/dist/admin/src/pages/PurchaseContentReleases.d.ts +2 -0
  32. package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +2 -0
  33. package/dist/admin/src/pages/ReleasesPage.d.ts +8 -0
  34. package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +181 -0
  35. package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +39 -0
  36. package/dist/admin/src/pluginId.d.ts +1 -0
  37. package/dist/admin/src/services/release.d.ts +105 -0
  38. package/dist/admin/src/store/hooks.d.ts +7 -0
  39. package/dist/admin/src/utils/api.d.ts +6 -0
  40. package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
  41. package/dist/admin/src/utils/time.d.ts +1 -0
  42. package/dist/server/index.js +1113 -418
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +1113 -418
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/server/src/bootstrap.d.ts +5 -0
  47. package/dist/server/src/bootstrap.d.ts.map +1 -0
  48. package/dist/server/src/constants.d.ts +12 -0
  49. package/dist/server/src/constants.d.ts.map +1 -0
  50. package/dist/server/src/content-types/index.d.ts +99 -0
  51. package/dist/server/src/content-types/index.d.ts.map +1 -0
  52. package/dist/server/src/content-types/release/index.d.ts +48 -0
  53. package/dist/server/src/content-types/release/index.d.ts.map +1 -0
  54. package/dist/server/src/content-types/release/schema.d.ts +47 -0
  55. package/dist/server/src/content-types/release/schema.d.ts.map +1 -0
  56. package/dist/server/src/content-types/release-action/index.d.ts +50 -0
  57. package/dist/server/src/content-types/release-action/index.d.ts.map +1 -0
  58. package/dist/server/src/content-types/release-action/schema.d.ts +49 -0
  59. package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
  60. package/dist/server/src/controllers/index.d.ts +20 -0
  61. package/dist/server/src/controllers/index.d.ts.map +1 -0
  62. package/dist/server/src/controllers/release-action.d.ts +10 -0
  63. package/dist/server/src/controllers/release-action.d.ts.map +1 -0
  64. package/dist/server/src/controllers/release.d.ts +12 -0
  65. package/dist/server/src/controllers/release.d.ts.map +1 -0
  66. package/dist/server/src/controllers/validation/release-action.d.ts +8 -0
  67. package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
  68. package/dist/server/src/controllers/validation/release.d.ts +2 -0
  69. package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
  70. package/dist/server/src/destroy.d.ts +5 -0
  71. package/dist/server/src/destroy.d.ts.map +1 -0
  72. package/dist/server/src/index.d.ts +2096 -0
  73. package/dist/server/src/index.d.ts.map +1 -0
  74. package/dist/server/src/migrations/index.d.ts +13 -0
  75. package/dist/server/src/migrations/index.d.ts.map +1 -0
  76. package/dist/server/src/register.d.ts +5 -0
  77. package/dist/server/src/register.d.ts.map +1 -0
  78. package/dist/server/src/routes/index.d.ts +35 -0
  79. package/dist/server/src/routes/index.d.ts.map +1 -0
  80. package/dist/server/src/routes/release-action.d.ts +18 -0
  81. package/dist/server/src/routes/release-action.d.ts.map +1 -0
  82. package/dist/server/src/routes/release.d.ts +18 -0
  83. package/dist/server/src/routes/release.d.ts.map +1 -0
  84. package/dist/server/src/services/index.d.ts +1826 -0
  85. package/dist/server/src/services/index.d.ts.map +1 -0
  86. package/dist/server/src/services/release.d.ts +66 -0
  87. package/dist/server/src/services/release.d.ts.map +1 -0
  88. package/dist/server/src/services/scheduling.d.ts +18 -0
  89. package/dist/server/src/services/scheduling.d.ts.map +1 -0
  90. package/dist/server/src/services/validation.d.ts +18 -0
  91. package/dist/server/src/services/validation.d.ts.map +1 -0
  92. package/dist/server/src/utils/index.d.ts +14 -0
  93. package/dist/server/src/utils/index.d.ts.map +1 -0
  94. package/dist/shared/contracts/release-actions.d.ts +131 -0
  95. package/dist/shared/contracts/release-actions.d.ts.map +1 -0
  96. package/dist/shared/contracts/releases.d.ts +182 -0
  97. package/dist/shared/contracts/releases.d.ts.map +1 -0
  98. package/dist/shared/types.d.ts +24 -0
  99. package/dist/shared/types.d.ts.map +1 -0
  100. package/dist/shared/validation-schemas.d.ts +2 -0
  101. package/dist/shared/validation-schemas.d.ts.map +1 -0
  102. package/package.json +31 -35
  103. package/dist/_chunks/App-0yPbcoGt.js +0 -1037
  104. package/dist/_chunks/App-0yPbcoGt.js.map +0 -1
  105. package/dist/_chunks/App-BWaM2ihP.mjs +0 -1015
  106. package/dist/_chunks/App-BWaM2ihP.mjs.map +0 -1
  107. package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
  108. package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
  109. package/dist/_chunks/index-EIe8S-cw.mjs.map +0 -1
  110. package/dist/_chunks/index-l5iuP0Hb.js.map +0 -1
@@ -0,0 +1,1344 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useNotification, useAPIErrorHandler, useQueryParams, useTracking, useRBAC, Page, Layouts, Pagination, isFetchError, ConfirmDialog, BackButton, useStrapiApp, Table } from "@strapi/admin/strapi-admin";
3
+ import { useLocation, useNavigate, NavLink, useParams, Navigate, Link as Link$1, Routes, Route } from "react-router-dom";
4
+ import { g as getTimezoneOffset, p as pluginId, u as useGetReleasesQuery, a as useCreateReleaseMutation, P as PERMISSIONS, b as useGetReleaseQuery, c as useUpdateReleaseMutation, d as useDeleteReleaseMutation, e as usePublishReleaseMutation, f as useGetReleaseActionsQuery, h as useUpdateReleaseActionMutation, R as ReleaseActionOptions, i as ReleaseActionMenu, r as releaseApi } from "./index-BomF0-yY.mjs";
5
+ import * as React from "react";
6
+ import { unstable_useDocument } from "@strapi/content-manager/strapi-admin";
7
+ import { ModalLayout, ModalHeader, Typography, ModalBody, Flex, Field, TextInput, Box, Checkbox, DatePicker, TimePicker, ModalFooter, Button, Combobox, ComboboxOption, Link, Alert, Main, TabGroup, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem, Badge, Menu, LinkButton, SingleSelect, SingleSelectOption, Tr, Td, Tooltip } from "@strapi/design-system";
8
+ import { Plus, Pencil, Trash, More, CrossCircle, CheckCircle } from "@strapi/icons";
9
+ import { EmptyDocuments } from "@strapi/icons/symbols";
10
+ import format from "date-fns/format";
11
+ import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
12
+ import { useIntl } from "react-intl";
13
+ import { styled } from "styled-components";
14
+ import { intervalToDuration, isPast, formatISO } from "date-fns";
15
+ import { Formik, Form, useFormikContext } from "formik";
16
+ import * as yup from "yup";
17
+ import { useDispatch } from "react-redux";
18
+ import { useLicenseLimits } from "@strapi/admin/strapi-admin/ee";
19
+ const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
20
+ const RelativeTime$1 = React.forwardRef(
21
+ ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
22
+ const { formatRelativeTime, formatDate, formatTime } = useIntl();
23
+ const interval = intervalToDuration({
24
+ start: timestamp,
25
+ end: Date.now()
26
+ // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
27
+ });
28
+ const unit = intervals.find((intervalUnit) => {
29
+ return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
30
+ });
31
+ const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
32
+ const customInterval = customIntervals.find(
33
+ (custom) => interval[custom.unit] < custom.threshold
34
+ );
35
+ const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
36
+ return /* @__PURE__ */ jsx(
37
+ "time",
38
+ {
39
+ ref: forwardedRef,
40
+ dateTime: timestamp.toISOString(),
41
+ role: "time",
42
+ title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
43
+ ...restProps,
44
+ children: displayText
45
+ }
46
+ );
47
+ }
48
+ );
49
+ const RELEASE_SCHEMA = yup.object().shape({
50
+ name: yup.string().trim().required(),
51
+ scheduledAt: yup.string().nullable(),
52
+ isScheduled: yup.boolean().optional(),
53
+ time: yup.string().when("isScheduled", {
54
+ is: true,
55
+ then: yup.string().trim().required(),
56
+ otherwise: yup.string().nullable()
57
+ }),
58
+ timezone: yup.string().when("isScheduled", {
59
+ is: true,
60
+ then: yup.string().required().nullable(),
61
+ otherwise: yup.string().nullable()
62
+ }),
63
+ date: yup.string().when("isScheduled", {
64
+ is: true,
65
+ then: yup.string().required().nullable(),
66
+ otherwise: yup.string().nullable()
67
+ })
68
+ }).required().noUnknown();
69
+ const ReleaseModal = ({
70
+ handleClose,
71
+ handleSubmit,
72
+ initialValues,
73
+ isLoading = false
74
+ }) => {
75
+ const { formatMessage } = useIntl();
76
+ const { pathname } = useLocation();
77
+ const isCreatingRelease = pathname === `/plugins/${pluginId}`;
78
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
79
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
80
+ );
81
+ const getScheduledTimestamp = (values) => {
82
+ const { date, time, timezone } = values;
83
+ if (!date || !time || !timezone)
84
+ return null;
85
+ const timezoneWithoutOffset = timezone.split("&")[1];
86
+ return zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
87
+ };
88
+ const getTimezoneWithOffset = () => {
89
+ const currentTimezone = timezoneList.find(
90
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
91
+ );
92
+ return currentTimezone?.value || systemTimezone.value;
93
+ };
94
+ return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
95
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
96
+ {
97
+ id: "content-releases.modal.title",
98
+ defaultMessage: "{isCreatingRelease, select, true {New release} other {Edit release}}"
99
+ },
100
+ { isCreatingRelease }
101
+ ) }) }),
102
+ /* @__PURE__ */ jsx(
103
+ Formik,
104
+ {
105
+ onSubmit: (values) => {
106
+ handleSubmit({
107
+ ...values,
108
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
109
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
110
+ });
111
+ },
112
+ initialValues: {
113
+ ...initialValues,
114
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
115
+ },
116
+ validationSchema: RELEASE_SCHEMA,
117
+ validateOnChange: false,
118
+ children: ({ values, errors, handleChange, setFieldValue }) => {
119
+ return /* @__PURE__ */ jsxs(Form, { children: [
120
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
121
+ /* @__PURE__ */ jsxs(Field.Root, { name: "name", error: errors.name, required: true, children: [
122
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
123
+ id: "content-releases.modal.form.input.label.release-name",
124
+ defaultMessage: "Name"
125
+ }) }),
126
+ /* @__PURE__ */ jsx(TextInput, { value: values.name, onChange: handleChange }),
127
+ /* @__PURE__ */ jsx(Field.Error, {})
128
+ ] }),
129
+ /* @__PURE__ */ jsx(Box, { width: "max-content", children: /* @__PURE__ */ jsx(
130
+ Checkbox,
131
+ {
132
+ name: "isScheduled",
133
+ value: values.isScheduled,
134
+ onChange: (event) => {
135
+ setFieldValue("isScheduled", event.target.checked);
136
+ if (!event.target.checked) {
137
+ setFieldValue("date", null);
138
+ setFieldValue("time", "");
139
+ setFieldValue("timezone", null);
140
+ } else {
141
+ setFieldValue("date", initialValues.date);
142
+ setFieldValue("time", initialValues.time);
143
+ setFieldValue(
144
+ "timezone",
145
+ initialValues.timezone ?? systemTimezone?.value
146
+ );
147
+ }
148
+ },
149
+ children: /* @__PURE__ */ jsx(
150
+ Typography,
151
+ {
152
+ textColor: values.isScheduled ? "primary600" : "neutral800",
153
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
154
+ children: formatMessage({
155
+ id: "modal.form.input.label.schedule-release",
156
+ defaultMessage: "Schedule release"
157
+ })
158
+ }
159
+ )
160
+ }
161
+ ) }),
162
+ values.isScheduled && /* @__PURE__ */ jsxs(Fragment, { children: [
163
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "start", children: [
164
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsxs(Field.Root, { name: "date", error: errors.date, required: true, children: [
165
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
166
+ id: "content-releases.modal.form.input.label.date",
167
+ defaultMessage: "Date"
168
+ }) }),
169
+ /* @__PURE__ */ jsx(
170
+ DatePicker,
171
+ {
172
+ onChange: (date) => {
173
+ const isoFormatDate = date ? formatISO(date, { representation: "date" }) : null;
174
+ setFieldValue("date", isoFormatDate);
175
+ },
176
+ clearLabel: formatMessage({
177
+ id: "content-releases.modal.form.input.clearLabel",
178
+ defaultMessage: "Clear"
179
+ }),
180
+ onClear: () => {
181
+ setFieldValue("date", null);
182
+ },
183
+ value: values.date ? new Date(values.date) : /* @__PURE__ */ new Date(),
184
+ minDate: utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
185
+ }
186
+ ),
187
+ /* @__PURE__ */ jsx(Field.Error, {})
188
+ ] }) }),
189
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsxs(Field.Root, { name: "time", error: errors.time, required: true, children: [
190
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
191
+ id: "content-releases.modal.form.input.label.time",
192
+ defaultMessage: "Time"
193
+ }) }),
194
+ /* @__PURE__ */ jsx(
195
+ TimePicker,
196
+ {
197
+ onChange: (time) => {
198
+ setFieldValue("time", time);
199
+ },
200
+ clearLabel: formatMessage({
201
+ id: "content-releases.modal.form.input.clearLabel",
202
+ defaultMessage: "Clear"
203
+ }),
204
+ onClear: () => {
205
+ setFieldValue("time", "");
206
+ },
207
+ value: values.time || void 0
208
+ }
209
+ ),
210
+ /* @__PURE__ */ jsx(Field.Error, {})
211
+ ] }) })
212
+ ] }),
213
+ /* @__PURE__ */ jsx(TimezoneComponent, { timezoneOptions: timezoneList })
214
+ ] })
215
+ ] }) }),
216
+ /* @__PURE__ */ jsx(
217
+ ModalFooter,
218
+ {
219
+ startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
220
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
221
+ {
222
+ id: "content-releases.modal.form.button.submit",
223
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
224
+ },
225
+ { isCreatingRelease }
226
+ ) })
227
+ }
228
+ )
229
+ ] });
230
+ }
231
+ }
232
+ )
233
+ ] });
234
+ };
235
+ const getTimezones = (selectedDate) => {
236
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
237
+ const utcOffset = getTimezoneOffset(timezone, selectedDate);
238
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
239
+ });
240
+ const systemTimezone = timezoneList.find(
241
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
242
+ );
243
+ return { timezoneList, systemTimezone };
244
+ };
245
+ const TimezoneComponent = ({ timezoneOptions }) => {
246
+ const { values, errors, setFieldValue } = useFormikContext();
247
+ const { formatMessage } = useIntl();
248
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
249
+ React.useEffect(() => {
250
+ if (values.date) {
251
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
252
+ setTimezoneList(timezoneList2);
253
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
254
+ if (updatedTimezone) {
255
+ setFieldValue("timezone", updatedTimezone.value);
256
+ }
257
+ }
258
+ }, [setFieldValue, values.date, values.timezone]);
259
+ return /* @__PURE__ */ jsxs(Field.Root, { name: "timezone", error: errors.timezone, required: true, children: [
260
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
261
+ id: "content-releases.modal.form.input.label.timezone",
262
+ defaultMessage: "Timezone"
263
+ }) }),
264
+ /* @__PURE__ */ jsx(
265
+ Combobox,
266
+ {
267
+ autocomplete: { type: "list", filter: "contains" },
268
+ value: values.timezone || void 0,
269
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
270
+ onChange: (timezone) => {
271
+ setFieldValue("timezone", timezone);
272
+ },
273
+ onTextValueChange: (timezone) => {
274
+ setFieldValue("timezone", timezone);
275
+ },
276
+ onClear: () => {
277
+ setFieldValue("timezone", "");
278
+ },
279
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
280
+ }
281
+ ),
282
+ /* @__PURE__ */ jsx(Field.Error, {})
283
+ ] });
284
+ };
285
+ const useTypedDispatch = useDispatch;
286
+ const isBaseQueryError = (error) => {
287
+ return typeof error !== "undefined" && error.name !== void 0;
288
+ };
289
+ const LinkCard = styled(Link)`
290
+ display: block;
291
+ `;
292
+ const RelativeTime = styled(RelativeTime$1)`
293
+ display: inline-block;
294
+ &::first-letter {
295
+ text-transform: uppercase;
296
+ }
297
+ `;
298
+ const getBadgeProps = (status) => {
299
+ let color;
300
+ switch (status) {
301
+ case "ready":
302
+ color = "success";
303
+ break;
304
+ case "blocked":
305
+ color = "warning";
306
+ break;
307
+ case "failed":
308
+ color = "danger";
309
+ break;
310
+ case "done":
311
+ color = "primary";
312
+ break;
313
+ case "empty":
314
+ default:
315
+ color = "neutral";
316
+ }
317
+ return {
318
+ textColor: `${color}600`,
319
+ backgroundColor: `${color}100`,
320
+ borderColor: `${color}200`
321
+ };
322
+ };
323
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
324
+ const { formatMessage } = useIntl();
325
+ if (isError) {
326
+ return /* @__PURE__ */ jsx(Page.Error, {});
327
+ }
328
+ if (releases?.length === 0) {
329
+ return /* @__PURE__ */ jsx(
330
+ EmptyStateLayout,
331
+ {
332
+ content: formatMessage(
333
+ {
334
+ id: "content-releases.page.Releases.tab.emptyEntries",
335
+ defaultMessage: "No releases"
336
+ },
337
+ {
338
+ target: sectionTitle
339
+ }
340
+ ),
341
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "16rem" })
342
+ }
343
+ );
344
+ }
345
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, scheduledAt, status }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { tag: NavLink, to: `${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
346
+ Flex,
347
+ {
348
+ direction: "column",
349
+ justifyContent: "space-between",
350
+ padding: 4,
351
+ hasRadius: true,
352
+ background: "neutral0",
353
+ shadow: "tableShadow",
354
+ height: "100%",
355
+ width: "100%",
356
+ alignItems: "start",
357
+ gap: 4,
358
+ children: [
359
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
360
+ /* @__PURE__ */ jsx(Typography, { tag: "h3", variant: "delta", fontWeight: "bold", children: name }),
361
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
362
+ id: "content-releases.pages.Releases.not-scheduled",
363
+ defaultMessage: "Not scheduled"
364
+ }) })
365
+ ] }),
366
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
367
+ ]
368
+ }
369
+ ) }) }, id)) });
370
+ };
371
+ const StyledAlert = styled(Alert)`
372
+ button {
373
+ display: none;
374
+ }
375
+ p + div {
376
+ margin-left: auto;
377
+ }
378
+ `;
379
+ const INITIAL_FORM_VALUES = {
380
+ name: "",
381
+ date: void 0,
382
+ time: "",
383
+ isScheduled: true,
384
+ scheduledAt: null,
385
+ timezone: null
386
+ };
387
+ const ReleasesPage = () => {
388
+ const tabRef = React.useRef(null);
389
+ const location = useLocation();
390
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
391
+ const { toggleNotification } = useNotification();
392
+ const { formatMessage } = useIntl();
393
+ const navigate = useNavigate();
394
+ const { formatAPIError } = useAPIErrorHandler();
395
+ const [{ query }, setQuery] = useQueryParams();
396
+ const response = useGetReleasesQuery(query);
397
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
398
+ const { getFeature } = useLicenseLimits();
399
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
400
+ const { trackUsage } = useTracking();
401
+ const {
402
+ allowedActions: { canCreate }
403
+ } = useRBAC(PERMISSIONS);
404
+ const { isLoading, isSuccess, isError } = response;
405
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
406
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
407
+ React.useEffect(() => {
408
+ if (location?.state?.errors) {
409
+ toggleNotification({
410
+ type: "danger",
411
+ title: formatMessage({
412
+ id: "content-releases.pages.Releases.notification.error.title",
413
+ defaultMessage: "Your request could not be processed."
414
+ }),
415
+ message: formatMessage({
416
+ id: "content-releases.pages.Releases.notification.error.message",
417
+ defaultMessage: "Please try again or open another release."
418
+ })
419
+ });
420
+ navigate("", { replace: true, state: null });
421
+ }
422
+ }, [formatMessage, location?.state?.errors, navigate, toggleNotification]);
423
+ React.useEffect(() => {
424
+ if (tabRef.current) {
425
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
426
+ }
427
+ }, [activeTabIndex]);
428
+ const toggleAddReleaseModal = () => {
429
+ setReleaseModalShown((prev) => !prev);
430
+ };
431
+ if (isLoading) {
432
+ return /* @__PURE__ */ jsx(Page.Loading, {});
433
+ }
434
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
435
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
436
+ const handleTabChange = (index) => {
437
+ setQuery({
438
+ ...query,
439
+ page: 1,
440
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
441
+ filters: {
442
+ releasedAt: {
443
+ $notNull: index === 0 ? false : true
444
+ }
445
+ }
446
+ });
447
+ };
448
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
449
+ const response2 = await createRelease({
450
+ name,
451
+ scheduledAt,
452
+ timezone
453
+ });
454
+ if ("data" in response2) {
455
+ toggleNotification({
456
+ type: "success",
457
+ message: formatMessage({
458
+ id: "content-releases.modal.release-created-notification-success",
459
+ defaultMessage: "Release created."
460
+ })
461
+ });
462
+ trackUsage("didCreateRelease");
463
+ navigate(response2.data.data.id.toString());
464
+ } else if (isFetchError(response2.error)) {
465
+ toggleNotification({
466
+ type: "danger",
467
+ message: formatAPIError(response2.error)
468
+ });
469
+ } else {
470
+ toggleNotification({
471
+ type: "danger",
472
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
473
+ });
474
+ }
475
+ };
476
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
477
+ /* @__PURE__ */ jsx(
478
+ Layouts.Header,
479
+ {
480
+ title: formatMessage({
481
+ id: "content-releases.pages.Releases.title",
482
+ defaultMessage: "Releases"
483
+ }),
484
+ subtitle: formatMessage({
485
+ id: "content-releases.pages.Releases.header-subtitle",
486
+ defaultMessage: "Create and manage content updates"
487
+ }),
488
+ primaryAction: canCreate ? /* @__PURE__ */ jsx(
489
+ Button,
490
+ {
491
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
492
+ onClick: toggleAddReleaseModal,
493
+ disabled: hasReachedMaximumPendingReleases,
494
+ children: formatMessage({
495
+ id: "content-releases.header.actions.add-release",
496
+ defaultMessage: "New release"
497
+ })
498
+ }
499
+ ) : null
500
+ }
501
+ ),
502
+ /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
503
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
504
+ StyledAlert,
505
+ {
506
+ marginBottom: 6,
507
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
508
+ id: "content-releases.pages.Releases.max-limit-reached.action",
509
+ defaultMessage: "Explore plans"
510
+ }) }),
511
+ title: formatMessage(
512
+ {
513
+ id: "content-releases.pages.Releases.max-limit-reached.title",
514
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
515
+ },
516
+ { number: maximumReleases }
517
+ ),
518
+ onClose: () => {
519
+ },
520
+ closeLabel: "",
521
+ children: formatMessage({
522
+ id: "content-releases.pages.Releases.max-limit-reached.message",
523
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
524
+ })
525
+ }
526
+ ),
527
+ /* @__PURE__ */ jsxs(
528
+ TabGroup,
529
+ {
530
+ label: formatMessage({
531
+ id: "content-releases.pages.Releases.tab-group.label",
532
+ defaultMessage: "Releases list"
533
+ }),
534
+ variant: "simple",
535
+ initialSelectedTabIndex: activeTabIndex,
536
+ onTabChange: handleTabChange,
537
+ ref: tabRef,
538
+ children: [
539
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
540
+ /* @__PURE__ */ jsxs(Tabs, { children: [
541
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
542
+ {
543
+ id: "content-releases.pages.Releases.tab.pending",
544
+ defaultMessage: "Pending ({count})"
545
+ },
546
+ {
547
+ count: totalPendingReleases
548
+ }
549
+ ) }),
550
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
551
+ id: "content-releases.pages.Releases.tab.done",
552
+ defaultMessage: "Done"
553
+ }) })
554
+ ] }),
555
+ /* @__PURE__ */ jsx(Divider, {})
556
+ ] }),
557
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
558
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
559
+ ReleasesGrid,
560
+ {
561
+ sectionTitle: "pending",
562
+ releases: response?.currentData?.data,
563
+ isError
564
+ }
565
+ ) }),
566
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
567
+ ReleasesGrid,
568
+ {
569
+ sectionTitle: "done",
570
+ releases: response?.currentData?.data,
571
+ isError
572
+ }
573
+ ) })
574
+ ] })
575
+ ]
576
+ }
577
+ ),
578
+ /* @__PURE__ */ jsxs(
579
+ Pagination.Root,
580
+ {
581
+ ...response?.currentData?.meta?.pagination,
582
+ defaultPageSize: response?.currentData?.meta?.pagination?.pageSize,
583
+ children: [
584
+ /* @__PURE__ */ jsx(Pagination.PageSize, { options: ["8", "16", "32", "64"] }),
585
+ /* @__PURE__ */ jsx(Pagination.Links, {})
586
+ ]
587
+ }
588
+ )
589
+ ] }) }),
590
+ releaseModalShown && /* @__PURE__ */ jsx(
591
+ ReleaseModal,
592
+ {
593
+ handleClose: toggleAddReleaseModal,
594
+ handleSubmit: handleAddRelease,
595
+ isLoading: isSubmittingForm,
596
+ initialValues: INITIAL_FORM_VALUES
597
+ }
598
+ )
599
+ ] });
600
+ };
601
+ const ReleaseInfoWrapper = styled(Flex)`
602
+ align-self: stretch;
603
+ border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
604
+ border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
605
+ border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
606
+ `;
607
+ const StyledMenuItem = styled(Menu.Item)`
608
+ svg path {
609
+ fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
610
+ }
611
+ span {
612
+ color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
613
+ }
614
+
615
+ &:hover {
616
+ background: ${({ theme, $variant = "neutral" }) => theme.colors[`${$variant}100`]};
617
+ }
618
+ `;
619
+ const PencilIcon = styled(Pencil)`
620
+ width: ${({ theme }) => theme.spaces[3]};
621
+ height: ${({ theme }) => theme.spaces[3]};
622
+ path {
623
+ fill: ${({ theme }) => theme.colors.neutral600};
624
+ }
625
+ `;
626
+ const TrashIcon = styled(Trash)`
627
+ width: ${({ theme }) => theme.spaces[3]};
628
+ height: ${({ theme }) => theme.spaces[3]};
629
+ path {
630
+ fill: ${({ theme }) => theme.colors.danger600};
631
+ }
632
+ `;
633
+ const TypographyMaxWidth = styled(Typography)`
634
+ max-width: 300px;
635
+ `;
636
+ const EntryValidationText = ({ action, schema, entry }) => {
637
+ const { formatMessage } = useIntl();
638
+ const { validate } = unstable_useDocument(
639
+ {
640
+ collectionType: schema?.kind ?? "",
641
+ model: schema?.uid ?? ""
642
+ },
643
+ {
644
+ skip: !schema
645
+ }
646
+ );
647
+ const errors = validate(entry) ?? {};
648
+ if (Object.keys(errors).length > 0) {
649
+ const validationErrorsMessages = Object.entries(errors).map(
650
+ ([key, value]) => formatMessage(
651
+ // @ts-expect-error – TODO: fix this will better checks
652
+ { id: `${value.id}.withField`, defaultMessage: value.defaultMessage },
653
+ { field: key }
654
+ )
655
+ ).join(" ");
656
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
657
+ /* @__PURE__ */ jsx(CrossCircle, { fill: "danger600" }),
658
+ /* @__PURE__ */ jsx(Tooltip, { description: validationErrorsMessages, children: /* @__PURE__ */ jsx(TypographyMaxWidth, { textColor: "danger600", variant: "omega", fontWeight: "semiBold", ellipsis: true, children: validationErrorsMessages }) })
659
+ ] });
660
+ }
661
+ if (action == "publish") {
662
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
663
+ /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
664
+ entry.publishedAt ? /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
665
+ id: "content-releases.pages.ReleaseDetails.entry-validation.already-published",
666
+ defaultMessage: "Already published"
667
+ }) }) : /* @__PURE__ */ jsx(Typography, { children: formatMessage({
668
+ id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish",
669
+ defaultMessage: "Ready to publish"
670
+ }) })
671
+ ] });
672
+ }
673
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
674
+ /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
675
+ !entry.publishedAt ? /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
676
+ id: "content-releases.pages.ReleaseDetails.entry-validation.already-unpublished",
677
+ defaultMessage: "Already unpublished"
678
+ }) }) : /* @__PURE__ */ jsx(Typography, { children: formatMessage({
679
+ id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish",
680
+ defaultMessage: "Ready to unpublish"
681
+ }) })
682
+ ] });
683
+ };
684
+ const ReleaseDetailsLayout = ({
685
+ toggleEditReleaseModal,
686
+ toggleWarningSubmit,
687
+ children
688
+ }) => {
689
+ const { formatMessage, formatDate, formatTime } = useIntl();
690
+ const { releaseId } = useParams();
691
+ const {
692
+ data,
693
+ isLoading: isLoadingDetails,
694
+ error
695
+ } = useGetReleaseQuery(
696
+ { id: releaseId },
697
+ {
698
+ skip: !releaseId
699
+ }
700
+ );
701
+ const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
702
+ const { toggleNotification } = useNotification();
703
+ const { formatAPIError } = useAPIErrorHandler();
704
+ const { allowedActions } = useRBAC(PERMISSIONS);
705
+ const { canUpdate, canDelete, canPublish } = allowedActions;
706
+ const dispatch = useTypedDispatch();
707
+ const { trackUsage } = useTracking();
708
+ const release = data?.data;
709
+ const handlePublishRelease = (id) => async () => {
710
+ const response = await publishRelease({ id });
711
+ if ("data" in response) {
712
+ toggleNotification({
713
+ type: "success",
714
+ message: formatMessage({
715
+ id: "content-releases.pages.ReleaseDetails.publish-notification-success",
716
+ defaultMessage: "Release was published successfully."
717
+ })
718
+ });
719
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
720
+ trackUsage("didPublishRelease", {
721
+ totalEntries: totalEntries2,
722
+ totalPublishedEntries,
723
+ totalUnpublishedEntries
724
+ });
725
+ } else if (isFetchError(response.error)) {
726
+ toggleNotification({
727
+ type: "danger",
728
+ message: formatAPIError(response.error)
729
+ });
730
+ } else {
731
+ toggleNotification({
732
+ type: "danger",
733
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
734
+ });
735
+ }
736
+ };
737
+ const handleRefresh = () => {
738
+ dispatch(
739
+ releaseApi.util.invalidateTags([
740
+ { type: "ReleaseAction", id: "LIST" },
741
+ { type: "Release", id: releaseId }
742
+ ])
743
+ );
744
+ };
745
+ const getCreatedByUser = () => {
746
+ if (!release?.createdBy) {
747
+ return null;
748
+ }
749
+ if (release.createdBy.username) {
750
+ return release.createdBy.username;
751
+ }
752
+ if (release.createdBy.firstname) {
753
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
754
+ }
755
+ return release.createdBy.email;
756
+ };
757
+ if (isLoadingDetails) {
758
+ return /* @__PURE__ */ jsx(Page.Loading, {});
759
+ }
760
+ if (isBaseQueryError(error) && "code" in error || !release) {
761
+ return /* @__PURE__ */ jsx(
762
+ Navigate,
763
+ {
764
+ to: "..",
765
+ state: {
766
+ errors: [
767
+ {
768
+ // @ts-expect-error – TODO: fix this weird error flow
769
+ code: error?.code
770
+ }
771
+ ]
772
+ }
773
+ }
774
+ );
775
+ }
776
+ const totalEntries = release.actions.meta.count || 0;
777
+ const hasCreatedByUser = Boolean(getCreatedByUser());
778
+ const isScheduled = release.scheduledAt && release.timezone;
779
+ const numberOfEntriesText = formatMessage(
780
+ {
781
+ id: "content-releases.pages.Details.header-subtitle",
782
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
783
+ },
784
+ { number: totalEntries }
785
+ );
786
+ const scheduledText = isScheduled ? formatMessage(
787
+ {
788
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
789
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
790
+ },
791
+ {
792
+ date: formatDate(new Date(release.scheduledAt), {
793
+ weekday: "long",
794
+ day: "numeric",
795
+ month: "long",
796
+ year: "numeric",
797
+ timeZone: release.timezone
798
+ }),
799
+ time: formatTime(new Date(release.scheduledAt), {
800
+ timeZone: release.timezone,
801
+ hourCycle: "h23"
802
+ }),
803
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
804
+ }
805
+ ) : "";
806
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
807
+ /* @__PURE__ */ jsx(
808
+ Layouts.Header,
809
+ {
810
+ title: release.name,
811
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
812
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
813
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
814
+ ] }),
815
+ navigationAction: /* @__PURE__ */ jsx(BackButton, {}),
816
+ primaryAction: !release.releasedAt && /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
817
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
818
+ /* @__PURE__ */ jsx(
819
+ Menu.Trigger,
820
+ {
821
+ paddingLeft: 2,
822
+ paddingRight: 2,
823
+ "aria-label": formatMessage({
824
+ id: "content-releases.header.actions.open-release-actions",
825
+ defaultMessage: "Release edit and delete menu"
826
+ }),
827
+ variant: "tertiary",
828
+ children: /* @__PURE__ */ jsx(More, {})
829
+ }
830
+ ),
831
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", maxHeight: void 0, children: [
832
+ /* @__PURE__ */ jsxs(
833
+ Flex,
834
+ {
835
+ alignItems: "center",
836
+ justifyContent: "center",
837
+ direction: "column",
838
+ padding: 1,
839
+ width: "100%",
840
+ children: [
841
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
842
+ /* @__PURE__ */ jsx(PencilIcon, {}),
843
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
844
+ id: "content-releases.header.actions.edit",
845
+ defaultMessage: "Edit"
846
+ }) })
847
+ ] }) }),
848
+ /* @__PURE__ */ jsx(
849
+ StyledMenuItem,
850
+ {
851
+ disabled: !canDelete,
852
+ onSelect: toggleWarningSubmit,
853
+ $variant: "danger",
854
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
855
+ /* @__PURE__ */ jsx(TrashIcon, {}),
856
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
857
+ id: "content-releases.header.actions.delete",
858
+ defaultMessage: "Delete"
859
+ }) })
860
+ ] })
861
+ }
862
+ )
863
+ ]
864
+ }
865
+ ),
866
+ /* @__PURE__ */ jsxs(
867
+ ReleaseInfoWrapper,
868
+ {
869
+ direction: "column",
870
+ justifyContent: "center",
871
+ alignItems: "flex-start",
872
+ gap: 1,
873
+ padding: 5,
874
+ children: [
875
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
876
+ id: "content-releases.header.actions.created",
877
+ defaultMessage: "Created"
878
+ }) }),
879
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
880
+ /* @__PURE__ */ jsx(RelativeTime$1, { timestamp: new Date(release.createdAt) }),
881
+ formatMessage(
882
+ {
883
+ id: "content-releases.header.actions.created.description",
884
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
885
+ },
886
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
887
+ )
888
+ ] })
889
+ ]
890
+ }
891
+ )
892
+ ] })
893
+ ] }),
894
+ /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
895
+ id: "content-releases.header.actions.refresh",
896
+ defaultMessage: "Refresh"
897
+ }) }),
898
+ canPublish ? /* @__PURE__ */ jsx(
899
+ Button,
900
+ {
901
+ size: "S",
902
+ variant: "default",
903
+ onClick: handlePublishRelease(release.id.toString()),
904
+ loading: isPublishing,
905
+ disabled: release.actions.meta.count === 0,
906
+ children: formatMessage({
907
+ id: "content-releases.header.actions.publish",
908
+ defaultMessage: "Publish"
909
+ })
910
+ }
911
+ ) : null
912
+ ] })
913
+ }
914
+ ),
915
+ children
916
+ ] });
917
+ };
918
+ const GROUP_BY_OPTIONS = ["contentType", "locale", "action"];
919
+ const GROUP_BY_OPTIONS_NO_LOCALE = ["contentType", "action"];
920
+ const getGroupByOptionLabel = (value) => {
921
+ if (value === "locale") {
922
+ return {
923
+ id: "content-releases.pages.ReleaseDetails.groupBy.option.locales",
924
+ defaultMessage: "Locales"
925
+ };
926
+ }
927
+ if (value === "action") {
928
+ return {
929
+ id: "content-releases.pages.ReleaseDetails.groupBy.option.actions",
930
+ defaultMessage: "Actions"
931
+ };
932
+ }
933
+ return {
934
+ id: "content-releases.pages.ReleaseDetails.groupBy.option.content-type",
935
+ defaultMessage: "Content-Types"
936
+ };
937
+ };
938
+ const ReleaseDetailsBody = ({ releaseId }) => {
939
+ const { formatMessage } = useIntl();
940
+ const [{ query }, setQuery] = useQueryParams();
941
+ const { toggleNotification } = useNotification();
942
+ const { formatAPIError } = useAPIErrorHandler();
943
+ const {
944
+ data: releaseData,
945
+ isLoading: isReleaseLoading,
946
+ error: releaseError
947
+ } = useGetReleaseQuery({ id: releaseId });
948
+ const {
949
+ allowedActions: { canUpdate }
950
+ } = useRBAC(PERMISSIONS);
951
+ const runHookWaterfall = useStrapiApp("ReleaseDetailsPage", (state) => state.runHookWaterfall);
952
+ const { hasI18nEnabled } = runHookWaterfall(
953
+ "ContentReleases/pages/ReleaseDetails/add-locale-in-releases",
954
+ {
955
+ displayedHeaders: {
956
+ label: formatMessage({
957
+ id: "content-releases.page.ReleaseDetails.table.header.label.locale",
958
+ defaultMessage: "locale"
959
+ }),
960
+ name: "locale"
961
+ },
962
+ hasI18nEnabled: false
963
+ }
964
+ );
965
+ const release = releaseData?.data;
966
+ const selectedGroupBy = query?.groupBy || "contentType";
967
+ const {
968
+ isLoading,
969
+ isFetching,
970
+ isError,
971
+ data,
972
+ error: releaseActionsError
973
+ } = useGetReleaseActionsQuery({
974
+ ...query,
975
+ releaseId
976
+ });
977
+ const [updateReleaseAction] = useUpdateReleaseActionMutation();
978
+ const handleChangeType = async (e, actionId, actionPath) => {
979
+ const response = await updateReleaseAction({
980
+ params: {
981
+ releaseId,
982
+ actionId
983
+ },
984
+ body: {
985
+ type: e.target.value
986
+ },
987
+ query,
988
+ // We are passing the query params to make optimistic updates
989
+ actionPath
990
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
991
+ });
992
+ if ("error" in response) {
993
+ if (isFetchError(response.error)) {
994
+ toggleNotification({
995
+ type: "danger",
996
+ message: formatAPIError(response.error)
997
+ });
998
+ } else {
999
+ toggleNotification({
1000
+ type: "danger",
1001
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1002
+ });
1003
+ }
1004
+ }
1005
+ };
1006
+ if (isLoading || isReleaseLoading) {
1007
+ return /* @__PURE__ */ jsx(Page.Loading, {});
1008
+ }
1009
+ const releaseActions = data?.data;
1010
+ const releaseMeta = data?.meta;
1011
+ const contentTypes = releaseMeta?.contentTypes || {};
1012
+ const components = releaseMeta?.components || {};
1013
+ if (isBaseQueryError(releaseError) || !release) {
1014
+ const errorsArray = [];
1015
+ if (releaseError && "code" in releaseError) {
1016
+ errorsArray.push({
1017
+ code: releaseError.code
1018
+ });
1019
+ }
1020
+ if (releaseActionsError && "code" in releaseActionsError) {
1021
+ errorsArray.push({
1022
+ code: releaseActionsError.code
1023
+ });
1024
+ }
1025
+ return /* @__PURE__ */ jsx(
1026
+ Navigate,
1027
+ {
1028
+ to: "..",
1029
+ state: {
1030
+ errors: errorsArray
1031
+ }
1032
+ }
1033
+ );
1034
+ }
1035
+ if (isError || !releaseActions) {
1036
+ return /* @__PURE__ */ jsx(Page.Error, {});
1037
+ }
1038
+ if (Object.keys(releaseActions).length === 0) {
1039
+ return /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsx(
1040
+ EmptyStateLayout,
1041
+ {
1042
+ action: /* @__PURE__ */ jsx(
1043
+ LinkButton,
1044
+ {
1045
+ tag: Link$1,
1046
+ to: {
1047
+ pathname: "/content-manager"
1048
+ },
1049
+ style: { textDecoration: "none" },
1050
+ variant: "secondary",
1051
+ children: formatMessage({
1052
+ id: "content-releases.page.Details.button.openContentManager",
1053
+ defaultMessage: "Open the Content Manager"
1054
+ })
1055
+ }
1056
+ ),
1057
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "16rem" }),
1058
+ content: formatMessage({
1059
+ id: "content-releases.pages.Details.tab.emptyEntries",
1060
+ defaultMessage: "This release is empty. Open the Content Manager, select an entry and add it to the release."
1061
+ })
1062
+ }
1063
+ ) });
1064
+ }
1065
+ const groupByLabel = formatMessage({
1066
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
1067
+ defaultMessage: "Group by"
1068
+ });
1069
+ const headers = [
1070
+ // ...displayedHeaders,
1071
+ {
1072
+ label: formatMessage({
1073
+ id: "content-releases.page.ReleaseDetails.table.header.label.name",
1074
+ defaultMessage: "name"
1075
+ }),
1076
+ name: "name"
1077
+ },
1078
+ {
1079
+ label: formatMessage({
1080
+ id: "content-releases.page.ReleaseDetails.table.header.label.content-type",
1081
+ defaultMessage: "content-type"
1082
+ }),
1083
+ name: "content-type"
1084
+ },
1085
+ {
1086
+ label: formatMessage({
1087
+ id: "content-releases.page.ReleaseDetails.table.header.label.action",
1088
+ defaultMessage: "action"
1089
+ }),
1090
+ name: "action"
1091
+ },
1092
+ ...!release.releasedAt ? [
1093
+ {
1094
+ label: formatMessage({
1095
+ id: "content-releases.page.ReleaseDetails.table.header.label.status",
1096
+ defaultMessage: "status"
1097
+ }),
1098
+ name: "status"
1099
+ }
1100
+ ] : []
1101
+ ];
1102
+ const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
1103
+ return /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsxs(Flex, { gap: 8, direction: "column", alignItems: "stretch", children: [
1104
+ /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
1105
+ SingleSelect,
1106
+ {
1107
+ placeholder: groupByLabel,
1108
+ "aria-label": groupByLabel,
1109
+ customizeContent: (value) => formatMessage(
1110
+ {
1111
+ id: `content-releases.pages.ReleaseDetails.groupBy.label`,
1112
+ defaultMessage: `Group by {groupBy}`
1113
+ },
1114
+ {
1115
+ groupBy: value
1116
+ }
1117
+ ),
1118
+ value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
1119
+ onChange: (value) => setQuery({ groupBy: value }),
1120
+ children: options.map((option) => /* @__PURE__ */ jsx(SingleSelectOption, { value: option, children: formatMessage(getGroupByOptionLabel(option)) }, option))
1121
+ }
1122
+ ) }),
1123
+ Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxs(Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
1124
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
1125
+ /* @__PURE__ */ jsx(
1126
+ Table.Root,
1127
+ {
1128
+ rows: releaseActions[key].map((item) => ({
1129
+ ...item,
1130
+ id: Number(item.entry.id)
1131
+ })),
1132
+ headers,
1133
+ isLoading: isLoading || isFetching,
1134
+ children: /* @__PURE__ */ jsxs(Table.Content, { children: [
1135
+ /* @__PURE__ */ jsx(Table.Head, { children: headers.map((header) => /* @__PURE__ */ jsx(Table.HeaderCell, { ...header }, header.name)) }),
1136
+ /* @__PURE__ */ jsx(Table.Loading, {}),
1137
+ /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(
1138
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxs(Tr, { children: [
1139
+ /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
1140
+ hasI18nEnabled && /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
1141
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
1142
+ /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
1143
+ {
1144
+ id: "content-releases.page.ReleaseDetails.table.action-published",
1145
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
1146
+ },
1147
+ {
1148
+ isPublish: type === "publish",
1149
+ b: (children) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children })
1150
+ }
1151
+ ) }) : /* @__PURE__ */ jsx(
1152
+ ReleaseActionOptions,
1153
+ {
1154
+ selected: type,
1155
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
1156
+ name: `release-action-${id}-type`,
1157
+ disabled: !canUpdate
1158
+ }
1159
+ ) }),
1160
+ !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
1161
+ /* @__PURE__ */ jsx(Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsx(
1162
+ EntryValidationText,
1163
+ {
1164
+ action: type,
1165
+ schema: contentTypes?.[contentType.uid],
1166
+ components,
1167
+ entry
1168
+ }
1169
+ ) }),
1170
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
1171
+ /* @__PURE__ */ jsx(
1172
+ ReleaseActionMenu.ReleaseActionEntryLinkItem,
1173
+ {
1174
+ contentTypeUid: contentType.uid,
1175
+ entryId: entry.id,
1176
+ locale: locale?.code
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ jsx(
1180
+ ReleaseActionMenu.DeleteReleaseActionItem,
1181
+ {
1182
+ releaseId: release.id,
1183
+ actionId: id
1184
+ }
1185
+ )
1186
+ ] }) }) })
1187
+ ] })
1188
+ ] }, id)
1189
+ ) })
1190
+ ] })
1191
+ }
1192
+ )
1193
+ ] }, `releases-group-${key}`)),
1194
+ /* @__PURE__ */ jsxs(
1195
+ Pagination.Root,
1196
+ {
1197
+ ...releaseMeta?.pagination,
1198
+ defaultPageSize: releaseMeta?.pagination?.pageSize,
1199
+ children: [
1200
+ /* @__PURE__ */ jsx(Pagination.PageSize, {}),
1201
+ /* @__PURE__ */ jsx(Pagination.Links, {})
1202
+ ]
1203
+ }
1204
+ )
1205
+ ] }) });
1206
+ };
1207
+ const ReleaseDetailsPage = () => {
1208
+ const { formatMessage } = useIntl();
1209
+ const { releaseId } = useParams();
1210
+ const { toggleNotification } = useNotification();
1211
+ const { formatAPIError } = useAPIErrorHandler();
1212
+ const navigate = useNavigate();
1213
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
1214
+ const [showWarningSubmit, setWarningSubmit] = React.useState(false);
1215
+ const {
1216
+ isLoading: isLoadingDetails,
1217
+ data,
1218
+ isSuccess: isSuccessDetails
1219
+ } = useGetReleaseQuery(
1220
+ { id: releaseId },
1221
+ {
1222
+ skip: !releaseId
1223
+ }
1224
+ );
1225
+ const [updateRelease, { isLoading: isSubmittingForm }] = useUpdateReleaseMutation();
1226
+ const [deleteRelease] = useDeleteReleaseMutation();
1227
+ const toggleEditReleaseModal = () => {
1228
+ setReleaseModalShown((prev) => !prev);
1229
+ };
1230
+ const toggleWarningSubmit = () => setWarningSubmit((prevState) => !prevState);
1231
+ if (isLoadingDetails) {
1232
+ return /* @__PURE__ */ jsx(
1233
+ ReleaseDetailsLayout,
1234
+ {
1235
+ toggleEditReleaseModal,
1236
+ toggleWarningSubmit,
1237
+ children: /* @__PURE__ */ jsx(Page.Loading, {})
1238
+ }
1239
+ );
1240
+ }
1241
+ if (!releaseId) {
1242
+ return /* @__PURE__ */ jsx(Navigate, { to: ".." });
1243
+ }
1244
+ const releaseData = isSuccessDetails && data?.data || null;
1245
+ const title = releaseData?.name || "";
1246
+ const timezone = releaseData?.timezone ?? null;
1247
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1248
+ const date = scheduledAt ? format(scheduledAt, "yyyy-MM-dd") : void 0;
1249
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
1250
+ const handleEditRelease = async (values) => {
1251
+ const response = await updateRelease({
1252
+ id: releaseId,
1253
+ name: values.name,
1254
+ scheduledAt: values.scheduledAt,
1255
+ timezone: values.timezone
1256
+ });
1257
+ if ("data" in response) {
1258
+ toggleNotification({
1259
+ type: "success",
1260
+ message: formatMessage({
1261
+ id: "content-releases.modal.release-updated-notification-success",
1262
+ defaultMessage: "Release updated."
1263
+ })
1264
+ });
1265
+ toggleEditReleaseModal();
1266
+ } else if (isFetchError(response.error)) {
1267
+ toggleNotification({
1268
+ type: "danger",
1269
+ message: formatAPIError(response.error)
1270
+ });
1271
+ } else {
1272
+ toggleNotification({
1273
+ type: "danger",
1274
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1275
+ });
1276
+ }
1277
+ };
1278
+ const handleDeleteRelease = async () => {
1279
+ const response = await deleteRelease({
1280
+ id: releaseId
1281
+ });
1282
+ if ("data" in response) {
1283
+ navigate("..");
1284
+ } else if (isFetchError(response.error)) {
1285
+ toggleNotification({
1286
+ type: "danger",
1287
+ message: formatAPIError(response.error)
1288
+ });
1289
+ } else {
1290
+ toggleNotification({
1291
+ type: "danger",
1292
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1293
+ });
1294
+ }
1295
+ };
1296
+ return /* @__PURE__ */ jsxs(
1297
+ ReleaseDetailsLayout,
1298
+ {
1299
+ toggleEditReleaseModal,
1300
+ toggleWarningSubmit,
1301
+ children: [
1302
+ /* @__PURE__ */ jsx(ReleaseDetailsBody, { releaseId }),
1303
+ releaseModalShown && /* @__PURE__ */ jsx(
1304
+ ReleaseModal,
1305
+ {
1306
+ handleClose: toggleEditReleaseModal,
1307
+ handleSubmit: handleEditRelease,
1308
+ isLoading: isLoadingDetails || isSubmittingForm,
1309
+ initialValues: {
1310
+ name: title || "",
1311
+ scheduledAt,
1312
+ date,
1313
+ time,
1314
+ isScheduled: Boolean(scheduledAt),
1315
+ timezone
1316
+ }
1317
+ }
1318
+ ),
1319
+ /* @__PURE__ */ jsx(
1320
+ ConfirmDialog,
1321
+ {
1322
+ isOpen: showWarningSubmit,
1323
+ onClose: toggleWarningSubmit,
1324
+ onConfirm: handleDeleteRelease,
1325
+ children: formatMessage({
1326
+ id: "content-releases.dialog.confirmation-message",
1327
+ defaultMessage: "Are you sure you want to delete this release?"
1328
+ })
1329
+ }
1330
+ )
1331
+ ]
1332
+ }
1333
+ );
1334
+ };
1335
+ const App = () => {
1336
+ return /* @__PURE__ */ jsx(Page.Protect, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Routes, { children: [
1337
+ /* @__PURE__ */ jsx(Route, { index: true, element: /* @__PURE__ */ jsx(ReleasesPage, {}) }),
1338
+ /* @__PURE__ */ jsx(Route, { path: ":releaseId", element: /* @__PURE__ */ jsx(ReleaseDetailsPage, {}) })
1339
+ ] }) });
1340
+ };
1341
+ export {
1342
+ App
1343
+ };
1344
+ //# sourceMappingURL=App-D_6Y9N2F.mjs.map