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

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