@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 0.0.0-next.898f8ae81b2cb3f89bd012e9db20a2d9b78a48d2

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-_20W9dYa.js → App-OK4Xac-O.js} +518 -240
  2. package/dist/_chunks/App-OK4Xac-O.js.map +1 -0
  3. package/dist/_chunks/{App-L1jSxCiL.mjs → App-xAkiD42p.mjs} +522 -245
  4. package/dist/_chunks/App-xAkiD42p.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-gYDqKYFd.js → en-r0otWaln.js} +18 -4
  10. package/dist/_chunks/en-r0otWaln.js.map +1 -0
  11. package/dist/_chunks/{en-MyLPoISH.mjs → en-veqvqeEr.mjs} +18 -4
  12. package/dist/_chunks/en-veqvqeEr.mjs.map +1 -0
  13. package/dist/_chunks/{index-KJa1Rb5F.js → index-JvA2_26n.js} +134 -27
  14. package/dist/_chunks/index-JvA2_26n.js.map +1 -0
  15. package/dist/_chunks/{index-c4zRX_sg.mjs → index-exoiSU3V.mjs} +139 -32
  16. package/dist/_chunks/index-exoiSU3V.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 +597 -382
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +597 -382
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +12 -9
  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
@@ -1,22 +1,42 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { useNotification, useAPIErrorHandler, LoadingIndicatorPage, ConfirmDialog, useRBAC, RelativeTime, CheckPermissions, useQueryParams, AnErrorOccurred, NoContent, Table, PageSizeURLQuery, PaginationURLQuery, CheckPagePermissions } from "@strapi/helper-plugin";
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-c4zRX_sg.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-exoiSU3V.mjs";
5
5
  import * as React from "react";
6
- import { unstable_useDocument } 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, 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";
6
+ import { unstable_useDocument, useLicenseLimits } from "@strapi/admin/strapi-admin";
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,
@@ -201,14 +351,8 @@ const ReleaseDetailsLayout = ({
201
351
  allowedActions: { canUpdate, canDelete }
202
352
  } = useRBAC(PERMISSIONS);
203
353
  const dispatch = useTypedDispatch();
354
+ const { trackUsage } = useTracking();
204
355
  const release = data?.data;
205
- const handleTogglePopover = () => {
206
- setIsPopoverVisible((prev) => !prev);
207
- };
208
- const openReleaseModal = () => {
209
- toggleEditReleaseModal();
210
- handleTogglePopover();
211
- };
212
356
  const handlePublishRelease = async () => {
213
357
  const response = await publishRelease({ id: releaseId });
214
358
  if ("data" in response) {
@@ -219,6 +363,12 @@ const ReleaseDetailsLayout = ({
219
363
  defaultMessage: "Release was published successfully."
220
364
  })
221
365
  });
366
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
367
+ trackUsage("didPublishRelease", {
368
+ totalEntries: totalEntries2,
369
+ totalPublishedEntries,
370
+ totalUnpublishedEntries
371
+ });
222
372
  } else if (isAxiosError(response.error)) {
223
373
  toggleNotification({
224
374
  type: "warning",
@@ -231,13 +381,21 @@ const ReleaseDetailsLayout = ({
231
381
  });
232
382
  }
233
383
  };
234
- const openWarningConfirmDialog = () => {
235
- toggleWarningSubmit();
236
- handleTogglePopover();
237
- };
238
384
  const handleRefresh = () => {
239
385
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
240
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
+ };
241
399
  if (isLoadingDetails) {
242
400
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
243
401
  }
@@ -259,90 +417,139 @@ const ReleaseDetailsLayout = ({
259
417
  );
260
418
  }
261
419
  const totalEntries = release.actions.meta.count || 0;
262
- 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
+ ) : "";
263
450
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
264
451
  /* @__PURE__ */ jsx(
265
452
  HeaderLayout,
266
453
  {
267
454
  title: release.name,
268
- subtitle: formatMessage(
269
- {
270
- id: "content-releases.pages.Details.header-subtitle",
271
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
272
- },
273
- { number: totalEntries }
274
- ),
455
+ subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
275
456
  navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
276
457
  id: "global.back",
277
458
  defaultMessage: "Back"
278
459
  }) }),
279
460
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
280
- /* @__PURE__ */ jsx(
281
- IconButton,
282
- {
283
- label: formatMessage({
284
- id: "content-releases.header.actions.open-release-actions",
285
- defaultMessage: "Release actions"
286
- }),
287
- ref: moreButtonRef,
288
- onClick: handleTogglePopover,
289
- children: /* @__PURE__ */ jsx(More, {})
290
- }
291
- ),
292
- isPopoverVisible && /* @__PURE__ */ jsxs(
293
- Popover,
294
- {
295
- source: moreButtonRef,
296
- placement: "bottom-end",
297
- onDismiss: handleTogglePopover,
298
- spacing: 4,
299
- minWidth: "242px",
300
- children: [
301
- /* @__PURE__ */ jsxs(Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
302
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
303
- /* @__PURE__ */ jsx(PencilIcon, {}),
304
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
305
- id: "content-releases.header.actions.edit",
306
- defaultMessage: "Edit"
307
- }) })
308
- ] }),
309
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
310
- /* @__PURE__ */ jsx(TrashIcon, {}),
311
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
312
- id: "content-releases.header.actions.delete",
313
- defaultMessage: "Delete"
314
- }) })
315
- ] })
316
- ] }),
317
- /* @__PURE__ */ jsxs(
318
- ReleaseInfoWrapper,
319
- {
320
- direction: "column",
321
- justifyContent: "center",
322
- alignItems: "flex-start",
323
- gap: 1,
324
- padding: 5,
325
- children: [
326
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
327
- id: "content-releases.header.actions.created",
328
- defaultMessage: "Created"
329
- }) }),
330
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
331
- /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
332
- formatMessage(
333
- {
334
- id: "content-releases.header.actions.created.description",
335
- defaultMessage: " by {createdBy}"
336
- },
337
- { createdBy }
338
- )
339
- ] })
340
- ]
341
- }
342
- )
343
- ]
344
- }
345
- ),
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
+ ] }),
346
553
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
347
554
  id: "content-releases.header.actions.refresh",
348
555
  defaultMessage: "Refresh"
@@ -398,6 +605,9 @@ const ReleaseDetailsBody = () => {
398
605
  isError: isReleaseError,
399
606
  error: releaseError
400
607
  } = useGetReleaseQuery({ id: releaseId });
608
+ const {
609
+ allowedActions: { canUpdate }
610
+ } = useRBAC(PERMISSIONS);
401
611
  const release = releaseData?.data;
402
612
  const selectedGroupBy = query?.groupBy || "contentType";
403
613
  const {
@@ -411,7 +621,7 @@ const ReleaseDetailsBody = () => {
411
621
  releaseId
412
622
  });
413
623
  const [updateReleaseAction] = useUpdateReleaseActionMutation();
414
- const handleChangeType = async (e, actionId) => {
624
+ const handleChangeType = async (e, actionId, actionPath) => {
415
625
  const response = await updateReleaseAction({
416
626
  params: {
417
627
  releaseId,
@@ -419,7 +629,11 @@ const ReleaseDetailsBody = () => {
419
629
  },
420
630
  body: {
421
631
  type: e.target.value
422
- }
632
+ },
633
+ query,
634
+ // We are passing the query params to make optimistic updates
635
+ actionPath
636
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
423
637
  });
424
638
  if ("error" in response) {
425
639
  if (isAxiosError(response.error)) {
@@ -500,7 +714,7 @@ const ReleaseDetailsBody = () => {
500
714
  SingleSelect,
501
715
  {
502
716
  "aria-label": formatMessage({
503
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
717
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
504
718
  defaultMessage: "Group by"
505
719
  }),
506
720
  customizeContent: (value) => formatMessage(
@@ -518,7 +732,7 @@ const ReleaseDetailsBody = () => {
518
732
  }
519
733
  ) }),
520
734
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxs(Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
521
- /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
735
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
522
736
  /* @__PURE__ */ jsx(
523
737
  Table.Root,
524
738
  {
@@ -588,56 +802,59 @@ const ReleaseDetailsBody = () => {
588
802
  )
589
803
  ] }),
590
804
  /* @__PURE__ */ jsx(Table.LoadingBody, {}),
591
- /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(({ id, contentType, locale, type, entry }) => /* @__PURE__ */ jsxs(Tr, { children: [
592
- /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
593
- /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
594
- /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
595
- /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
596
- {
597
- id: "content-releases.page.ReleaseDetails.table.action-published",
598
- defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
599
- },
600
- {
601
- isPublish: type === "publish",
602
- b: (children) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children })
603
- }
604
- ) }) : /* @__PURE__ */ jsx(
605
- ReleaseActionOptions,
606
- {
607
- selected: type,
608
- handleChange: (e) => handleChangeType(e, id),
609
- name: `release-action-${id}-type`
610
- }
611
- ) }),
612
- !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
613
- /* @__PURE__ */ jsx(Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsx(
614
- EntryValidationText,
805
+ /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(
806
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxs(Tr, { children: [
807
+ /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
808
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
809
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
810
+ /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
811
+ {
812
+ id: "content-releases.page.ReleaseDetails.table.action-published",
813
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
814
+ },
615
815
  {
616
- action: type,
617
- schema: contentTypes?.[contentType.uid],
618
- components,
619
- entry
816
+ isPublish: type === "publish",
817
+ b: (children) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children })
818
+ }
819
+ ) }) : /* @__PURE__ */ jsx(
820
+ ReleaseActionOptions,
821
+ {
822
+ selected: type,
823
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
824
+ name: `release-action-${id}-type`,
825
+ disabled: !canUpdate
620
826
  }
621
827
  ) }),
622
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
623
- /* @__PURE__ */ jsx(
624
- ReleaseActionMenu.ReleaseActionEntryLinkItem,
625
- {
626
- contentTypeUid: contentType.uid,
627
- entryId: entry.id,
628
- locale: locale?.code
629
- }
630
- ),
631
- /* @__PURE__ */ jsx(
632
- ReleaseActionMenu.DeleteReleaseActionItem,
828
+ !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
829
+ /* @__PURE__ */ jsx(Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsx(
830
+ EntryValidationText,
633
831
  {
634
- releaseId: release.id,
635
- actionId: id
832
+ action: type,
833
+ schema: contentTypes?.[contentType.uid],
834
+ components,
835
+ entry
636
836
  }
637
- )
638
- ] }) }) })
639
- ] })
640
- ] }, id)) })
837
+ ) }),
838
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
839
+ /* @__PURE__ */ jsx(
840
+ ReleaseActionMenu.ReleaseActionEntryLinkItem,
841
+ {
842
+ contentTypeUid: contentType.uid,
843
+ entryId: entry.id,
844
+ locale: locale?.code
845
+ }
846
+ ),
847
+ /* @__PURE__ */ jsx(
848
+ ReleaseActionMenu.DeleteReleaseActionItem,
849
+ {
850
+ releaseId: release.id,
851
+ actionId: id
852
+ }
853
+ )
854
+ ] }) }) })
855
+ ] })
856
+ ] }, id)
857
+ ) })
641
858
  ] })
642
859
  }
643
860
  )
@@ -684,11 +901,18 @@ const ReleaseDetailsPage = () => {
684
901
  }
685
902
  );
686
903
  }
687
- 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") : "";
688
910
  const handleEditRelease = async (values) => {
689
911
  const response = await updateRelease({
690
912
  id: releaseId,
691
- name: values.name
913
+ name: values.name,
914
+ scheduledAt: values.scheduledAt,
915
+ timezone: values.timezone
692
916
  });
693
917
  if ("data" in response) {
694
918
  toggleNotification({
@@ -742,7 +966,14 @@ const ReleaseDetailsPage = () => {
742
966
  handleClose: toggleEditReleaseModal,
743
967
  handleSubmit: handleEditRelease,
744
968
  isLoading: isLoadingDetails || isSubmittingForm,
745
- initialValues: { name: title || "" }
969
+ initialValues: {
970
+ name: title || "",
971
+ scheduledAt,
972
+ date,
973
+ time,
974
+ isScheduled: Boolean(scheduledAt),
975
+ timezone
976
+ }
746
977
  }
747
978
  ),
748
979
  /* @__PURE__ */ jsx(
@@ -762,42 +993,12 @@ const ReleaseDetailsPage = () => {
762
993
  }
763
994
  );
764
995
  };
765
- const ReleasesLayout = ({
766
- isLoading,
767
- totalReleases,
768
- onClickAddRelease,
769
- children
770
- }) => {
771
- const { formatMessage } = useIntl();
772
- return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
773
- /* @__PURE__ */ jsx(
774
- HeaderLayout,
775
- {
776
- title: formatMessage({
777
- id: "content-releases.pages.Releases.title",
778
- defaultMessage: "Releases"
779
- }),
780
- subtitle: !isLoading && formatMessage(
781
- {
782
- id: "content-releases.pages.Releases.header-subtitle",
783
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
784
- },
785
- { number: totalReleases }
786
- ),
787
- primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(Plus, {}), onClick: onClickAddRelease, children: formatMessage({
788
- id: "content-releases.header.actions.add-release",
789
- defaultMessage: "New release"
790
- }) }) })
791
- }
792
- ),
793
- children
794
- ] });
795
- };
796
996
  const LinkCard = styled(Link$2)`
797
997
  display: block;
798
998
  `;
799
999
  const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
800
1000
  const { formatMessage } = useIntl();
1001
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
801
1002
  if (isError) {
802
1003
  return /* @__PURE__ */ jsx(AnErrorOccurred, {});
803
1004
  }
@@ -818,7 +1019,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
818
1019
  }
819
1020
  );
820
1021
  }
821
- 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(
822
1023
  Flex,
823
1024
  {
824
1025
  direction: "column",
@@ -833,7 +1034,10 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
833
1034
  gap: 2,
834
1035
  children: [
835
1036
  /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
836
- /* @__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(
837
1041
  {
838
1042
  id: "content-releases.page.Releases.release-item.entries",
839
1043
  defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
@@ -844,8 +1048,22 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
844
1048
  }
845
1049
  ) }) }, id)) });
846
1050
  };
1051
+ const StyledAlert = styled(Alert)`
1052
+ button {
1053
+ display: none;
1054
+ }
1055
+ p + div {
1056
+ margin-left: auto;
1057
+ }
1058
+ `;
847
1059
  const INITIAL_FORM_VALUES = {
848
- 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
849
1067
  };
850
1068
  const ReleasesPage = () => {
851
1069
  const tabRef = React.useRef(null);
@@ -858,6 +1076,9 @@ const ReleasesPage = () => {
858
1076
  const [{ query }, setQuery] = useQueryParams();
859
1077
  const response = useGetReleasesQuery(query);
860
1078
  const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
1079
+ const { getFeature } = useLicenseLimits();
1080
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
1081
+ const { trackUsage } = useTracking();
861
1082
  const { isLoading, isSuccess, isError } = response;
862
1083
  const activeTab = response?.currentData?.meta?.activeTab || "pending";
863
1084
  const activeTabIndex = ["pending", "done"].indexOf(activeTab);
@@ -886,9 +1107,10 @@ const ReleasesPage = () => {
886
1107
  setReleaseModalShown((prev) => !prev);
887
1108
  };
888
1109
  if (isLoading) {
889
- return /* @__PURE__ */ jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) }) });
1110
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
890
1111
  }
891
1112
  const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
1113
+ const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
892
1114
  const handleTabChange = (index) => {
893
1115
  setQuery({
894
1116
  ...query,
@@ -901,9 +1123,11 @@ const ReleasesPage = () => {
901
1123
  }
902
1124
  });
903
1125
  };
904
- const handleAddRelease = async (values) => {
1126
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
905
1127
  const response2 = await createRelease({
906
- name: values.name
1128
+ name,
1129
+ scheduledAt,
1130
+ timezone
907
1131
  });
908
1132
  if ("data" in response2) {
909
1133
  toggleNotification({
@@ -913,6 +1137,7 @@ const ReleasesPage = () => {
913
1137
  defaultMessage: "Release created."
914
1138
  })
915
1139
  });
1140
+ trackUsage("didCreateRelease");
916
1141
  push(`/plugins/content-releases/${response2.data.data.id}`);
917
1142
  } else if (isAxiosError(response2.error)) {
918
1143
  toggleNotification({
@@ -926,8 +1151,60 @@ const ReleasesPage = () => {
926
1151
  });
927
1152
  }
928
1153
  };
929
- return /* @__PURE__ */ jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
1154
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
1155
+ /* @__PURE__ */ jsx(
1156
+ HeaderLayout,
1157
+ {
1158
+ title: formatMessage({
1159
+ id: "content-releases.pages.Releases.title",
1160
+ defaultMessage: "Releases"
1161
+ }),
1162
+ subtitle: formatMessage(
1163
+ {
1164
+ id: "content-releases.pages.Releases.header-subtitle",
1165
+ defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
1166
+ },
1167
+ { number: totalReleases }
1168
+ ),
1169
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
1170
+ Button,
1171
+ {
1172
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
1173
+ onClick: toggleAddReleaseModal,
1174
+ disabled: hasReachedMaximumPendingReleases,
1175
+ children: formatMessage({
1176
+ id: "content-releases.header.actions.add-release",
1177
+ defaultMessage: "New release"
1178
+ })
1179
+ }
1180
+ ) })
1181
+ }
1182
+ ),
930
1183
  /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
1184
+ activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
1185
+ StyledAlert,
1186
+ {
1187
+ marginBottom: 6,
1188
+ action: /* @__PURE__ */ jsx(Link$2, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
1189
+ id: "content-releases.pages.Releases.max-limit-reached.action",
1190
+ defaultMessage: "Explore plans"
1191
+ }) }),
1192
+ title: formatMessage(
1193
+ {
1194
+ id: "content-releases.pages.Releases.max-limit-reached.title",
1195
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
1196
+ },
1197
+ { number: maximumReleases }
1198
+ ),
1199
+ onClose: () => {
1200
+ },
1201
+ closeLabel: "",
1202
+ children: formatMessage({
1203
+ id: "content-releases.pages.Releases.max-limit-reached.message",
1204
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
1205
+ })
1206
+ }
1207
+ ),
931
1208
  /* @__PURE__ */ jsxs(
932
1209
  TabGroup,
933
1210
  {
@@ -1012,4 +1289,4 @@ const App = () => {
1012
1289
  export {
1013
1290
  App
1014
1291
  };
1015
- //# sourceMappingURL=App-L1jSxCiL.mjs.map
1292
+ //# sourceMappingURL=App-xAkiD42p.mjs.map