@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
@@ -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-uGex_IIQ.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,33 +211,83 @@ const ReleaseModal = ({
84
211
  )
85
212
  ] });
86
213
  };
214
+ const getTimezones = (selectedDate) => {
215
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
216
+ const utcOffset = getTimezoneOffset(timezone, selectedDate);
217
+ return { offset: utcOffset, value: `${utcOffset}_${timezone}` };
218
+ });
219
+ const systemTimezone = timezoneList.find(
220
+ (timezone) => timezone.value.split("_")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
221
+ );
222
+ return { timezoneList, systemTimezone };
223
+ };
224
+ const TimezoneComponent = ({ timezoneOptions }) => {
225
+ const { values, errors, setFieldValue } = useFormikContext();
226
+ const { formatMessage } = useIntl();
227
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
228
+ React.useEffect(() => {
229
+ if (values.date) {
230
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
231
+ setTimezoneList(timezoneList2);
232
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("_")[1] === values.timezone.split("_")[1]);
233
+ if (updatedTimezone) {
234
+ setFieldValue("timezone", updatedTimezone.value);
235
+ }
236
+ }
237
+ }, [setFieldValue, values.date, values.timezone]);
238
+ return /* @__PURE__ */ jsx(
239
+ Combobox,
240
+ {
241
+ label: formatMessage({
242
+ id: "content-releases.modal.form.input.label.timezone",
243
+ defaultMessage: "Timezone"
244
+ }),
245
+ name: "timezone",
246
+ value: values.timezone || void 0,
247
+ textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
248
+ onChange: (timezone) => {
249
+ setFieldValue("timezone", timezone);
250
+ },
251
+ onTextValueChange: (timezone) => {
252
+ setFieldValue("timezone", timezone);
253
+ },
254
+ onClear: () => {
255
+ setFieldValue("timezone", "");
256
+ },
257
+ error: errors.timezone,
258
+ required: true,
259
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
260
+ }
261
+ );
262
+ };
87
263
  const ReleaseInfoWrapper = styled(Flex)`
88
264
  align-self: stretch;
89
265
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
266
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
267
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
268
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
269
+ const StyledMenuItem = styled(Menu.Item)`
97
270
  svg path {
98
271
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
272
  }
100
273
  span {
101
274
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
102
275
  }
276
+
277
+ &:hover {
278
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
279
+ }
103
280
  `;
104
281
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
282
+ width: ${({ theme }) => theme.spaces[3]};
283
+ height: ${({ theme }) => theme.spaces[3]};
107
284
  path {
108
285
  fill: ${({ theme }) => theme.colors.neutral600};
109
286
  }
110
287
  `;
111
288
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
289
+ width: ${({ theme }) => theme.spaces[3]};
290
+ height: ${({ theme }) => theme.spaces[3]};
114
291
  path {
115
292
  fill: ${({ theme }) => theme.colors.danger600};
116
293
  }
@@ -118,24 +295,6 @@ const TrashIcon = styled(Trash)`
118
295
  const TypographyMaxWidth = styled(Typography)`
119
296
  max-width: 300px;
120
297
  `;
121
- const PopoverButton = ({ onClick, disabled, children }) => {
122
- return /* @__PURE__ */ jsx(
123
- StyledFlex,
124
- {
125
- paddingTop: 2,
126
- paddingBottom: 2,
127
- paddingLeft: 4,
128
- paddingRight: 4,
129
- alignItems: "center",
130
- gap: 2,
131
- as: "button",
132
- hasRadius: true,
133
- onClick,
134
- disabled,
135
- children
136
- }
137
- );
138
- };
139
298
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
299
  const { formatMessage } = useIntl();
141
300
  const { validate } = unstable_useDocument();
@@ -184,10 +343,8 @@ const ReleaseDetailsLayout = ({
184
343
  toggleWarningSubmit,
185
344
  children
186
345
  }) => {
187
- const { formatMessage } = useIntl();
346
+ const { formatMessage, formatDate, formatTime } = useIntl();
188
347
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
348
  const {
192
349
  data,
193
350
  isLoading: isLoadingDetails,
@@ -201,14 +358,8 @@ const ReleaseDetailsLayout = ({
201
358
  allowedActions: { canUpdate, canDelete }
202
359
  } = useRBAC(PERMISSIONS);
203
360
  const dispatch = useTypedDispatch();
361
+ const { trackUsage } = useTracking();
204
362
  const release = data?.data;
205
- const handleTogglePopover = () => {
206
- setIsPopoverVisible((prev) => !prev);
207
- };
208
- const openReleaseModal = () => {
209
- toggleEditReleaseModal();
210
- handleTogglePopover();
211
- };
212
363
  const handlePublishRelease = async () => {
213
364
  const response = await publishRelease({ id: releaseId });
214
365
  if ("data" in response) {
@@ -219,6 +370,12 @@ const ReleaseDetailsLayout = ({
219
370
  defaultMessage: "Release was published successfully."
220
371
  })
221
372
  });
373
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
374
+ trackUsage("didPublishRelease", {
375
+ totalEntries: totalEntries2,
376
+ totalPublishedEntries,
377
+ totalUnpublishedEntries
378
+ });
222
379
  } else if (isAxiosError(response.error)) {
223
380
  toggleNotification({
224
381
  type: "warning",
@@ -231,13 +388,21 @@ const ReleaseDetailsLayout = ({
231
388
  });
232
389
  }
233
390
  };
234
- const openWarningConfirmDialog = () => {
235
- toggleWarningSubmit();
236
- handleTogglePopover();
237
- };
238
391
  const handleRefresh = () => {
239
392
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
240
393
  };
394
+ const getCreatedByUser = () => {
395
+ if (!release?.createdBy) {
396
+ return null;
397
+ }
398
+ if (release.createdBy.username) {
399
+ return release.createdBy.username;
400
+ }
401
+ if (release.createdBy.firstname) {
402
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
403
+ }
404
+ return release.createdBy.email;
405
+ };
241
406
  if (isLoadingDetails) {
242
407
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
243
408
  }
@@ -259,90 +424,125 @@ const ReleaseDetailsLayout = ({
259
424
  );
260
425
  }
261
426
  const totalEntries = release.actions.meta.count || 0;
262
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
427
+ const hasCreatedByUser = Boolean(getCreatedByUser());
428
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
429
+ const isScheduled = release.scheduledAt && release.timezone;
430
+ const numberOfEntriesText = formatMessage(
431
+ {
432
+ id: "content-releases.pages.Details.header-subtitle",
433
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
434
+ },
435
+ { number: totalEntries }
436
+ );
437
+ const scheduledText = isScheduled ? formatMessage(
438
+ {
439
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
440
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
441
+ },
442
+ {
443
+ date: formatDate(new Date(release.scheduledAt), {
444
+ weekday: "long",
445
+ day: "numeric",
446
+ month: "long",
447
+ year: "numeric",
448
+ timeZone: release.timezone
449
+ }),
450
+ time: formatTime(new Date(release.scheduledAt), {
451
+ timeZone: release.timezone,
452
+ hourCycle: "h23"
453
+ }),
454
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
455
+ }
456
+ ) : "";
263
457
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
264
458
  /* @__PURE__ */ jsx(
265
459
  HeaderLayout,
266
460
  {
267
461
  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
- ),
462
+ subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
275
463
  navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
276
464
  id: "global.back",
277
465
  defaultMessage: "Back"
278
466
  }) }),
279
467
  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
- ),
468
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
469
+ /* @__PURE__ */ jsx(
470
+ Menu.Trigger,
471
+ {
472
+ as: IconButton,
473
+ paddingLeft: 2,
474
+ paddingRight: 2,
475
+ "aria-label": formatMessage({
476
+ id: "content-releases.header.actions.open-release-actions",
477
+ defaultMessage: "Release edit and delete menu"
478
+ }),
479
+ icon: /* @__PURE__ */ jsx(More, {}),
480
+ variant: "tertiary"
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
484
+ /* @__PURE__ */ jsxs(
485
+ Flex,
486
+ {
487
+ alignItems: "center",
488
+ justifyContent: "center",
489
+ direction: "column",
490
+ padding: 1,
491
+ width: "100%",
492
+ children: [
493
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
494
+ /* @__PURE__ */ jsx(PencilIcon, {}),
495
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
496
+ id: "content-releases.header.actions.edit",
497
+ defaultMessage: "Edit"
498
+ }) })
499
+ ] }) }),
500
+ /* @__PURE__ */ jsx(
501
+ StyledMenuItem,
502
+ {
503
+ disabled: !canDelete,
504
+ onSelect: toggleWarningSubmit,
505
+ variant: "danger",
506
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
507
+ /* @__PURE__ */ jsx(TrashIcon, {}),
508
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
509
+ id: "content-releases.header.actions.delete",
510
+ defaultMessage: "Delete"
511
+ }) })
512
+ ] })
513
+ }
514
+ )
515
+ ]
516
+ }
517
+ ),
518
+ /* @__PURE__ */ jsxs(
519
+ ReleaseInfoWrapper,
520
+ {
521
+ direction: "column",
522
+ justifyContent: "center",
523
+ alignItems: "flex-start",
524
+ gap: 1,
525
+ padding: 5,
526
+ children: [
527
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
528
+ id: "content-releases.header.actions.created",
529
+ defaultMessage: "Created"
530
+ }) }),
531
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
532
+ /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
533
+ formatMessage(
534
+ {
535
+ id: "content-releases.header.actions.created.description",
536
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
537
+ },
538
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
539
+ )
540
+ ] })
541
+ ]
542
+ }
543
+ )
544
+ ] })
545
+ ] }),
346
546
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
347
547
  id: "content-releases.header.actions.refresh",
348
548
  defaultMessage: "Refresh"
@@ -398,6 +598,9 @@ const ReleaseDetailsBody = () => {
398
598
  isError: isReleaseError,
399
599
  error: releaseError
400
600
  } = useGetReleaseQuery({ id: releaseId });
601
+ const {
602
+ allowedActions: { canUpdate }
603
+ } = useRBAC(PERMISSIONS);
401
604
  const release = releaseData?.data;
402
605
  const selectedGroupBy = query?.groupBy || "contentType";
403
606
  const {
@@ -411,7 +614,7 @@ const ReleaseDetailsBody = () => {
411
614
  releaseId
412
615
  });
413
616
  const [updateReleaseAction] = useUpdateReleaseActionMutation();
414
- const handleChangeType = async (e, actionId) => {
617
+ const handleChangeType = async (e, actionId, actionPath) => {
415
618
  const response = await updateReleaseAction({
416
619
  params: {
417
620
  releaseId,
@@ -419,7 +622,11 @@ const ReleaseDetailsBody = () => {
419
622
  },
420
623
  body: {
421
624
  type: e.target.value
422
- }
625
+ },
626
+ query,
627
+ // We are passing the query params to make optimistic updates
628
+ actionPath
629
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
423
630
  });
424
631
  if ("error" in response) {
425
632
  if (isAxiosError(response.error)) {
@@ -500,7 +707,7 @@ const ReleaseDetailsBody = () => {
500
707
  SingleSelect,
501
708
  {
502
709
  "aria-label": formatMessage({
503
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
710
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
504
711
  defaultMessage: "Group by"
505
712
  }),
506
713
  customizeContent: (value) => formatMessage(
@@ -518,7 +725,7 @@ const ReleaseDetailsBody = () => {
518
725
  }
519
726
  ) }),
520
727
  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 }) }),
728
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
522
729
  /* @__PURE__ */ jsx(
523
730
  Table.Root,
524
731
  {
@@ -588,56 +795,59 @@ const ReleaseDetailsBody = () => {
588
795
  )
589
796
  ] }),
590
797
  /* @__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,
798
+ /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(
799
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxs(Tr, { children: [
800
+ /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
801
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
802
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
803
+ /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
804
+ {
805
+ id: "content-releases.page.ReleaseDetails.table.action-published",
806
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
807
+ },
808
+ {
809
+ isPublish: type === "publish",
810
+ b: (children) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children })
811
+ }
812
+ ) }) : /* @__PURE__ */ jsx(
813
+ ReleaseActionOptions,
615
814
  {
616
- action: type,
617
- schema: contentTypes?.[contentType.uid],
618
- components,
619
- entry
815
+ selected: type,
816
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
817
+ name: `release-action-${id}-type`,
818
+ disabled: !canUpdate
620
819
  }
621
820
  ) }),
622
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
623
- /* @__PURE__ */ jsx(
624
- ReleaseActionMenu.ReleaseActionEntryLinkItem,
821
+ !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
822
+ /* @__PURE__ */ jsx(Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsx(
823
+ EntryValidationText,
625
824
  {
626
- contentTypeUid: contentType.uid,
627
- entryId: entry.id,
628
- locale: locale?.code
825
+ action: type,
826
+ schema: contentTypes?.[contentType.uid],
827
+ components,
828
+ entry
629
829
  }
630
- ),
631
- /* @__PURE__ */ jsx(
632
- ReleaseActionMenu.DeleteReleaseActionItem,
633
- {
634
- releaseId: release.id,
635
- actionId: id
636
- }
637
- )
638
- ] }) }) })
639
- ] })
640
- ] }, id)) })
830
+ ) }),
831
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
832
+ /* @__PURE__ */ jsx(
833
+ ReleaseActionMenu.ReleaseActionEntryLinkItem,
834
+ {
835
+ contentTypeUid: contentType.uid,
836
+ entryId: entry.id,
837
+ locale: locale?.code
838
+ }
839
+ ),
840
+ /* @__PURE__ */ jsx(
841
+ ReleaseActionMenu.DeleteReleaseActionItem,
842
+ {
843
+ releaseId: release.id,
844
+ actionId: id
845
+ }
846
+ )
847
+ ] }) }) })
848
+ ] })
849
+ ] }, id)
850
+ ) })
641
851
  ] })
642
852
  }
643
853
  )
@@ -684,11 +894,18 @@ const ReleaseDetailsPage = () => {
684
894
  }
685
895
  );
686
896
  }
687
- const title = isSuccessDetails && data?.data?.name || "";
897
+ const releaseData = isSuccessDetails && data?.data || null;
898
+ const title = releaseData?.name || "";
899
+ const timezone = releaseData?.timezone ?? null;
900
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
901
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
902
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
688
903
  const handleEditRelease = async (values) => {
689
904
  const response = await updateRelease({
690
905
  id: releaseId,
691
- name: values.name
906
+ name: values.name,
907
+ scheduledAt: values.scheduledAt,
908
+ timezone: values.timezone
692
909
  });
693
910
  if ("data" in response) {
694
911
  toggleNotification({
@@ -742,7 +959,14 @@ const ReleaseDetailsPage = () => {
742
959
  handleClose: toggleEditReleaseModal,
743
960
  handleSubmit: handleEditRelease,
744
961
  isLoading: isLoadingDetails || isSubmittingForm,
745
- initialValues: { name: title || "" }
962
+ initialValues: {
963
+ name: title || "",
964
+ scheduledAt,
965
+ date,
966
+ time,
967
+ isScheduled: Boolean(scheduledAt),
968
+ timezone
969
+ }
746
970
  }
747
971
  ),
748
972
  /* @__PURE__ */ jsx(
@@ -762,42 +986,12 @@ const ReleaseDetailsPage = () => {
762
986
  }
763
987
  );
764
988
  };
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
989
  const LinkCard = styled(Link$2)`
797
990
  display: block;
798
991
  `;
799
992
  const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
800
993
  const { formatMessage } = useIntl();
994
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
801
995
  if (isError) {
802
996
  return /* @__PURE__ */ jsx(AnErrorOccurred, {});
803
997
  }
@@ -818,7 +1012,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
818
1012
  }
819
1013
  );
820
1014
  }
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(
1015
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
822
1016
  Flex,
823
1017
  {
824
1018
  direction: "column",
@@ -833,7 +1027,10 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
833
1027
  gap: 2,
834
1028
  children: [
835
1029
  /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
836
- /* @__PURE__ */ jsx(Typography, { variant: "pi", children: formatMessage(
1030
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
1031
+ id: "content-releases.pages.Releases.not-scheduled",
1032
+ defaultMessage: "Not scheduled"
1033
+ }) : formatMessage(
837
1034
  {
838
1035
  id: "content-releases.page.Releases.release-item.entries",
839
1036
  defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
@@ -844,8 +1041,22 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
844
1041
  }
845
1042
  ) }) }, id)) });
846
1043
  };
1044
+ const StyledAlert = styled(Alert)`
1045
+ button {
1046
+ display: none;
1047
+ }
1048
+ p + div {
1049
+ margin-left: auto;
1050
+ }
1051
+ `;
847
1052
  const INITIAL_FORM_VALUES = {
848
- name: ""
1053
+ name: "",
1054
+ date: null,
1055
+ time: "",
1056
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
1057
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
1058
+ scheduledAt: null,
1059
+ timezone: null
849
1060
  };
850
1061
  const ReleasesPage = () => {
851
1062
  const tabRef = React.useRef(null);
@@ -858,6 +1069,9 @@ const ReleasesPage = () => {
858
1069
  const [{ query }, setQuery] = useQueryParams();
859
1070
  const response = useGetReleasesQuery(query);
860
1071
  const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
1072
+ const { getFeature } = useLicenseLimits();
1073
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
1074
+ const { trackUsage } = useTracking();
861
1075
  const { isLoading, isSuccess, isError } = response;
862
1076
  const activeTab = response?.currentData?.meta?.activeTab || "pending";
863
1077
  const activeTabIndex = ["pending", "done"].indexOf(activeTab);
@@ -886,9 +1100,10 @@ const ReleasesPage = () => {
886
1100
  setReleaseModalShown((prev) => !prev);
887
1101
  };
888
1102
  if (isLoading) {
889
- return /* @__PURE__ */ jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) }) });
1103
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
890
1104
  }
891
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
1105
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
1106
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
892
1107
  const handleTabChange = (index) => {
893
1108
  setQuery({
894
1109
  ...query,
@@ -901,9 +1116,11 @@ const ReleasesPage = () => {
901
1116
  }
902
1117
  });
903
1118
  };
904
- const handleAddRelease = async (values) => {
1119
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
905
1120
  const response2 = await createRelease({
906
- name: values.name
1121
+ name,
1122
+ scheduledAt,
1123
+ timezone
907
1124
  });
908
1125
  if ("data" in response2) {
909
1126
  toggleNotification({
@@ -913,6 +1130,7 @@ const ReleasesPage = () => {
913
1130
  defaultMessage: "Release created."
914
1131
  })
915
1132
  });
1133
+ trackUsage("didCreateRelease");
916
1134
  push(`/plugins/content-releases/${response2.data.data.id}`);
917
1135
  } else if (isAxiosError(response2.error)) {
918
1136
  toggleNotification({
@@ -926,8 +1144,57 @@ const ReleasesPage = () => {
926
1144
  });
927
1145
  }
928
1146
  };
929
- return /* @__PURE__ */ jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
1147
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
1148
+ /* @__PURE__ */ jsx(
1149
+ HeaderLayout,
1150
+ {
1151
+ title: formatMessage({
1152
+ id: "content-releases.pages.Releases.title",
1153
+ defaultMessage: "Releases"
1154
+ }),
1155
+ subtitle: formatMessage({
1156
+ id: "content-releases.pages.Releases.header-subtitle",
1157
+ defaultMessage: "Create and manage content updates"
1158
+ }),
1159
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
1160
+ Button,
1161
+ {
1162
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
1163
+ onClick: toggleAddReleaseModal,
1164
+ disabled: hasReachedMaximumPendingReleases,
1165
+ children: formatMessage({
1166
+ id: "content-releases.header.actions.add-release",
1167
+ defaultMessage: "New release"
1168
+ })
1169
+ }
1170
+ ) })
1171
+ }
1172
+ ),
930
1173
  /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
1174
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
1175
+ StyledAlert,
1176
+ {
1177
+ marginBottom: 6,
1178
+ action: /* @__PURE__ */ jsx(Link$2, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
1179
+ id: "content-releases.pages.Releases.max-limit-reached.action",
1180
+ defaultMessage: "Explore plans"
1181
+ }) }),
1182
+ title: formatMessage(
1183
+ {
1184
+ id: "content-releases.pages.Releases.max-limit-reached.title",
1185
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
1186
+ },
1187
+ { number: maximumReleases }
1188
+ ),
1189
+ onClose: () => {
1190
+ },
1191
+ closeLabel: "",
1192
+ children: formatMessage({
1193
+ id: "content-releases.pages.Releases.max-limit-reached.message",
1194
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
1195
+ })
1196
+ }
1197
+ ),
931
1198
  /* @__PURE__ */ jsxs(
932
1199
  TabGroup,
933
1200
  {
@@ -942,10 +1209,15 @@ const ReleasesPage = () => {
942
1209
  children: [
943
1210
  /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
944
1211
  /* @__PURE__ */ jsxs(Tabs, { children: [
945
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
946
- id: "content-releases.pages.Releases.tab.pending",
947
- defaultMessage: "Pending"
948
- }) }),
1212
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
1213
+ {
1214
+ id: "content-releases.pages.Releases.tab.pending",
1215
+ defaultMessage: "Pending ({count})"
1216
+ },
1217
+ {
1218
+ count: totalPendingReleases
1219
+ }
1220
+ ) }),
949
1221
  /* @__PURE__ */ jsx(Tab, { children: formatMessage({
950
1222
  id: "content-releases.pages.Releases.tab.done",
951
1223
  defaultMessage: "Done"
@@ -974,7 +1246,7 @@ const ReleasesPage = () => {
974
1246
  ]
975
1247
  }
976
1248
  ),
977
- totalReleases > 0 && /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1249
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
978
1250
  /* @__PURE__ */ jsx(
979
1251
  PageSizeURLQuery,
980
1252
  {
@@ -990,7 +1262,7 @@ const ReleasesPage = () => {
990
1262
  }
991
1263
  }
992
1264
  )
993
- ] })
1265
+ ] }) : null
994
1266
  ] }) }),
995
1267
  releaseModalShown && /* @__PURE__ */ jsx(
996
1268
  ReleaseModal,
@@ -1012,4 +1284,4 @@ const App = () => {
1012
1284
  export {
1013
1285
  App
1014
1286
  };
1015
- //# sourceMappingURL=App-L1jSxCiL.mjs.map
1287
+ //# sourceMappingURL=App-g3vtS2Wa.mjs.map