@strapi/content-releases 0.0.0-experimental.check-license → 0.0.0-experimental.d23c1d5b0e45dd06ef09977f526c85468be05403

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