@strapi/content-releases 0.0.0-next.6d384ed205b7f0792d9bea79195f01b30463cfa0 → 0.0.0-next.73143c28059b343ba62d98c29672ab114562fbbc

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-WkwjSaDY.mjs → App-g3vtS2Wa.mjs} +385 -174
  2. package/dist/_chunks/App-g3vtS2Wa.mjs.map +1 -0
  3. package/dist/_chunks/{App-IQIHCiSO.js → App-lnXbSPgp.js} +383 -171
  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-m9eTk4UF.mjs → en-WuuhP6Bn.mjs} +15 -4
  10. package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
  11. package/dist/_chunks/{en-r9YocBH0.js → en-gcJJ5htG.js} +15 -4
  12. package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
  13. package/dist/_chunks/{index-u_da7WHb.js → index-ItlgrLcr.js} +122 -19
  14. package/dist/_chunks/index-ItlgrLcr.js.map +1 -0
  15. package/dist/_chunks/{index-CyVlvX8h.mjs → index-uGex_IIQ.mjs} +126 -23
  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 +814 -418
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +813 -418
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +13 -11
  24. package/dist/_chunks/App-IQIHCiSO.js.map +0 -1
  25. package/dist/_chunks/App-WkwjSaDY.mjs.map +0 -1
  26. package/dist/_chunks/en-m9eTk4UF.mjs.map +0 -1
  27. package/dist/_chunks/en-r9YocBH0.js.map +0 -1
  28. package/dist/_chunks/index-CyVlvX8h.mjs.map +0 -1
  29. package/dist/_chunks/index-u_da7WHb.js.map +0 -1
@@ -1,15 +1,18 @@
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-CyVlvX8h.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";
@@ -17,8 +20,23 @@ import "@reduxjs/toolkit/query/react";
17
20
  import "react-redux";
18
21
  const RELEASE_SCHEMA = yup.object().shape({
19
22
  name: yup.string().trim().required(),
20
- // scheduledAt is a date, but we always receive strings from the client
21
- scheduledAt: yup.string()
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
+ })
22
40
  }).required().noUnknown();
23
41
  const ReleaseModal = ({
24
42
  handleClose,
@@ -29,6 +47,24 @@ const ReleaseModal = ({
29
47
  const { formatMessage } = useIntl();
30
48
  const { pathname } = useLocation();
31
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
+ };
32
68
  return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
33
69
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
34
70
  {
@@ -40,45 +76,134 @@ const ReleaseModal = ({
40
76
  /* @__PURE__ */ jsx(
41
77
  Formik,
42
78
  {
43
- validateOnChange: false,
44
- onSubmit: handleSubmit,
45
- 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
+ },
46
90
  validationSchema: RELEASE_SCHEMA,
47
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxs(Form, { children: [
48
- /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx(
49
- TextInput,
50
- {
51
- label: formatMessage({
52
- id: "content-releases.modal.form.input.label.release-name",
53
- defaultMessage: "Name"
54
- }),
55
- name: "name",
56
- value: values.name,
57
- error: errors.name,
58
- onChange: handleChange,
59
- required: true
60
- }
61
- ) }),
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
+ ] }) }),
62
196
  /* @__PURE__ */ jsx(
63
197
  ModalFooter,
64
198
  {
65
199
  startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
66
- endActions: /* @__PURE__ */ jsx(
67
- Button,
200
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
68
201
  {
69
- name: "submit",
70
- loading: isLoading,
71
- disabled: !values.name || values.name === initialValues.name,
72
- type: "submit",
73
- children: formatMessage(
74
- {
75
- id: "content-releases.modal.form.button.submit",
76
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
77
- },
78
- { isCreatingRelease }
79
- )
80
- }
81
- )
202
+ id: "content-releases.modal.form.button.submit",
203
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
204
+ },
205
+ { isCreatingRelease }
206
+ ) })
82
207
  }
83
208
  )
84
209
  ] })
@@ -86,22 +211,72 @@ const ReleaseModal = ({
86
211
  )
87
212
  ] });
88
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
+ };
89
263
  const ReleaseInfoWrapper = styled(Flex)`
90
264
  align-self: stretch;
91
265
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
92
266
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
93
267
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
94
268
  `;
95
- const StyledFlex = styled(Flex)`
96
- align-self: stretch;
97
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
98
-
269
+ const StyledMenuItem = styled(Menu.Item)`
99
270
  svg path {
100
271
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
101
272
  }
102
273
  span {
103
274
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
104
275
  }
276
+
277
+ &:hover {
278
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
279
+ }
105
280
  `;
106
281
  const PencilIcon = styled(Pencil)`
107
282
  width: ${({ theme }) => theme.spaces[3]};
@@ -120,24 +295,6 @@ const TrashIcon = styled(Trash)`
120
295
  const TypographyMaxWidth = styled(Typography)`
121
296
  max-width: 300px;
122
297
  `;
123
- const PopoverButton = ({ onClick, disabled, children }) => {
124
- return /* @__PURE__ */ jsx(
125
- StyledFlex,
126
- {
127
- paddingTop: 2,
128
- paddingBottom: 2,
129
- paddingLeft: 4,
130
- paddingRight: 4,
131
- alignItems: "center",
132
- gap: 2,
133
- as: "button",
134
- hasRadius: true,
135
- onClick,
136
- disabled,
137
- children
138
- }
139
- );
140
- };
141
298
  const EntryValidationText = ({ action, schema, components, entry }) => {
142
299
  const { formatMessage } = useIntl();
143
300
  const { validate } = unstable_useDocument();
@@ -186,10 +343,8 @@ const ReleaseDetailsLayout = ({
186
343
  toggleWarningSubmit,
187
344
  children
188
345
  }) => {
189
- const { formatMessage } = useIntl();
346
+ const { formatMessage, formatDate, formatTime } = useIntl();
190
347
  const { releaseId } = useParams();
191
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
192
- const moreButtonRef = React.useRef(null);
193
348
  const {
194
349
  data,
195
350
  isLoading: isLoadingDetails,
@@ -205,13 +360,6 @@ const ReleaseDetailsLayout = ({
205
360
  const dispatch = useTypedDispatch();
206
361
  const { trackUsage } = useTracking();
207
362
  const release = data?.data;
208
- const handleTogglePopover = () => {
209
- setIsPopoverVisible((prev) => !prev);
210
- };
211
- const openReleaseModal = () => {
212
- toggleEditReleaseModal();
213
- handleTogglePopover();
214
- };
215
363
  const handlePublishRelease = async () => {
216
364
  const response = await publishRelease({ id: releaseId });
217
365
  if ("data" in response) {
@@ -240,10 +388,6 @@ const ReleaseDetailsLayout = ({
240
388
  });
241
389
  }
242
390
  };
243
- const openWarningConfirmDialog = () => {
244
- toggleWarningSubmit();
245
- handleTogglePopover();
246
- };
247
391
  const handleRefresh = () => {
248
392
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
249
393
  };
@@ -281,89 +425,124 @@ const ReleaseDetailsLayout = ({
281
425
  }
282
426
  const totalEntries = release.actions.meta.count || 0;
283
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
+ ) : "";
284
457
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
285
458
  /* @__PURE__ */ jsx(
286
459
  HeaderLayout,
287
460
  {
288
461
  title: release.name,
289
- subtitle: formatMessage(
290
- {
291
- id: "content-releases.pages.Details.header-subtitle",
292
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
293
- },
294
- { number: totalEntries }
295
- ),
462
+ subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
296
463
  navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
297
464
  id: "global.back",
298
465
  defaultMessage: "Back"
299
466
  }) }),
300
467
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
301
- /* @__PURE__ */ jsx(
302
- IconButton,
303
- {
304
- label: formatMessage({
305
- id: "content-releases.header.actions.open-release-actions",
306
- defaultMessage: "Release edit and delete menu"
307
- }),
308
- ref: moreButtonRef,
309
- onClick: handleTogglePopover,
310
- children: /* @__PURE__ */ jsx(More, {})
311
- }
312
- ),
313
- isPopoverVisible && /* @__PURE__ */ jsxs(
314
- Popover,
315
- {
316
- source: moreButtonRef,
317
- placement: "bottom-end",
318
- onDismiss: handleTogglePopover,
319
- spacing: 4,
320
- minWidth: "242px",
321
- children: [
322
- /* @__PURE__ */ jsxs(Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
323
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
324
- /* @__PURE__ */ jsx(PencilIcon, {}),
325
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
326
- id: "content-releases.header.actions.edit",
327
- defaultMessage: "Edit"
328
- }) })
329
- ] }),
330
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
331
- /* @__PURE__ */ jsx(TrashIcon, {}),
332
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
333
- id: "content-releases.header.actions.delete",
334
- defaultMessage: "Delete"
335
- }) })
336
- ] })
337
- ] }),
338
- /* @__PURE__ */ jsxs(
339
- ReleaseInfoWrapper,
340
- {
341
- direction: "column",
342
- justifyContent: "center",
343
- alignItems: "flex-start",
344
- gap: 1,
345
- padding: 5,
346
- children: [
347
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
348
- id: "content-releases.header.actions.created",
349
- defaultMessage: "Created"
350
- }) }),
351
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
352
- /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
353
- formatMessage(
354
- {
355
- id: "content-releases.header.actions.created.description",
356
- defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
357
- },
358
- { createdBy: getCreatedByUser(), hasCreatedByUser }
359
- )
360
- ] })
361
- ]
362
- }
363
- )
364
- ]
365
- }
366
- ),
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
+ ] }),
367
546
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
368
547
  id: "content-releases.header.actions.refresh",
369
548
  defaultMessage: "Refresh"
@@ -419,6 +598,9 @@ const ReleaseDetailsBody = () => {
419
598
  isError: isReleaseError,
420
599
  error: releaseError
421
600
  } = useGetReleaseQuery({ id: releaseId });
601
+ const {
602
+ allowedActions: { canUpdate }
603
+ } = useRBAC(PERMISSIONS);
422
604
  const release = releaseData?.data;
423
605
  const selectedGroupBy = query?.groupBy || "contentType";
424
606
  const {
@@ -632,7 +814,8 @@ const ReleaseDetailsBody = () => {
632
814
  {
633
815
  selected: type,
634
816
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
635
- name: `release-action-${id}-type`
817
+ name: `release-action-${id}-type`,
818
+ disabled: !canUpdate
636
819
  }
637
820
  ) }),
638
821
  !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -711,11 +894,18 @@ const ReleaseDetailsPage = () => {
711
894
  }
712
895
  );
713
896
  }
714
- 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") : "";
715
903
  const handleEditRelease = async (values) => {
716
904
  const response = await updateRelease({
717
905
  id: releaseId,
718
- name: values.name
906
+ name: values.name,
907
+ scheduledAt: values.scheduledAt,
908
+ timezone: values.timezone
719
909
  });
720
910
  if ("data" in response) {
721
911
  toggleNotification({
@@ -769,7 +959,14 @@ const ReleaseDetailsPage = () => {
769
959
  handleClose: toggleEditReleaseModal,
770
960
  handleSubmit: handleEditRelease,
771
961
  isLoading: isLoadingDetails || isSubmittingForm,
772
- initialValues: { name: title || "" }
962
+ initialValues: {
963
+ name: title || "",
964
+ scheduledAt,
965
+ date,
966
+ time,
967
+ isScheduled: Boolean(scheduledAt),
968
+ timezone
969
+ }
773
970
  }
774
971
  ),
775
972
  /* @__PURE__ */ jsx(
@@ -794,6 +991,7 @@ const LinkCard = styled(Link$2)`
794
991
  `;
795
992
  const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
796
993
  const { formatMessage } = useIntl();
994
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
797
995
  if (isError) {
798
996
  return /* @__PURE__ */ jsx(AnErrorOccurred, {});
799
997
  }
@@ -814,7 +1012,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
814
1012
  }
815
1013
  );
816
1014
  }
817
- 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(
818
1016
  Flex,
819
1017
  {
820
1018
  direction: "column",
@@ -829,7 +1027,10 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
829
1027
  gap: 2,
830
1028
  children: [
831
1029
  /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
832
- /* @__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(
833
1034
  {
834
1035
  id: "content-releases.page.Releases.release-item.entries",
835
1036
  defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
@@ -849,7 +1050,13 @@ const StyledAlert = styled(Alert)`
849
1050
  }
850
1051
  `;
851
1052
  const INITIAL_FORM_VALUES = {
852
- 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
853
1060
  };
854
1061
  const ReleasesPage = () => {
855
1062
  const tabRef = React.useRef(null);
@@ -895,8 +1102,8 @@ const ReleasesPage = () => {
895
1102
  if (isLoading) {
896
1103
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
897
1104
  }
898
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
899
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
1105
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
1106
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
900
1107
  const handleTabChange = (index) => {
901
1108
  setQuery({
902
1109
  ...query,
@@ -909,9 +1116,11 @@ const ReleasesPage = () => {
909
1116
  }
910
1117
  });
911
1118
  };
912
- const handleAddRelease = async (values) => {
1119
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
913
1120
  const response2 = await createRelease({
914
- name: values.name
1121
+ name,
1122
+ scheduledAt,
1123
+ timezone
915
1124
  });
916
1125
  if ("data" in response2) {
917
1126
  toggleNotification({
@@ -943,13 +1152,10 @@ const ReleasesPage = () => {
943
1152
  id: "content-releases.pages.Releases.title",
944
1153
  defaultMessage: "Releases"
945
1154
  }),
946
- subtitle: formatMessage(
947
- {
948
- id: "content-releases.pages.Releases.header-subtitle",
949
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
950
- },
951
- { number: totalReleases }
952
- ),
1155
+ subtitle: formatMessage({
1156
+ id: "content-releases.pages.Releases.header-subtitle",
1157
+ defaultMessage: "Create and manage content updates"
1158
+ }),
953
1159
  primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
954
1160
  Button,
955
1161
  {
@@ -965,7 +1171,7 @@ const ReleasesPage = () => {
965
1171
  }
966
1172
  ),
967
1173
  /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
968
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
1174
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
969
1175
  StyledAlert,
970
1176
  {
971
1177
  marginBottom: 6,
@@ -1003,10 +1209,15 @@ const ReleasesPage = () => {
1003
1209
  children: [
1004
1210
  /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
1005
1211
  /* @__PURE__ */ jsxs(Tabs, { children: [
1006
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
1007
- id: "content-releases.pages.Releases.tab.pending",
1008
- defaultMessage: "Pending"
1009
- }) }),
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
+ ) }),
1010
1221
  /* @__PURE__ */ jsx(Tab, { children: formatMessage({
1011
1222
  id: "content-releases.pages.Releases.tab.done",
1012
1223
  defaultMessage: "Done"
@@ -1035,7 +1246,7 @@ const ReleasesPage = () => {
1035
1246
  ]
1036
1247
  }
1037
1248
  ),
1038
- 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: [
1039
1250
  /* @__PURE__ */ jsx(
1040
1251
  PageSizeURLQuery,
1041
1252
  {
@@ -1051,7 +1262,7 @@ const ReleasesPage = () => {
1051
1262
  }
1052
1263
  }
1053
1264
  )
1054
- ] })
1265
+ ] }) : null
1055
1266
  ] }) }),
1056
1267
  releaseModalShown && /* @__PURE__ */ jsx(
1057
1268
  ReleaseModal,
@@ -1073,4 +1284,4 @@ const App = () => {
1073
1284
  export {
1074
1285
  App
1075
1286
  };
1076
- //# sourceMappingURL=App-WkwjSaDY.mjs.map
1287
+ //# sourceMappingURL=App-g3vtS2Wa.mjs.map