@strapi/content-releases 0.0.0-next.e1ede8c55a0e1e22ce20137bf238fc374bd5dd51 → 0.0.0-next.f8af92b375dc730ba47ed2117f25df893aae696c

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 (32) hide show
  1. package/dist/_chunks/{App-o5_WfqR-.js → App-OK4Xac-O.js} +572 -224
  2. package/dist/_chunks/App-OK4Xac-O.js.map +1 -0
  3. package/dist/_chunks/App-xAkiD42p.mjs +1292 -0
  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-haKSQIo8.js → en-r0otWaln.js} +19 -4
  10. package/dist/_chunks/en-r0otWaln.js.map +1 -0
  11. package/dist/_chunks/{en-ngTk74JV.mjs → en-veqvqeEr.mjs} +19 -4
  12. package/dist/_chunks/en-veqvqeEr.mjs.map +1 -0
  13. package/dist/_chunks/{index-EdBmRHRU.js → index-JvA2_26n.js} +220 -54
  14. package/dist/_chunks/index-JvA2_26n.js.map +1 -0
  15. package/dist/_chunks/{index-XAQOX_IB.mjs → index-exoiSU3V.mjs} +231 -65
  16. package/dist/_chunks/index-exoiSU3V.mjs.map +1 -0
  17. package/dist/admin/index.js +2 -1
  18. package/dist/admin/index.js.map +1 -1
  19. package/dist/admin/index.mjs +3 -2
  20. package/dist/admin/index.mjs.map +1 -1
  21. package/dist/server/index.js +749 -302
  22. package/dist/server/index.js.map +1 -1
  23. package/dist/server/index.mjs +749 -303
  24. package/dist/server/index.mjs.map +1 -1
  25. package/package.json +13 -9
  26. package/dist/_chunks/App-g2P5kbSm.mjs +0 -945
  27. package/dist/_chunks/App-g2P5kbSm.mjs.map +0 -1
  28. package/dist/_chunks/App-o5_WfqR-.js.map +0 -1
  29. package/dist/_chunks/en-haKSQIo8.js.map +0 -1
  30. package/dist/_chunks/en-ngTk74JV.mjs.map +0 -1
  31. package/dist/_chunks/index-EdBmRHRU.js.map +0 -1
  32. package/dist/_chunks/index-XAQOX_IB.mjs.map +0 -1
@@ -3,18 +3,23 @@ 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-EdBmRHRU.js");
6
+ const index = require("./index-JvA2_26n.js");
7
7
  const React = require("react");
8
+ const strapiAdmin = require("@strapi/admin/strapi-admin");
8
9
  const designSystem = require("@strapi/design-system");
9
10
  const v2 = require("@strapi/design-system/v2");
10
11
  const icons = require("@strapi/icons");
12
+ const format = require("date-fns/format");
13
+ const dateFnsTz = require("date-fns-tz");
11
14
  const reactIntl = require("react-intl");
12
15
  const styled = require("styled-components");
16
+ const dateFns = require("date-fns");
13
17
  const formik = require("formik");
14
18
  const yup = require("yup");
15
19
  require("@reduxjs/toolkit/query");
16
20
  require("axios");
17
21
  require("@reduxjs/toolkit/query/react");
22
+ require("react-redux");
18
23
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
19
24
  function _interopNamespace(e) {
20
25
  if (e && e.__esModule)
@@ -35,10 +40,28 @@ function _interopNamespace(e) {
35
40
  return Object.freeze(n);
36
41
  }
37
42
  const React__namespace = /* @__PURE__ */ _interopNamespace(React);
43
+ const format__default = /* @__PURE__ */ _interopDefault(format);
38
44
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
39
45
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
40
46
  const RELEASE_SCHEMA = yup__namespace.object().shape({
41
- 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
+ })
42
65
  }).required().noUnknown();
43
66
  const ReleaseModal = ({
44
67
  handleClose,
@@ -49,6 +72,24 @@ const ReleaseModal = ({
49
72
  const { formatMessage } = reactIntl.useIntl();
50
73
  const { pathname } = reactRouterDom.useLocation();
51
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
+ };
52
93
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
53
94
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
54
95
  {
@@ -60,45 +101,134 @@ const ReleaseModal = ({
60
101
  /* @__PURE__ */ jsxRuntime.jsx(
61
102
  formik.Formik,
62
103
  {
63
- validateOnChange: false,
64
- onSubmit: handleSubmit,
65
- 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
+ },
66
115
  validationSchema: RELEASE_SCHEMA,
67
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
68
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
69
- designSystem.TextInput,
70
- {
71
- label: formatMessage({
72
- id: "content-releases.modal.form.input.label.release-name",
73
- defaultMessage: "Name"
74
- }),
75
- name: "name",
76
- value: values.name,
77
- error: errors.name,
78
- onChange: handleChange,
79
- required: true
80
- }
81
- ) }),
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
+ ] }) }),
82
221
  /* @__PURE__ */ jsxRuntime.jsx(
83
222
  designSystem.ModalFooter,
84
223
  {
85
224
  startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
86
- endActions: /* @__PURE__ */ jsxRuntime.jsx(
87
- designSystem.Button,
225
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
88
226
  {
89
- name: "submit",
90
- loading: isLoading,
91
- disabled: !values.name || values.name === initialValues.name,
92
- type: "submit",
93
- children: formatMessage(
94
- {
95
- id: "content-releases.modal.form.button.submit",
96
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
97
- },
98
- { isCreatingRelease }
99
- )
100
- }
101
- )
227
+ id: "content-releases.modal.form.button.submit",
228
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
229
+ },
230
+ { isCreatingRelease }
231
+ ) })
102
232
  }
103
233
  )
104
234
  ] })
@@ -106,16 +236,59 @@ const ReleaseModal = ({
106
236
  )
107
237
  ] });
108
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
+ onClear: () => {
277
+ setFieldValue("timezone", "");
278
+ },
279
+ error: errors.timezone,
280
+ required: true,
281
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
282
+ }
283
+ );
284
+ };
109
285
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
110
286
  align-self: stretch;
111
287
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
112
288
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
113
289
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
114
290
  `;
115
- const StyledFlex = styled__default.default(designSystem.Flex)`
116
- align-self: stretch;
117
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
118
-
291
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
119
292
  svg path {
120
293
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
121
294
  }
@@ -124,43 +297,46 @@ const StyledFlex = styled__default.default(designSystem.Flex)`
124
297
  }
125
298
  `;
126
299
  const PencilIcon = styled__default.default(icons.Pencil)`
127
- width: ${({ theme }) => theme.spaces[4]};
128
- height: ${({ theme }) => theme.spaces[4]};
300
+ width: ${({ theme }) => theme.spaces[3]};
301
+ height: ${({ theme }) => theme.spaces[3]};
129
302
  path {
130
303
  fill: ${({ theme }) => theme.colors.neutral600};
131
304
  }
132
305
  `;
133
306
  const TrashIcon = styled__default.default(icons.Trash)`
134
- width: ${({ theme }) => theme.spaces[4]};
135
- height: ${({ theme }) => theme.spaces[4]};
307
+ width: ${({ theme }) => theme.spaces[3]};
308
+ height: ${({ theme }) => theme.spaces[3]};
136
309
  path {
137
310
  fill: ${({ theme }) => theme.colors.danger600};
138
311
  }
139
312
  `;
140
- const PopoverButton = ({ onClick, disabled, children }) => {
141
- return /* @__PURE__ */ jsxRuntime.jsx(
142
- StyledFlex,
143
- {
144
- paddingTop: 2,
145
- paddingBottom: 2,
146
- paddingLeft: 4,
147
- paddingRight: 4,
148
- alignItems: "center",
149
- gap: 2,
150
- as: "button",
151
- hasRadius: true,
152
- onClick,
153
- disabled,
154
- children
155
- }
156
- );
157
- };
158
- const EntryValidationText = ({ status, action }) => {
313
+ const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
314
+ max-width: 300px;
315
+ `;
316
+ const EntryValidationText = ({ action, schema, components, entry }) => {
159
317
  const { formatMessage } = reactIntl.useIntl();
318
+ const { validate } = strapiAdmin.unstable_useDocument();
319
+ const { errors } = validate(entry, {
320
+ contentType: schema,
321
+ components,
322
+ isCreatingEntry: false
323
+ });
324
+ if (Object.keys(errors).length > 0) {
325
+ const validationErrorsMessages = Object.entries(errors).map(
326
+ ([key, value]) => formatMessage(
327
+ { id: `${value.id}.withField`, defaultMessage: value.defaultMessage },
328
+ { field: key }
329
+ )
330
+ ).join(" ");
331
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
332
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "danger600", as: icons.CrossCircle }),
333
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { description: validationErrorsMessages, children: /* @__PURE__ */ jsxRuntime.jsx(TypographyMaxWidth, { textColor: "danger600", variant: "omega", fontWeight: "semiBold", ellipsis: true, children: validationErrorsMessages }) })
334
+ ] });
335
+ }
160
336
  if (action == "publish") {
161
337
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
162
338
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "success600", as: icons.CheckCircle }),
163
- status === "published" ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
339
+ entry.publishedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
164
340
  id: "content-releases.pages.ReleaseDetails.entry-validation.already-published",
165
341
  defaultMessage: "Already published"
166
342
  }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
@@ -171,7 +347,7 @@ const EntryValidationText = ({ status, action }) => {
171
347
  }
172
348
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
173
349
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "success600", as: icons.CheckCircle }),
174
- status === "draft" ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
350
+ !entry.publishedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
175
351
  id: "content-releases.pages.ReleaseDetails.entry-validation.already-unpublished",
176
352
  defaultMessage: "Already unpublished"
177
353
  }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
@@ -185,10 +361,8 @@ const ReleaseDetailsLayout = ({
185
361
  toggleWarningSubmit,
186
362
  children
187
363
  }) => {
188
- const { formatMessage } = reactIntl.useIntl();
364
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
189
365
  const { releaseId } = reactRouterDom.useParams();
190
- const [isPopoverVisible, setIsPopoverVisible] = React__namespace.useState(false);
191
- const moreButtonRef = React__namespace.useRef(null);
192
366
  const {
193
367
  data,
194
368
  isLoading: isLoadingDetails,
@@ -201,14 +375,9 @@ const ReleaseDetailsLayout = ({
201
375
  const {
202
376
  allowedActions: { canUpdate, canDelete }
203
377
  } = helperPlugin.useRBAC(index.PERMISSIONS);
378
+ const dispatch = index.useTypedDispatch();
379
+ const { trackUsage } = helperPlugin.useTracking();
204
380
  const release = data?.data;
205
- const handleTogglePopover = () => {
206
- setIsPopoverVisible((prev) => !prev);
207
- };
208
- const openReleaseModal = () => {
209
- toggleEditReleaseModal();
210
- handleTogglePopover();
211
- };
212
381
  const handlePublishRelease = async () => {
213
382
  const response = await publishRelease({ id: releaseId });
214
383
  if ("data" in response) {
@@ -219,6 +388,12 @@ const ReleaseDetailsLayout = ({
219
388
  defaultMessage: "Release was published successfully."
220
389
  })
221
390
  });
391
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
392
+ trackUsage("didPublishRelease", {
393
+ totalEntries: totalEntries2,
394
+ totalPublishedEntries,
395
+ totalUnpublishedEntries
396
+ });
222
397
  } else if (index.isAxiosError(response.error)) {
223
398
  toggleNotification({
224
399
  type: "warning",
@@ -231,9 +406,20 @@ const ReleaseDetailsLayout = ({
231
406
  });
232
407
  }
233
408
  };
234
- const openWarningConfirmDialog = () => {
235
- toggleWarningSubmit();
236
- handleTogglePopover();
409
+ const handleRefresh = () => {
410
+ dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
411
+ };
412
+ const getCreatedByUser = () => {
413
+ if (!release?.createdBy) {
414
+ return null;
415
+ }
416
+ if (release.createdBy.username) {
417
+ return release.createdBy.username;
418
+ }
419
+ if (release.createdBy.firstname) {
420
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
421
+ }
422
+ return release.createdBy.email;
237
423
  };
238
424
  if (isLoadingDetails) {
239
425
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
@@ -256,90 +442,143 @@ const ReleaseDetailsLayout = ({
256
442
  );
257
443
  }
258
444
  const totalEntries = release.actions.meta.count || 0;
259
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
445
+ const hasCreatedByUser = Boolean(getCreatedByUser());
446
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
447
+ const isScheduled = release.scheduledAt && release.timezone;
448
+ const numberOfEntriesText = formatMessage(
449
+ {
450
+ id: "content-releases.pages.Details.header-subtitle",
451
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
452
+ },
453
+ { number: totalEntries }
454
+ );
455
+ const scheduledText = isScheduled ? formatMessage(
456
+ {
457
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
458
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
459
+ },
460
+ {
461
+ date: formatDate(new Date(release.scheduledAt), {
462
+ weekday: "long",
463
+ day: "numeric",
464
+ month: "long",
465
+ year: "numeric",
466
+ timeZone: release.timezone
467
+ }),
468
+ time: formatTime(new Date(release.scheduledAt), {
469
+ timeZone: release.timezone,
470
+ hourCycle: "h23"
471
+ }),
472
+ offset: index.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
473
+ }
474
+ ) : "";
260
475
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
261
476
  /* @__PURE__ */ jsxRuntime.jsx(
262
477
  designSystem.HeaderLayout,
263
478
  {
264
479
  title: release.name,
265
- subtitle: formatMessage(
266
- {
267
- id: "content-releases.pages.Details.header-subtitle",
268
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
269
- },
270
- { number: totalEntries }
271
- ),
480
+ subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
272
481
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
273
482
  id: "global.back",
274
483
  defaultMessage: "Back"
275
484
  }) }),
276
485
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
277
- /* @__PURE__ */ jsxRuntime.jsx(
278
- designSystem.IconButton,
279
- {
280
- label: formatMessage({
281
- id: "content-releases.header.actions.open-release-actions",
282
- defaultMessage: "Release actions"
283
- }),
284
- ref: moreButtonRef,
285
- onClick: handleTogglePopover,
286
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {})
287
- }
288
- ),
289
- isPopoverVisible && /* @__PURE__ */ jsxRuntime.jsxs(
290
- designSystem.Popover,
291
- {
292
- source: moreButtonRef,
293
- placement: "bottom-end",
294
- onDismiss: handleTogglePopover,
295
- spacing: 4,
296
- minWidth: "242px",
297
- children: [
298
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
299
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
300
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
301
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
302
- id: "content-releases.header.actions.edit",
303
- defaultMessage: "Edit"
304
- }) })
305
- ] }),
306
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
307
- /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
308
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
309
- id: "content-releases.header.actions.delete",
310
- defaultMessage: "Delete"
311
- }) })
312
- ] })
313
- ] }),
314
- /* @__PURE__ */ jsxRuntime.jsxs(
315
- ReleaseInfoWrapper,
316
- {
317
- direction: "column",
318
- justifyContent: "center",
319
- alignItems: "flex-start",
320
- gap: 1,
321
- padding: 5,
322
- children: [
323
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
324
- id: "content-releases.header.actions.created",
325
- defaultMessage: "Created"
326
- }) }),
327
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
328
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
329
- formatMessage(
330
- {
331
- id: "content-releases.header.actions.created.description",
332
- defaultMessage: " by {createdBy}"
333
- },
334
- { createdBy }
335
- )
336
- ] })
337
- ]
338
- }
339
- )
340
- ]
341
- }
342
- ),
486
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Root, { children: [
487
+ /* @__PURE__ */ jsxRuntime.jsx(
488
+ v2.Menu.Trigger,
489
+ {
490
+ as: designSystem.IconButton,
491
+ paddingLeft: 2,
492
+ paddingRight: 2,
493
+ "aria-label": formatMessage({
494
+ id: "content-releases.header.actions.open-release-actions",
495
+ defaultMessage: "Release edit and delete menu"
496
+ }),
497
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {}),
498
+ variant: "tertiary"
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
502
+ /* @__PURE__ */ jsxRuntime.jsxs(
503
+ designSystem.Flex,
504
+ {
505
+ alignItems: "center",
506
+ justifyContent: "center",
507
+ direction: "column",
508
+ padding: 1,
509
+ width: "100%",
510
+ children: [
511
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(
512
+ designSystem.Flex,
513
+ {
514
+ paddingTop: 2,
515
+ paddingBottom: 2,
516
+ alignItems: "center",
517
+ gap: 2,
518
+ hasRadius: true,
519
+ width: "100%",
520
+ children: [
521
+ /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
522
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
523
+ id: "content-releases.header.actions.edit",
524
+ defaultMessage: "Edit"
525
+ }) })
526
+ ]
527
+ }
528
+ ) }),
529
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canDelete, onSelect: toggleWarningSubmit, children: /* @__PURE__ */ jsxRuntime.jsxs(
530
+ designSystem.Flex,
531
+ {
532
+ paddingTop: 2,
533
+ paddingBottom: 2,
534
+ alignItems: "center",
535
+ gap: 2,
536
+ hasRadius: true,
537
+ width: "100%",
538
+ children: [
539
+ /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
540
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
541
+ id: "content-releases.header.actions.delete",
542
+ defaultMessage: "Delete"
543
+ }) })
544
+ ]
545
+ }
546
+ ) })
547
+ ]
548
+ }
549
+ ),
550
+ /* @__PURE__ */ jsxRuntime.jsxs(
551
+ ReleaseInfoWrapper,
552
+ {
553
+ direction: "column",
554
+ justifyContent: "center",
555
+ alignItems: "flex-start",
556
+ gap: 1,
557
+ padding: 5,
558
+ children: [
559
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
560
+ id: "content-releases.header.actions.created",
561
+ defaultMessage: "Created"
562
+ }) }),
563
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
564
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
565
+ formatMessage(
566
+ {
567
+ id: "content-releases.header.actions.created.description",
568
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
569
+ },
570
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
571
+ )
572
+ ] })
573
+ ]
574
+ }
575
+ )
576
+ ] })
577
+ ] }),
578
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
579
+ id: "content-releases.header.actions.refresh",
580
+ defaultMessage: "Refresh"
581
+ }) }),
343
582
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.publish, children: /* @__PURE__ */ jsxRuntime.jsx(
344
583
  designSystem.Button,
345
584
  {
@@ -391,6 +630,9 @@ const ReleaseDetailsBody = () => {
391
630
  isError: isReleaseError,
392
631
  error: releaseError
393
632
  } = index.useGetReleaseQuery({ id: releaseId });
633
+ const {
634
+ allowedActions: { canUpdate }
635
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
394
636
  const release = releaseData?.data;
395
637
  const selectedGroupBy = query?.groupBy || "contentType";
396
638
  const {
@@ -404,7 +646,7 @@ const ReleaseDetailsBody = () => {
404
646
  releaseId
405
647
  });
406
648
  const [updateReleaseAction] = index.useUpdateReleaseActionMutation();
407
- const handleChangeType = async (e, actionId) => {
649
+ const handleChangeType = async (e, actionId, actionPath) => {
408
650
  const response = await updateReleaseAction({
409
651
  params: {
410
652
  releaseId,
@@ -412,7 +654,11 @@ const ReleaseDetailsBody = () => {
412
654
  },
413
655
  body: {
414
656
  type: e.target.value
415
- }
657
+ },
658
+ query,
659
+ // We are passing the query params to make optimistic updates
660
+ actionPath
661
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
416
662
  });
417
663
  if ("error" in response) {
418
664
  if (index.isAxiosError(response.error)) {
@@ -433,7 +679,9 @@ const ReleaseDetailsBody = () => {
433
679
  }
434
680
  const releaseActions = data?.data;
435
681
  const releaseMeta = data?.meta;
436
- if (isError || isReleaseError || !release || !releaseActions) {
682
+ const contentTypes = releaseMeta?.contentTypes || {};
683
+ const components = releaseMeta?.components || {};
684
+ if (isReleaseError || !release) {
437
685
  const errorsArray = [];
438
686
  if (releaseError) {
439
687
  errorsArray.push({
@@ -457,6 +705,9 @@ const ReleaseDetailsBody = () => {
457
705
  }
458
706
  );
459
707
  }
708
+ if (isError || !releaseActions) {
709
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {}) });
710
+ }
460
711
  if (Object.keys(releaseActions).length === 0) {
461
712
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(
462
713
  helperPlugin.NoContent,
@@ -488,7 +739,7 @@ const ReleaseDetailsBody = () => {
488
739
  designSystem.SingleSelect,
489
740
  {
490
741
  "aria-label": formatMessage({
491
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
742
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
492
743
  defaultMessage: "Group by"
493
744
  }),
494
745
  customizeContent: (value) => formatMessage(
@@ -506,7 +757,7 @@ const ReleaseDetailsBody = () => {
506
757
  }
507
758
  ) }),
508
759
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
509
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
760
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
510
761
  /* @__PURE__ */ jsxRuntime.jsx(
511
762
  helperPlugin.Table.Root,
512
763
  {
@@ -576,30 +827,59 @@ const ReleaseDetailsBody = () => {
576
827
  )
577
828
  ] }),
578
829
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.LoadingBody, {}),
579
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(({ id, type, entry }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
580
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${entry.contentType.mainFieldValue || entry.id}` }) }),
581
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${entry?.locale?.name ? entry.locale.name : "-"}` }) }),
582
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: entry.contentType.displayName || "" }) }),
583
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
584
- {
585
- id: "content-releases.page.ReleaseDetails.table.action-published",
586
- defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
587
- },
588
- {
589
- isPublish: type === "publish",
590
- b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
591
- }
592
- ) }) : /* @__PURE__ */ jsxRuntime.jsx(
593
- index.ReleaseActionOptions,
594
- {
595
- selected: type,
596
- handleChange: (e) => handleChangeType(e, id),
597
- name: `release-action-${id}-type`
598
- }
599
- ) }),
600
- !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(EntryValidationText, { status: entry.status, action: type }) }),
601
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(index.ReleaseActionMenu, { releaseId, actionId: id }) }) })
602
- ] }, id)) })
830
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(
831
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
832
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
833
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
834
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
835
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
836
+ {
837
+ id: "content-releases.page.ReleaseDetails.table.action-published",
838
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
839
+ },
840
+ {
841
+ isPublish: type === "publish",
842
+ b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
843
+ }
844
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
845
+ index.ReleaseActionOptions,
846
+ {
847
+ selected: type,
848
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
849
+ name: `release-action-${id}-type`,
850
+ disabled: !canUpdate
851
+ }
852
+ ) }),
853
+ !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
854
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(
855
+ EntryValidationText,
856
+ {
857
+ action: type,
858
+ schema: contentTypes?.[contentType.uid],
859
+ components,
860
+ entry
861
+ }
862
+ ) }),
863
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(index.ReleaseActionMenu.Root, { children: [
864
+ /* @__PURE__ */ jsxRuntime.jsx(
865
+ index.ReleaseActionMenu.ReleaseActionEntryLinkItem,
866
+ {
867
+ contentTypeUid: contentType.uid,
868
+ entryId: entry.id,
869
+ locale: locale?.code
870
+ }
871
+ ),
872
+ /* @__PURE__ */ jsxRuntime.jsx(
873
+ index.ReleaseActionMenu.DeleteReleaseActionItem,
874
+ {
875
+ releaseId: release.id,
876
+ actionId: id
877
+ }
878
+ )
879
+ ] }) }) })
880
+ ] })
881
+ ] }, id)
882
+ ) })
603
883
  ] })
604
884
  }
605
885
  )
@@ -646,11 +926,18 @@ const ReleaseDetailsPage = () => {
646
926
  }
647
927
  );
648
928
  }
649
- const title = isSuccessDetails && data?.data?.name || "";
929
+ const releaseData = isSuccessDetails && data?.data || null;
930
+ const title = releaseData?.name || "";
931
+ const timezone = releaseData?.timezone ?? null;
932
+ const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
933
+ const date = scheduledAt ? new Date(format__default.default(scheduledAt, "yyyy-MM-dd")) : null;
934
+ const time = scheduledAt ? format__default.default(scheduledAt, "HH:mm") : "";
650
935
  const handleEditRelease = async (values) => {
651
936
  const response = await updateRelease({
652
937
  id: releaseId,
653
- name: values.name
938
+ name: values.name,
939
+ scheduledAt: values.scheduledAt,
940
+ timezone: values.timezone
654
941
  });
655
942
  if ("data" in response) {
656
943
  toggleNotification({
@@ -704,7 +991,14 @@ const ReleaseDetailsPage = () => {
704
991
  handleClose: toggleEditReleaseModal,
705
992
  handleSubmit: handleEditRelease,
706
993
  isLoading: isLoadingDetails || isSubmittingForm,
707
- initialValues: { name: title || "" }
994
+ initialValues: {
995
+ name: title || "",
996
+ scheduledAt,
997
+ date,
998
+ time,
999
+ isScheduled: Boolean(scheduledAt),
1000
+ timezone
1001
+ }
708
1002
  }
709
1003
  ),
710
1004
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -724,42 +1018,12 @@ const ReleaseDetailsPage = () => {
724
1018
  }
725
1019
  );
726
1020
  };
727
- const ReleasesLayout = ({
728
- isLoading,
729
- totalReleases,
730
- onClickAddRelease,
731
- children
732
- }) => {
733
- const { formatMessage } = reactIntl.useIntl();
734
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
735
- /* @__PURE__ */ jsxRuntime.jsx(
736
- designSystem.HeaderLayout,
737
- {
738
- title: formatMessage({
739
- id: "content-releases.pages.Releases.title",
740
- defaultMessage: "Releases"
741
- }),
742
- subtitle: !isLoading && formatMessage(
743
- {
744
- id: "content-releases.pages.Releases.header-subtitle",
745
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
746
- },
747
- { number: totalReleases }
748
- ),
749
- 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({
750
- id: "content-releases.header.actions.add-release",
751
- defaultMessage: "New release"
752
- }) }) })
753
- }
754
- ),
755
- children
756
- ] });
757
- };
758
1021
  const LinkCard = styled__default.default(v2.Link)`
759
1022
  display: block;
760
1023
  `;
761
1024
  const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
762
1025
  const { formatMessage } = reactIntl.useIntl();
1026
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
763
1027
  if (isError) {
764
1028
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
765
1029
  }
@@ -780,7 +1044,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
780
1044
  }
781
1045
  );
782
1046
  }
783
- 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(
1047
+ 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(
784
1048
  designSystem.Flex,
785
1049
  {
786
1050
  direction: "column",
@@ -795,7 +1059,10 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
795
1059
  gap: 2,
796
1060
  children: [
797
1061
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
798
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: formatMessage(
1062
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
1063
+ id: "content-releases.pages.Releases.not-scheduled",
1064
+ defaultMessage: "Not scheduled"
1065
+ }) : formatMessage(
799
1066
  {
800
1067
  id: "content-releases.page.Releases.release-item.entries",
801
1068
  defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
@@ -806,10 +1073,25 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
806
1073
  }
807
1074
  ) }) }, id)) });
808
1075
  };
1076
+ const StyledAlert = styled__default.default(designSystem.Alert)`
1077
+ button {
1078
+ display: none;
1079
+ }
1080
+ p + div {
1081
+ margin-left: auto;
1082
+ }
1083
+ `;
809
1084
  const INITIAL_FORM_VALUES = {
810
- name: ""
1085
+ name: "",
1086
+ date: null,
1087
+ time: "",
1088
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
1089
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
1090
+ scheduledAt: null,
1091
+ timezone: null
811
1092
  };
812
1093
  const ReleasesPage = () => {
1094
+ const tabRef = React__namespace.useRef(null);
813
1095
  const location = reactRouterDom.useLocation();
814
1096
  const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
815
1097
  const toggleNotification = helperPlugin.useNotification();
@@ -819,7 +1101,12 @@ const ReleasesPage = () => {
819
1101
  const [{ query }, setQuery] = helperPlugin.useQueryParams();
820
1102
  const response = index.useGetReleasesQuery(query);
821
1103
  const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
1104
+ const { getFeature } = strapiAdmin.useLicenseLimits();
1105
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
1106
+ const { trackUsage } = helperPlugin.useTracking();
822
1107
  const { isLoading, isSuccess, isError } = response;
1108
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
1109
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
823
1110
  React__namespace.useEffect(() => {
824
1111
  if (location?.state?.errors) {
825
1112
  toggleNotification({
@@ -836,13 +1123,19 @@ const ReleasesPage = () => {
836
1123
  replace({ state: null });
837
1124
  }
838
1125
  }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
1126
+ React__namespace.useEffect(() => {
1127
+ if (tabRef.current) {
1128
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
1129
+ }
1130
+ }, [activeTabIndex]);
839
1131
  const toggleAddReleaseModal = () => {
840
1132
  setReleaseModalShown((prev) => !prev);
841
1133
  };
842
1134
  if (isLoading) {
843
- return /* @__PURE__ */ jsxRuntime.jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) }) });
1135
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
844
1136
  }
845
1137
  const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
1138
+ const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
846
1139
  const handleTabChange = (index2) => {
847
1140
  setQuery({
848
1141
  ...query,
@@ -855,10 +1148,11 @@ const ReleasesPage = () => {
855
1148
  }
856
1149
  });
857
1150
  };
858
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
859
- const handleAddRelease = async (values) => {
1151
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
860
1152
  const response2 = await createRelease({
861
- name: values.name
1153
+ name,
1154
+ scheduledAt,
1155
+ timezone
862
1156
  });
863
1157
  if ("data" in response2) {
864
1158
  toggleNotification({
@@ -868,6 +1162,7 @@ const ReleasesPage = () => {
868
1162
  defaultMessage: "Release created."
869
1163
  })
870
1164
  });
1165
+ trackUsage("didCreateRelease");
871
1166
  push(`/plugins/content-releases/${response2.data.data.id}`);
872
1167
  } else if (index.isAxiosError(response2.error)) {
873
1168
  toggleNotification({
@@ -881,8 +1176,60 @@ const ReleasesPage = () => {
881
1176
  });
882
1177
  }
883
1178
  };
884
- return /* @__PURE__ */ jsxRuntime.jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
1179
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
1180
+ /* @__PURE__ */ jsxRuntime.jsx(
1181
+ designSystem.HeaderLayout,
1182
+ {
1183
+ title: formatMessage({
1184
+ id: "content-releases.pages.Releases.title",
1185
+ defaultMessage: "Releases"
1186
+ }),
1187
+ subtitle: formatMessage(
1188
+ {
1189
+ id: "content-releases.pages.Releases.header-subtitle",
1190
+ defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
1191
+ },
1192
+ { number: totalReleases }
1193
+ ),
1194
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
1195
+ designSystem.Button,
1196
+ {
1197
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1198
+ onClick: toggleAddReleaseModal,
1199
+ disabled: hasReachedMaximumPendingReleases,
1200
+ children: formatMessage({
1201
+ id: "content-releases.header.actions.add-release",
1202
+ defaultMessage: "New release"
1203
+ })
1204
+ }
1205
+ ) })
1206
+ }
1207
+ ),
885
1208
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1209
+ activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
1210
+ StyledAlert,
1211
+ {
1212
+ marginBottom: 6,
1213
+ action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
1214
+ id: "content-releases.pages.Releases.max-limit-reached.action",
1215
+ defaultMessage: "Explore plans"
1216
+ }) }),
1217
+ title: formatMessage(
1218
+ {
1219
+ id: "content-releases.pages.Releases.max-limit-reached.title",
1220
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
1221
+ },
1222
+ { number: maximumReleases }
1223
+ ),
1224
+ onClose: () => {
1225
+ },
1226
+ closeLabel: "",
1227
+ children: formatMessage({
1228
+ id: "content-releases.pages.Releases.max-limit-reached.message",
1229
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
1230
+ })
1231
+ }
1232
+ ),
886
1233
  /* @__PURE__ */ jsxRuntime.jsxs(
887
1234
  designSystem.TabGroup,
888
1235
  {
@@ -891,8 +1238,9 @@ const ReleasesPage = () => {
891
1238
  defaultMessage: "Releases list"
892
1239
  }),
893
1240
  variant: "simple",
894
- initialSelectedTabIndex: ["pending", "done"].indexOf(activeTab),
1241
+ initialSelectedTabIndex: activeTabIndex,
895
1242
  onTabChange: handleTabChange,
1243
+ ref: tabRef,
896
1244
  children: [
897
1245
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
898
1246
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
@@ -964,4 +1312,4 @@ const App = () => {
964
1312
  ] }) });
965
1313
  };
966
1314
  exports.App = App;
967
- //# sourceMappingURL=App-o5_WfqR-.js.map
1315
+ //# sourceMappingURL=App-OK4Xac-O.js.map