@strapi/content-releases 0.0.0-next.504ae21185714e6995d2bdd6458efe2e20371a84 → 0.0.0-next.583e758623dc82206a4b2758d01dd5948b6e3f6a

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-6ugQxqYE.mjs} +699 -440
  2. package/dist/_chunks/App-6ugQxqYE.mjs.map +1 -0
  3. package/dist/_chunks/{App-5PsAyVt2.js → App-P1kyM3gT.js} +691 -431
  4. package/dist/_chunks/App-P1kyM3gT.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-WuuhP6Bn.mjs} +18 -6
  10. package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
  11. package/dist/_chunks/{en-2DuPv5k0.js → en-gcJJ5htG.js} +18 -6
  12. package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
  13. package/dist/_chunks/{index-D57Rztnc.js → index-2xzbhaQP.js} +127 -23
  14. package/dist/_chunks/index-2xzbhaQP.js.map +1 -0
  15. package/dist/_chunks/{index-4gUWuCQV.mjs → index-_eBuegHN.mjs} +138 -34
  16. package/dist/_chunks/index-_eBuegHN.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 +863 -419
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +862 -419
  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-_eBuegHN.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 { zonedTimeToUtc, utcToZonedTime } 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,24 @@ const ReleaseModal = ({
27
47
  const { formatMessage } = useIntl();
28
48
  const { pathname } = useLocation();
29
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
+ };
30
68
  return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
31
69
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
32
70
  {
@@ -38,45 +76,134 @@ const ReleaseModal = ({
38
76
  /* @__PURE__ */ jsx(
39
77
  Formik,
40
78
  {
41
- validateOnChange: false,
42
- onSubmit: handleSubmit,
43
- initialValues,
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
+ },
44
90
  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
- ) }),
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
+ ] }) }),
60
196
  /* @__PURE__ */ jsx(
61
197
  ModalFooter,
62
198
  {
63
199
  startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
64
- endActions: /* @__PURE__ */ jsx(
65
- Button,
200
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
66
201
  {
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
- )
202
+ id: "content-releases.modal.form.button.submit",
203
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
204
+ },
205
+ { isCreatingRelease }
206
+ ) })
80
207
  }
81
208
  )
82
209
  ] })
@@ -84,33 +211,403 @@ const ReleaseModal = ({
84
211
  )
85
212
  ] });
86
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
+ onTextValueChange: (timezone) => {
252
+ setFieldValue("timezone", timezone);
253
+ },
254
+ onClear: () => {
255
+ setFieldValue("timezone", "");
256
+ },
257
+ error: errors.timezone,
258
+ required: true,
259
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
260
+ }
261
+ );
262
+ };
263
+ const LinkCard = styled(Link)`
264
+ display: block;
265
+ `;
266
+ const CapitalizeRelativeTime = styled(RelativeTime)`
267
+ text-transform: capitalize;
268
+ `;
269
+ const getBadgeProps = (status) => {
270
+ let color;
271
+ switch (status) {
272
+ case "ready":
273
+ color = "success";
274
+ break;
275
+ case "blocked":
276
+ color = "warning";
277
+ break;
278
+ case "failed":
279
+ color = "danger";
280
+ break;
281
+ case "done":
282
+ color = "primary";
283
+ break;
284
+ case "empty":
285
+ default:
286
+ color = "neutral";
287
+ }
288
+ return {
289
+ textColor: `${color}600`,
290
+ backgroundColor: `${color}100`,
291
+ borderColor: `${color}200`
292
+ };
293
+ };
294
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
295
+ const { formatMessage } = useIntl();
296
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
297
+ if (isError) {
298
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
299
+ }
300
+ if (releases?.length === 0) {
301
+ return /* @__PURE__ */ jsx(
302
+ EmptyStateLayout,
303
+ {
304
+ content: formatMessage(
305
+ {
306
+ id: "content-releases.page.Releases.tab.emptyEntries",
307
+ defaultMessage: "No releases"
308
+ },
309
+ {
310
+ target: sectionTitle
311
+ }
312
+ ),
313
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
314
+ }
315
+ );
316
+ }
317
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt, status }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
318
+ Flex,
319
+ {
320
+ direction: "column",
321
+ justifyContent: "space-between",
322
+ padding: 4,
323
+ hasRadius: true,
324
+ background: "neutral0",
325
+ shadow: "tableShadow",
326
+ height: "100%",
327
+ width: "100%",
328
+ alignItems: "start",
329
+ gap: 4,
330
+ children: [
331
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
332
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
333
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
334
+ id: "content-releases.pages.Releases.not-scheduled",
335
+ defaultMessage: "Not scheduled"
336
+ }) : formatMessage(
337
+ {
338
+ id: "content-releases.page.Releases.release-item.entries",
339
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
340
+ },
341
+ { number: actions.meta.count }
342
+ ) })
343
+ ] }),
344
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
345
+ ]
346
+ }
347
+ ) }) }, id)) });
348
+ };
349
+ const StyledAlert = styled(Alert)`
350
+ button {
351
+ display: none;
352
+ }
353
+ p + div {
354
+ margin-left: auto;
355
+ }
356
+ `;
357
+ const INITIAL_FORM_VALUES = {
358
+ name: "",
359
+ date: null,
360
+ time: "",
361
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
362
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
363
+ scheduledAt: null,
364
+ timezone: null
365
+ };
366
+ const ReleasesPage = () => {
367
+ const tabRef = React.useRef(null);
368
+ const location = useLocation();
369
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
370
+ const toggleNotification = useNotification();
371
+ const { formatMessage } = useIntl();
372
+ const { push, replace } = useHistory();
373
+ const { formatAPIError } = useAPIErrorHandler();
374
+ const [{ query }, setQuery] = useQueryParams();
375
+ const response = useGetReleasesQuery(query);
376
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
377
+ const { getFeature } = useLicenseLimits();
378
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
379
+ const { trackUsage } = useTracking();
380
+ const { isLoading, isSuccess, isError } = response;
381
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
382
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
383
+ React.useEffect(() => {
384
+ if (location?.state?.errors) {
385
+ toggleNotification({
386
+ type: "warning",
387
+ title: formatMessage({
388
+ id: "content-releases.pages.Releases.notification.error.title",
389
+ defaultMessage: "Your request could not be processed."
390
+ }),
391
+ message: formatMessage({
392
+ id: "content-releases.pages.Releases.notification.error.message",
393
+ defaultMessage: "Please try again or open another release."
394
+ })
395
+ });
396
+ replace({ state: null });
397
+ }
398
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
399
+ React.useEffect(() => {
400
+ if (tabRef.current) {
401
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
402
+ }
403
+ }, [activeTabIndex]);
404
+ const toggleAddReleaseModal = () => {
405
+ setReleaseModalShown((prev) => !prev);
406
+ };
407
+ if (isLoading) {
408
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
409
+ }
410
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
411
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
412
+ const handleTabChange = (index) => {
413
+ setQuery({
414
+ ...query,
415
+ page: 1,
416
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
417
+ filters: {
418
+ releasedAt: {
419
+ $notNull: index === 0 ? false : true
420
+ }
421
+ }
422
+ });
423
+ };
424
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
425
+ const response2 = await createRelease({
426
+ name,
427
+ scheduledAt,
428
+ timezone
429
+ });
430
+ if ("data" in response2) {
431
+ toggleNotification({
432
+ type: "success",
433
+ message: formatMessage({
434
+ id: "content-releases.modal.release-created-notification-success",
435
+ defaultMessage: "Release created."
436
+ })
437
+ });
438
+ trackUsage("didCreateRelease");
439
+ push(`/plugins/content-releases/${response2.data.data.id}`);
440
+ } else if (isAxiosError(response2.error)) {
441
+ toggleNotification({
442
+ type: "warning",
443
+ message: formatAPIError(response2.error)
444
+ });
445
+ } else {
446
+ toggleNotification({
447
+ type: "warning",
448
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
449
+ });
450
+ }
451
+ };
452
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
453
+ /* @__PURE__ */ jsx(
454
+ HeaderLayout,
455
+ {
456
+ title: formatMessage({
457
+ id: "content-releases.pages.Releases.title",
458
+ defaultMessage: "Releases"
459
+ }),
460
+ subtitle: formatMessage({
461
+ id: "content-releases.pages.Releases.header-subtitle",
462
+ defaultMessage: "Create and manage content updates"
463
+ }),
464
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
465
+ Button,
466
+ {
467
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
468
+ onClick: toggleAddReleaseModal,
469
+ disabled: hasReachedMaximumPendingReleases,
470
+ children: formatMessage({
471
+ id: "content-releases.header.actions.add-release",
472
+ defaultMessage: "New release"
473
+ })
474
+ }
475
+ ) })
476
+ }
477
+ ),
478
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
479
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
480
+ StyledAlert,
481
+ {
482
+ marginBottom: 6,
483
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
484
+ id: "content-releases.pages.Releases.max-limit-reached.action",
485
+ defaultMessage: "Explore plans"
486
+ }) }),
487
+ title: formatMessage(
488
+ {
489
+ id: "content-releases.pages.Releases.max-limit-reached.title",
490
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
491
+ },
492
+ { number: maximumReleases }
493
+ ),
494
+ onClose: () => {
495
+ },
496
+ closeLabel: "",
497
+ children: formatMessage({
498
+ id: "content-releases.pages.Releases.max-limit-reached.message",
499
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
500
+ })
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsxs(
504
+ TabGroup,
505
+ {
506
+ label: formatMessage({
507
+ id: "content-releases.pages.Releases.tab-group.label",
508
+ defaultMessage: "Releases list"
509
+ }),
510
+ variant: "simple",
511
+ initialSelectedTabIndex: activeTabIndex,
512
+ onTabChange: handleTabChange,
513
+ ref: tabRef,
514
+ children: [
515
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
516
+ /* @__PURE__ */ jsxs(Tabs, { children: [
517
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
518
+ {
519
+ id: "content-releases.pages.Releases.tab.pending",
520
+ defaultMessage: "Pending ({count})"
521
+ },
522
+ {
523
+ count: totalPendingReleases
524
+ }
525
+ ) }),
526
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
527
+ id: "content-releases.pages.Releases.tab.done",
528
+ defaultMessage: "Done"
529
+ }) })
530
+ ] }),
531
+ /* @__PURE__ */ jsx(Divider, {})
532
+ ] }),
533
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
534
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
535
+ ReleasesGrid,
536
+ {
537
+ sectionTitle: "pending",
538
+ releases: response?.currentData?.data,
539
+ isError
540
+ }
541
+ ) }),
542
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
543
+ ReleasesGrid,
544
+ {
545
+ sectionTitle: "done",
546
+ releases: response?.currentData?.data,
547
+ isError
548
+ }
549
+ ) })
550
+ ] })
551
+ ]
552
+ }
553
+ ),
554
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
555
+ /* @__PURE__ */ jsx(
556
+ PageSizeURLQuery,
557
+ {
558
+ options: ["8", "16", "32", "64"],
559
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
560
+ }
561
+ ),
562
+ /* @__PURE__ */ jsx(
563
+ PaginationURLQuery,
564
+ {
565
+ pagination: {
566
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
567
+ }
568
+ }
569
+ )
570
+ ] }) : null
571
+ ] }) }),
572
+ releaseModalShown && /* @__PURE__ */ jsx(
573
+ ReleaseModal,
574
+ {
575
+ handleClose: toggleAddReleaseModal,
576
+ handleSubmit: handleAddRelease,
577
+ isLoading: isSubmittingForm,
578
+ initialValues: INITIAL_FORM_VALUES
579
+ }
580
+ )
581
+ ] });
582
+ };
87
583
  const ReleaseInfoWrapper = styled(Flex)`
88
584
  align-self: stretch;
89
585
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
586
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
587
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
588
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
589
+ const StyledMenuItem = styled(Menu.Item)`
97
590
  svg path {
98
591
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
592
  }
100
593
  span {
101
594
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
102
595
  }
596
+
597
+ &:hover {
598
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
599
+ }
103
600
  `;
104
601
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
602
+ width: ${({ theme }) => theme.spaces[3]};
603
+ height: ${({ theme }) => theme.spaces[3]};
107
604
  path {
108
605
  fill: ${({ theme }) => theme.colors.neutral600};
109
606
  }
110
607
  `;
111
608
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
609
+ width: ${({ theme }) => theme.spaces[3]};
610
+ height: ${({ theme }) => theme.spaces[3]};
114
611
  path {
115
612
  fill: ${({ theme }) => theme.colors.danger600};
116
613
  }
@@ -118,24 +615,6 @@ const TrashIcon = styled(Trash)`
118
615
  const TypographyMaxWidth = styled(Typography)`
119
616
  max-width: 300px;
120
617
  `;
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
618
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
619
  const { formatMessage } = useIntl();
141
620
  const { validate } = unstable_useDocument();
@@ -184,10 +663,8 @@ const ReleaseDetailsLayout = ({
184
663
  toggleWarningSubmit,
185
664
  children
186
665
  }) => {
187
- const { formatMessage } = useIntl();
666
+ const { formatMessage, formatDate, formatTime } = useIntl();
188
667
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
668
  const {
192
669
  data,
193
670
  isLoading: isLoadingDetails,
@@ -203,13 +680,6 @@ const ReleaseDetailsLayout = ({
203
680
  const dispatch = useTypedDispatch();
204
681
  const { trackUsage } = useTracking();
205
682
  const release = data?.data;
206
- const handleTogglePopover = () => {
207
- setIsPopoverVisible((prev) => !prev);
208
- };
209
- const openReleaseModal = () => {
210
- toggleEditReleaseModal();
211
- handleTogglePopover();
212
- };
213
683
  const handlePublishRelease = async () => {
214
684
  const response = await publishRelease({ id: releaseId });
215
685
  if ("data" in response) {
@@ -238,13 +708,21 @@ const ReleaseDetailsLayout = ({
238
708
  });
239
709
  }
240
710
  };
241
- const openWarningConfirmDialog = () => {
242
- toggleWarningSubmit();
243
- handleTogglePopover();
244
- };
245
711
  const handleRefresh = () => {
246
712
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
247
713
  };
714
+ const getCreatedByUser = () => {
715
+ if (!release?.createdBy) {
716
+ return null;
717
+ }
718
+ if (release.createdBy.username) {
719
+ return release.createdBy.username;
720
+ }
721
+ if (release.createdBy.firstname) {
722
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
723
+ }
724
+ return release.createdBy.email;
725
+ };
248
726
  if (isLoadingDetails) {
249
727
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
250
728
  }
@@ -266,90 +744,128 @@ const ReleaseDetailsLayout = ({
266
744
  );
267
745
  }
268
746
  const totalEntries = release.actions.meta.count || 0;
269
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
747
+ const hasCreatedByUser = Boolean(getCreatedByUser());
748
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
749
+ const isScheduled = release.scheduledAt && release.timezone;
750
+ const numberOfEntriesText = formatMessage(
751
+ {
752
+ id: "content-releases.pages.Details.header-subtitle",
753
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
754
+ },
755
+ { number: totalEntries }
756
+ );
757
+ const scheduledText = isScheduled ? formatMessage(
758
+ {
759
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
760
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
761
+ },
762
+ {
763
+ date: formatDate(new Date(release.scheduledAt), {
764
+ weekday: "long",
765
+ day: "numeric",
766
+ month: "long",
767
+ year: "numeric",
768
+ timeZone: release.timezone
769
+ }),
770
+ time: formatTime(new Date(release.scheduledAt), {
771
+ timeZone: release.timezone,
772
+ hourCycle: "h23"
773
+ }),
774
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
775
+ }
776
+ ) : "";
270
777
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
271
778
  /* @__PURE__ */ jsx(
272
779
  HeaderLayout,
273
780
  {
274
781
  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({
782
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
783
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : "") }),
784
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
785
+ ] }),
786
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
283
787
  id: "global.back",
284
788
  defaultMessage: "Back"
285
789
  }) }),
286
790
  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
- ),
791
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
792
+ /* @__PURE__ */ jsx(
793
+ Menu.Trigger,
794
+ {
795
+ as: IconButton,
796
+ paddingLeft: 2,
797
+ paddingRight: 2,
798
+ "aria-label": formatMessage({
799
+ id: "content-releases.header.actions.open-release-actions",
800
+ defaultMessage: "Release edit and delete menu"
801
+ }),
802
+ icon: /* @__PURE__ */ jsx(More, {}),
803
+ variant: "tertiary"
804
+ }
805
+ ),
806
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
807
+ /* @__PURE__ */ jsxs(
808
+ Flex,
809
+ {
810
+ alignItems: "center",
811
+ justifyContent: "center",
812
+ direction: "column",
813
+ padding: 1,
814
+ width: "100%",
815
+ children: [
816
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
817
+ /* @__PURE__ */ jsx(PencilIcon, {}),
818
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
819
+ id: "content-releases.header.actions.edit",
820
+ defaultMessage: "Edit"
821
+ }) })
822
+ ] }) }),
823
+ /* @__PURE__ */ jsx(
824
+ StyledMenuItem,
825
+ {
826
+ disabled: !canDelete,
827
+ onSelect: toggleWarningSubmit,
828
+ variant: "danger",
829
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
830
+ /* @__PURE__ */ jsx(TrashIcon, {}),
831
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
832
+ id: "content-releases.header.actions.delete",
833
+ defaultMessage: "Delete"
834
+ }) })
835
+ ] })
836
+ }
837
+ )
838
+ ]
839
+ }
840
+ ),
841
+ /* @__PURE__ */ jsxs(
842
+ ReleaseInfoWrapper,
843
+ {
844
+ direction: "column",
845
+ justifyContent: "center",
846
+ alignItems: "flex-start",
847
+ gap: 1,
848
+ padding: 5,
849
+ children: [
850
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
851
+ id: "content-releases.header.actions.created",
852
+ defaultMessage: "Created"
853
+ }) }),
854
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
855
+ /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
856
+ formatMessage(
857
+ {
858
+ id: "content-releases.header.actions.created.description",
859
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
860
+ },
861
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
862
+ )
863
+ ] })
864
+ ]
865
+ }
866
+ )
867
+ ] })
868
+ ] }),
353
869
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
354
870
  id: "content-releases.header.actions.refresh",
355
871
  defaultMessage: "Refresh"
@@ -405,6 +921,9 @@ const ReleaseDetailsBody = () => {
405
921
  isError: isReleaseError,
406
922
  error: releaseError
407
923
  } = useGetReleaseQuery({ id: releaseId });
924
+ const {
925
+ allowedActions: { canUpdate }
926
+ } = useRBAC(PERMISSIONS);
408
927
  const release = releaseData?.data;
409
928
  const selectedGroupBy = query?.groupBy || "contentType";
410
929
  const {
@@ -491,7 +1010,7 @@ const ReleaseDetailsBody = () => {
491
1010
  action: /* @__PURE__ */ jsx(
492
1011
  LinkButton,
493
1012
  {
494
- as: Link$1,
1013
+ as: Link$2,
495
1014
  to: {
496
1015
  pathname: "/content-manager"
497
1016
  },
@@ -511,7 +1030,7 @@ const ReleaseDetailsBody = () => {
511
1030
  SingleSelect,
512
1031
  {
513
1032
  "aria-label": formatMessage({
514
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1033
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
515
1034
  defaultMessage: "Group by"
516
1035
  }),
517
1036
  customizeContent: (value) => formatMessage(
@@ -529,7 +1048,7 @@ const ReleaseDetailsBody = () => {
529
1048
  }
530
1049
  ) }),
531
1050
  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 }) }),
1051
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
533
1052
  /* @__PURE__ */ jsx(
534
1053
  Table.Root,
535
1054
  {
@@ -618,7 +1137,8 @@ const ReleaseDetailsBody = () => {
618
1137
  {
619
1138
  selected: type,
620
1139
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
621
- name: `release-action-${id}-type`
1140
+ name: `release-action-${id}-type`,
1141
+ disabled: !canUpdate
622
1142
  }
623
1143
  ) }),
624
1144
  !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -697,11 +1217,18 @@ const ReleaseDetailsPage = () => {
697
1217
  }
698
1218
  );
699
1219
  }
700
- const title = isSuccessDetails && data?.data?.name || "";
1220
+ const releaseData = isSuccessDetails && data?.data || null;
1221
+ const title = releaseData?.name || "";
1222
+ const timezone = releaseData?.timezone ?? null;
1223
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1224
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
1225
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
701
1226
  const handleEditRelease = async (values) => {
702
1227
  const response = await updateRelease({
703
1228
  id: releaseId,
704
- name: values.name
1229
+ name: values.name,
1230
+ scheduledAt: values.scheduledAt,
1231
+ timezone: values.timezone
705
1232
  });
706
1233
  if ("data" in response) {
707
1234
  toggleNotification({
@@ -755,7 +1282,14 @@ const ReleaseDetailsPage = () => {
755
1282
  handleClose: toggleEditReleaseModal,
756
1283
  handleSubmit: handleEditRelease,
757
1284
  isLoading: isLoadingDetails || isSubmittingForm,
758
- initialValues: { name: title || "" }
1285
+ initialValues: {
1286
+ name: title || "",
1287
+ scheduledAt,
1288
+ date,
1289
+ time,
1290
+ isScheduled: Boolean(scheduledAt),
1291
+ timezone
1292
+ }
759
1293
  }
760
1294
  ),
761
1295
  /* @__PURE__ */ jsx(
@@ -775,281 +1309,6 @@ const ReleaseDetailsPage = () => {
775
1309
  }
776
1310
  );
777
1311
  };
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
1312
  const App = () => {
1054
1313
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
1055
1314
  /* @__PURE__ */ jsx(Route, { exact: true, path: `/plugins/${pluginId}`, component: ReleasesPage }),
@@ -1059,4 +1318,4 @@ const App = () => {
1059
1318
  export {
1060
1319
  App
1061
1320
  };
1062
- //# sourceMappingURL=App-3ycH2d3s.mjs.map
1321
+ //# sourceMappingURL=App-6ugQxqYE.mjs.map