@strapi/content-releases 0.0.0-next.c5f067b5650921187770124e9b6c8186e805e242 → 0.0.0-next.d0bd7aa4c25bfb448b93a62f3d47db9b6fdd8ee3

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 (29) hide show
  1. package/dist/_chunks/{App-3ycH2d3s.mjs → App-HVXzE3i3.mjs} +694 -443
  2. package/dist/_chunks/App-HVXzE3i3.mjs.map +1 -0
  3. package/dist/_chunks/{App-5PsAyVt2.js → App-l62gIUTX.js} +686 -434
  4. package/dist/_chunks/App-l62gIUTX.js.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-SOqjCdyh.mjs → en-RdapH-9X.mjs} +18 -7
  10. package/dist/_chunks/en-RdapH-9X.mjs.map +1 -0
  11. package/dist/_chunks/{en-2DuPv5k0.js → en-faJDuv3q.js} +18 -7
  12. package/dist/_chunks/en-faJDuv3q.js.map +1 -0
  13. package/dist/_chunks/{index-D57Rztnc.js → index-ML_b3php.js} +129 -25
  14. package/dist/_chunks/index-ML_b3php.js.map +1 -0
  15. package/dist/_chunks/{index-4gUWuCQV.mjs → index-Ys87ROOe.mjs} +140 -36
  16. package/dist/_chunks/index-Ys87ROOe.mjs.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +2 -2
  19. package/dist/server/index.js +962 -408
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +961 -408
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -11
  24. package/dist/_chunks/App-3ycH2d3s.mjs.map +0 -1
  25. package/dist/_chunks/App-5PsAyVt2.js.map +0 -1
  26. package/dist/_chunks/en-2DuPv5k0.js.map +0 -1
  27. package/dist/_chunks/en-SOqjCdyh.mjs.map +0 -1
  28. package/dist/_chunks/index-4gUWuCQV.mjs.map +0 -1
  29. package/dist/_chunks/index-D57Rztnc.js.map +0 -1
@@ -1,22 +1,42 @@
1
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 { 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, g as ReleaseActionMenu, i as isAxiosError, r as releaseApi, h as useGetReleasesQuery, j as useCreateReleaseMutation } from "./index-4gUWuCQV.mjs";
2
+ import { RelativeTime, useNotification, useAPIErrorHandler, useQueryParams, useTracking, LoadingIndicatorPage, CheckPermissions, PageSizeURLQuery, PaginationURLQuery, AnErrorOccurred, ConfirmDialog, useRBAC, NoContent, Table, CheckPagePermissions } from "@strapi/helper-plugin";
3
+ import { useLocation, useHistory, useParams, Redirect, Link as Link$2, Switch, Route } from "react-router-dom";
4
+ import { g as getTimezoneOffset, p as pluginId, u as useGetReleasesQuery, a as useCreateReleaseMutation, P as PERMISSIONS, i as isAxiosError, b as useGetReleaseQuery, c as useUpdateReleaseMutation, d as useDeleteReleaseMutation, e as usePublishReleaseMutation, f as useTypedDispatch, h as useGetReleaseActionsQuery, j as useUpdateReleaseActionMutation, R as ReleaseActionOptions, k as ReleaseActionMenu, r as releaseApi } from "./index-Ys87ROOe.mjs";
5
5
  import * as React from "react";
6
- import { unstable_useDocument, useLicenseLimits } from "@strapi/admin/strapi-admin";
7
- import { ModalLayout, ModalHeader, Typography, ModalBody, TextInput, ModalFooter, Button, Flex, ContentLayout, Main, HeaderLayout, Link, IconButton, Popover, SingleSelect, SingleSelectOption, Badge, Tr, Td, Icon, Tooltip, Alert, TabGroup, Box, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem } from "@strapi/design-system";
8
- import { LinkButton, Link as Link$2 } from "@strapi/design-system/v2";
9
- import { Pencil, Trash, ArrowLeft, More, CrossCircle, CheckCircle, Plus, EmptyDocuments } from "@strapi/icons";
6
+ import { useLicenseLimits, unstable_useDocument } from "@strapi/admin/strapi-admin";
7
+ import { ModalLayout, ModalHeader, Typography, ModalBody, Flex, TextInput, Box, Checkbox, DatePicker, TimePicker, ModalFooter, Button, Combobox, ComboboxOption, Alert, Main, HeaderLayout, ContentLayout, TabGroup, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem, Badge, Link as Link$1, IconButton, SingleSelect, SingleSelectOption, Tr, Td, Icon, Tooltip } from "@strapi/design-system";
8
+ import { Link, Menu, LinkButton } from "@strapi/design-system/v2";
9
+ import { Plus, EmptyDocuments, Pencil, Trash, ArrowLeft, More, CrossCircle, CheckCircle } from "@strapi/icons";
10
+ import format from "date-fns/format";
11
+ import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
10
12
  import { useIntl } from "react-intl";
11
13
  import styled from "styled-components";
12
- import { Formik, Form } from "formik";
14
+ import { formatISO, parse } from "date-fns";
15
+ import { Formik, Form, useFormikContext } from "formik";
13
16
  import * as yup from "yup";
14
17
  import "@reduxjs/toolkit/query";
15
18
  import "axios";
16
19
  import "@reduxjs/toolkit/query/react";
17
20
  import "react-redux";
18
21
  const RELEASE_SCHEMA = yup.object().shape({
19
- name: yup.string().trim().required()
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
+ })
20
40
  }).required().noUnknown();
21
41
  const ReleaseModal = ({
22
42
  handleClose,
@@ -27,6 +47,23 @@ const ReleaseModal = ({
27
47
  const { formatMessage } = useIntl();
28
48
  const { pathname } = useLocation();
29
49
  const isCreatingRelease = pathname === `/plugins/${pluginId}`;
50
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
51
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
52
+ );
53
+ const getScheduledTimestamp = (values) => {
54
+ const { date, time, timezone } = values;
55
+ if (!date || !time || !timezone)
56
+ return null;
57
+ const formattedDate = parse(time, "HH:mm", new Date(date));
58
+ const timezoneWithoutOffset = timezone.split("&")[1];
59
+ return zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
60
+ };
61
+ const getTimezoneWithOffset = () => {
62
+ const currentTimezone = timezoneList.find(
63
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
64
+ );
65
+ return currentTimezone?.value || systemTimezone.value;
66
+ };
30
67
  return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
31
68
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
32
69
  {
@@ -38,45 +75,130 @@ const ReleaseModal = ({
38
75
  /* @__PURE__ */ jsx(
39
76
  Formik,
40
77
  {
41
- validateOnChange: false,
42
- onSubmit: handleSubmit,
43
- initialValues,
78
+ onSubmit: (values) => {
79
+ handleSubmit({
80
+ ...values,
81
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
82
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
83
+ });
84
+ },
85
+ initialValues: {
86
+ ...initialValues,
87
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
88
+ },
44
89
  validationSchema: RELEASE_SCHEMA,
45
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxs(Form, { children: [
46
- /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx(
47
- TextInput,
48
- {
49
- label: formatMessage({
50
- id: "content-releases.modal.form.input.label.release-name",
51
- defaultMessage: "Name"
52
- }),
53
- name: "name",
54
- value: values.name,
55
- error: errors.name,
56
- onChange: handleChange,
57
- required: true
58
- }
59
- ) }),
90
+ validateOnChange: false,
91
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxs(Form, { children: [
92
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
93
+ /* @__PURE__ */ jsx(
94
+ TextInput,
95
+ {
96
+ label: formatMessage({
97
+ id: "content-releases.modal.form.input.label.release-name",
98
+ defaultMessage: "Name"
99
+ }),
100
+ name: "name",
101
+ value: values.name,
102
+ error: errors.name,
103
+ onChange: handleChange,
104
+ required: true
105
+ }
106
+ ),
107
+ /* @__PURE__ */ jsx(Box, { width: "max-content", children: /* @__PURE__ */ jsx(
108
+ Checkbox,
109
+ {
110
+ name: "isScheduled",
111
+ value: values.isScheduled,
112
+ onChange: (event) => {
113
+ setFieldValue("isScheduled", event.target.checked);
114
+ if (!event.target.checked) {
115
+ setFieldValue("date", null);
116
+ setFieldValue("time", "");
117
+ setFieldValue("timezone", null);
118
+ } else {
119
+ setFieldValue("date", initialValues.date);
120
+ setFieldValue("time", initialValues.time);
121
+ setFieldValue("timezone", initialValues.timezone ?? systemTimezone?.value);
122
+ }
123
+ },
124
+ children: /* @__PURE__ */ jsx(
125
+ Typography,
126
+ {
127
+ textColor: values.isScheduled ? "primary600" : "neutral800",
128
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
129
+ children: formatMessage({
130
+ id: "modal.form.input.label.schedule-release",
131
+ defaultMessage: "Schedule release"
132
+ })
133
+ }
134
+ )
135
+ }
136
+ ) }),
137
+ values.isScheduled && /* @__PURE__ */ jsxs(Fragment, { children: [
138
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "start", children: [
139
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
140
+ DatePicker,
141
+ {
142
+ label: formatMessage({
143
+ id: "content-releases.modal.form.input.label.date",
144
+ defaultMessage: "Date"
145
+ }),
146
+ name: "date",
147
+ error: errors.date,
148
+ onChange: (date) => {
149
+ const isoFormatDate = date ? formatISO(date, { representation: "date" }) : null;
150
+ setFieldValue("date", isoFormatDate);
151
+ },
152
+ clearLabel: formatMessage({
153
+ id: "content-releases.modal.form.input.clearLabel",
154
+ defaultMessage: "Clear"
155
+ }),
156
+ onClear: () => {
157
+ setFieldValue("date", null);
158
+ },
159
+ selectedDate: values.date || void 0,
160
+ required: true,
161
+ minDate: utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
162
+ }
163
+ ) }),
164
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
165
+ TimePicker,
166
+ {
167
+ label: formatMessage({
168
+ id: "content-releases.modal.form.input.label.time",
169
+ defaultMessage: "Time"
170
+ }),
171
+ name: "time",
172
+ error: errors.time,
173
+ onChange: (time) => {
174
+ setFieldValue("time", time);
175
+ },
176
+ clearLabel: formatMessage({
177
+ id: "content-releases.modal.form.input.clearLabel",
178
+ defaultMessage: "Clear"
179
+ }),
180
+ onClear: () => {
181
+ setFieldValue("time", "");
182
+ },
183
+ value: values.time || void 0,
184
+ required: true
185
+ }
186
+ ) })
187
+ ] }),
188
+ /* @__PURE__ */ jsx(TimezoneComponent, { timezoneOptions: timezoneList })
189
+ ] })
190
+ ] }) }),
60
191
  /* @__PURE__ */ jsx(
61
192
  ModalFooter,
62
193
  {
63
194
  startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
64
- endActions: /* @__PURE__ */ jsx(
65
- Button,
195
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
66
196
  {
67
- name: "submit",
68
- loading: isLoading,
69
- disabled: !values.name || values.name === initialValues.name,
70
- type: "submit",
71
- children: formatMessage(
72
- {
73
- id: "content-releases.modal.form.button.submit",
74
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
75
- },
76
- { isCreatingRelease }
77
- )
78
- }
79
- )
197
+ id: "content-releases.modal.form.button.submit",
198
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
199
+ },
200
+ { isCreatingRelease }
201
+ ) })
80
202
  }
81
203
  )
82
204
  ] })
@@ -84,33 +206,396 @@ const ReleaseModal = ({
84
206
  )
85
207
  ] });
86
208
  };
209
+ const getTimezones = (selectedDate) => {
210
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
211
+ const utcOffset = getTimezoneOffset(timezone, selectedDate);
212
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
213
+ });
214
+ const systemTimezone = timezoneList.find(
215
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
216
+ );
217
+ return { timezoneList, systemTimezone };
218
+ };
219
+ const TimezoneComponent = ({ timezoneOptions }) => {
220
+ const { values, errors, setFieldValue } = useFormikContext();
221
+ const { formatMessage } = useIntl();
222
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
223
+ React.useEffect(() => {
224
+ if (values.date) {
225
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
226
+ setTimezoneList(timezoneList2);
227
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
228
+ if (updatedTimezone) {
229
+ setFieldValue("timezone", updatedTimezone.value);
230
+ }
231
+ }
232
+ }, [setFieldValue, values.date, values.timezone]);
233
+ return /* @__PURE__ */ jsx(
234
+ Combobox,
235
+ {
236
+ label: formatMessage({
237
+ id: "content-releases.modal.form.input.label.timezone",
238
+ defaultMessage: "Timezone"
239
+ }),
240
+ autocomplete: { type: "list", filter: "contains" },
241
+ name: "timezone",
242
+ value: values.timezone || void 0,
243
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
244
+ onChange: (timezone) => {
245
+ setFieldValue("timezone", timezone);
246
+ },
247
+ onTextValueChange: (timezone) => {
248
+ setFieldValue("timezone", timezone);
249
+ },
250
+ onClear: () => {
251
+ setFieldValue("timezone", "");
252
+ },
253
+ error: errors.timezone,
254
+ required: true,
255
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
256
+ }
257
+ );
258
+ };
259
+ const LinkCard = styled(Link)`
260
+ display: block;
261
+ `;
262
+ const CapitalizeRelativeTime = styled(RelativeTime)`
263
+ text-transform: capitalize;
264
+ `;
265
+ const getBadgeProps = (status) => {
266
+ let color;
267
+ switch (status) {
268
+ case "ready":
269
+ color = "success";
270
+ break;
271
+ case "blocked":
272
+ color = "warning";
273
+ break;
274
+ case "failed":
275
+ color = "danger";
276
+ break;
277
+ case "done":
278
+ color = "primary";
279
+ break;
280
+ case "empty":
281
+ default:
282
+ color = "neutral";
283
+ }
284
+ return {
285
+ textColor: `${color}600`,
286
+ backgroundColor: `${color}100`,
287
+ borderColor: `${color}200`
288
+ };
289
+ };
290
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
291
+ const { formatMessage } = useIntl();
292
+ if (isError) {
293
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
294
+ }
295
+ if (releases?.length === 0) {
296
+ return /* @__PURE__ */ jsx(
297
+ EmptyStateLayout,
298
+ {
299
+ content: formatMessage(
300
+ {
301
+ id: "content-releases.page.Releases.tab.emptyEntries",
302
+ defaultMessage: "No releases"
303
+ },
304
+ {
305
+ target: sectionTitle
306
+ }
307
+ ),
308
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
309
+ }
310
+ );
311
+ }
312
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, scheduledAt, status }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
313
+ Flex,
314
+ {
315
+ direction: "column",
316
+ justifyContent: "space-between",
317
+ padding: 4,
318
+ hasRadius: true,
319
+ background: "neutral0",
320
+ shadow: "tableShadow",
321
+ height: "100%",
322
+ width: "100%",
323
+ alignItems: "start",
324
+ gap: 4,
325
+ children: [
326
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
327
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
328
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
329
+ id: "content-releases.pages.Releases.not-scheduled",
330
+ defaultMessage: "Not scheduled"
331
+ }) })
332
+ ] }),
333
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
334
+ ]
335
+ }
336
+ ) }) }, id)) });
337
+ };
338
+ const StyledAlert = styled(Alert)`
339
+ button {
340
+ display: none;
341
+ }
342
+ p + div {
343
+ margin-left: auto;
344
+ }
345
+ `;
346
+ const INITIAL_FORM_VALUES = {
347
+ name: "",
348
+ date: null,
349
+ time: "",
350
+ isScheduled: true,
351
+ scheduledAt: null,
352
+ timezone: null
353
+ };
354
+ const ReleasesPage = () => {
355
+ const tabRef = React.useRef(null);
356
+ const location = useLocation();
357
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
358
+ const toggleNotification = useNotification();
359
+ const { formatMessage } = useIntl();
360
+ const { push, replace } = useHistory();
361
+ const { formatAPIError } = useAPIErrorHandler();
362
+ const [{ query }, setQuery] = useQueryParams();
363
+ const response = useGetReleasesQuery(query);
364
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
365
+ const { getFeature } = useLicenseLimits();
366
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
367
+ const { trackUsage } = useTracking();
368
+ const { isLoading, isSuccess, isError } = response;
369
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
370
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
371
+ React.useEffect(() => {
372
+ if (location?.state?.errors) {
373
+ toggleNotification({
374
+ type: "warning",
375
+ title: formatMessage({
376
+ id: "content-releases.pages.Releases.notification.error.title",
377
+ defaultMessage: "Your request could not be processed."
378
+ }),
379
+ message: formatMessage({
380
+ id: "content-releases.pages.Releases.notification.error.message",
381
+ defaultMessage: "Please try again or open another release."
382
+ })
383
+ });
384
+ replace({ state: null });
385
+ }
386
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
387
+ React.useEffect(() => {
388
+ if (tabRef.current) {
389
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
390
+ }
391
+ }, [activeTabIndex]);
392
+ const toggleAddReleaseModal = () => {
393
+ setReleaseModalShown((prev) => !prev);
394
+ };
395
+ if (isLoading) {
396
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
397
+ }
398
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
399
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
400
+ const handleTabChange = (index) => {
401
+ setQuery({
402
+ ...query,
403
+ page: 1,
404
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
405
+ filters: {
406
+ releasedAt: {
407
+ $notNull: index === 0 ? false : true
408
+ }
409
+ }
410
+ });
411
+ };
412
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
413
+ const response2 = await createRelease({
414
+ name,
415
+ scheduledAt,
416
+ timezone
417
+ });
418
+ if ("data" in response2) {
419
+ toggleNotification({
420
+ type: "success",
421
+ message: formatMessage({
422
+ id: "content-releases.modal.release-created-notification-success",
423
+ defaultMessage: "Release created."
424
+ })
425
+ });
426
+ trackUsage("didCreateRelease");
427
+ push(`/plugins/content-releases/${response2.data.data.id}`);
428
+ } else if (isAxiosError(response2.error)) {
429
+ toggleNotification({
430
+ type: "warning",
431
+ message: formatAPIError(response2.error)
432
+ });
433
+ } else {
434
+ toggleNotification({
435
+ type: "warning",
436
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
437
+ });
438
+ }
439
+ };
440
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
441
+ /* @__PURE__ */ jsx(
442
+ HeaderLayout,
443
+ {
444
+ title: formatMessage({
445
+ id: "content-releases.pages.Releases.title",
446
+ defaultMessage: "Releases"
447
+ }),
448
+ subtitle: formatMessage({
449
+ id: "content-releases.pages.Releases.header-subtitle",
450
+ defaultMessage: "Create and manage content updates"
451
+ }),
452
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
453
+ Button,
454
+ {
455
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
456
+ onClick: toggleAddReleaseModal,
457
+ disabled: hasReachedMaximumPendingReleases,
458
+ children: formatMessage({
459
+ id: "content-releases.header.actions.add-release",
460
+ defaultMessage: "New release"
461
+ })
462
+ }
463
+ ) })
464
+ }
465
+ ),
466
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
467
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
468
+ StyledAlert,
469
+ {
470
+ marginBottom: 6,
471
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
472
+ id: "content-releases.pages.Releases.max-limit-reached.action",
473
+ defaultMessage: "Explore plans"
474
+ }) }),
475
+ title: formatMessage(
476
+ {
477
+ id: "content-releases.pages.Releases.max-limit-reached.title",
478
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
479
+ },
480
+ { number: maximumReleases }
481
+ ),
482
+ onClose: () => {
483
+ },
484
+ closeLabel: "",
485
+ children: formatMessage({
486
+ id: "content-releases.pages.Releases.max-limit-reached.message",
487
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
488
+ })
489
+ }
490
+ ),
491
+ /* @__PURE__ */ jsxs(
492
+ TabGroup,
493
+ {
494
+ label: formatMessage({
495
+ id: "content-releases.pages.Releases.tab-group.label",
496
+ defaultMessage: "Releases list"
497
+ }),
498
+ variant: "simple",
499
+ initialSelectedTabIndex: activeTabIndex,
500
+ onTabChange: handleTabChange,
501
+ ref: tabRef,
502
+ children: [
503
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
504
+ /* @__PURE__ */ jsxs(Tabs, { children: [
505
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
506
+ {
507
+ id: "content-releases.pages.Releases.tab.pending",
508
+ defaultMessage: "Pending ({count})"
509
+ },
510
+ {
511
+ count: totalPendingReleases
512
+ }
513
+ ) }),
514
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
515
+ id: "content-releases.pages.Releases.tab.done",
516
+ defaultMessage: "Done"
517
+ }) })
518
+ ] }),
519
+ /* @__PURE__ */ jsx(Divider, {})
520
+ ] }),
521
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
522
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
523
+ ReleasesGrid,
524
+ {
525
+ sectionTitle: "pending",
526
+ releases: response?.currentData?.data,
527
+ isError
528
+ }
529
+ ) }),
530
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
531
+ ReleasesGrid,
532
+ {
533
+ sectionTitle: "done",
534
+ releases: response?.currentData?.data,
535
+ isError
536
+ }
537
+ ) })
538
+ ] })
539
+ ]
540
+ }
541
+ ),
542
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
543
+ /* @__PURE__ */ jsx(
544
+ PageSizeURLQuery,
545
+ {
546
+ options: ["8", "16", "32", "64"],
547
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
548
+ }
549
+ ),
550
+ /* @__PURE__ */ jsx(
551
+ PaginationURLQuery,
552
+ {
553
+ pagination: {
554
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
555
+ }
556
+ }
557
+ )
558
+ ] }) : null
559
+ ] }) }),
560
+ releaseModalShown && /* @__PURE__ */ jsx(
561
+ ReleaseModal,
562
+ {
563
+ handleClose: toggleAddReleaseModal,
564
+ handleSubmit: handleAddRelease,
565
+ isLoading: isSubmittingForm,
566
+ initialValues: INITIAL_FORM_VALUES
567
+ }
568
+ )
569
+ ] });
570
+ };
87
571
  const ReleaseInfoWrapper = styled(Flex)`
88
572
  align-self: stretch;
89
573
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
574
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
575
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
576
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
577
+ const StyledMenuItem = styled(Menu.Item)`
97
578
  svg path {
98
579
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
580
  }
100
581
  span {
101
582
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
102
583
  }
584
+
585
+ &:hover {
586
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
587
+ }
103
588
  `;
104
589
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
590
+ width: ${({ theme }) => theme.spaces[3]};
591
+ height: ${({ theme }) => theme.spaces[3]};
107
592
  path {
108
593
  fill: ${({ theme }) => theme.colors.neutral600};
109
594
  }
110
595
  `;
111
596
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
597
+ width: ${({ theme }) => theme.spaces[3]};
598
+ height: ${({ theme }) => theme.spaces[3]};
114
599
  path {
115
600
  fill: ${({ theme }) => theme.colors.danger600};
116
601
  }
@@ -118,24 +603,6 @@ const TrashIcon = styled(Trash)`
118
603
  const TypographyMaxWidth = styled(Typography)`
119
604
  max-width: 300px;
120
605
  `;
121
- const PopoverButton = ({ onClick, disabled, children }) => {
122
- return /* @__PURE__ */ jsx(
123
- StyledFlex,
124
- {
125
- paddingTop: 2,
126
- paddingBottom: 2,
127
- paddingLeft: 4,
128
- paddingRight: 4,
129
- alignItems: "center",
130
- gap: 2,
131
- as: "button",
132
- hasRadius: true,
133
- onClick,
134
- disabled,
135
- children
136
- }
137
- );
138
- };
139
606
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
607
  const { formatMessage } = useIntl();
141
608
  const { validate } = unstable_useDocument();
@@ -184,10 +651,8 @@ const ReleaseDetailsLayout = ({
184
651
  toggleWarningSubmit,
185
652
  children
186
653
  }) => {
187
- const { formatMessage } = useIntl();
654
+ const { formatMessage, formatDate, formatTime } = useIntl();
188
655
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
656
  const {
192
657
  data,
193
658
  isLoading: isLoadingDetails,
@@ -203,13 +668,6 @@ const ReleaseDetailsLayout = ({
203
668
  const dispatch = useTypedDispatch();
204
669
  const { trackUsage } = useTracking();
205
670
  const release = data?.data;
206
- const handleTogglePopover = () => {
207
- setIsPopoverVisible((prev) => !prev);
208
- };
209
- const openReleaseModal = () => {
210
- toggleEditReleaseModal();
211
- handleTogglePopover();
212
- };
213
671
  const handlePublishRelease = async () => {
214
672
  const response = await publishRelease({ id: releaseId });
215
673
  if ("data" in response) {
@@ -238,12 +696,25 @@ const ReleaseDetailsLayout = ({
238
696
  });
239
697
  }
240
698
  };
241
- const openWarningConfirmDialog = () => {
242
- toggleWarningSubmit();
243
- handleTogglePopover();
244
- };
245
699
  const handleRefresh = () => {
246
- dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
700
+ dispatch(
701
+ releaseApi.util.invalidateTags([
702
+ { type: "ReleaseAction", id: "LIST" },
703
+ { type: "Release", id: releaseId }
704
+ ])
705
+ );
706
+ };
707
+ const getCreatedByUser = () => {
708
+ if (!release?.createdBy) {
709
+ return null;
710
+ }
711
+ if (release.createdBy.username) {
712
+ return release.createdBy.username;
713
+ }
714
+ if (release.createdBy.firstname) {
715
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
716
+ }
717
+ return release.createdBy.email;
247
718
  };
248
719
  if (isLoadingDetails) {
249
720
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
@@ -266,90 +737,127 @@ const ReleaseDetailsLayout = ({
266
737
  );
267
738
  }
268
739
  const totalEntries = release.actions.meta.count || 0;
269
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
740
+ const hasCreatedByUser = Boolean(getCreatedByUser());
741
+ const isScheduled = release.scheduledAt && release.timezone;
742
+ const numberOfEntriesText = formatMessage(
743
+ {
744
+ id: "content-releases.pages.Details.header-subtitle",
745
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
746
+ },
747
+ { number: totalEntries }
748
+ );
749
+ const scheduledText = isScheduled ? formatMessage(
750
+ {
751
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
752
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
753
+ },
754
+ {
755
+ date: formatDate(new Date(release.scheduledAt), {
756
+ weekday: "long",
757
+ day: "numeric",
758
+ month: "long",
759
+ year: "numeric",
760
+ timeZone: release.timezone
761
+ }),
762
+ time: formatTime(new Date(release.scheduledAt), {
763
+ timeZone: release.timezone,
764
+ hourCycle: "h23"
765
+ }),
766
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
767
+ }
768
+ ) : "";
270
769
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
271
770
  /* @__PURE__ */ jsx(
272
771
  HeaderLayout,
273
772
  {
274
773
  title: release.name,
275
- subtitle: formatMessage(
276
- {
277
- id: "content-releases.pages.Details.header-subtitle",
278
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
279
- },
280
- { number: totalEntries }
281
- ),
282
- navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
774
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
775
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
776
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
777
+ ] }),
778
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
283
779
  id: "global.back",
284
780
  defaultMessage: "Back"
285
781
  }) }),
286
782
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
287
- /* @__PURE__ */ jsx(
288
- IconButton,
289
- {
290
- label: formatMessage({
291
- id: "content-releases.header.actions.open-release-actions",
292
- defaultMessage: "Release actions"
293
- }),
294
- ref: moreButtonRef,
295
- onClick: handleTogglePopover,
296
- children: /* @__PURE__ */ jsx(More, {})
297
- }
298
- ),
299
- isPopoverVisible && /* @__PURE__ */ jsxs(
300
- Popover,
301
- {
302
- source: moreButtonRef,
303
- placement: "bottom-end",
304
- onDismiss: handleTogglePopover,
305
- spacing: 4,
306
- minWidth: "242px",
307
- children: [
308
- /* @__PURE__ */ jsxs(Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
309
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
310
- /* @__PURE__ */ jsx(PencilIcon, {}),
311
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
312
- id: "content-releases.header.actions.edit",
313
- defaultMessage: "Edit"
314
- }) })
315
- ] }),
316
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
317
- /* @__PURE__ */ jsx(TrashIcon, {}),
318
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
319
- id: "content-releases.header.actions.delete",
320
- defaultMessage: "Delete"
321
- }) })
322
- ] })
323
- ] }),
324
- /* @__PURE__ */ jsxs(
325
- ReleaseInfoWrapper,
326
- {
327
- direction: "column",
328
- justifyContent: "center",
329
- alignItems: "flex-start",
330
- gap: 1,
331
- padding: 5,
332
- children: [
333
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
334
- id: "content-releases.header.actions.created",
335
- defaultMessage: "Created"
336
- }) }),
337
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
338
- /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
339
- formatMessage(
340
- {
341
- id: "content-releases.header.actions.created.description",
342
- defaultMessage: " by {createdBy}"
343
- },
344
- { createdBy }
345
- )
346
- ] })
347
- ]
348
- }
349
- )
350
- ]
351
- }
352
- ),
783
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
784
+ /* @__PURE__ */ jsx(
785
+ Menu.Trigger,
786
+ {
787
+ as: IconButton,
788
+ paddingLeft: 2,
789
+ paddingRight: 2,
790
+ "aria-label": formatMessage({
791
+ id: "content-releases.header.actions.open-release-actions",
792
+ defaultMessage: "Release edit and delete menu"
793
+ }),
794
+ icon: /* @__PURE__ */ jsx(More, {}),
795
+ variant: "tertiary"
796
+ }
797
+ ),
798
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
799
+ /* @__PURE__ */ jsxs(
800
+ Flex,
801
+ {
802
+ alignItems: "center",
803
+ justifyContent: "center",
804
+ direction: "column",
805
+ padding: 1,
806
+ width: "100%",
807
+ children: [
808
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
809
+ /* @__PURE__ */ jsx(PencilIcon, {}),
810
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
811
+ id: "content-releases.header.actions.edit",
812
+ defaultMessage: "Edit"
813
+ }) })
814
+ ] }) }),
815
+ /* @__PURE__ */ jsx(
816
+ StyledMenuItem,
817
+ {
818
+ disabled: !canDelete,
819
+ onSelect: toggleWarningSubmit,
820
+ variant: "danger",
821
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
822
+ /* @__PURE__ */ jsx(TrashIcon, {}),
823
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
824
+ id: "content-releases.header.actions.delete",
825
+ defaultMessage: "Delete"
826
+ }) })
827
+ ] })
828
+ }
829
+ )
830
+ ]
831
+ }
832
+ ),
833
+ /* @__PURE__ */ jsxs(
834
+ ReleaseInfoWrapper,
835
+ {
836
+ direction: "column",
837
+ justifyContent: "center",
838
+ alignItems: "flex-start",
839
+ gap: 1,
840
+ padding: 5,
841
+ children: [
842
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
843
+ id: "content-releases.header.actions.created",
844
+ defaultMessage: "Created"
845
+ }) }),
846
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
847
+ /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
848
+ formatMessage(
849
+ {
850
+ id: "content-releases.header.actions.created.description",
851
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
852
+ },
853
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
854
+ )
855
+ ] })
856
+ ]
857
+ }
858
+ )
859
+ ] })
860
+ ] }),
353
861
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
354
862
  id: "content-releases.header.actions.refresh",
355
863
  defaultMessage: "Refresh"
@@ -405,6 +913,9 @@ const ReleaseDetailsBody = () => {
405
913
  isError: isReleaseError,
406
914
  error: releaseError
407
915
  } = useGetReleaseQuery({ id: releaseId });
916
+ const {
917
+ allowedActions: { canUpdate }
918
+ } = useRBAC(PERMISSIONS);
408
919
  const release = releaseData?.data;
409
920
  const selectedGroupBy = query?.groupBy || "contentType";
410
921
  const {
@@ -491,7 +1002,7 @@ const ReleaseDetailsBody = () => {
491
1002
  action: /* @__PURE__ */ jsx(
492
1003
  LinkButton,
493
1004
  {
494
- as: Link$1,
1005
+ as: Link$2,
495
1006
  to: {
496
1007
  pathname: "/content-manager"
497
1008
  },
@@ -511,7 +1022,7 @@ const ReleaseDetailsBody = () => {
511
1022
  SingleSelect,
512
1023
  {
513
1024
  "aria-label": formatMessage({
514
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1025
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
515
1026
  defaultMessage: "Group by"
516
1027
  }),
517
1028
  customizeContent: (value) => formatMessage(
@@ -529,7 +1040,7 @@ const ReleaseDetailsBody = () => {
529
1040
  }
530
1041
  ) }),
531
1042
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxs(Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
532
- /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
1043
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
533
1044
  /* @__PURE__ */ jsx(
534
1045
  Table.Root,
535
1046
  {
@@ -618,7 +1129,8 @@ const ReleaseDetailsBody = () => {
618
1129
  {
619
1130
  selected: type,
620
1131
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
621
- name: `release-action-${id}-type`
1132
+ name: `release-action-${id}-type`,
1133
+ disabled: !canUpdate
622
1134
  }
623
1135
  ) }),
624
1136
  !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -673,7 +1185,7 @@ const ReleaseDetailsPage = () => {
673
1185
  const { releaseId } = useParams();
674
1186
  const toggleNotification = useNotification();
675
1187
  const { formatAPIError } = useAPIErrorHandler();
676
- const { push } = useHistory();
1188
+ const { replace } = useHistory();
677
1189
  const [releaseModalShown, setReleaseModalShown] = React.useState(false);
678
1190
  const [showWarningSubmit, setWarningSubmit] = React.useState(false);
679
1191
  const {
@@ -697,11 +1209,18 @@ const ReleaseDetailsPage = () => {
697
1209
  }
698
1210
  );
699
1211
  }
700
- const title = isSuccessDetails && data?.data?.name || "";
1212
+ const releaseData = isSuccessDetails && data?.data || null;
1213
+ const title = releaseData?.name || "";
1214
+ const timezone = releaseData?.timezone ?? null;
1215
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1216
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
1217
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
701
1218
  const handleEditRelease = async (values) => {
702
1219
  const response = await updateRelease({
703
1220
  id: releaseId,
704
- name: values.name
1221
+ name: values.name,
1222
+ scheduledAt: values.scheduledAt,
1223
+ timezone: values.timezone
705
1224
  });
706
1225
  if ("data" in response) {
707
1226
  toggleNotification({
@@ -729,7 +1248,7 @@ const ReleaseDetailsPage = () => {
729
1248
  id: releaseId
730
1249
  });
731
1250
  if ("data" in response) {
732
- push("/plugins/content-releases");
1251
+ replace("/plugins/content-releases");
733
1252
  } else if (isAxiosError(response.error)) {
734
1253
  toggleNotification({
735
1254
  type: "warning",
@@ -755,7 +1274,14 @@ const ReleaseDetailsPage = () => {
755
1274
  handleClose: toggleEditReleaseModal,
756
1275
  handleSubmit: handleEditRelease,
757
1276
  isLoading: isLoadingDetails || isSubmittingForm,
758
- initialValues: { name: title || "" }
1277
+ initialValues: {
1278
+ name: title || "",
1279
+ scheduledAt,
1280
+ date,
1281
+ time,
1282
+ isScheduled: Boolean(scheduledAt),
1283
+ timezone
1284
+ }
759
1285
  }
760
1286
  ),
761
1287
  /* @__PURE__ */ jsx(
@@ -775,281 +1301,6 @@ const ReleaseDetailsPage = () => {
775
1301
  }
776
1302
  );
777
1303
  };
778
- const LinkCard = styled(Link$2)`
779
- display: block;
780
- `;
781
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
782
- const { formatMessage } = useIntl();
783
- if (isError) {
784
- return /* @__PURE__ */ jsx(AnErrorOccurred, {});
785
- }
786
- if (releases?.length === 0) {
787
- return /* @__PURE__ */ jsx(
788
- EmptyStateLayout,
789
- {
790
- content: formatMessage(
791
- {
792
- id: "content-releases.page.Releases.tab.emptyEntries",
793
- defaultMessage: "No releases"
794
- },
795
- {
796
- target: sectionTitle
797
- }
798
- ),
799
- icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
800
- }
801
- );
802
- }
803
- return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
804
- Flex,
805
- {
806
- direction: "column",
807
- justifyContent: "space-between",
808
- padding: 4,
809
- hasRadius: true,
810
- background: "neutral0",
811
- shadow: "tableShadow",
812
- height: "100%",
813
- width: "100%",
814
- alignItems: "start",
815
- gap: 2,
816
- children: [
817
- /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
818
- /* @__PURE__ */ jsx(Typography, { variant: "pi", children: formatMessage(
819
- {
820
- id: "content-releases.page.Releases.release-item.entries",
821
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
822
- },
823
- { number: actions.meta.count }
824
- ) })
825
- ]
826
- }
827
- ) }) }, id)) });
828
- };
829
- const StyledAlert = styled(Alert)`
830
- button {
831
- display: none;
832
- }
833
- p + div {
834
- margin-left: auto;
835
- }
836
- `;
837
- const INITIAL_FORM_VALUES = {
838
- name: ""
839
- };
840
- const ReleasesPage = () => {
841
- const tabRef = React.useRef(null);
842
- const location = useLocation();
843
- const [releaseModalShown, setReleaseModalShown] = React.useState(false);
844
- const toggleNotification = useNotification();
845
- const { formatMessage } = useIntl();
846
- const { push, replace } = useHistory();
847
- const { formatAPIError } = useAPIErrorHandler();
848
- const [{ query }, setQuery] = useQueryParams();
849
- const response = useGetReleasesQuery(query);
850
- const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
851
- const { getFeature } = useLicenseLimits();
852
- const { maximumReleases = 3 } = getFeature("cms-content-releases");
853
- const { trackUsage } = useTracking();
854
- const { isLoading, isSuccess, isError } = response;
855
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
856
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
857
- React.useEffect(() => {
858
- if (location?.state?.errors) {
859
- toggleNotification({
860
- type: "warning",
861
- title: formatMessage({
862
- id: "content-releases.pages.Releases.notification.error.title",
863
- defaultMessage: "Your request could not be processed."
864
- }),
865
- message: formatMessage({
866
- id: "content-releases.pages.Releases.notification.error.message",
867
- defaultMessage: "Please try again or open another release."
868
- })
869
- });
870
- replace({ state: null });
871
- }
872
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
873
- React.useEffect(() => {
874
- if (tabRef.current) {
875
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
876
- }
877
- }, [activeTabIndex]);
878
- const toggleAddReleaseModal = () => {
879
- setReleaseModalShown((prev) => !prev);
880
- };
881
- if (isLoading) {
882
- return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
883
- }
884
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
885
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
886
- const handleTabChange = (index) => {
887
- setQuery({
888
- ...query,
889
- page: 1,
890
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
891
- filters: {
892
- releasedAt: {
893
- $notNull: index === 0 ? false : true
894
- }
895
- }
896
- });
897
- };
898
- const handleAddRelease = async (values) => {
899
- const response2 = await createRelease({
900
- name: values.name
901
- });
902
- if ("data" in response2) {
903
- toggleNotification({
904
- type: "success",
905
- message: formatMessage({
906
- id: "content-releases.modal.release-created-notification-success",
907
- defaultMessage: "Release created."
908
- })
909
- });
910
- trackUsage("didCreateRelease");
911
- push(`/plugins/content-releases/${response2.data.data.id}`);
912
- } else if (isAxiosError(response2.error)) {
913
- toggleNotification({
914
- type: "warning",
915
- message: formatAPIError(response2.error)
916
- });
917
- } else {
918
- toggleNotification({
919
- type: "warning",
920
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
921
- });
922
- }
923
- };
924
- return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
925
- /* @__PURE__ */ jsx(
926
- HeaderLayout,
927
- {
928
- title: formatMessage({
929
- id: "content-releases.pages.Releases.title",
930
- defaultMessage: "Releases"
931
- }),
932
- subtitle: formatMessage(
933
- {
934
- id: "content-releases.pages.Releases.header-subtitle",
935
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
936
- },
937
- { number: totalReleases }
938
- ),
939
- primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
940
- Button,
941
- {
942
- startIcon: /* @__PURE__ */ jsx(Plus, {}),
943
- onClick: toggleAddReleaseModal,
944
- disabled: hasReachedMaximumPendingReleases,
945
- children: formatMessage({
946
- id: "content-releases.header.actions.add-release",
947
- defaultMessage: "New release"
948
- })
949
- }
950
- ) })
951
- }
952
- ),
953
- /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
954
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
955
- StyledAlert,
956
- {
957
- marginBottom: 6,
958
- action: /* @__PURE__ */ jsx(Link$2, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
959
- id: "content-releases.pages.Releases.max-limit-reached.action",
960
- defaultMessage: "Explore plans"
961
- }) }),
962
- title: formatMessage(
963
- {
964
- id: "content-releases.pages.Releases.max-limit-reached.title",
965
- defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
966
- },
967
- { number: maximumReleases }
968
- ),
969
- onClose: () => {
970
- },
971
- closeLabel: "",
972
- children: formatMessage({
973
- id: "content-releases.pages.Releases.max-limit-reached.message",
974
- defaultMessage: "Upgrade to manage an unlimited number of releases."
975
- })
976
- }
977
- ),
978
- /* @__PURE__ */ jsxs(
979
- TabGroup,
980
- {
981
- label: formatMessage({
982
- id: "content-releases.pages.Releases.tab-group.label",
983
- defaultMessage: "Releases list"
984
- }),
985
- variant: "simple",
986
- initialSelectedTabIndex: activeTabIndex,
987
- onTabChange: handleTabChange,
988
- ref: tabRef,
989
- children: [
990
- /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
991
- /* @__PURE__ */ jsxs(Tabs, { children: [
992
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
993
- id: "content-releases.pages.Releases.tab.pending",
994
- defaultMessage: "Pending"
995
- }) }),
996
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
997
- id: "content-releases.pages.Releases.tab.done",
998
- defaultMessage: "Done"
999
- }) })
1000
- ] }),
1001
- /* @__PURE__ */ jsx(Divider, {})
1002
- ] }),
1003
- /* @__PURE__ */ jsxs(TabPanels, { children: [
1004
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1005
- ReleasesGrid,
1006
- {
1007
- sectionTitle: "pending",
1008
- releases: response?.currentData?.data,
1009
- isError
1010
- }
1011
- ) }),
1012
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1013
- ReleasesGrid,
1014
- {
1015
- sectionTitle: "done",
1016
- releases: response?.currentData?.data,
1017
- isError
1018
- }
1019
- ) })
1020
- ] })
1021
- ]
1022
- }
1023
- ),
1024
- totalReleases > 0 && /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1025
- /* @__PURE__ */ jsx(
1026
- PageSizeURLQuery,
1027
- {
1028
- options: ["8", "16", "32", "64"],
1029
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1030
- }
1031
- ),
1032
- /* @__PURE__ */ jsx(
1033
- PaginationURLQuery,
1034
- {
1035
- pagination: {
1036
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1037
- }
1038
- }
1039
- )
1040
- ] })
1041
- ] }) }),
1042
- releaseModalShown && /* @__PURE__ */ jsx(
1043
- ReleaseModal,
1044
- {
1045
- handleClose: toggleAddReleaseModal,
1046
- handleSubmit: handleAddRelease,
1047
- isLoading: isSubmittingForm,
1048
- initialValues: INITIAL_FORM_VALUES
1049
- }
1050
- )
1051
- ] });
1052
- };
1053
1304
  const App = () => {
1054
1305
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
1055
1306
  /* @__PURE__ */ jsx(Route, { exact: true, path: `/plugins/${pluginId}`, component: ReleasesPage }),
@@ -1059,4 +1310,4 @@ const App = () => {
1059
1310
  export {
1060
1311
  App
1061
1312
  };
1062
- //# sourceMappingURL=App-3ycH2d3s.mjs.map
1313
+ //# sourceMappingURL=App-HVXzE3i3.mjs.map