@strapi/content-releases 0.0.0-next.e1ede8c55a0e1e22ce20137bf238fc374bd5dd51 → 0.0.0-next.f8af92b375dc730ba47ed2117f25df893aae696c

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