@strapi/content-releases 0.0.0-next.e6eaa3d0563c85f80fd88b258df70a55c057096e → 0.0.0-next.ee56af7ae29770097422de95c0d5500908dce15c

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