@strapi/content-releases 0.0.0-next.44f19b3d2f81d983c343a219aa2781ee0deecb5f → 0.0.0-next.4efa407e7fb70117eaf6eac9ed93e2e4cc0cbda5

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-5PsAyVt2.js → App-5G7GEzBM.js} +410 -177
  2. package/dist/_chunks/App-5G7GEzBM.js.map +1 -0
  3. package/dist/_chunks/{App-3ycH2d3s.mjs → App-WMxox0mk.mjs} +412 -180
  4. package/dist/_chunks/App-WMxox0mk.mjs.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
  9. package/dist/_chunks/{en-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-4gUWuCQV.mjs → index-BZ8RPGiV.mjs} +131 -27
  14. package/dist/_chunks/index-BZ8RPGiV.mjs.map +1 -0
  15. package/dist/_chunks/{index-D57Rztnc.js → index-pQ3hnZJy.js} +127 -23
  16. package/dist/_chunks/index-pQ3hnZJy.js.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 +12 -9
  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-BZ8RPGiV.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,16 +211,59 @@ 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
+ onClear: () => {
252
+ setFieldValue("timezone", "");
253
+ },
254
+ error: errors.timezone,
255
+ required: true,
256
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
257
+ }
258
+ );
259
+ };
87
260
  const ReleaseInfoWrapper = styled(Flex)`
88
261
  align-self: stretch;
89
262
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
263
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
264
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
265
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
266
+ const StyledMenuItem = styled(Menu.Item)`
97
267
  svg path {
98
268
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
269
  }
@@ -102,15 +272,15 @@ const StyledFlex = styled(Flex)`
102
272
  }
103
273
  `;
104
274
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
275
+ width: ${({ theme }) => theme.spaces[3]};
276
+ height: ${({ theme }) => theme.spaces[3]};
107
277
  path {
108
278
  fill: ${({ theme }) => theme.colors.neutral600};
109
279
  }
110
280
  `;
111
281
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
282
+ width: ${({ theme }) => theme.spaces[3]};
283
+ height: ${({ theme }) => theme.spaces[3]};
114
284
  path {
115
285
  fill: ${({ theme }) => theme.colors.danger600};
116
286
  }
@@ -118,24 +288,6 @@ const TrashIcon = styled(Trash)`
118
288
  const TypographyMaxWidth = styled(Typography)`
119
289
  max-width: 300px;
120
290
  `;
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
291
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
292
  const { formatMessage } = useIntl();
141
293
  const { validate } = unstable_useDocument();
@@ -184,10 +336,8 @@ const ReleaseDetailsLayout = ({
184
336
  toggleWarningSubmit,
185
337
  children
186
338
  }) => {
187
- const { formatMessage } = useIntl();
339
+ const { formatMessage, formatDate, formatTime } = useIntl();
188
340
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
341
  const {
192
342
  data,
193
343
  isLoading: isLoadingDetails,
@@ -203,13 +353,6 @@ const ReleaseDetailsLayout = ({
203
353
  const dispatch = useTypedDispatch();
204
354
  const { trackUsage } = useTracking();
205
355
  const release = data?.data;
206
- const handleTogglePopover = () => {
207
- setIsPopoverVisible((prev) => !prev);
208
- };
209
- const openReleaseModal = () => {
210
- toggleEditReleaseModal();
211
- handleTogglePopover();
212
- };
213
356
  const handlePublishRelease = async () => {
214
357
  const response = await publishRelease({ id: releaseId });
215
358
  if ("data" in response) {
@@ -238,13 +381,21 @@ const ReleaseDetailsLayout = ({
238
381
  });
239
382
  }
240
383
  };
241
- const openWarningConfirmDialog = () => {
242
- toggleWarningSubmit();
243
- handleTogglePopover();
244
- };
245
384
  const handleRefresh = () => {
246
385
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
247
386
  };
387
+ const getCreatedByUser = () => {
388
+ if (!release?.createdBy) {
389
+ return null;
390
+ }
391
+ if (release.createdBy.username) {
392
+ return release.createdBy.username;
393
+ }
394
+ if (release.createdBy.firstname) {
395
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
396
+ }
397
+ return release.createdBy.email;
398
+ };
248
399
  if (isLoadingDetails) {
249
400
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
250
401
  }
@@ -266,90 +417,139 @@ const ReleaseDetailsLayout = ({
266
417
  );
267
418
  }
268
419
  const totalEntries = release.actions.meta.count || 0;
269
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
420
+ const hasCreatedByUser = Boolean(getCreatedByUser());
421
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
422
+ const isScheduled = release.scheduledAt && release.timezone;
423
+ const numberOfEntriesText = formatMessage(
424
+ {
425
+ id: "content-releases.pages.Details.header-subtitle",
426
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
427
+ },
428
+ { number: totalEntries }
429
+ );
430
+ const scheduledText = isScheduled ? formatMessage(
431
+ {
432
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
433
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
434
+ },
435
+ {
436
+ date: formatDate(new Date(release.scheduledAt), {
437
+ weekday: "long",
438
+ day: "numeric",
439
+ month: "long",
440
+ year: "numeric",
441
+ timeZone: release.timezone
442
+ }),
443
+ time: formatTime(new Date(release.scheduledAt), {
444
+ timeZone: release.timezone,
445
+ hourCycle: "h23"
446
+ }),
447
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
448
+ }
449
+ ) : "";
270
450
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
271
451
  /* @__PURE__ */ jsx(
272
452
  HeaderLayout,
273
453
  {
274
454
  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
- ),
455
+ subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
282
456
  navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
283
457
  id: "global.back",
284
458
  defaultMessage: "Back"
285
459
  }) }),
286
460
  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
- ),
461
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
462
+ /* @__PURE__ */ jsx(
463
+ Menu.Trigger,
464
+ {
465
+ as: IconButton,
466
+ paddingLeft: 2,
467
+ paddingRight: 2,
468
+ "aria-label": formatMessage({
469
+ id: "content-releases.header.actions.open-release-actions",
470
+ defaultMessage: "Release edit and delete menu"
471
+ }),
472
+ icon: /* @__PURE__ */ jsx(More, {}),
473
+ variant: "tertiary"
474
+ }
475
+ ),
476
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
477
+ /* @__PURE__ */ jsxs(
478
+ Flex,
479
+ {
480
+ alignItems: "center",
481
+ justifyContent: "center",
482
+ direction: "column",
483
+ padding: 1,
484
+ width: "100%",
485
+ children: [
486
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(
487
+ Flex,
488
+ {
489
+ paddingTop: 2,
490
+ paddingBottom: 2,
491
+ alignItems: "center",
492
+ gap: 2,
493
+ hasRadius: true,
494
+ width: "100%",
495
+ children: [
496
+ /* @__PURE__ */ jsx(PencilIcon, {}),
497
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
498
+ id: "content-releases.header.actions.edit",
499
+ defaultMessage: "Edit"
500
+ }) })
501
+ ]
502
+ }
503
+ ) }),
504
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canDelete, onSelect: toggleWarningSubmit, children: /* @__PURE__ */ jsxs(
505
+ Flex,
506
+ {
507
+ paddingTop: 2,
508
+ paddingBottom: 2,
509
+ alignItems: "center",
510
+ gap: 2,
511
+ hasRadius: true,
512
+ width: "100%",
513
+ children: [
514
+ /* @__PURE__ */ jsx(TrashIcon, {}),
515
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
516
+ id: "content-releases.header.actions.delete",
517
+ defaultMessage: "Delete"
518
+ }) })
519
+ ]
520
+ }
521
+ ) })
522
+ ]
523
+ }
524
+ ),
525
+ /* @__PURE__ */ jsxs(
526
+ ReleaseInfoWrapper,
527
+ {
528
+ direction: "column",
529
+ justifyContent: "center",
530
+ alignItems: "flex-start",
531
+ gap: 1,
532
+ padding: 5,
533
+ children: [
534
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
535
+ id: "content-releases.header.actions.created",
536
+ defaultMessage: "Created"
537
+ }) }),
538
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
539
+ /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
540
+ formatMessage(
541
+ {
542
+ id: "content-releases.header.actions.created.description",
543
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
544
+ },
545
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
546
+ )
547
+ ] })
548
+ ]
549
+ }
550
+ )
551
+ ] })
552
+ ] }),
353
553
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
354
554
  id: "content-releases.header.actions.refresh",
355
555
  defaultMessage: "Refresh"
@@ -405,6 +605,9 @@ const ReleaseDetailsBody = () => {
405
605
  isError: isReleaseError,
406
606
  error: releaseError
407
607
  } = useGetReleaseQuery({ id: releaseId });
608
+ const {
609
+ allowedActions: { canUpdate }
610
+ } = useRBAC(PERMISSIONS);
408
611
  const release = releaseData?.data;
409
612
  const selectedGroupBy = query?.groupBy || "contentType";
410
613
  const {
@@ -511,7 +714,7 @@ const ReleaseDetailsBody = () => {
511
714
  SingleSelect,
512
715
  {
513
716
  "aria-label": formatMessage({
514
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
717
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
515
718
  defaultMessage: "Group by"
516
719
  }),
517
720
  customizeContent: (value) => formatMessage(
@@ -529,7 +732,7 @@ const ReleaseDetailsBody = () => {
529
732
  }
530
733
  ) }),
531
734
  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 }) }),
735
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
533
736
  /* @__PURE__ */ jsx(
534
737
  Table.Root,
535
738
  {
@@ -618,7 +821,8 @@ const ReleaseDetailsBody = () => {
618
821
  {
619
822
  selected: type,
620
823
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
621
- name: `release-action-${id}-type`
824
+ name: `release-action-${id}-type`,
825
+ disabled: !canUpdate
622
826
  }
623
827
  ) }),
624
828
  !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -697,11 +901,18 @@ const ReleaseDetailsPage = () => {
697
901
  }
698
902
  );
699
903
  }
700
- const title = isSuccessDetails && data?.data?.name || "";
904
+ const releaseData = isSuccessDetails && data?.data || null;
905
+ const title = releaseData?.name || "";
906
+ const timezone = releaseData?.timezone ?? null;
907
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
908
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
909
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
701
910
  const handleEditRelease = async (values) => {
702
911
  const response = await updateRelease({
703
912
  id: releaseId,
704
- name: values.name
913
+ name: values.name,
914
+ scheduledAt: values.scheduledAt,
915
+ timezone: values.timezone
705
916
  });
706
917
  if ("data" in response) {
707
918
  toggleNotification({
@@ -755,7 +966,14 @@ const ReleaseDetailsPage = () => {
755
966
  handleClose: toggleEditReleaseModal,
756
967
  handleSubmit: handleEditRelease,
757
968
  isLoading: isLoadingDetails || isSubmittingForm,
758
- initialValues: { name: title || "" }
969
+ initialValues: {
970
+ name: title || "",
971
+ scheduledAt,
972
+ date,
973
+ time,
974
+ isScheduled: Boolean(scheduledAt),
975
+ timezone
976
+ }
759
977
  }
760
978
  ),
761
979
  /* @__PURE__ */ jsx(
@@ -780,6 +998,7 @@ const LinkCard = styled(Link$2)`
780
998
  `;
781
999
  const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
782
1000
  const { formatMessage } = useIntl();
1001
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
783
1002
  if (isError) {
784
1003
  return /* @__PURE__ */ jsx(AnErrorOccurred, {});
785
1004
  }
@@ -800,7 +1019,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
800
1019
  }
801
1020
  );
802
1021
  }
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(
1022
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
804
1023
  Flex,
805
1024
  {
806
1025
  direction: "column",
@@ -815,7 +1034,10 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
815
1034
  gap: 2,
816
1035
  children: [
817
1036
  /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
818
- /* @__PURE__ */ jsx(Typography, { variant: "pi", children: formatMessage(
1037
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
1038
+ id: "content-releases.pages.Releases.not-scheduled",
1039
+ defaultMessage: "Not scheduled"
1040
+ }) : formatMessage(
819
1041
  {
820
1042
  id: "content-releases.page.Releases.release-item.entries",
821
1043
  defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
@@ -835,7 +1057,13 @@ const StyledAlert = styled(Alert)`
835
1057
  }
836
1058
  `;
837
1059
  const INITIAL_FORM_VALUES = {
838
- name: ""
1060
+ name: "",
1061
+ date: null,
1062
+ time: "",
1063
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
1064
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
1065
+ scheduledAt: null,
1066
+ timezone: null
839
1067
  };
840
1068
  const ReleasesPage = () => {
841
1069
  const tabRef = React.useRef(null);
@@ -881,8 +1109,8 @@ const ReleasesPage = () => {
881
1109
  if (isLoading) {
882
1110
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
883
1111
  }
884
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
885
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
1112
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
1113
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
886
1114
  const handleTabChange = (index) => {
887
1115
  setQuery({
888
1116
  ...query,
@@ -895,9 +1123,11 @@ const ReleasesPage = () => {
895
1123
  }
896
1124
  });
897
1125
  };
898
- const handleAddRelease = async (values) => {
1126
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
899
1127
  const response2 = await createRelease({
900
- name: values.name
1128
+ name,
1129
+ scheduledAt,
1130
+ timezone
901
1131
  });
902
1132
  if ("data" in response2) {
903
1133
  toggleNotification({
@@ -929,13 +1159,10 @@ const ReleasesPage = () => {
929
1159
  id: "content-releases.pages.Releases.title",
930
1160
  defaultMessage: "Releases"
931
1161
  }),
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
- ),
1162
+ subtitle: formatMessage({
1163
+ id: "content-releases.pages.Releases.header-subtitle",
1164
+ defaultMessage: "Create and manage content updates"
1165
+ }),
939
1166
  primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
940
1167
  Button,
941
1168
  {
@@ -951,7 +1178,7 @@ const ReleasesPage = () => {
951
1178
  }
952
1179
  ),
953
1180
  /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
954
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
1181
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
955
1182
  StyledAlert,
956
1183
  {
957
1184
  marginBottom: 6,
@@ -989,10 +1216,15 @@ const ReleasesPage = () => {
989
1216
  children: [
990
1217
  /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
991
1218
  /* @__PURE__ */ jsxs(Tabs, { children: [
992
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
993
- id: "content-releases.pages.Releases.tab.pending",
994
- defaultMessage: "Pending"
995
- }) }),
1219
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
1220
+ {
1221
+ id: "content-releases.pages.Releases.tab.pending",
1222
+ defaultMessage: "Pending ({count})"
1223
+ },
1224
+ {
1225
+ count: totalPendingReleases
1226
+ }
1227
+ ) }),
996
1228
  /* @__PURE__ */ jsx(Tab, { children: formatMessage({
997
1229
  id: "content-releases.pages.Releases.tab.done",
998
1230
  defaultMessage: "Done"
@@ -1021,7 +1253,7 @@ const ReleasesPage = () => {
1021
1253
  ]
1022
1254
  }
1023
1255
  ),
1024
- totalReleases > 0 && /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1256
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1025
1257
  /* @__PURE__ */ jsx(
1026
1258
  PageSizeURLQuery,
1027
1259
  {
@@ -1037,7 +1269,7 @@ const ReleasesPage = () => {
1037
1269
  }
1038
1270
  }
1039
1271
  )
1040
- ] })
1272
+ ] }) : null
1041
1273
  ] }) }),
1042
1274
  releaseModalShown && /* @__PURE__ */ jsx(
1043
1275
  ReleaseModal,
@@ -1059,4 +1291,4 @@ const App = () => {
1059
1291
  export {
1060
1292
  App
1061
1293
  };
1062
- //# sourceMappingURL=App-3ycH2d3s.mjs.map
1294
+ //# sourceMappingURL=App-WMxox0mk.mjs.map