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

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-g3vtS2Wa.mjs} +405 -180
  2. package/dist/_chunks/App-g3vtS2Wa.mjs.map +1 -0
  3. package/dist/_chunks/{App-5PsAyVt2.js → App-lnXbSPgp.js} +403 -177
  4. package/dist/_chunks/App-lnXbSPgp.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-ItlgrLcr.js} +127 -23
  14. package/dist/_chunks/index-ItlgrLcr.js.map +1 -0
  15. package/dist/_chunks/{index-4gUWuCQV.mjs → index-uGex_IIQ.mjs} +131 -27
  16. package/dist/_chunks/index-uGex_IIQ.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
2
  import { useNotification, useAPIErrorHandler, LoadingIndicatorPage, ConfirmDialog, useRBAC, useTracking, RelativeTime, CheckPermissions, useQueryParams, AnErrorOccurred, NoContent, Table, PageSizeURLQuery, PaginationURLQuery, CheckPagePermissions } from "@strapi/helper-plugin";
3
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";
4
+ import { g as getTimezoneOffset, p as pluginId, u as useGetReleaseQuery, a as useUpdateReleaseMutation, b as useDeleteReleaseMutation, c as usePublishReleaseMutation, P as PERMISSIONS, d as useTypedDispatch, e as useGetReleaseActionsQuery, f as useUpdateReleaseActionMutation, R as ReleaseActionOptions, h as ReleaseActionMenu, i as isAxiosError, r as releaseApi, j as useGetReleasesQuery, k as useCreateReleaseMutation } from "./index-uGex_IIQ.mjs";
5
5
  import * as React from "react";
6
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";
7
+ import { ModalLayout, ModalHeader, Typography, ModalBody, Flex, TextInput, Box, Checkbox, DatePicker, TimePicker, ModalFooter, Button, Combobox, ComboboxOption, ContentLayout, Main, HeaderLayout, Link, IconButton, SingleSelect, SingleSelectOption, Badge, Tr, Td, Icon, Tooltip, Alert, TabGroup, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem } from "@strapi/design-system";
8
+ import { Menu, LinkButton, Link as Link$2 } from "@strapi/design-system/v2";
9
9
  import { Pencil, Trash, ArrowLeft, More, CrossCircle, CheckCircle, Plus, EmptyDocuments } from "@strapi/icons";
10
+ import format from "date-fns/format";
11
+ import { zonedTimeToUtc, utcToZonedTime } from "date-fns-tz";
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,83 @@ 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
+ };
87
263
  const ReleaseInfoWrapper = styled(Flex)`
88
264
  align-self: stretch;
89
265
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
266
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
267
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
268
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
269
+ const StyledMenuItem = styled(Menu.Item)`
97
270
  svg path {
98
271
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
272
  }
100
273
  span {
101
274
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
102
275
  }
276
+
277
+ &:hover {
278
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
279
+ }
103
280
  `;
104
281
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
282
+ width: ${({ theme }) => theme.spaces[3]};
283
+ height: ${({ theme }) => theme.spaces[3]};
107
284
  path {
108
285
  fill: ${({ theme }) => theme.colors.neutral600};
109
286
  }
110
287
  `;
111
288
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
289
+ width: ${({ theme }) => theme.spaces[3]};
290
+ height: ${({ theme }) => theme.spaces[3]};
114
291
  path {
115
292
  fill: ${({ theme }) => theme.colors.danger600};
116
293
  }
@@ -118,24 +295,6 @@ const TrashIcon = styled(Trash)`
118
295
  const TypographyMaxWidth = styled(Typography)`
119
296
  max-width: 300px;
120
297
  `;
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
298
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
299
  const { formatMessage } = useIntl();
141
300
  const { validate } = unstable_useDocument();
@@ -184,10 +343,8 @@ const ReleaseDetailsLayout = ({
184
343
  toggleWarningSubmit,
185
344
  children
186
345
  }) => {
187
- const { formatMessage } = useIntl();
346
+ const { formatMessage, formatDate, formatTime } = useIntl();
188
347
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
348
  const {
192
349
  data,
193
350
  isLoading: isLoadingDetails,
@@ -203,13 +360,6 @@ const ReleaseDetailsLayout = ({
203
360
  const dispatch = useTypedDispatch();
204
361
  const { trackUsage } = useTracking();
205
362
  const release = data?.data;
206
- const handleTogglePopover = () => {
207
- setIsPopoverVisible((prev) => !prev);
208
- };
209
- const openReleaseModal = () => {
210
- toggleEditReleaseModal();
211
- handleTogglePopover();
212
- };
213
363
  const handlePublishRelease = async () => {
214
364
  const response = await publishRelease({ id: releaseId });
215
365
  if ("data" in response) {
@@ -238,13 +388,21 @@ const ReleaseDetailsLayout = ({
238
388
  });
239
389
  }
240
390
  };
241
- const openWarningConfirmDialog = () => {
242
- toggleWarningSubmit();
243
- handleTogglePopover();
244
- };
245
391
  const handleRefresh = () => {
246
392
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
247
393
  };
394
+ const getCreatedByUser = () => {
395
+ if (!release?.createdBy) {
396
+ return null;
397
+ }
398
+ if (release.createdBy.username) {
399
+ return release.createdBy.username;
400
+ }
401
+ if (release.createdBy.firstname) {
402
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
403
+ }
404
+ return release.createdBy.email;
405
+ };
248
406
  if (isLoadingDetails) {
249
407
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
250
408
  }
@@ -266,90 +424,125 @@ const ReleaseDetailsLayout = ({
266
424
  );
267
425
  }
268
426
  const totalEntries = release.actions.meta.count || 0;
269
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
427
+ const hasCreatedByUser = Boolean(getCreatedByUser());
428
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
429
+ const isScheduled = release.scheduledAt && release.timezone;
430
+ const numberOfEntriesText = formatMessage(
431
+ {
432
+ id: "content-releases.pages.Details.header-subtitle",
433
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
434
+ },
435
+ { number: totalEntries }
436
+ );
437
+ const scheduledText = isScheduled ? formatMessage(
438
+ {
439
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
440
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
441
+ },
442
+ {
443
+ date: formatDate(new Date(release.scheduledAt), {
444
+ weekday: "long",
445
+ day: "numeric",
446
+ month: "long",
447
+ year: "numeric",
448
+ timeZone: release.timezone
449
+ }),
450
+ time: formatTime(new Date(release.scheduledAt), {
451
+ timeZone: release.timezone,
452
+ hourCycle: "h23"
453
+ }),
454
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
455
+ }
456
+ ) : "";
270
457
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
271
458
  /* @__PURE__ */ jsx(
272
459
  HeaderLayout,
273
460
  {
274
461
  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
- ),
462
+ subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
282
463
  navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
283
464
  id: "global.back",
284
465
  defaultMessage: "Back"
285
466
  }) }),
286
467
  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
- ),
468
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
469
+ /* @__PURE__ */ jsx(
470
+ Menu.Trigger,
471
+ {
472
+ as: IconButton,
473
+ paddingLeft: 2,
474
+ paddingRight: 2,
475
+ "aria-label": formatMessage({
476
+ id: "content-releases.header.actions.open-release-actions",
477
+ defaultMessage: "Release edit and delete menu"
478
+ }),
479
+ icon: /* @__PURE__ */ jsx(More, {}),
480
+ variant: "tertiary"
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
484
+ /* @__PURE__ */ jsxs(
485
+ Flex,
486
+ {
487
+ alignItems: "center",
488
+ justifyContent: "center",
489
+ direction: "column",
490
+ padding: 1,
491
+ width: "100%",
492
+ children: [
493
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
494
+ /* @__PURE__ */ jsx(PencilIcon, {}),
495
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
496
+ id: "content-releases.header.actions.edit",
497
+ defaultMessage: "Edit"
498
+ }) })
499
+ ] }) }),
500
+ /* @__PURE__ */ jsx(
501
+ StyledMenuItem,
502
+ {
503
+ disabled: !canDelete,
504
+ onSelect: toggleWarningSubmit,
505
+ variant: "danger",
506
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
507
+ /* @__PURE__ */ jsx(TrashIcon, {}),
508
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
509
+ id: "content-releases.header.actions.delete",
510
+ defaultMessage: "Delete"
511
+ }) })
512
+ ] })
513
+ }
514
+ )
515
+ ]
516
+ }
517
+ ),
518
+ /* @__PURE__ */ jsxs(
519
+ ReleaseInfoWrapper,
520
+ {
521
+ direction: "column",
522
+ justifyContent: "center",
523
+ alignItems: "flex-start",
524
+ gap: 1,
525
+ padding: 5,
526
+ children: [
527
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
528
+ id: "content-releases.header.actions.created",
529
+ defaultMessage: "Created"
530
+ }) }),
531
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
532
+ /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
533
+ formatMessage(
534
+ {
535
+ id: "content-releases.header.actions.created.description",
536
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
537
+ },
538
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
539
+ )
540
+ ] })
541
+ ]
542
+ }
543
+ )
544
+ ] })
545
+ ] }),
353
546
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
354
547
  id: "content-releases.header.actions.refresh",
355
548
  defaultMessage: "Refresh"
@@ -405,6 +598,9 @@ const ReleaseDetailsBody = () => {
405
598
  isError: isReleaseError,
406
599
  error: releaseError
407
600
  } = useGetReleaseQuery({ id: releaseId });
601
+ const {
602
+ allowedActions: { canUpdate }
603
+ } = useRBAC(PERMISSIONS);
408
604
  const release = releaseData?.data;
409
605
  const selectedGroupBy = query?.groupBy || "contentType";
410
606
  const {
@@ -511,7 +707,7 @@ const ReleaseDetailsBody = () => {
511
707
  SingleSelect,
512
708
  {
513
709
  "aria-label": formatMessage({
514
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
710
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
515
711
  defaultMessage: "Group by"
516
712
  }),
517
713
  customizeContent: (value) => formatMessage(
@@ -529,7 +725,7 @@ const ReleaseDetailsBody = () => {
529
725
  }
530
726
  ) }),
531
727
  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 }) }),
728
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
533
729
  /* @__PURE__ */ jsx(
534
730
  Table.Root,
535
731
  {
@@ -618,7 +814,8 @@ const ReleaseDetailsBody = () => {
618
814
  {
619
815
  selected: type,
620
816
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
621
- name: `release-action-${id}-type`
817
+ name: `release-action-${id}-type`,
818
+ disabled: !canUpdate
622
819
  }
623
820
  ) }),
624
821
  !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -697,11 +894,18 @@ const ReleaseDetailsPage = () => {
697
894
  }
698
895
  );
699
896
  }
700
- const title = isSuccessDetails && data?.data?.name || "";
897
+ const releaseData = isSuccessDetails && data?.data || null;
898
+ const title = releaseData?.name || "";
899
+ const timezone = releaseData?.timezone ?? null;
900
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
901
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
902
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
701
903
  const handleEditRelease = async (values) => {
702
904
  const response = await updateRelease({
703
905
  id: releaseId,
704
- name: values.name
906
+ name: values.name,
907
+ scheduledAt: values.scheduledAt,
908
+ timezone: values.timezone
705
909
  });
706
910
  if ("data" in response) {
707
911
  toggleNotification({
@@ -755,7 +959,14 @@ const ReleaseDetailsPage = () => {
755
959
  handleClose: toggleEditReleaseModal,
756
960
  handleSubmit: handleEditRelease,
757
961
  isLoading: isLoadingDetails || isSubmittingForm,
758
- initialValues: { name: title || "" }
962
+ initialValues: {
963
+ name: title || "",
964
+ scheduledAt,
965
+ date,
966
+ time,
967
+ isScheduled: Boolean(scheduledAt),
968
+ timezone
969
+ }
759
970
  }
760
971
  ),
761
972
  /* @__PURE__ */ jsx(
@@ -780,6 +991,7 @@ const LinkCard = styled(Link$2)`
780
991
  `;
781
992
  const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
782
993
  const { formatMessage } = useIntl();
994
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
783
995
  if (isError) {
784
996
  return /* @__PURE__ */ jsx(AnErrorOccurred, {});
785
997
  }
@@ -800,7 +1012,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
800
1012
  }
801
1013
  );
802
1014
  }
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(
1015
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
804
1016
  Flex,
805
1017
  {
806
1018
  direction: "column",
@@ -815,7 +1027,10 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
815
1027
  gap: 2,
816
1028
  children: [
817
1029
  /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
818
- /* @__PURE__ */ jsx(Typography, { variant: "pi", children: formatMessage(
1030
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
1031
+ id: "content-releases.pages.Releases.not-scheduled",
1032
+ defaultMessage: "Not scheduled"
1033
+ }) : formatMessage(
819
1034
  {
820
1035
  id: "content-releases.page.Releases.release-item.entries",
821
1036
  defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
@@ -835,7 +1050,13 @@ const StyledAlert = styled(Alert)`
835
1050
  }
836
1051
  `;
837
1052
  const INITIAL_FORM_VALUES = {
838
- name: ""
1053
+ name: "",
1054
+ date: null,
1055
+ time: "",
1056
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
1057
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
1058
+ scheduledAt: null,
1059
+ timezone: null
839
1060
  };
840
1061
  const ReleasesPage = () => {
841
1062
  const tabRef = React.useRef(null);
@@ -881,8 +1102,8 @@ const ReleasesPage = () => {
881
1102
  if (isLoading) {
882
1103
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
883
1104
  }
884
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
885
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
1105
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
1106
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
886
1107
  const handleTabChange = (index) => {
887
1108
  setQuery({
888
1109
  ...query,
@@ -895,9 +1116,11 @@ const ReleasesPage = () => {
895
1116
  }
896
1117
  });
897
1118
  };
898
- const handleAddRelease = async (values) => {
1119
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
899
1120
  const response2 = await createRelease({
900
- name: values.name
1121
+ name,
1122
+ scheduledAt,
1123
+ timezone
901
1124
  });
902
1125
  if ("data" in response2) {
903
1126
  toggleNotification({
@@ -929,13 +1152,10 @@ const ReleasesPage = () => {
929
1152
  id: "content-releases.pages.Releases.title",
930
1153
  defaultMessage: "Releases"
931
1154
  }),
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
- ),
1155
+ subtitle: formatMessage({
1156
+ id: "content-releases.pages.Releases.header-subtitle",
1157
+ defaultMessage: "Create and manage content updates"
1158
+ }),
939
1159
  primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
940
1160
  Button,
941
1161
  {
@@ -951,7 +1171,7 @@ const ReleasesPage = () => {
951
1171
  }
952
1172
  ),
953
1173
  /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
954
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
1174
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
955
1175
  StyledAlert,
956
1176
  {
957
1177
  marginBottom: 6,
@@ -989,10 +1209,15 @@ const ReleasesPage = () => {
989
1209
  children: [
990
1210
  /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
991
1211
  /* @__PURE__ */ jsxs(Tabs, { children: [
992
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
993
- id: "content-releases.pages.Releases.tab.pending",
994
- defaultMessage: "Pending"
995
- }) }),
1212
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
1213
+ {
1214
+ id: "content-releases.pages.Releases.tab.pending",
1215
+ defaultMessage: "Pending ({count})"
1216
+ },
1217
+ {
1218
+ count: totalPendingReleases
1219
+ }
1220
+ ) }),
996
1221
  /* @__PURE__ */ jsx(Tab, { children: formatMessage({
997
1222
  id: "content-releases.pages.Releases.tab.done",
998
1223
  defaultMessage: "Done"
@@ -1021,7 +1246,7 @@ const ReleasesPage = () => {
1021
1246
  ]
1022
1247
  }
1023
1248
  ),
1024
- totalReleases > 0 && /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1249
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1025
1250
  /* @__PURE__ */ jsx(
1026
1251
  PageSizeURLQuery,
1027
1252
  {
@@ -1037,7 +1262,7 @@ const ReleasesPage = () => {
1037
1262
  }
1038
1263
  }
1039
1264
  )
1040
- ] })
1265
+ ] }) : null
1041
1266
  ] }) }),
1042
1267
  releaseModalShown && /* @__PURE__ */ jsx(
1043
1268
  ReleaseModal,
@@ -1059,4 +1284,4 @@ const App = () => {
1059
1284
  export {
1060
1285
  App
1061
1286
  };
1062
- //# sourceMappingURL=App-3ycH2d3s.mjs.map
1287
+ //# sourceMappingURL=App-g3vtS2Wa.mjs.map