@strapi/content-releases 0.0.0-next.c5f067b5650921187770124e9b6c8186e805e242 → 0.0.0-next.ca6c7c80ec0b8e77d4b03cd0411e5302de26b8ce

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-5PsAyVt2.js → App-HjWtUYmc.js} +728 -459
  2. package/dist/_chunks/App-HjWtUYmc.js.map +1 -0
  3. package/dist/_chunks/{App-3ycH2d3s.mjs → App-gu1aiP6i.mjs} +738 -470
  4. package/dist/_chunks/App-gu1aiP6i.mjs.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  9. package/dist/_chunks/{en-2DuPv5k0.js → en-HrREghh3.js} +27 -7
  10. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  11. package/dist/_chunks/{en-SOqjCdyh.mjs → en-ltT1TlKQ.mjs} +27 -7
  12. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  13. package/dist/_chunks/{index-D57Rztnc.js → index-ZNwxYN8H.js} +437 -32
  14. package/dist/_chunks/index-ZNwxYN8H.js.map +1 -0
  15. package/dist/_chunks/{index-4gUWuCQV.mjs → index-mvj9PSKd.mjs} +453 -48
  16. package/dist/_chunks/index-mvj9PSKd.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 +1037 -421
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +1036 -421
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +15 -12
  24. package/dist/_chunks/App-3ycH2d3s.mjs.map +0 -1
  25. package/dist/_chunks/App-5PsAyVt2.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-4gUWuCQV.mjs.map +0 -1
  29. package/dist/_chunks/index-D57Rztnc.js.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-D57Rztnc.js");
6
+ const index = require("./index-ZNwxYN8H.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,22 @@ const ReleaseModal = ({
51
72
  const { formatMessage } = reactIntl.useIntl();
52
73
  const { pathname } = reactRouterDom.useLocation();
53
74
  const isCreatingRelease = pathname === `/plugins/${index.pluginId}`;
75
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
76
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
77
+ );
78
+ const getScheduledTimestamp = (values) => {
79
+ const { date, time, timezone } = values;
80
+ if (!date || !time || !timezone)
81
+ return null;
82
+ const timezoneWithoutOffset = timezone.split("&")[1];
83
+ return dateFnsTz.zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
84
+ };
85
+ const getTimezoneWithOffset = () => {
86
+ const currentTimezone = timezoneList.find(
87
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
88
+ );
89
+ return currentTimezone?.value || systemTimezone.value;
90
+ };
54
91
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
55
92
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
56
93
  {
@@ -62,45 +99,130 @@ const ReleaseModal = ({
62
99
  /* @__PURE__ */ jsxRuntime.jsx(
63
100
  formik.Formik,
64
101
  {
65
- validateOnChange: false,
66
- onSubmit: handleSubmit,
67
- initialValues,
102
+ onSubmit: (values) => {
103
+ handleSubmit({
104
+ ...values,
105
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
106
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
107
+ });
108
+ },
109
+ initialValues: {
110
+ ...initialValues,
111
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
112
+ },
68
113
  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
- ) }),
114
+ validateOnChange: false,
115
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
116
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
117
+ /* @__PURE__ */ jsxRuntime.jsx(
118
+ designSystem.TextInput,
119
+ {
120
+ label: formatMessage({
121
+ id: "content-releases.modal.form.input.label.release-name",
122
+ defaultMessage: "Name"
123
+ }),
124
+ name: "name",
125
+ value: values.name,
126
+ error: errors.name,
127
+ onChange: handleChange,
128
+ required: true
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "max-content", children: /* @__PURE__ */ jsxRuntime.jsx(
132
+ designSystem.Checkbox,
133
+ {
134
+ name: "isScheduled",
135
+ value: values.isScheduled,
136
+ onChange: (event) => {
137
+ setFieldValue("isScheduled", event.target.checked);
138
+ if (!event.target.checked) {
139
+ setFieldValue("date", null);
140
+ setFieldValue("time", "");
141
+ setFieldValue("timezone", null);
142
+ } else {
143
+ setFieldValue("date", initialValues.date);
144
+ setFieldValue("time", initialValues.time);
145
+ setFieldValue("timezone", initialValues.timezone ?? systemTimezone?.value);
146
+ }
147
+ },
148
+ children: /* @__PURE__ */ jsxRuntime.jsx(
149
+ designSystem.Typography,
150
+ {
151
+ textColor: values.isScheduled ? "primary600" : "neutral800",
152
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
153
+ children: formatMessage({
154
+ id: "modal.form.input.label.schedule-release",
155
+ defaultMessage: "Schedule release"
156
+ })
157
+ }
158
+ )
159
+ }
160
+ ) }),
161
+ values.isScheduled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
162
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "start", children: [
163
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
164
+ designSystem.DatePicker,
165
+ {
166
+ label: formatMessage({
167
+ id: "content-releases.modal.form.input.label.date",
168
+ defaultMessage: "Date"
169
+ }),
170
+ name: "date",
171
+ error: errors.date,
172
+ onChange: (date) => {
173
+ const isoFormatDate = date ? dateFns.formatISO(date, { representation: "date" }) : null;
174
+ setFieldValue("date", isoFormatDate);
175
+ },
176
+ clearLabel: formatMessage({
177
+ id: "content-releases.modal.form.input.clearLabel",
178
+ defaultMessage: "Clear"
179
+ }),
180
+ onClear: () => {
181
+ setFieldValue("date", null);
182
+ },
183
+ selectedDate: values.date || void 0,
184
+ required: true,
185
+ minDate: dateFnsTz.utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
186
+ }
187
+ ) }),
188
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
189
+ designSystem.TimePicker,
190
+ {
191
+ label: formatMessage({
192
+ id: "content-releases.modal.form.input.label.time",
193
+ defaultMessage: "Time"
194
+ }),
195
+ name: "time",
196
+ error: errors.time,
197
+ onChange: (time) => {
198
+ setFieldValue("time", time);
199
+ },
200
+ clearLabel: formatMessage({
201
+ id: "content-releases.modal.form.input.clearLabel",
202
+ defaultMessage: "Clear"
203
+ }),
204
+ onClear: () => {
205
+ setFieldValue("time", "");
206
+ },
207
+ value: values.time || void 0,
208
+ required: true
209
+ }
210
+ ) })
211
+ ] }),
212
+ /* @__PURE__ */ jsxRuntime.jsx(TimezoneComponent, { timezoneOptions: timezoneList })
213
+ ] })
214
+ ] }) }),
84
215
  /* @__PURE__ */ jsxRuntime.jsx(
85
216
  designSystem.ModalFooter,
86
217
  {
87
218
  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,
219
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
90
220
  {
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
- )
221
+ id: "content-releases.modal.form.button.submit",
222
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
223
+ },
224
+ { isCreatingRelease }
225
+ ) })
104
226
  }
105
227
  )
106
228
  ] })
@@ -108,33 +230,399 @@ const ReleaseModal = ({
108
230
  )
109
231
  ] });
110
232
  };
233
+ const getTimezones = (selectedDate) => {
234
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
235
+ const utcOffset = index.getTimezoneOffset(timezone, selectedDate);
236
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
237
+ });
238
+ const systemTimezone = timezoneList.find(
239
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
240
+ );
241
+ return { timezoneList, systemTimezone };
242
+ };
243
+ const TimezoneComponent = ({ timezoneOptions }) => {
244
+ const { values, errors, setFieldValue } = formik.useFormikContext();
245
+ const { formatMessage } = reactIntl.useIntl();
246
+ const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
247
+ React__namespace.useEffect(() => {
248
+ if (values.date) {
249
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
250
+ setTimezoneList(timezoneList2);
251
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
252
+ if (updatedTimezone) {
253
+ setFieldValue("timezone", updatedTimezone.value);
254
+ }
255
+ }
256
+ }, [setFieldValue, values.date, values.timezone]);
257
+ return /* @__PURE__ */ jsxRuntime.jsx(
258
+ designSystem.Combobox,
259
+ {
260
+ label: formatMessage({
261
+ id: "content-releases.modal.form.input.label.timezone",
262
+ defaultMessage: "Timezone"
263
+ }),
264
+ autocomplete: { type: "list", filter: "contains" },
265
+ name: "timezone",
266
+ value: values.timezone || void 0,
267
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
268
+ onChange: (timezone) => {
269
+ setFieldValue("timezone", timezone);
270
+ },
271
+ onTextValueChange: (timezone) => {
272
+ setFieldValue("timezone", timezone);
273
+ },
274
+ onClear: () => {
275
+ setFieldValue("timezone", "");
276
+ },
277
+ error: errors.timezone,
278
+ required: true,
279
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
280
+ }
281
+ );
282
+ };
283
+ const LinkCard = styled__default.default(v2.Link)`
284
+ display: block;
285
+ `;
286
+ const RelativeTime = styled__default.default(helperPlugin.RelativeTime)`
287
+ display: inline-block;
288
+ &::first-letter {
289
+ text-transform: uppercase;
290
+ }
291
+ `;
292
+ const getBadgeProps = (status) => {
293
+ let color;
294
+ switch (status) {
295
+ case "ready":
296
+ color = "success";
297
+ break;
298
+ case "blocked":
299
+ color = "warning";
300
+ break;
301
+ case "failed":
302
+ color = "danger";
303
+ break;
304
+ case "done":
305
+ color = "primary";
306
+ break;
307
+ case "empty":
308
+ default:
309
+ color = "neutral";
310
+ }
311
+ return {
312
+ textColor: `${color}600`,
313
+ backgroundColor: `${color}100`,
314
+ borderColor: `${color}200`
315
+ };
316
+ };
317
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
318
+ const { formatMessage } = reactIntl.useIntl();
319
+ if (isError) {
320
+ return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
321
+ }
322
+ if (releases?.length === 0) {
323
+ return /* @__PURE__ */ jsxRuntime.jsx(
324
+ designSystem.EmptyStateLayout,
325
+ {
326
+ content: formatMessage(
327
+ {
328
+ id: "content-releases.page.Releases.tab.emptyEntries",
329
+ defaultMessage: "No releases"
330
+ },
331
+ {
332
+ target: sectionTitle
333
+ }
334
+ ),
335
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
336
+ }
337
+ );
338
+ }
339
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid, { gap: 4, children: releases.map(({ id, name, scheduledAt, status }) => /* @__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(
340
+ designSystem.Flex,
341
+ {
342
+ direction: "column",
343
+ justifyContent: "space-between",
344
+ padding: 4,
345
+ hasRadius: true,
346
+ background: "neutral0",
347
+ shadow: "tableShadow",
348
+ height: "100%",
349
+ width: "100%",
350
+ alignItems: "start",
351
+ gap: 4,
352
+ children: [
353
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "start", gap: 1, children: [
354
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
355
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsxRuntime.jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
356
+ id: "content-releases.pages.Releases.not-scheduled",
357
+ defaultMessage: "Not scheduled"
358
+ }) })
359
+ ] }),
360
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(status), children: status })
361
+ ]
362
+ }
363
+ ) }) }, id)) });
364
+ };
365
+ const StyledAlert = styled__default.default(designSystem.Alert)`
366
+ button {
367
+ display: none;
368
+ }
369
+ p + div {
370
+ margin-left: auto;
371
+ }
372
+ `;
373
+ const INITIAL_FORM_VALUES = {
374
+ name: "",
375
+ date: null,
376
+ time: "",
377
+ isScheduled: true,
378
+ scheduledAt: null,
379
+ timezone: null
380
+ };
381
+ const ReleasesPage = () => {
382
+ const tabRef = React__namespace.useRef(null);
383
+ const location = reactRouterDom.useLocation();
384
+ const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
385
+ const toggleNotification = helperPlugin.useNotification();
386
+ const { formatMessage } = reactIntl.useIntl();
387
+ const { push, replace } = reactRouterDom.useHistory();
388
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
389
+ const [{ query }, setQuery] = helperPlugin.useQueryParams();
390
+ const response = index.useGetReleasesQuery(query);
391
+ const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
392
+ const { getFeature } = strapiAdmin.useLicenseLimits();
393
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
394
+ const { trackUsage } = helperPlugin.useTracking();
395
+ const { isLoading, isSuccess, isError } = response;
396
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
397
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
398
+ React__namespace.useEffect(() => {
399
+ if (location?.state?.errors) {
400
+ toggleNotification({
401
+ type: "warning",
402
+ title: formatMessage({
403
+ id: "content-releases.pages.Releases.notification.error.title",
404
+ defaultMessage: "Your request could not be processed."
405
+ }),
406
+ message: formatMessage({
407
+ id: "content-releases.pages.Releases.notification.error.message",
408
+ defaultMessage: "Please try again or open another release."
409
+ })
410
+ });
411
+ replace({ state: null });
412
+ }
413
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
414
+ React__namespace.useEffect(() => {
415
+ if (tabRef.current) {
416
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
417
+ }
418
+ }, [activeTabIndex]);
419
+ const toggleAddReleaseModal = () => {
420
+ setReleaseModalShown((prev) => !prev);
421
+ };
422
+ if (isLoading) {
423
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
424
+ }
425
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
426
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
427
+ const handleTabChange = (index2) => {
428
+ setQuery({
429
+ ...query,
430
+ page: 1,
431
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
432
+ filters: {
433
+ releasedAt: {
434
+ $notNull: index2 === 0 ? false : true
435
+ }
436
+ }
437
+ });
438
+ };
439
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
440
+ const response2 = await createRelease({
441
+ name,
442
+ scheduledAt,
443
+ timezone
444
+ });
445
+ if ("data" in response2) {
446
+ toggleNotification({
447
+ type: "success",
448
+ message: formatMessage({
449
+ id: "content-releases.modal.release-created-notification-success",
450
+ defaultMessage: "Release created."
451
+ })
452
+ });
453
+ trackUsage("didCreateRelease");
454
+ push(`/plugins/content-releases/${response2.data.data.id}`);
455
+ } else if (index.isAxiosError(response2.error)) {
456
+ toggleNotification({
457
+ type: "warning",
458
+ message: formatAPIError(response2.error)
459
+ });
460
+ } else {
461
+ toggleNotification({
462
+ type: "warning",
463
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
464
+ });
465
+ }
466
+ };
467
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
468
+ /* @__PURE__ */ jsxRuntime.jsx(
469
+ designSystem.HeaderLayout,
470
+ {
471
+ title: formatMessage({
472
+ id: "content-releases.pages.Releases.title",
473
+ defaultMessage: "Releases"
474
+ }),
475
+ subtitle: formatMessage({
476
+ id: "content-releases.pages.Releases.header-subtitle",
477
+ defaultMessage: "Create and manage content updates"
478
+ }),
479
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
480
+ designSystem.Button,
481
+ {
482
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
483
+ onClick: toggleAddReleaseModal,
484
+ disabled: hasReachedMaximumPendingReleases,
485
+ children: formatMessage({
486
+ id: "content-releases.header.actions.add-release",
487
+ defaultMessage: "New release"
488
+ })
489
+ }
490
+ ) })
491
+ }
492
+ ),
493
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
494
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
495
+ StyledAlert,
496
+ {
497
+ marginBottom: 6,
498
+ action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
499
+ id: "content-releases.pages.Releases.max-limit-reached.action",
500
+ defaultMessage: "Explore plans"
501
+ }) }),
502
+ title: formatMessage(
503
+ {
504
+ id: "content-releases.pages.Releases.max-limit-reached.title",
505
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
506
+ },
507
+ { number: maximumReleases }
508
+ ),
509
+ onClose: () => {
510
+ },
511
+ closeLabel: "",
512
+ children: formatMessage({
513
+ id: "content-releases.pages.Releases.max-limit-reached.message",
514
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
515
+ })
516
+ }
517
+ ),
518
+ /* @__PURE__ */ jsxRuntime.jsxs(
519
+ designSystem.TabGroup,
520
+ {
521
+ label: formatMessage({
522
+ id: "content-releases.pages.Releases.tab-group.label",
523
+ defaultMessage: "Releases list"
524
+ }),
525
+ variant: "simple",
526
+ initialSelectedTabIndex: activeTabIndex,
527
+ onTabChange: handleTabChange,
528
+ ref: tabRef,
529
+ children: [
530
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
531
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
532
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage(
533
+ {
534
+ id: "content-releases.pages.Releases.tab.pending",
535
+ defaultMessage: "Pending ({count})"
536
+ },
537
+ {
538
+ count: totalPendingReleases
539
+ }
540
+ ) }),
541
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
542
+ id: "content-releases.pages.Releases.tab.done",
543
+ defaultMessage: "Done"
544
+ }) })
545
+ ] }),
546
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
547
+ ] }),
548
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
549
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
550
+ ReleasesGrid,
551
+ {
552
+ sectionTitle: "pending",
553
+ releases: response?.currentData?.data,
554
+ isError
555
+ }
556
+ ) }),
557
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
558
+ ReleasesGrid,
559
+ {
560
+ sectionTitle: "done",
561
+ releases: response?.currentData?.data,
562
+ isError
563
+ }
564
+ ) })
565
+ ] })
566
+ ]
567
+ }
568
+ ),
569
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
570
+ /* @__PURE__ */ jsxRuntime.jsx(
571
+ helperPlugin.PageSizeURLQuery,
572
+ {
573
+ options: ["8", "16", "32", "64"],
574
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
575
+ }
576
+ ),
577
+ /* @__PURE__ */ jsxRuntime.jsx(
578
+ helperPlugin.PaginationURLQuery,
579
+ {
580
+ pagination: {
581
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
582
+ }
583
+ }
584
+ )
585
+ ] }) : null
586
+ ] }) }),
587
+ releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
588
+ ReleaseModal,
589
+ {
590
+ handleClose: toggleAddReleaseModal,
591
+ handleSubmit: handleAddRelease,
592
+ isLoading: isSubmittingForm,
593
+ initialValues: INITIAL_FORM_VALUES
594
+ }
595
+ )
596
+ ] });
597
+ };
111
598
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
112
599
  align-self: stretch;
113
600
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
114
601
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
115
602
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
116
603
  `;
117
- const StyledFlex = styled__default.default(designSystem.Flex)`
118
- align-self: stretch;
119
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
120
-
604
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
121
605
  svg path {
122
606
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
123
607
  }
124
608
  span {
125
609
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
126
610
  }
611
+
612
+ &:hover {
613
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
614
+ }
127
615
  `;
128
616
  const PencilIcon = styled__default.default(icons.Pencil)`
129
- width: ${({ theme }) => theme.spaces[4]};
130
- height: ${({ theme }) => theme.spaces[4]};
617
+ width: ${({ theme }) => theme.spaces[3]};
618
+ height: ${({ theme }) => theme.spaces[3]};
131
619
  path {
132
620
  fill: ${({ theme }) => theme.colors.neutral600};
133
621
  }
134
622
  `;
135
623
  const TrashIcon = styled__default.default(icons.Trash)`
136
- width: ${({ theme }) => theme.spaces[4]};
137
- height: ${({ theme }) => theme.spaces[4]};
624
+ width: ${({ theme }) => theme.spaces[3]};
625
+ height: ${({ theme }) => theme.spaces[3]};
138
626
  path {
139
627
  fill: ${({ theme }) => theme.colors.danger600};
140
628
  }
@@ -142,24 +630,6 @@ const TrashIcon = styled__default.default(icons.Trash)`
142
630
  const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
143
631
  max-width: 300px;
144
632
  `;
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
633
  const EntryValidationText = ({ action, schema, components, entry }) => {
164
634
  const { formatMessage } = reactIntl.useIntl();
165
635
  const { validate } = strapiAdmin.unstable_useDocument();
@@ -208,10 +678,8 @@ const ReleaseDetailsLayout = ({
208
678
  toggleWarningSubmit,
209
679
  children
210
680
  }) => {
211
- const { formatMessage } = reactIntl.useIntl();
681
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
212
682
  const { releaseId } = reactRouterDom.useParams();
213
- const [isPopoverVisible, setIsPopoverVisible] = React__namespace.useState(false);
214
- const moreButtonRef = React__namespace.useRef(null);
215
683
  const {
216
684
  data,
217
685
  isLoading: isLoadingDetails,
@@ -227,13 +695,6 @@ const ReleaseDetailsLayout = ({
227
695
  const dispatch = index.useTypedDispatch();
228
696
  const { trackUsage } = helperPlugin.useTracking();
229
697
  const release = data?.data;
230
- const handleTogglePopover = () => {
231
- setIsPopoverVisible((prev) => !prev);
232
- };
233
- const openReleaseModal = () => {
234
- toggleEditReleaseModal();
235
- handleTogglePopover();
236
- };
237
698
  const handlePublishRelease = async () => {
238
699
  const response = await publishRelease({ id: releaseId });
239
700
  if ("data" in response) {
@@ -262,12 +723,25 @@ const ReleaseDetailsLayout = ({
262
723
  });
263
724
  }
264
725
  };
265
- const openWarningConfirmDialog = () => {
266
- toggleWarningSubmit();
267
- handleTogglePopover();
268
- };
269
726
  const handleRefresh = () => {
270
- dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
727
+ dispatch(
728
+ index.releaseApi.util.invalidateTags([
729
+ { type: "ReleaseAction", id: "LIST" },
730
+ { type: "Release", id: releaseId }
731
+ ])
732
+ );
733
+ };
734
+ const getCreatedByUser = () => {
735
+ if (!release?.createdBy) {
736
+ return null;
737
+ }
738
+ if (release.createdBy.username) {
739
+ return release.createdBy.username;
740
+ }
741
+ if (release.createdBy.firstname) {
742
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
743
+ }
744
+ return release.createdBy.email;
271
745
  };
272
746
  if (isLoadingDetails) {
273
747
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
@@ -290,90 +764,127 @@ const ReleaseDetailsLayout = ({
290
764
  );
291
765
  }
292
766
  const totalEntries = release.actions.meta.count || 0;
293
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
767
+ const hasCreatedByUser = Boolean(getCreatedByUser());
768
+ const isScheduled = release.scheduledAt && release.timezone;
769
+ const numberOfEntriesText = formatMessage(
770
+ {
771
+ id: "content-releases.pages.Details.header-subtitle",
772
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
773
+ },
774
+ { number: totalEntries }
775
+ );
776
+ const scheduledText = isScheduled ? formatMessage(
777
+ {
778
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
779
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
780
+ },
781
+ {
782
+ date: formatDate(new Date(release.scheduledAt), {
783
+ weekday: "long",
784
+ day: "numeric",
785
+ month: "long",
786
+ year: "numeric",
787
+ timeZone: release.timezone
788
+ }),
789
+ time: formatTime(new Date(release.scheduledAt), {
790
+ timeZone: release.timezone,
791
+ hourCycle: "h23"
792
+ }),
793
+ offset: index.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
794
+ }
795
+ ) : "";
294
796
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
295
797
  /* @__PURE__ */ jsxRuntime.jsx(
296
798
  designSystem.HeaderLayout,
297
799
  {
298
800
  title: release.name,
299
- subtitle: formatMessage(
300
- {
301
- id: "content-releases.pages.Details.header-subtitle",
302
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
303
- },
304
- { number: totalEntries }
305
- ),
801
+ subtitle: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, lineHeight: 6, children: [
802
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
803
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(release.status), children: release.status })
804
+ ] }),
306
805
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
307
806
  id: "global.back",
308
807
  defaultMessage: "Back"
309
808
  }) }),
310
809
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
311
- /* @__PURE__ */ jsxRuntime.jsx(
312
- designSystem.IconButton,
313
- {
314
- label: formatMessage({
315
- id: "content-releases.header.actions.open-release-actions",
316
- defaultMessage: "Release actions"
317
- }),
318
- ref: moreButtonRef,
319
- onClick: handleTogglePopover,
320
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {})
321
- }
322
- ),
323
- isPopoverVisible && /* @__PURE__ */ jsxRuntime.jsxs(
324
- designSystem.Popover,
325
- {
326
- source: moreButtonRef,
327
- placement: "bottom-end",
328
- onDismiss: handleTogglePopover,
329
- spacing: 4,
330
- minWidth: "242px",
331
- children: [
332
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
333
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
334
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
335
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
336
- id: "content-releases.header.actions.edit",
337
- defaultMessage: "Edit"
338
- }) })
339
- ] }),
340
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
341
- /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
342
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
343
- id: "content-releases.header.actions.delete",
344
- defaultMessage: "Delete"
345
- }) })
346
- ] })
347
- ] }),
348
- /* @__PURE__ */ jsxRuntime.jsxs(
349
- ReleaseInfoWrapper,
350
- {
351
- direction: "column",
352
- justifyContent: "center",
353
- alignItems: "flex-start",
354
- gap: 1,
355
- padding: 5,
356
- children: [
357
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
358
- id: "content-releases.header.actions.created",
359
- defaultMessage: "Created"
360
- }) }),
361
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
362
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
363
- formatMessage(
364
- {
365
- id: "content-releases.header.actions.created.description",
366
- defaultMessage: " by {createdBy}"
367
- },
368
- { createdBy }
369
- )
370
- ] })
371
- ]
372
- }
373
- )
374
- ]
375
- }
376
- ),
810
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Root, { children: [
811
+ /* @__PURE__ */ jsxRuntime.jsx(
812
+ v2.Menu.Trigger,
813
+ {
814
+ as: designSystem.IconButton,
815
+ paddingLeft: 2,
816
+ paddingRight: 2,
817
+ "aria-label": formatMessage({
818
+ id: "content-releases.header.actions.open-release-actions",
819
+ defaultMessage: "Release edit and delete menu"
820
+ }),
821
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {}),
822
+ variant: "tertiary"
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
826
+ /* @__PURE__ */ jsxRuntime.jsxs(
827
+ designSystem.Flex,
828
+ {
829
+ alignItems: "center",
830
+ justifyContent: "center",
831
+ direction: "column",
832
+ padding: 1,
833
+ width: "100%",
834
+ children: [
835
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
836
+ /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
837
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
838
+ id: "content-releases.header.actions.edit",
839
+ defaultMessage: "Edit"
840
+ }) })
841
+ ] }) }),
842
+ /* @__PURE__ */ jsxRuntime.jsx(
843
+ StyledMenuItem,
844
+ {
845
+ disabled: !canDelete,
846
+ onSelect: toggleWarningSubmit,
847
+ variant: "danger",
848
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
849
+ /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
850
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
851
+ id: "content-releases.header.actions.delete",
852
+ defaultMessage: "Delete"
853
+ }) })
854
+ ] })
855
+ }
856
+ )
857
+ ]
858
+ }
859
+ ),
860
+ /* @__PURE__ */ jsxRuntime.jsxs(
861
+ ReleaseInfoWrapper,
862
+ {
863
+ direction: "column",
864
+ justifyContent: "center",
865
+ alignItems: "flex-start",
866
+ gap: 1,
867
+ padding: 5,
868
+ children: [
869
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
870
+ id: "content-releases.header.actions.created",
871
+ defaultMessage: "Created"
872
+ }) }),
873
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
874
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
875
+ formatMessage(
876
+ {
877
+ id: "content-releases.header.actions.created.description",
878
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
879
+ },
880
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
881
+ )
882
+ ] })
883
+ ]
884
+ }
885
+ )
886
+ ] })
887
+ ] }),
377
888
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
378
889
  id: "content-releases.header.actions.refresh",
379
890
  defaultMessage: "Refresh"
@@ -399,6 +910,7 @@ const ReleaseDetailsLayout = ({
399
910
  ] });
400
911
  };
401
912
  const GROUP_BY_OPTIONS = ["contentType", "locale", "action"];
913
+ const GROUP_BY_OPTIONS_NO_LOCALE = ["contentType", "action"];
402
914
  const getGroupByOptionLabel = (value) => {
403
915
  if (value === "locale") {
404
916
  return {
@@ -417,6 +929,21 @@ const getGroupByOptionLabel = (value) => {
417
929
  defaultMessage: "Content-Types"
418
930
  };
419
931
  };
932
+ const DEFAULT_RELEASE_DETAILS_HEADER = [
933
+ {
934
+ key: "__name__",
935
+ fieldSchema: { type: "string" },
936
+ metadatas: {
937
+ label: {
938
+ id: "content-releases.page.ReleaseDetails.table.header.label.name",
939
+ defaultMessage: "name"
940
+ },
941
+ searchable: false,
942
+ sortable: false
943
+ },
944
+ name: "name"
945
+ }
946
+ ];
420
947
  const ReleaseDetailsBody = () => {
421
948
  const { formatMessage } = reactIntl.useIntl();
422
949
  const { releaseId } = reactRouterDom.useParams();
@@ -429,6 +956,20 @@ const ReleaseDetailsBody = () => {
429
956
  isError: isReleaseError,
430
957
  error: releaseError
431
958
  } = index.useGetReleaseQuery({ id: releaseId });
959
+ const {
960
+ allowedActions: { canUpdate }
961
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
962
+ const { runHookWaterfall } = helperPlugin.useStrapiApp();
963
+ const {
964
+ displayedHeaders,
965
+ hasI18nEnabled
966
+ } = runHookWaterfall(
967
+ "ContentReleases/pages/ReleaseDetails/add-locale-in-releases",
968
+ {
969
+ displayedHeaders: DEFAULT_RELEASE_DETAILS_HEADER,
970
+ hasI18nEnabled: false
971
+ }
972
+ );
432
973
  const release = releaseData?.data;
433
974
  const selectedGroupBy = query?.groupBy || "contentType";
434
975
  const {
@@ -530,12 +1071,13 @@ const ReleaseDetailsBody = () => {
530
1071
  }
531
1072
  ) });
532
1073
  }
1074
+ const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
533
1075
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 8, direction: "column", alignItems: "stretch", children: [
534
1076
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(
535
1077
  designSystem.SingleSelect,
536
1078
  {
537
1079
  "aria-label": formatMessage({
538
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1080
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
539
1081
  defaultMessage: "Group by"
540
1082
  }),
541
1083
  customizeContent: (value) => formatMessage(
@@ -549,11 +1091,11 @@ const ReleaseDetailsBody = () => {
549
1091
  ),
550
1092
  value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
551
1093
  onChange: (value) => setQuery({ groupBy: value }),
552
- children: GROUP_BY_OPTIONS.map((option) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: option, children: formatMessage(getGroupByOptionLabel(option)) }, option))
1094
+ children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: option, children: formatMessage(getGroupByOptionLabel(option)) }, option))
553
1095
  }
554
1096
  ) }),
555
1097
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
556
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
1098
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
557
1099
  /* @__PURE__ */ jsxRuntime.jsx(
558
1100
  helperPlugin.Table.Root,
559
1101
  {
@@ -566,28 +1108,15 @@ const ReleaseDetailsBody = () => {
566
1108
  isFetching,
567
1109
  children: /* @__PURE__ */ jsxRuntime.jsxs(helperPlugin.Table.Content, { children: [
568
1110
  /* @__PURE__ */ jsxRuntime.jsxs(helperPlugin.Table.Head, { children: [
569
- /* @__PURE__ */ jsxRuntime.jsx(
570
- helperPlugin.Table.HeaderCell,
571
- {
572
- fieldSchemaType: "string",
573
- label: formatMessage({
574
- id: "content-releases.page.ReleaseDetails.table.header.label.name",
575
- defaultMessage: "name"
576
- }),
577
- name: "name"
578
- }
579
- ),
580
- /* @__PURE__ */ jsxRuntime.jsx(
581
- helperPlugin.Table.HeaderCell,
582
- {
583
- fieldSchemaType: "string",
584
- label: formatMessage({
585
- id: "content-releases.page.ReleaseDetails.table.header.label.locale",
586
- defaultMessage: "locale"
587
- }),
588
- name: "locale"
589
- }
590
- ),
1111
+ displayedHeaders.map(({ key: key2, fieldSchema, metadatas, name }) => /* @__PURE__ */ jsxRuntime.jsx(
1112
+ helperPlugin.Table.HeaderCell,
1113
+ {
1114
+ fieldSchemaType: fieldSchema.type,
1115
+ label: formatMessage(metadatas.label),
1116
+ name
1117
+ },
1118
+ key2
1119
+ )),
591
1120
  /* @__PURE__ */ jsxRuntime.jsx(
592
1121
  helperPlugin.Table.HeaderCell,
593
1122
  {
@@ -626,7 +1155,7 @@ const ReleaseDetailsBody = () => {
626
1155
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(
627
1156
  ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
628
1157
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
629
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
1158
+ hasI18nEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
630
1159
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
631
1160
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
632
1161
  {
@@ -642,7 +1171,8 @@ const ReleaseDetailsBody = () => {
642
1171
  {
643
1172
  selected: type,
644
1173
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
645
- name: `release-action-${id}-type`
1174
+ name: `release-action-${id}-type`,
1175
+ disabled: !canUpdate
646
1176
  }
647
1177
  ) }),
648
1178
  !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -697,7 +1227,7 @@ const ReleaseDetailsPage = () => {
697
1227
  const { releaseId } = reactRouterDom.useParams();
698
1228
  const toggleNotification = helperPlugin.useNotification();
699
1229
  const { formatAPIError } = helperPlugin.useAPIErrorHandler();
700
- const { push } = reactRouterDom.useHistory();
1230
+ const { replace } = reactRouterDom.useHistory();
701
1231
  const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
702
1232
  const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
703
1233
  const {
@@ -721,11 +1251,18 @@ const ReleaseDetailsPage = () => {
721
1251
  }
722
1252
  );
723
1253
  }
724
- const title = isSuccessDetails && data?.data?.name || "";
1254
+ const releaseData = isSuccessDetails && data?.data || null;
1255
+ const title = releaseData?.name || "";
1256
+ const timezone = releaseData?.timezone ?? null;
1257
+ const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1258
+ const date = scheduledAt ? format__default.default(scheduledAt, "yyyy-MM-dd") : null;
1259
+ const time = scheduledAt ? format__default.default(scheduledAt, "HH:mm") : "";
725
1260
  const handleEditRelease = async (values) => {
726
1261
  const response = await updateRelease({
727
1262
  id: releaseId,
728
- name: values.name
1263
+ name: values.name,
1264
+ scheduledAt: values.scheduledAt,
1265
+ timezone: values.timezone
729
1266
  });
730
1267
  if ("data" in response) {
731
1268
  toggleNotification({
@@ -735,6 +1272,7 @@ const ReleaseDetailsPage = () => {
735
1272
  defaultMessage: "Release updated."
736
1273
  })
737
1274
  });
1275
+ toggleEditReleaseModal();
738
1276
  } else if (index.isAxiosError(response.error)) {
739
1277
  toggleNotification({
740
1278
  type: "warning",
@@ -746,14 +1284,13 @@ const ReleaseDetailsPage = () => {
746
1284
  message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
747
1285
  });
748
1286
  }
749
- toggleEditReleaseModal();
750
1287
  };
751
1288
  const handleDeleteRelease = async () => {
752
1289
  const response = await deleteRelease({
753
1290
  id: releaseId
754
1291
  });
755
1292
  if ("data" in response) {
756
- push("/plugins/content-releases");
1293
+ replace("/plugins/content-releases");
757
1294
  } else if (index.isAxiosError(response.error)) {
758
1295
  toggleNotification({
759
1296
  type: "warning",
@@ -779,7 +1316,14 @@ const ReleaseDetailsPage = () => {
779
1316
  handleClose: toggleEditReleaseModal,
780
1317
  handleSubmit: handleEditRelease,
781
1318
  isLoading: isLoadingDetails || isSubmittingForm,
782
- initialValues: { name: title || "" }
1319
+ initialValues: {
1320
+ name: title || "",
1321
+ scheduledAt,
1322
+ date,
1323
+ time,
1324
+ isScheduled: Boolean(scheduledAt),
1325
+ timezone
1326
+ }
783
1327
  }
784
1328
  ),
785
1329
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -799,281 +1343,6 @@ const ReleaseDetailsPage = () => {
799
1343
  }
800
1344
  );
801
1345
  };
802
- const LinkCard = styled__default.default(v2.Link)`
803
- display: block;
804
- `;
805
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
806
- const { formatMessage } = reactIntl.useIntl();
807
- if (isError) {
808
- return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
809
- }
810
- if (releases?.length === 0) {
811
- return /* @__PURE__ */ jsxRuntime.jsx(
812
- designSystem.EmptyStateLayout,
813
- {
814
- content: formatMessage(
815
- {
816
- id: "content-releases.page.Releases.tab.emptyEntries",
817
- defaultMessage: "No releases"
818
- },
819
- {
820
- target: sectionTitle
821
- }
822
- ),
823
- icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
824
- }
825
- );
826
- }
827
- 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(
828
- designSystem.Flex,
829
- {
830
- direction: "column",
831
- justifyContent: "space-between",
832
- padding: 4,
833
- hasRadius: true,
834
- background: "neutral0",
835
- shadow: "tableShadow",
836
- height: "100%",
837
- width: "100%",
838
- alignItems: "start",
839
- gap: 2,
840
- children: [
841
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
842
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: formatMessage(
843
- {
844
- id: "content-releases.page.Releases.release-item.entries",
845
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
846
- },
847
- { number: actions.meta.count }
848
- ) })
849
- ]
850
- }
851
- ) }) }, id)) });
852
- };
853
- const StyledAlert = styled__default.default(designSystem.Alert)`
854
- button {
855
- display: none;
856
- }
857
- p + div {
858
- margin-left: auto;
859
- }
860
- `;
861
- const INITIAL_FORM_VALUES = {
862
- name: ""
863
- };
864
- const ReleasesPage = () => {
865
- const tabRef = React__namespace.useRef(null);
866
- const location = reactRouterDom.useLocation();
867
- const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
868
- const toggleNotification = helperPlugin.useNotification();
869
- const { formatMessage } = reactIntl.useIntl();
870
- const { push, replace } = reactRouterDom.useHistory();
871
- const { formatAPIError } = helperPlugin.useAPIErrorHandler();
872
- const [{ query }, setQuery] = helperPlugin.useQueryParams();
873
- const response = index.useGetReleasesQuery(query);
874
- const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
875
- const { getFeature } = strapiAdmin.useLicenseLimits();
876
- const { maximumReleases = 3 } = getFeature("cms-content-releases");
877
- const { trackUsage } = helperPlugin.useTracking();
878
- const { isLoading, isSuccess, isError } = response;
879
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
880
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
881
- React__namespace.useEffect(() => {
882
- if (location?.state?.errors) {
883
- toggleNotification({
884
- type: "warning",
885
- title: formatMessage({
886
- id: "content-releases.pages.Releases.notification.error.title",
887
- defaultMessage: "Your request could not be processed."
888
- }),
889
- message: formatMessage({
890
- id: "content-releases.pages.Releases.notification.error.message",
891
- defaultMessage: "Please try again or open another release."
892
- })
893
- });
894
- replace({ state: null });
895
- }
896
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
897
- React__namespace.useEffect(() => {
898
- if (tabRef.current) {
899
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
900
- }
901
- }, [activeTabIndex]);
902
- const toggleAddReleaseModal = () => {
903
- setReleaseModalShown((prev) => !prev);
904
- };
905
- if (isLoading) {
906
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
907
- }
908
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
909
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
910
- const handleTabChange = (index2) => {
911
- setQuery({
912
- ...query,
913
- page: 1,
914
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
915
- filters: {
916
- releasedAt: {
917
- $notNull: index2 === 0 ? false : true
918
- }
919
- }
920
- });
921
- };
922
- const handleAddRelease = async (values) => {
923
- const response2 = await createRelease({
924
- name: values.name
925
- });
926
- if ("data" in response2) {
927
- toggleNotification({
928
- type: "success",
929
- message: formatMessage({
930
- id: "content-releases.modal.release-created-notification-success",
931
- defaultMessage: "Release created."
932
- })
933
- });
934
- trackUsage("didCreateRelease");
935
- push(`/plugins/content-releases/${response2.data.data.id}`);
936
- } else if (index.isAxiosError(response2.error)) {
937
- toggleNotification({
938
- type: "warning",
939
- message: formatAPIError(response2.error)
940
- });
941
- } else {
942
- toggleNotification({
943
- type: "warning",
944
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
945
- });
946
- }
947
- };
948
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
949
- /* @__PURE__ */ jsxRuntime.jsx(
950
- designSystem.HeaderLayout,
951
- {
952
- title: formatMessage({
953
- id: "content-releases.pages.Releases.title",
954
- defaultMessage: "Releases"
955
- }),
956
- subtitle: formatMessage(
957
- {
958
- id: "content-releases.pages.Releases.header-subtitle",
959
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
960
- },
961
- { number: totalReleases }
962
- ),
963
- primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
964
- designSystem.Button,
965
- {
966
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
967
- onClick: toggleAddReleaseModal,
968
- disabled: hasReachedMaximumPendingReleases,
969
- children: formatMessage({
970
- id: "content-releases.header.actions.add-release",
971
- defaultMessage: "New release"
972
- })
973
- }
974
- ) })
975
- }
976
- ),
977
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
978
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
979
- StyledAlert,
980
- {
981
- marginBottom: 6,
982
- action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
983
- id: "content-releases.pages.Releases.max-limit-reached.action",
984
- defaultMessage: "Explore plans"
985
- }) }),
986
- title: formatMessage(
987
- {
988
- id: "content-releases.pages.Releases.max-limit-reached.title",
989
- defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
990
- },
991
- { number: maximumReleases }
992
- ),
993
- onClose: () => {
994
- },
995
- closeLabel: "",
996
- children: formatMessage({
997
- id: "content-releases.pages.Releases.max-limit-reached.message",
998
- defaultMessage: "Upgrade to manage an unlimited number of releases."
999
- })
1000
- }
1001
- ),
1002
- /* @__PURE__ */ jsxRuntime.jsxs(
1003
- designSystem.TabGroup,
1004
- {
1005
- label: formatMessage({
1006
- id: "content-releases.pages.Releases.tab-group.label",
1007
- defaultMessage: "Releases list"
1008
- }),
1009
- variant: "simple",
1010
- initialSelectedTabIndex: activeTabIndex,
1011
- onTabChange: handleTabChange,
1012
- ref: tabRef,
1013
- children: [
1014
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
1015
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
1016
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
1017
- id: "content-releases.pages.Releases.tab.pending",
1018
- defaultMessage: "Pending"
1019
- }) }),
1020
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
1021
- id: "content-releases.pages.Releases.tab.done",
1022
- defaultMessage: "Done"
1023
- }) })
1024
- ] }),
1025
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
1026
- ] }),
1027
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
1028
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
1029
- ReleasesGrid,
1030
- {
1031
- sectionTitle: "pending",
1032
- releases: response?.currentData?.data,
1033
- isError
1034
- }
1035
- ) }),
1036
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
1037
- ReleasesGrid,
1038
- {
1039
- sectionTitle: "done",
1040
- releases: response?.currentData?.data,
1041
- isError
1042
- }
1043
- ) })
1044
- ] })
1045
- ]
1046
- }
1047
- ),
1048
- totalReleases > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1049
- /* @__PURE__ */ jsxRuntime.jsx(
1050
- helperPlugin.PageSizeURLQuery,
1051
- {
1052
- options: ["8", "16", "32", "64"],
1053
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1054
- }
1055
- ),
1056
- /* @__PURE__ */ jsxRuntime.jsx(
1057
- helperPlugin.PaginationURLQuery,
1058
- {
1059
- pagination: {
1060
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1061
- }
1062
- }
1063
- )
1064
- ] })
1065
- ] }) }),
1066
- releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
1067
- ReleaseModal,
1068
- {
1069
- handleClose: toggleAddReleaseModal,
1070
- handleSubmit: handleAddRelease,
1071
- isLoading: isSubmittingForm,
1072
- initialValues: INITIAL_FORM_VALUES
1073
- }
1074
- )
1075
- ] });
1076
- };
1077
1346
  const App = () => {
1078
1347
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPagePermissions, { permissions: index.PERMISSIONS.main, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Switch, { children: [
1079
1348
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}`, component: ReleasesPage }),
@@ -1081,4 +1350,4 @@ const App = () => {
1081
1350
  ] }) });
1082
1351
  };
1083
1352
  exports.App = App;
1084
- //# sourceMappingURL=App-5PsAyVt2.js.map
1353
+ //# sourceMappingURL=App-HjWtUYmc.js.map