@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 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-L1jSxCiL.mjs → App-g3vtS2Wa.mjs} +524 -252
  2. package/dist/_chunks/App-g3vtS2Wa.mjs.map +1 -0
  3. package/dist/_chunks/{App-_20W9dYa.js → App-lnXbSPgp.js} +520 -247
  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-MyLPoISH.mjs → en-WuuhP6Bn.mjs} +21 -6
  10. package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
  11. package/dist/_chunks/{en-gYDqKYFd.js → en-gcJJ5htG.js} +21 -6
  12. package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
  13. package/dist/_chunks/{index-KJa1Rb5F.js → index-ItlgrLcr.js} +158 -32
  14. package/dist/_chunks/index-ItlgrLcr.js.map +1 -0
  15. package/dist/_chunks/{index-c4zRX_sg.mjs → index-uGex_IIQ.mjs} +163 -37
  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 +887 -398
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +886 -398
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -11
  24. package/dist/_chunks/App-L1jSxCiL.mjs.map +0 -1
  25. package/dist/_chunks/App-_20W9dYa.js.map +0 -1
  26. package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
  27. package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
  28. package/dist/_chunks/index-KJa1Rb5F.js.map +0 -1
  29. package/dist/_chunks/index-c4zRX_sg.mjs.map +0 -1
@@ -3,14 +3,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const helperPlugin = require("@strapi/helper-plugin");
5
5
  const reactRouterDom = require("react-router-dom");
6
- const index = require("./index-KJa1Rb5F.js");
6
+ const index = require("./index-ItlgrLcr.js");
7
7
  const React = require("react");
8
8
  const strapiAdmin = require("@strapi/admin/strapi-admin");
9
9
  const designSystem = require("@strapi/design-system");
10
10
  const v2 = require("@strapi/design-system/v2");
11
11
  const icons = require("@strapi/icons");
12
+ const format = require("date-fns/format");
13
+ const dateFnsTz = require("date-fns-tz");
12
14
  const reactIntl = require("react-intl");
13
15
  const styled = require("styled-components");
16
+ const dateFns = require("date-fns");
14
17
  const formik = require("formik");
15
18
  const yup = require("yup");
16
19
  require("@reduxjs/toolkit/query");
@@ -37,10 +40,28 @@ function _interopNamespace(e) {
37
40
  return Object.freeze(n);
38
41
  }
39
42
  const React__namespace = /* @__PURE__ */ _interopNamespace(React);
43
+ const format__default = /* @__PURE__ */ _interopDefault(format);
40
44
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
41
45
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
42
46
  const RELEASE_SCHEMA = yup__namespace.object().shape({
43
- name: yup__namespace.string().trim().required()
47
+ name: yup__namespace.string().trim().required(),
48
+ scheduledAt: yup__namespace.string().nullable(),
49
+ isScheduled: yup__namespace.boolean().optional(),
50
+ time: yup__namespace.string().when("isScheduled", {
51
+ is: true,
52
+ then: yup__namespace.string().trim().required(),
53
+ otherwise: yup__namespace.string().nullable()
54
+ }),
55
+ timezone: yup__namespace.string().when("isScheduled", {
56
+ is: true,
57
+ then: yup__namespace.string().required().nullable(),
58
+ otherwise: yup__namespace.string().nullable()
59
+ }),
60
+ date: yup__namespace.string().when("isScheduled", {
61
+ is: true,
62
+ then: yup__namespace.string().required().nullable(),
63
+ otherwise: yup__namespace.string().nullable()
64
+ })
44
65
  }).required().noUnknown();
45
66
  const ReleaseModal = ({
46
67
  handleClose,
@@ -51,6 +72,24 @@ const ReleaseModal = ({
51
72
  const { formatMessage } = reactIntl.useIntl();
52
73
  const { pathname } = reactRouterDom.useLocation();
53
74
  const isCreatingRelease = pathname === `/plugins/${index.pluginId}`;
75
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
76
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
77
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
78
+ );
79
+ const getScheduledTimestamp = (values) => {
80
+ const { date, time, timezone } = values;
81
+ if (!date || !time || !timezone)
82
+ return null;
83
+ const formattedDate = dateFns.parse(time, "HH:mm", new Date(date));
84
+ const timezoneWithoutOffset = timezone.split("_")[1];
85
+ return dateFnsTz.zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
86
+ };
87
+ const getTimezoneWithOffset = () => {
88
+ const currentTimezone = timezoneList.find(
89
+ (timezone) => timezone.value.split("_")[1] === initialValues.timezone
90
+ );
91
+ return currentTimezone?.value || systemTimezone.value;
92
+ };
54
93
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
55
94
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
56
95
  {
@@ -62,45 +101,134 @@ const ReleaseModal = ({
62
101
  /* @__PURE__ */ jsxRuntime.jsx(
63
102
  formik.Formik,
64
103
  {
65
- validateOnChange: false,
66
- onSubmit: handleSubmit,
67
- initialValues,
104
+ onSubmit: (values) => {
105
+ handleSubmit({
106
+ ...values,
107
+ timezone: values.timezone ? values.timezone.split("_")[1] : null,
108
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
109
+ });
110
+ },
111
+ initialValues: {
112
+ ...initialValues,
113
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
114
+ },
68
115
  validationSchema: RELEASE_SCHEMA,
69
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
70
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
71
- designSystem.TextInput,
72
- {
73
- label: formatMessage({
74
- id: "content-releases.modal.form.input.label.release-name",
75
- defaultMessage: "Name"
76
- }),
77
- name: "name",
78
- value: values.name,
79
- error: errors.name,
80
- onChange: handleChange,
81
- required: true
82
- }
83
- ) }),
116
+ validateOnChange: false,
117
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx(
120
+ designSystem.TextInput,
121
+ {
122
+ label: formatMessage({
123
+ id: "content-releases.modal.form.input.label.release-name",
124
+ defaultMessage: "Name"
125
+ }),
126
+ name: "name",
127
+ value: values.name,
128
+ error: errors.name,
129
+ onChange: handleChange,
130
+ required: true
131
+ }
132
+ ),
133
+ IsSchedulingEnabled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
134
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "max-content", children: /* @__PURE__ */ jsxRuntime.jsx(
135
+ designSystem.Checkbox,
136
+ {
137
+ name: "isScheduled",
138
+ value: values.isScheduled,
139
+ onChange: (event) => {
140
+ setFieldValue("isScheduled", event.target.checked);
141
+ if (!event.target.checked) {
142
+ setFieldValue("date", null);
143
+ setFieldValue("time", "");
144
+ setFieldValue("timezone", null);
145
+ } else {
146
+ setFieldValue("date", initialValues.date);
147
+ setFieldValue("time", initialValues.time);
148
+ setFieldValue(
149
+ "timezone",
150
+ initialValues.timezone ?? systemTimezone?.value
151
+ );
152
+ }
153
+ },
154
+ children: /* @__PURE__ */ jsxRuntime.jsx(
155
+ designSystem.Typography,
156
+ {
157
+ textColor: values.isScheduled ? "primary600" : "neutral800",
158
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
159
+ children: formatMessage({
160
+ id: "modal.form.input.label.schedule-release",
161
+ defaultMessage: "Schedule release"
162
+ })
163
+ }
164
+ )
165
+ }
166
+ ) }),
167
+ values.isScheduled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
168
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "start", children: [
169
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
170
+ designSystem.DatePicker,
171
+ {
172
+ label: formatMessage({
173
+ id: "content-releases.modal.form.input.label.date",
174
+ defaultMessage: "Date"
175
+ }),
176
+ name: "date",
177
+ error: errors.date,
178
+ onChange: (date) => {
179
+ const isoFormatDate = date ? dateFns.formatISO(date, { representation: "date" }) : null;
180
+ setFieldValue("date", isoFormatDate);
181
+ },
182
+ clearLabel: formatMessage({
183
+ id: "content-releases.modal.form.input.clearLabel",
184
+ defaultMessage: "Clear"
185
+ }),
186
+ onClear: () => {
187
+ setFieldValue("date", null);
188
+ },
189
+ selectedDate: values.date || void 0,
190
+ required: true
191
+ }
192
+ ) }),
193
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
194
+ designSystem.TimePicker,
195
+ {
196
+ label: formatMessage({
197
+ id: "content-releases.modal.form.input.label.time",
198
+ defaultMessage: "Time"
199
+ }),
200
+ name: "time",
201
+ error: errors.time,
202
+ onChange: (time) => {
203
+ setFieldValue("time", time);
204
+ },
205
+ clearLabel: formatMessage({
206
+ id: "content-releases.modal.form.input.clearLabel",
207
+ defaultMessage: "Clear"
208
+ }),
209
+ onClear: () => {
210
+ setFieldValue("time", "");
211
+ },
212
+ value: values.time || void 0,
213
+ required: true
214
+ }
215
+ ) })
216
+ ] }),
217
+ /* @__PURE__ */ jsxRuntime.jsx(TimezoneComponent, { timezoneOptions: timezoneList })
218
+ ] })
219
+ ] })
220
+ ] }) }),
84
221
  /* @__PURE__ */ jsxRuntime.jsx(
85
222
  designSystem.ModalFooter,
86
223
  {
87
224
  startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
88
- endActions: /* @__PURE__ */ jsxRuntime.jsx(
89
- designSystem.Button,
225
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
90
226
  {
91
- name: "submit",
92
- loading: isLoading,
93
- disabled: !values.name || values.name === initialValues.name,
94
- type: "submit",
95
- children: formatMessage(
96
- {
97
- id: "content-releases.modal.form.button.submit",
98
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
99
- },
100
- { isCreatingRelease }
101
- )
102
- }
103
- )
227
+ id: "content-releases.modal.form.button.submit",
228
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
229
+ },
230
+ { isCreatingRelease }
231
+ ) })
104
232
  }
105
233
  )
106
234
  ] })
@@ -108,33 +236,83 @@ const ReleaseModal = ({
108
236
  )
109
237
  ] });
110
238
  };
239
+ const getTimezones = (selectedDate) => {
240
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
241
+ const utcOffset = index.getTimezoneOffset(timezone, selectedDate);
242
+ return { offset: utcOffset, value: `${utcOffset}_${timezone}` };
243
+ });
244
+ const systemTimezone = timezoneList.find(
245
+ (timezone) => timezone.value.split("_")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
246
+ );
247
+ return { timezoneList, systemTimezone };
248
+ };
249
+ const TimezoneComponent = ({ timezoneOptions }) => {
250
+ const { values, errors, setFieldValue } = formik.useFormikContext();
251
+ const { formatMessage } = reactIntl.useIntl();
252
+ const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
253
+ React__namespace.useEffect(() => {
254
+ if (values.date) {
255
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
256
+ setTimezoneList(timezoneList2);
257
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("_")[1] === values.timezone.split("_")[1]);
258
+ if (updatedTimezone) {
259
+ setFieldValue("timezone", updatedTimezone.value);
260
+ }
261
+ }
262
+ }, [setFieldValue, values.date, values.timezone]);
263
+ return /* @__PURE__ */ jsxRuntime.jsx(
264
+ designSystem.Combobox,
265
+ {
266
+ label: formatMessage({
267
+ id: "content-releases.modal.form.input.label.timezone",
268
+ defaultMessage: "Timezone"
269
+ }),
270
+ name: "timezone",
271
+ value: values.timezone || void 0,
272
+ textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
273
+ onChange: (timezone) => {
274
+ setFieldValue("timezone", timezone);
275
+ },
276
+ onTextValueChange: (timezone) => {
277
+ setFieldValue("timezone", timezone);
278
+ },
279
+ onClear: () => {
280
+ setFieldValue("timezone", "");
281
+ },
282
+ error: errors.timezone,
283
+ required: true,
284
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
285
+ }
286
+ );
287
+ };
111
288
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
112
289
  align-self: stretch;
113
290
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
114
291
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
115
292
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
116
293
  `;
117
- const StyledFlex = styled__default.default(designSystem.Flex)`
118
- align-self: stretch;
119
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
120
-
294
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
121
295
  svg path {
122
296
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
123
297
  }
124
298
  span {
125
299
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
126
300
  }
301
+
302
+ &:hover {
303
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
304
+ }
127
305
  `;
128
306
  const PencilIcon = styled__default.default(icons.Pencil)`
129
- width: ${({ theme }) => theme.spaces[4]};
130
- height: ${({ theme }) => theme.spaces[4]};
307
+ width: ${({ theme }) => theme.spaces[3]};
308
+ height: ${({ theme }) => theme.spaces[3]};
131
309
  path {
132
310
  fill: ${({ theme }) => theme.colors.neutral600};
133
311
  }
134
312
  `;
135
313
  const TrashIcon = styled__default.default(icons.Trash)`
136
- width: ${({ theme }) => theme.spaces[4]};
137
- height: ${({ theme }) => theme.spaces[4]};
314
+ width: ${({ theme }) => theme.spaces[3]};
315
+ height: ${({ theme }) => theme.spaces[3]};
138
316
  path {
139
317
  fill: ${({ theme }) => theme.colors.danger600};
140
318
  }
@@ -142,24 +320,6 @@ const TrashIcon = styled__default.default(icons.Trash)`
142
320
  const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
143
321
  max-width: 300px;
144
322
  `;
145
- const PopoverButton = ({ onClick, disabled, children }) => {
146
- return /* @__PURE__ */ jsxRuntime.jsx(
147
- StyledFlex,
148
- {
149
- paddingTop: 2,
150
- paddingBottom: 2,
151
- paddingLeft: 4,
152
- paddingRight: 4,
153
- alignItems: "center",
154
- gap: 2,
155
- as: "button",
156
- hasRadius: true,
157
- onClick,
158
- disabled,
159
- children
160
- }
161
- );
162
- };
163
323
  const EntryValidationText = ({ action, schema, components, entry }) => {
164
324
  const { formatMessage } = reactIntl.useIntl();
165
325
  const { validate } = strapiAdmin.unstable_useDocument();
@@ -208,10 +368,8 @@ const ReleaseDetailsLayout = ({
208
368
  toggleWarningSubmit,
209
369
  children
210
370
  }) => {
211
- const { formatMessage } = reactIntl.useIntl();
371
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
212
372
  const { releaseId } = reactRouterDom.useParams();
213
- const [isPopoverVisible, setIsPopoverVisible] = React__namespace.useState(false);
214
- const moreButtonRef = React__namespace.useRef(null);
215
373
  const {
216
374
  data,
217
375
  isLoading: isLoadingDetails,
@@ -225,14 +383,8 @@ const ReleaseDetailsLayout = ({
225
383
  allowedActions: { canUpdate, canDelete }
226
384
  } = helperPlugin.useRBAC(index.PERMISSIONS);
227
385
  const dispatch = index.useTypedDispatch();
386
+ const { trackUsage } = helperPlugin.useTracking();
228
387
  const release = data?.data;
229
- const handleTogglePopover = () => {
230
- setIsPopoverVisible((prev) => !prev);
231
- };
232
- const openReleaseModal = () => {
233
- toggleEditReleaseModal();
234
- handleTogglePopover();
235
- };
236
388
  const handlePublishRelease = async () => {
237
389
  const response = await publishRelease({ id: releaseId });
238
390
  if ("data" in response) {
@@ -243,6 +395,12 @@ const ReleaseDetailsLayout = ({
243
395
  defaultMessage: "Release was published successfully."
244
396
  })
245
397
  });
398
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
399
+ trackUsage("didPublishRelease", {
400
+ totalEntries: totalEntries2,
401
+ totalPublishedEntries,
402
+ totalUnpublishedEntries
403
+ });
246
404
  } else if (index.isAxiosError(response.error)) {
247
405
  toggleNotification({
248
406
  type: "warning",
@@ -255,13 +413,21 @@ const ReleaseDetailsLayout = ({
255
413
  });
256
414
  }
257
415
  };
258
- const openWarningConfirmDialog = () => {
259
- toggleWarningSubmit();
260
- handleTogglePopover();
261
- };
262
416
  const handleRefresh = () => {
263
417
  dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
264
418
  };
419
+ const getCreatedByUser = () => {
420
+ if (!release?.createdBy) {
421
+ return null;
422
+ }
423
+ if (release.createdBy.username) {
424
+ return release.createdBy.username;
425
+ }
426
+ if (release.createdBy.firstname) {
427
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
428
+ }
429
+ return release.createdBy.email;
430
+ };
265
431
  if (isLoadingDetails) {
266
432
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
267
433
  }
@@ -283,90 +449,125 @@ const ReleaseDetailsLayout = ({
283
449
  );
284
450
  }
285
451
  const totalEntries = release.actions.meta.count || 0;
286
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
452
+ const hasCreatedByUser = Boolean(getCreatedByUser());
453
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
454
+ const isScheduled = release.scheduledAt && release.timezone;
455
+ const numberOfEntriesText = formatMessage(
456
+ {
457
+ id: "content-releases.pages.Details.header-subtitle",
458
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
459
+ },
460
+ { number: totalEntries }
461
+ );
462
+ const scheduledText = isScheduled ? formatMessage(
463
+ {
464
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
465
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
466
+ },
467
+ {
468
+ date: formatDate(new Date(release.scheduledAt), {
469
+ weekday: "long",
470
+ day: "numeric",
471
+ month: "long",
472
+ year: "numeric",
473
+ timeZone: release.timezone
474
+ }),
475
+ time: formatTime(new Date(release.scheduledAt), {
476
+ timeZone: release.timezone,
477
+ hourCycle: "h23"
478
+ }),
479
+ offset: index.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
480
+ }
481
+ ) : "";
287
482
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
288
483
  /* @__PURE__ */ jsxRuntime.jsx(
289
484
  designSystem.HeaderLayout,
290
485
  {
291
486
  title: release.name,
292
- subtitle: formatMessage(
293
- {
294
- id: "content-releases.pages.Details.header-subtitle",
295
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
296
- },
297
- { number: totalEntries }
298
- ),
487
+ subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
299
488
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
300
489
  id: "global.back",
301
490
  defaultMessage: "Back"
302
491
  }) }),
303
492
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
304
- /* @__PURE__ */ jsxRuntime.jsx(
305
- designSystem.IconButton,
306
- {
307
- label: formatMessage({
308
- id: "content-releases.header.actions.open-release-actions",
309
- defaultMessage: "Release actions"
310
- }),
311
- ref: moreButtonRef,
312
- onClick: handleTogglePopover,
313
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {})
314
- }
315
- ),
316
- isPopoverVisible && /* @__PURE__ */ jsxRuntime.jsxs(
317
- designSystem.Popover,
318
- {
319
- source: moreButtonRef,
320
- placement: "bottom-end",
321
- onDismiss: handleTogglePopover,
322
- spacing: 4,
323
- minWidth: "242px",
324
- children: [
325
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
326
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
327
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
328
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
329
- id: "content-releases.header.actions.edit",
330
- defaultMessage: "Edit"
331
- }) })
332
- ] }),
333
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
334
- /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
335
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
336
- id: "content-releases.header.actions.delete",
337
- defaultMessage: "Delete"
338
- }) })
339
- ] })
340
- ] }),
341
- /* @__PURE__ */ jsxRuntime.jsxs(
342
- ReleaseInfoWrapper,
343
- {
344
- direction: "column",
345
- justifyContent: "center",
346
- alignItems: "flex-start",
347
- gap: 1,
348
- padding: 5,
349
- children: [
350
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
351
- id: "content-releases.header.actions.created",
352
- defaultMessage: "Created"
353
- }) }),
354
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
355
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
356
- formatMessage(
357
- {
358
- id: "content-releases.header.actions.created.description",
359
- defaultMessage: " by {createdBy}"
360
- },
361
- { createdBy }
362
- )
363
- ] })
364
- ]
365
- }
366
- )
367
- ]
368
- }
369
- ),
493
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Root, { children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx(
495
+ v2.Menu.Trigger,
496
+ {
497
+ as: designSystem.IconButton,
498
+ paddingLeft: 2,
499
+ paddingRight: 2,
500
+ "aria-label": formatMessage({
501
+ id: "content-releases.header.actions.open-release-actions",
502
+ defaultMessage: "Release edit and delete menu"
503
+ }),
504
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {}),
505
+ variant: "tertiary"
506
+ }
507
+ ),
508
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
509
+ /* @__PURE__ */ jsxRuntime.jsxs(
510
+ designSystem.Flex,
511
+ {
512
+ alignItems: "center",
513
+ justifyContent: "center",
514
+ direction: "column",
515
+ padding: 1,
516
+ width: "100%",
517
+ children: [
518
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
519
+ /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
520
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
521
+ id: "content-releases.header.actions.edit",
522
+ defaultMessage: "Edit"
523
+ }) })
524
+ ] }) }),
525
+ /* @__PURE__ */ jsxRuntime.jsx(
526
+ StyledMenuItem,
527
+ {
528
+ disabled: !canDelete,
529
+ onSelect: toggleWarningSubmit,
530
+ variant: "danger",
531
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
532
+ /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
533
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
534
+ id: "content-releases.header.actions.delete",
535
+ defaultMessage: "Delete"
536
+ }) })
537
+ ] })
538
+ }
539
+ )
540
+ ]
541
+ }
542
+ ),
543
+ /* @__PURE__ */ jsxRuntime.jsxs(
544
+ ReleaseInfoWrapper,
545
+ {
546
+ direction: "column",
547
+ justifyContent: "center",
548
+ alignItems: "flex-start",
549
+ gap: 1,
550
+ padding: 5,
551
+ children: [
552
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
553
+ id: "content-releases.header.actions.created",
554
+ defaultMessage: "Created"
555
+ }) }),
556
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
557
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
558
+ formatMessage(
559
+ {
560
+ id: "content-releases.header.actions.created.description",
561
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
562
+ },
563
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
564
+ )
565
+ ] })
566
+ ]
567
+ }
568
+ )
569
+ ] })
570
+ ] }),
370
571
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
371
572
  id: "content-releases.header.actions.refresh",
372
573
  defaultMessage: "Refresh"
@@ -422,6 +623,9 @@ const ReleaseDetailsBody = () => {
422
623
  isError: isReleaseError,
423
624
  error: releaseError
424
625
  } = index.useGetReleaseQuery({ id: releaseId });
626
+ const {
627
+ allowedActions: { canUpdate }
628
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
425
629
  const release = releaseData?.data;
426
630
  const selectedGroupBy = query?.groupBy || "contentType";
427
631
  const {
@@ -435,7 +639,7 @@ const ReleaseDetailsBody = () => {
435
639
  releaseId
436
640
  });
437
641
  const [updateReleaseAction] = index.useUpdateReleaseActionMutation();
438
- const handleChangeType = async (e, actionId) => {
642
+ const handleChangeType = async (e, actionId, actionPath) => {
439
643
  const response = await updateReleaseAction({
440
644
  params: {
441
645
  releaseId,
@@ -443,7 +647,11 @@ const ReleaseDetailsBody = () => {
443
647
  },
444
648
  body: {
445
649
  type: e.target.value
446
- }
650
+ },
651
+ query,
652
+ // We are passing the query params to make optimistic updates
653
+ actionPath
654
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
447
655
  });
448
656
  if ("error" in response) {
449
657
  if (index.isAxiosError(response.error)) {
@@ -524,7 +732,7 @@ const ReleaseDetailsBody = () => {
524
732
  designSystem.SingleSelect,
525
733
  {
526
734
  "aria-label": formatMessage({
527
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
735
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
528
736
  defaultMessage: "Group by"
529
737
  }),
530
738
  customizeContent: (value) => formatMessage(
@@ -542,7 +750,7 @@ const ReleaseDetailsBody = () => {
542
750
  }
543
751
  ) }),
544
752
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
545
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
753
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
546
754
  /* @__PURE__ */ jsxRuntime.jsx(
547
755
  helperPlugin.Table.Root,
548
756
  {
@@ -612,56 +820,59 @@ const ReleaseDetailsBody = () => {
612
820
  )
613
821
  ] }),
614
822
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.LoadingBody, {}),
615
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(({ id, contentType, locale, type, entry }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
616
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
617
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
618
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
619
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
620
- {
621
- id: "content-releases.page.ReleaseDetails.table.action-published",
622
- defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
623
- },
624
- {
625
- isPublish: type === "publish",
626
- b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
627
- }
628
- ) }) : /* @__PURE__ */ jsxRuntime.jsx(
629
- index.ReleaseActionOptions,
630
- {
631
- selected: type,
632
- handleChange: (e) => handleChangeType(e, id),
633
- name: `release-action-${id}-type`
634
- }
635
- ) }),
636
- !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
637
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(
638
- EntryValidationText,
823
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(
824
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
825
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
826
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
827
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
828
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
829
+ {
830
+ id: "content-releases.page.ReleaseDetails.table.action-published",
831
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
832
+ },
833
+ {
834
+ isPublish: type === "publish",
835
+ b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
836
+ }
837
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
838
+ index.ReleaseActionOptions,
639
839
  {
640
- action: type,
641
- schema: contentTypes?.[contentType.uid],
642
- components,
643
- entry
840
+ selected: type,
841
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
842
+ name: `release-action-${id}-type`,
843
+ disabled: !canUpdate
644
844
  }
645
845
  ) }),
646
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(index.ReleaseActionMenu.Root, { children: [
647
- /* @__PURE__ */ jsxRuntime.jsx(
648
- index.ReleaseActionMenu.ReleaseActionEntryLinkItem,
846
+ !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
847
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(
848
+ EntryValidationText,
649
849
  {
650
- contentTypeUid: contentType.uid,
651
- entryId: entry.id,
652
- locale: locale?.code
850
+ action: type,
851
+ schema: contentTypes?.[contentType.uid],
852
+ components,
853
+ entry
653
854
  }
654
- ),
655
- /* @__PURE__ */ jsxRuntime.jsx(
656
- index.ReleaseActionMenu.DeleteReleaseActionItem,
657
- {
658
- releaseId: release.id,
659
- actionId: id
660
- }
661
- )
662
- ] }) }) })
663
- ] })
664
- ] }, id)) })
855
+ ) }),
856
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(index.ReleaseActionMenu.Root, { children: [
857
+ /* @__PURE__ */ jsxRuntime.jsx(
858
+ index.ReleaseActionMenu.ReleaseActionEntryLinkItem,
859
+ {
860
+ contentTypeUid: contentType.uid,
861
+ entryId: entry.id,
862
+ locale: locale?.code
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsxRuntime.jsx(
866
+ index.ReleaseActionMenu.DeleteReleaseActionItem,
867
+ {
868
+ releaseId: release.id,
869
+ actionId: id
870
+ }
871
+ )
872
+ ] }) }) })
873
+ ] })
874
+ ] }, id)
875
+ ) })
665
876
  ] })
666
877
  }
667
878
  )
@@ -708,11 +919,18 @@ const ReleaseDetailsPage = () => {
708
919
  }
709
920
  );
710
921
  }
711
- const title = isSuccessDetails && data?.data?.name || "";
922
+ const releaseData = isSuccessDetails && data?.data || null;
923
+ const title = releaseData?.name || "";
924
+ const timezone = releaseData?.timezone ?? null;
925
+ const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
926
+ const date = scheduledAt ? new Date(format__default.default(scheduledAt, "yyyy-MM-dd")) : null;
927
+ const time = scheduledAt ? format__default.default(scheduledAt, "HH:mm") : "";
712
928
  const handleEditRelease = async (values) => {
713
929
  const response = await updateRelease({
714
930
  id: releaseId,
715
- name: values.name
931
+ name: values.name,
932
+ scheduledAt: values.scheduledAt,
933
+ timezone: values.timezone
716
934
  });
717
935
  if ("data" in response) {
718
936
  toggleNotification({
@@ -766,7 +984,14 @@ const ReleaseDetailsPage = () => {
766
984
  handleClose: toggleEditReleaseModal,
767
985
  handleSubmit: handleEditRelease,
768
986
  isLoading: isLoadingDetails || isSubmittingForm,
769
- initialValues: { name: title || "" }
987
+ initialValues: {
988
+ name: title || "",
989
+ scheduledAt,
990
+ date,
991
+ time,
992
+ isScheduled: Boolean(scheduledAt),
993
+ timezone
994
+ }
770
995
  }
771
996
  ),
772
997
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -786,42 +1011,12 @@ const ReleaseDetailsPage = () => {
786
1011
  }
787
1012
  );
788
1013
  };
789
- const ReleasesLayout = ({
790
- isLoading,
791
- totalReleases,
792
- onClickAddRelease,
793
- children
794
- }) => {
795
- const { formatMessage } = reactIntl.useIntl();
796
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
797
- /* @__PURE__ */ jsxRuntime.jsx(
798
- designSystem.HeaderLayout,
799
- {
800
- title: formatMessage({
801
- id: "content-releases.pages.Releases.title",
802
- defaultMessage: "Releases"
803
- }),
804
- subtitle: !isLoading && formatMessage(
805
- {
806
- id: "content-releases.pages.Releases.header-subtitle",
807
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
808
- },
809
- { number: totalReleases }
810
- ),
811
- primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}), onClick: onClickAddRelease, children: formatMessage({
812
- id: "content-releases.header.actions.add-release",
813
- defaultMessage: "New release"
814
- }) }) })
815
- }
816
- ),
817
- children
818
- ] });
819
- };
820
1014
  const LinkCard = styled__default.default(v2.Link)`
821
1015
  display: block;
822
1016
  `;
823
1017
  const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
824
1018
  const { formatMessage } = reactIntl.useIntl();
1019
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
825
1020
  if (isError) {
826
1021
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
827
1022
  }
@@ -842,7 +1037,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
842
1037
  }
843
1038
  );
844
1039
  }
845
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid, { gap: 4, children: releases.map(({ id, name, actions }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxRuntime.jsxs(
1040
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxRuntime.jsxs(
846
1041
  designSystem.Flex,
847
1042
  {
848
1043
  direction: "column",
@@ -857,7 +1052,10 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
857
1052
  gap: 2,
858
1053
  children: [
859
1054
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
860
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: formatMessage(
1055
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
1056
+ id: "content-releases.pages.Releases.not-scheduled",
1057
+ defaultMessage: "Not scheduled"
1058
+ }) : formatMessage(
861
1059
  {
862
1060
  id: "content-releases.page.Releases.release-item.entries",
863
1061
  defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
@@ -868,8 +1066,22 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
868
1066
  }
869
1067
  ) }) }, id)) });
870
1068
  };
1069
+ const StyledAlert = styled__default.default(designSystem.Alert)`
1070
+ button {
1071
+ display: none;
1072
+ }
1073
+ p + div {
1074
+ margin-left: auto;
1075
+ }
1076
+ `;
871
1077
  const INITIAL_FORM_VALUES = {
872
- name: ""
1078
+ name: "",
1079
+ date: null,
1080
+ time: "",
1081
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
1082
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
1083
+ scheduledAt: null,
1084
+ timezone: null
873
1085
  };
874
1086
  const ReleasesPage = () => {
875
1087
  const tabRef = React__namespace.useRef(null);
@@ -882,6 +1094,9 @@ const ReleasesPage = () => {
882
1094
  const [{ query }, setQuery] = helperPlugin.useQueryParams();
883
1095
  const response = index.useGetReleasesQuery(query);
884
1096
  const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
1097
+ const { getFeature } = strapiAdmin.useLicenseLimits();
1098
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
1099
+ const { trackUsage } = helperPlugin.useTracking();
885
1100
  const { isLoading, isSuccess, isError } = response;
886
1101
  const activeTab = response?.currentData?.meta?.activeTab || "pending";
887
1102
  const activeTabIndex = ["pending", "done"].indexOf(activeTab);
@@ -910,9 +1125,10 @@ const ReleasesPage = () => {
910
1125
  setReleaseModalShown((prev) => !prev);
911
1126
  };
912
1127
  if (isLoading) {
913
- return /* @__PURE__ */ jsxRuntime.jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) }) });
1128
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
914
1129
  }
915
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
1130
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
1131
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
916
1132
  const handleTabChange = (index2) => {
917
1133
  setQuery({
918
1134
  ...query,
@@ -925,9 +1141,11 @@ const ReleasesPage = () => {
925
1141
  }
926
1142
  });
927
1143
  };
928
- const handleAddRelease = async (values) => {
1144
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
929
1145
  const response2 = await createRelease({
930
- name: values.name
1146
+ name,
1147
+ scheduledAt,
1148
+ timezone
931
1149
  });
932
1150
  if ("data" in response2) {
933
1151
  toggleNotification({
@@ -937,6 +1155,7 @@ const ReleasesPage = () => {
937
1155
  defaultMessage: "Release created."
938
1156
  })
939
1157
  });
1158
+ trackUsage("didCreateRelease");
940
1159
  push(`/plugins/content-releases/${response2.data.data.id}`);
941
1160
  } else if (index.isAxiosError(response2.error)) {
942
1161
  toggleNotification({
@@ -950,8 +1169,57 @@ const ReleasesPage = () => {
950
1169
  });
951
1170
  }
952
1171
  };
953
- return /* @__PURE__ */ jsxRuntime.jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
1172
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
1173
+ /* @__PURE__ */ jsxRuntime.jsx(
1174
+ designSystem.HeaderLayout,
1175
+ {
1176
+ title: formatMessage({
1177
+ id: "content-releases.pages.Releases.title",
1178
+ defaultMessage: "Releases"
1179
+ }),
1180
+ subtitle: formatMessage({
1181
+ id: "content-releases.pages.Releases.header-subtitle",
1182
+ defaultMessage: "Create and manage content updates"
1183
+ }),
1184
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
1185
+ designSystem.Button,
1186
+ {
1187
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1188
+ onClick: toggleAddReleaseModal,
1189
+ disabled: hasReachedMaximumPendingReleases,
1190
+ children: formatMessage({
1191
+ id: "content-releases.header.actions.add-release",
1192
+ defaultMessage: "New release"
1193
+ })
1194
+ }
1195
+ ) })
1196
+ }
1197
+ ),
954
1198
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1199
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
1200
+ StyledAlert,
1201
+ {
1202
+ marginBottom: 6,
1203
+ action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
1204
+ id: "content-releases.pages.Releases.max-limit-reached.action",
1205
+ defaultMessage: "Explore plans"
1206
+ }) }),
1207
+ title: formatMessage(
1208
+ {
1209
+ id: "content-releases.pages.Releases.max-limit-reached.title",
1210
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
1211
+ },
1212
+ { number: maximumReleases }
1213
+ ),
1214
+ onClose: () => {
1215
+ },
1216
+ closeLabel: "",
1217
+ children: formatMessage({
1218
+ id: "content-releases.pages.Releases.max-limit-reached.message",
1219
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
1220
+ })
1221
+ }
1222
+ ),
955
1223
  /* @__PURE__ */ jsxRuntime.jsxs(
956
1224
  designSystem.TabGroup,
957
1225
  {
@@ -966,10 +1234,15 @@ const ReleasesPage = () => {
966
1234
  children: [
967
1235
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
968
1236
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
969
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
970
- id: "content-releases.pages.Releases.tab.pending",
971
- defaultMessage: "Pending"
972
- }) }),
1237
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage(
1238
+ {
1239
+ id: "content-releases.pages.Releases.tab.pending",
1240
+ defaultMessage: "Pending ({count})"
1241
+ },
1242
+ {
1243
+ count: totalPendingReleases
1244
+ }
1245
+ ) }),
973
1246
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
974
1247
  id: "content-releases.pages.Releases.tab.done",
975
1248
  defaultMessage: "Done"
@@ -998,7 +1271,7 @@ const ReleasesPage = () => {
998
1271
  ]
999
1272
  }
1000
1273
  ),
1001
- totalReleases > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1274
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1002
1275
  /* @__PURE__ */ jsxRuntime.jsx(
1003
1276
  helperPlugin.PageSizeURLQuery,
1004
1277
  {
@@ -1014,7 +1287,7 @@ const ReleasesPage = () => {
1014
1287
  }
1015
1288
  }
1016
1289
  )
1017
- ] })
1290
+ ] }) : null
1018
1291
  ] }) }),
1019
1292
  releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
1020
1293
  ReleaseModal,
@@ -1034,4 +1307,4 @@ const App = () => {
1034
1307
  ] }) });
1035
1308
  };
1036
1309
  exports.App = App;
1037
- //# sourceMappingURL=App-_20W9dYa.js.map
1310
+ //# sourceMappingURL=App-lnXbSPgp.js.map