@strapi/content-releases 0.0.0-experimental.cae3a5a17d131a6f59673b62d01cfac869ea9cc2 → 0.0.0-experimental.d4cb32ce579e12a4436d68036f2327132fba1309

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-iqqoPnBO.js → App-OK4Xac-O.js} +404 -164
  2. package/dist/_chunks/App-OK4Xac-O.js.map +1 -0
  3. package/dist/_chunks/{App-_Jj3tWts.mjs → App-xAkiD42p.mjs} +407 -168
  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-2DuPv5k0.js → en-r0otWaln.js} +15 -4
  10. package/dist/_chunks/en-r0otWaln.js.map +1 -0
  11. package/dist/_chunks/{en-SOqjCdyh.mjs → en-veqvqeEr.mjs} +15 -4
  12. package/dist/_chunks/en-veqvqeEr.mjs.map +1 -0
  13. package/dist/_chunks/{index-_lT-gI3M.js → index-JvA2_26n.js} +113 -26
  14. package/dist/_chunks/index-JvA2_26n.js.map +1 -0
  15. package/dist/_chunks/{index-bsuc8ZwZ.mjs → index-exoiSU3V.mjs} +118 -31
  16. package/dist/_chunks/index-exoiSU3V.mjs.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +2 -2
  19. package/dist/server/index.js +597 -382
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +597 -382
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +12 -9
  24. package/dist/_chunks/App-_Jj3tWts.mjs.map +0 -1
  25. package/dist/_chunks/App-iqqoPnBO.js.map +0 -1
  26. package/dist/_chunks/en-2DuPv5k0.js.map +0 -1
  27. package/dist/_chunks/en-SOqjCdyh.mjs.map +0 -1
  28. package/dist/_chunks/index-_lT-gI3M.js.map +0 -1
  29. package/dist/_chunks/index-bsuc8ZwZ.mjs.map +0 -1
@@ -3,14 +3,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const helperPlugin = require("@strapi/helper-plugin");
5
5
  const reactRouterDom = require("react-router-dom");
6
- const index = require("./index-_lT-gI3M.js");
6
+ const index = require("./index-JvA2_26n.js");
7
7
  const React = require("react");
8
8
  const strapiAdmin = require("@strapi/admin/strapi-admin");
9
9
  const designSystem = require("@strapi/design-system");
10
10
  const v2 = require("@strapi/design-system/v2");
11
11
  const icons = require("@strapi/icons");
12
+ const format = require("date-fns/format");
13
+ const dateFnsTz = require("date-fns-tz");
12
14
  const reactIntl = require("react-intl");
13
15
  const styled = require("styled-components");
16
+ const dateFns = require("date-fns");
14
17
  const formik = require("formik");
15
18
  const yup = require("yup");
16
19
  require("@reduxjs/toolkit/query");
@@ -37,10 +40,28 @@ function _interopNamespace(e) {
37
40
  return Object.freeze(n);
38
41
  }
39
42
  const React__namespace = /* @__PURE__ */ _interopNamespace(React);
43
+ const format__default = /* @__PURE__ */ _interopDefault(format);
40
44
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
41
45
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
42
46
  const RELEASE_SCHEMA = yup__namespace.object().shape({
43
- name: yup__namespace.string().trim().required()
47
+ name: yup__namespace.string().trim().required(),
48
+ scheduledAt: yup__namespace.string().nullable(),
49
+ isScheduled: yup__namespace.boolean().optional(),
50
+ time: yup__namespace.string().when("isScheduled", {
51
+ is: true,
52
+ then: yup__namespace.string().trim().required(),
53
+ otherwise: yup__namespace.string().nullable()
54
+ }),
55
+ timezone: yup__namespace.string().when("isScheduled", {
56
+ is: true,
57
+ then: yup__namespace.string().required().nullable(),
58
+ otherwise: yup__namespace.string().nullable()
59
+ }),
60
+ date: yup__namespace.string().when("isScheduled", {
61
+ is: true,
62
+ then: yup__namespace.string().required().nullable(),
63
+ otherwise: yup__namespace.string().nullable()
64
+ })
44
65
  }).required().noUnknown();
45
66
  const ReleaseModal = ({
46
67
  handleClose,
@@ -51,6 +72,24 @@ const ReleaseModal = ({
51
72
  const { formatMessage } = reactIntl.useIntl();
52
73
  const { pathname } = reactRouterDom.useLocation();
53
74
  const isCreatingRelease = pathname === `/plugins/${index.pluginId}`;
75
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
76
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
77
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
78
+ );
79
+ const getScheduledTimestamp = (values) => {
80
+ const { date, time, timezone } = values;
81
+ if (!date || !time || !timezone)
82
+ return null;
83
+ const formattedDate = dateFns.parse(time, "HH:mm", new Date(date));
84
+ const timezoneWithoutOffset = timezone.split("_")[1];
85
+ return dateFnsTz.zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
86
+ };
87
+ const getTimezoneWithOffset = () => {
88
+ const currentTimezone = timezoneList.find(
89
+ (timezone) => timezone.value.split("_")[1] === initialValues.timezone
90
+ );
91
+ return currentTimezone?.value || systemTimezone.value;
92
+ };
54
93
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
55
94
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
56
95
  {
@@ -62,45 +101,134 @@ const ReleaseModal = ({
62
101
  /* @__PURE__ */ jsxRuntime.jsx(
63
102
  formik.Formik,
64
103
  {
65
- validateOnChange: false,
66
- onSubmit: handleSubmit,
67
- initialValues,
104
+ onSubmit: (values) => {
105
+ handleSubmit({
106
+ ...values,
107
+ timezone: values.timezone ? values.timezone.split("_")[1] : null,
108
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
109
+ });
110
+ },
111
+ initialValues: {
112
+ ...initialValues,
113
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
114
+ },
68
115
  validationSchema: RELEASE_SCHEMA,
69
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
70
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
71
- designSystem.TextInput,
72
- {
73
- label: formatMessage({
74
- id: "content-releases.modal.form.input.label.release-name",
75
- defaultMessage: "Name"
76
- }),
77
- name: "name",
78
- value: values.name,
79
- error: errors.name,
80
- onChange: handleChange,
81
- required: true
82
- }
83
- ) }),
116
+ validateOnChange: false,
117
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx(
120
+ designSystem.TextInput,
121
+ {
122
+ label: formatMessage({
123
+ id: "content-releases.modal.form.input.label.release-name",
124
+ defaultMessage: "Name"
125
+ }),
126
+ name: "name",
127
+ value: values.name,
128
+ error: errors.name,
129
+ onChange: handleChange,
130
+ required: true
131
+ }
132
+ ),
133
+ IsSchedulingEnabled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
134
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "max-content", children: /* @__PURE__ */ jsxRuntime.jsx(
135
+ designSystem.Checkbox,
136
+ {
137
+ name: "isScheduled",
138
+ value: values.isScheduled,
139
+ onChange: (event) => {
140
+ setFieldValue("isScheduled", event.target.checked);
141
+ if (!event.target.checked) {
142
+ setFieldValue("date", null);
143
+ setFieldValue("time", "");
144
+ setFieldValue("timezone", null);
145
+ } else {
146
+ setFieldValue("date", initialValues.date);
147
+ setFieldValue("time", initialValues.time);
148
+ setFieldValue(
149
+ "timezone",
150
+ initialValues.timezone ?? systemTimezone?.value
151
+ );
152
+ }
153
+ },
154
+ children: /* @__PURE__ */ jsxRuntime.jsx(
155
+ designSystem.Typography,
156
+ {
157
+ textColor: values.isScheduled ? "primary600" : "neutral800",
158
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
159
+ children: formatMessage({
160
+ id: "modal.form.input.label.schedule-release",
161
+ defaultMessage: "Schedule release"
162
+ })
163
+ }
164
+ )
165
+ }
166
+ ) }),
167
+ values.isScheduled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
168
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "start", children: [
169
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
170
+ designSystem.DatePicker,
171
+ {
172
+ label: formatMessage({
173
+ id: "content-releases.modal.form.input.label.date",
174
+ defaultMessage: "Date"
175
+ }),
176
+ name: "date",
177
+ error: errors.date,
178
+ onChange: (date) => {
179
+ const isoFormatDate = date ? dateFns.formatISO(date, { representation: "date" }) : null;
180
+ setFieldValue("date", isoFormatDate);
181
+ },
182
+ clearLabel: formatMessage({
183
+ id: "content-releases.modal.form.input.clearLabel",
184
+ defaultMessage: "Clear"
185
+ }),
186
+ onClear: () => {
187
+ setFieldValue("date", null);
188
+ },
189
+ selectedDate: values.date || void 0,
190
+ required: true
191
+ }
192
+ ) }),
193
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
194
+ designSystem.TimePicker,
195
+ {
196
+ label: formatMessage({
197
+ id: "content-releases.modal.form.input.label.time",
198
+ defaultMessage: "Time"
199
+ }),
200
+ name: "time",
201
+ error: errors.time,
202
+ onChange: (time) => {
203
+ setFieldValue("time", time);
204
+ },
205
+ clearLabel: formatMessage({
206
+ id: "content-releases.modal.form.input.clearLabel",
207
+ defaultMessage: "Clear"
208
+ }),
209
+ onClear: () => {
210
+ setFieldValue("time", "");
211
+ },
212
+ value: values.time || void 0,
213
+ required: true
214
+ }
215
+ ) })
216
+ ] }),
217
+ /* @__PURE__ */ jsxRuntime.jsx(TimezoneComponent, { timezoneOptions: timezoneList })
218
+ ] })
219
+ ] })
220
+ ] }) }),
84
221
  /* @__PURE__ */ jsxRuntime.jsx(
85
222
  designSystem.ModalFooter,
86
223
  {
87
224
  startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
88
- endActions: /* @__PURE__ */ jsxRuntime.jsx(
89
- designSystem.Button,
225
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
90
226
  {
91
- name: "submit",
92
- loading: isLoading,
93
- disabled: !values.name || values.name === initialValues.name,
94
- type: "submit",
95
- children: formatMessage(
96
- {
97
- id: "content-releases.modal.form.button.submit",
98
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
99
- },
100
- { isCreatingRelease }
101
- )
102
- }
103
- )
227
+ id: "content-releases.modal.form.button.submit",
228
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
229
+ },
230
+ { isCreatingRelease }
231
+ ) })
104
232
  }
105
233
  )
106
234
  ] })
@@ -108,16 +236,59 @@ const ReleaseModal = ({
108
236
  )
109
237
  ] });
110
238
  };
239
+ const getTimezones = (selectedDate) => {
240
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
241
+ const utcOffset = index.getTimezoneOffset(timezone, selectedDate);
242
+ return { offset: utcOffset, value: `${utcOffset}_${timezone}` };
243
+ });
244
+ const systemTimezone = timezoneList.find(
245
+ (timezone) => timezone.value.split("_")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
246
+ );
247
+ return { timezoneList, systemTimezone };
248
+ };
249
+ const TimezoneComponent = ({ timezoneOptions }) => {
250
+ const { values, errors, setFieldValue } = formik.useFormikContext();
251
+ const { formatMessage } = reactIntl.useIntl();
252
+ const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
253
+ React__namespace.useEffect(() => {
254
+ if (values.date) {
255
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
256
+ setTimezoneList(timezoneList2);
257
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("_")[1] === values.timezone.split("_")[1]);
258
+ if (updatedTimezone) {
259
+ setFieldValue("timezone", updatedTimezone.value);
260
+ }
261
+ }
262
+ }, [setFieldValue, values.date, values.timezone]);
263
+ return /* @__PURE__ */ jsxRuntime.jsx(
264
+ designSystem.Combobox,
265
+ {
266
+ label: formatMessage({
267
+ id: "content-releases.modal.form.input.label.timezone",
268
+ defaultMessage: "Timezone"
269
+ }),
270
+ name: "timezone",
271
+ value: values.timezone || void 0,
272
+ textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
273
+ onChange: (timezone) => {
274
+ setFieldValue("timezone", timezone);
275
+ },
276
+ 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
+ };
111
285
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
112
286
  align-self: stretch;
113
287
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
114
288
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
115
289
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
116
290
  `;
117
- const StyledFlex = styled__default.default(designSystem.Flex)`
118
- align-self: stretch;
119
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
120
-
291
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
121
292
  svg path {
122
293
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
123
294
  }
@@ -126,15 +297,15 @@ const StyledFlex = styled__default.default(designSystem.Flex)`
126
297
  }
127
298
  `;
128
299
  const PencilIcon = styled__default.default(icons.Pencil)`
129
- width: ${({ theme }) => theme.spaces[4]};
130
- height: ${({ theme }) => theme.spaces[4]};
300
+ width: ${({ theme }) => theme.spaces[3]};
301
+ height: ${({ theme }) => theme.spaces[3]};
131
302
  path {
132
303
  fill: ${({ theme }) => theme.colors.neutral600};
133
304
  }
134
305
  `;
135
306
  const TrashIcon = styled__default.default(icons.Trash)`
136
- width: ${({ theme }) => theme.spaces[4]};
137
- height: ${({ theme }) => theme.spaces[4]};
307
+ width: ${({ theme }) => theme.spaces[3]};
308
+ height: ${({ theme }) => theme.spaces[3]};
138
309
  path {
139
310
  fill: ${({ theme }) => theme.colors.danger600};
140
311
  }
@@ -142,24 +313,6 @@ const TrashIcon = styled__default.default(icons.Trash)`
142
313
  const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
143
314
  max-width: 300px;
144
315
  `;
145
- const PopoverButton = ({ onClick, disabled, children }) => {
146
- return /* @__PURE__ */ jsxRuntime.jsx(
147
- StyledFlex,
148
- {
149
- paddingTop: 2,
150
- paddingBottom: 2,
151
- paddingLeft: 4,
152
- paddingRight: 4,
153
- alignItems: "center",
154
- gap: 2,
155
- as: "button",
156
- hasRadius: true,
157
- onClick,
158
- disabled,
159
- children
160
- }
161
- );
162
- };
163
316
  const EntryValidationText = ({ action, schema, components, entry }) => {
164
317
  const { formatMessage } = reactIntl.useIntl();
165
318
  const { validate } = strapiAdmin.unstable_useDocument();
@@ -208,10 +361,8 @@ const ReleaseDetailsLayout = ({
208
361
  toggleWarningSubmit,
209
362
  children
210
363
  }) => {
211
- const { formatMessage } = reactIntl.useIntl();
364
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
212
365
  const { releaseId } = reactRouterDom.useParams();
213
- const [isPopoverVisible, setIsPopoverVisible] = React__namespace.useState(false);
214
- const moreButtonRef = React__namespace.useRef(null);
215
366
  const {
216
367
  data,
217
368
  isLoading: isLoadingDetails,
@@ -225,14 +376,8 @@ const ReleaseDetailsLayout = ({
225
376
  allowedActions: { canUpdate, canDelete }
226
377
  } = helperPlugin.useRBAC(index.PERMISSIONS);
227
378
  const dispatch = index.useTypedDispatch();
379
+ const { trackUsage } = helperPlugin.useTracking();
228
380
  const release = data?.data;
229
- const handleTogglePopover = () => {
230
- setIsPopoverVisible((prev) => !prev);
231
- };
232
- const openReleaseModal = () => {
233
- toggleEditReleaseModal();
234
- handleTogglePopover();
235
- };
236
381
  const handlePublishRelease = async () => {
237
382
  const response = await publishRelease({ id: releaseId });
238
383
  if ("data" in response) {
@@ -243,6 +388,12 @@ const ReleaseDetailsLayout = ({
243
388
  defaultMessage: "Release was published successfully."
244
389
  })
245
390
  });
391
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
392
+ trackUsage("didPublishRelease", {
393
+ totalEntries: totalEntries2,
394
+ totalPublishedEntries,
395
+ totalUnpublishedEntries
396
+ });
246
397
  } else if (index.isAxiosError(response.error)) {
247
398
  toggleNotification({
248
399
  type: "warning",
@@ -255,13 +406,21 @@ const ReleaseDetailsLayout = ({
255
406
  });
256
407
  }
257
408
  };
258
- const openWarningConfirmDialog = () => {
259
- toggleWarningSubmit();
260
- handleTogglePopover();
261
- };
262
409
  const handleRefresh = () => {
263
410
  dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
264
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;
423
+ };
265
424
  if (isLoadingDetails) {
266
425
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
267
426
  }
@@ -283,90 +442,139 @@ const ReleaseDetailsLayout = ({
283
442
  );
284
443
  }
285
444
  const totalEntries = release.actions.meta.count || 0;
286
- 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
+ ) : "";
287
475
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
288
476
  /* @__PURE__ */ jsxRuntime.jsx(
289
477
  designSystem.HeaderLayout,
290
478
  {
291
479
  title: release.name,
292
- subtitle: formatMessage(
293
- {
294
- id: "content-releases.pages.Details.header-subtitle",
295
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
296
- },
297
- { number: totalEntries }
298
- ),
480
+ subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
299
481
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
300
482
  id: "global.back",
301
483
  defaultMessage: "Back"
302
484
  }) }),
303
485
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
304
- /* @__PURE__ */ jsxRuntime.jsx(
305
- designSystem.IconButton,
306
- {
307
- label: formatMessage({
308
- id: "content-releases.header.actions.open-release-actions",
309
- defaultMessage: "Release actions"
310
- }),
311
- ref: moreButtonRef,
312
- onClick: handleTogglePopover,
313
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {})
314
- }
315
- ),
316
- isPopoverVisible && /* @__PURE__ */ jsxRuntime.jsxs(
317
- designSystem.Popover,
318
- {
319
- source: moreButtonRef,
320
- placement: "bottom-end",
321
- onDismiss: handleTogglePopover,
322
- spacing: 4,
323
- minWidth: "242px",
324
- children: [
325
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
326
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
327
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
328
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
329
- id: "content-releases.header.actions.edit",
330
- defaultMessage: "Edit"
331
- }) })
332
- ] }),
333
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
334
- /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
335
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
336
- id: "content-releases.header.actions.delete",
337
- defaultMessage: "Delete"
338
- }) })
339
- ] })
340
- ] }),
341
- /* @__PURE__ */ jsxRuntime.jsxs(
342
- ReleaseInfoWrapper,
343
- {
344
- direction: "column",
345
- justifyContent: "center",
346
- alignItems: "flex-start",
347
- gap: 1,
348
- padding: 5,
349
- children: [
350
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
351
- id: "content-releases.header.actions.created",
352
- defaultMessage: "Created"
353
- }) }),
354
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
355
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
356
- formatMessage(
357
- {
358
- id: "content-releases.header.actions.created.description",
359
- defaultMessage: " by {createdBy}"
360
- },
361
- { createdBy }
362
- )
363
- ] })
364
- ]
365
- }
366
- )
367
- ]
368
- }
369
- ),
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
+ ] }),
370
578
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
371
579
  id: "content-releases.header.actions.refresh",
372
580
  defaultMessage: "Refresh"
@@ -422,6 +630,9 @@ const ReleaseDetailsBody = () => {
422
630
  isError: isReleaseError,
423
631
  error: releaseError
424
632
  } = index.useGetReleaseQuery({ id: releaseId });
633
+ const {
634
+ allowedActions: { canUpdate }
635
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
425
636
  const release = releaseData?.data;
426
637
  const selectedGroupBy = query?.groupBy || "contentType";
427
638
  const {
@@ -528,7 +739,7 @@ const ReleaseDetailsBody = () => {
528
739
  designSystem.SingleSelect,
529
740
  {
530
741
  "aria-label": formatMessage({
531
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
742
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
532
743
  defaultMessage: "Group by"
533
744
  }),
534
745
  customizeContent: (value) => formatMessage(
@@ -546,7 +757,7 @@ const ReleaseDetailsBody = () => {
546
757
  }
547
758
  ) }),
548
759
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
549
- /* @__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 }) }),
550
761
  /* @__PURE__ */ jsxRuntime.jsx(
551
762
  helperPlugin.Table.Root,
552
763
  {
@@ -635,7 +846,8 @@ const ReleaseDetailsBody = () => {
635
846
  {
636
847
  selected: type,
637
848
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
638
- name: `release-action-${id}-type`
849
+ name: `release-action-${id}-type`,
850
+ disabled: !canUpdate
639
851
  }
640
852
  ) }),
641
853
  !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -714,11 +926,18 @@ const ReleaseDetailsPage = () => {
714
926
  }
715
927
  );
716
928
  }
717
- 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") : "";
718
935
  const handleEditRelease = async (values) => {
719
936
  const response = await updateRelease({
720
937
  id: releaseId,
721
- name: values.name
938
+ name: values.name,
939
+ scheduledAt: values.scheduledAt,
940
+ timezone: values.timezone
722
941
  });
723
942
  if ("data" in response) {
724
943
  toggleNotification({
@@ -772,7 +991,14 @@ const ReleaseDetailsPage = () => {
772
991
  handleClose: toggleEditReleaseModal,
773
992
  handleSubmit: handleEditRelease,
774
993
  isLoading: isLoadingDetails || isSubmittingForm,
775
- initialValues: { name: title || "" }
994
+ initialValues: {
995
+ name: title || "",
996
+ scheduledAt,
997
+ date,
998
+ time,
999
+ isScheduled: Boolean(scheduledAt),
1000
+ timezone
1001
+ }
776
1002
  }
777
1003
  ),
778
1004
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -797,6 +1023,7 @@ const LinkCard = styled__default.default(v2.Link)`
797
1023
  `;
798
1024
  const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
799
1025
  const { formatMessage } = reactIntl.useIntl();
1026
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
800
1027
  if (isError) {
801
1028
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
802
1029
  }
@@ -817,7 +1044,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
817
1044
  }
818
1045
  );
819
1046
  }
820
- 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(
821
1048
  designSystem.Flex,
822
1049
  {
823
1050
  direction: "column",
@@ -832,7 +1059,10 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
832
1059
  gap: 2,
833
1060
  children: [
834
1061
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
835
- /* @__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(
836
1066
  {
837
1067
  id: "content-releases.page.Releases.release-item.entries",
838
1068
  defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
@@ -852,7 +1082,13 @@ const StyledAlert = styled__default.default(designSystem.Alert)`
852
1082
  }
853
1083
  `;
854
1084
  const INITIAL_FORM_VALUES = {
855
- 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
856
1092
  };
857
1093
  const ReleasesPage = () => {
858
1094
  const tabRef = React__namespace.useRef(null);
@@ -866,7 +1102,8 @@ const ReleasesPage = () => {
866
1102
  const response = index.useGetReleasesQuery(query);
867
1103
  const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
868
1104
  const { getFeature } = strapiAdmin.useLicenseLimits();
869
- const { maximumNumberOfPendingReleases = 3 } = getFeature("cms-content-releases");
1105
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
1106
+ const { trackUsage } = helperPlugin.useTracking();
870
1107
  const { isLoading, isSuccess, isError } = response;
871
1108
  const activeTab = response?.currentData?.meta?.activeTab || "pending";
872
1109
  const activeTabIndex = ["pending", "done"].indexOf(activeTab);
@@ -898,7 +1135,7 @@ const ReleasesPage = () => {
898
1135
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
899
1136
  }
900
1137
  const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
901
- const hasReachedMaximumPendingReleases = totalReleases >= maximumNumberOfPendingReleases;
1138
+ const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
902
1139
  const handleTabChange = (index2) => {
903
1140
  setQuery({
904
1141
  ...query,
@@ -911,9 +1148,11 @@ const ReleasesPage = () => {
911
1148
  }
912
1149
  });
913
1150
  };
914
- const handleAddRelease = async (values) => {
1151
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
915
1152
  const response2 = await createRelease({
916
- name: values.name
1153
+ name,
1154
+ scheduledAt,
1155
+ timezone
917
1156
  });
918
1157
  if ("data" in response2) {
919
1158
  toggleNotification({
@@ -923,6 +1162,7 @@ const ReleasesPage = () => {
923
1162
  defaultMessage: "Release created."
924
1163
  })
925
1164
  });
1165
+ trackUsage("didCreateRelease");
926
1166
  push(`/plugins/content-releases/${response2.data.data.id}`);
927
1167
  } else if (index.isAxiosError(response2.error)) {
928
1168
  toggleNotification({
@@ -979,7 +1219,7 @@ const ReleasesPage = () => {
979
1219
  id: "content-releases.pages.Releases.max-limit-reached.title",
980
1220
  defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
981
1221
  },
982
- { number: maximumNumberOfPendingReleases }
1222
+ { number: maximumReleases }
983
1223
  ),
984
1224
  onClose: () => {
985
1225
  },
@@ -1072,4 +1312,4 @@ const App = () => {
1072
1312
  ] }) });
1073
1313
  };
1074
1314
  exports.App = App;
1075
- //# sourceMappingURL=App-iqqoPnBO.js.map
1315
+ //# sourceMappingURL=App-OK4Xac-O.js.map