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

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-3ycH2d3s.mjs → App-HVXzE3i3.mjs} +694 -443
  2. package/dist/_chunks/App-HVXzE3i3.mjs.map +1 -0
  3. package/dist/_chunks/{App-5PsAyVt2.js → App-l62gIUTX.js} +686 -434
  4. package/dist/_chunks/App-l62gIUTX.js.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
  9. package/dist/_chunks/{en-SOqjCdyh.mjs → en-RdapH-9X.mjs} +18 -7
  10. package/dist/_chunks/en-RdapH-9X.mjs.map +1 -0
  11. package/dist/_chunks/{en-2DuPv5k0.js → en-faJDuv3q.js} +18 -7
  12. package/dist/_chunks/en-faJDuv3q.js.map +1 -0
  13. package/dist/_chunks/{index-D57Rztnc.js → index-ML_b3php.js} +129 -25
  14. package/dist/_chunks/index-ML_b3php.js.map +1 -0
  15. package/dist/_chunks/{index-4gUWuCQV.mjs → index-Ys87ROOe.mjs} +140 -36
  16. package/dist/_chunks/index-Ys87ROOe.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 +962 -408
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +961 -408
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -11
  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-ML_b3php.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,23 @@ 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 formattedDate = dateFns.parse(time, "HH:mm", new Date(date));
83
+ const timezoneWithoutOffset = timezone.split("&")[1];
84
+ return dateFnsTz.zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
85
+ };
86
+ const getTimezoneWithOffset = () => {
87
+ const currentTimezone = timezoneList.find(
88
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
89
+ );
90
+ return currentTimezone?.value || systemTimezone.value;
91
+ };
54
92
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
55
93
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
56
94
  {
@@ -62,45 +100,130 @@ const ReleaseModal = ({
62
100
  /* @__PURE__ */ jsxRuntime.jsx(
63
101
  formik.Formik,
64
102
  {
65
- validateOnChange: false,
66
- onSubmit: handleSubmit,
67
- initialValues,
103
+ onSubmit: (values) => {
104
+ handleSubmit({
105
+ ...values,
106
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
107
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
108
+ });
109
+ },
110
+ initialValues: {
111
+ ...initialValues,
112
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
113
+ },
68
114
  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
- ) }),
115
+ validateOnChange: false,
116
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
117
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx(
119
+ designSystem.TextInput,
120
+ {
121
+ label: formatMessage({
122
+ id: "content-releases.modal.form.input.label.release-name",
123
+ defaultMessage: "Name"
124
+ }),
125
+ name: "name",
126
+ value: values.name,
127
+ error: errors.name,
128
+ onChange: handleChange,
129
+ required: true
130
+ }
131
+ ),
132
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "max-content", children: /* @__PURE__ */ jsxRuntime.jsx(
133
+ designSystem.Checkbox,
134
+ {
135
+ name: "isScheduled",
136
+ value: values.isScheduled,
137
+ onChange: (event) => {
138
+ setFieldValue("isScheduled", event.target.checked);
139
+ if (!event.target.checked) {
140
+ setFieldValue("date", null);
141
+ setFieldValue("time", "");
142
+ setFieldValue("timezone", null);
143
+ } else {
144
+ setFieldValue("date", initialValues.date);
145
+ setFieldValue("time", initialValues.time);
146
+ setFieldValue("timezone", initialValues.timezone ?? systemTimezone?.value);
147
+ }
148
+ },
149
+ children: /* @__PURE__ */ jsxRuntime.jsx(
150
+ designSystem.Typography,
151
+ {
152
+ textColor: values.isScheduled ? "primary600" : "neutral800",
153
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
154
+ children: formatMessage({
155
+ id: "modal.form.input.label.schedule-release",
156
+ defaultMessage: "Schedule release"
157
+ })
158
+ }
159
+ )
160
+ }
161
+ ) }),
162
+ values.isScheduled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
163
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "start", children: [
164
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
165
+ designSystem.DatePicker,
166
+ {
167
+ label: formatMessage({
168
+ id: "content-releases.modal.form.input.label.date",
169
+ defaultMessage: "Date"
170
+ }),
171
+ name: "date",
172
+ error: errors.date,
173
+ onChange: (date) => {
174
+ const isoFormatDate = date ? dateFns.formatISO(date, { representation: "date" }) : null;
175
+ setFieldValue("date", isoFormatDate);
176
+ },
177
+ clearLabel: formatMessage({
178
+ id: "content-releases.modal.form.input.clearLabel",
179
+ defaultMessage: "Clear"
180
+ }),
181
+ onClear: () => {
182
+ setFieldValue("date", null);
183
+ },
184
+ selectedDate: values.date || void 0,
185
+ required: true,
186
+ minDate: dateFnsTz.utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
187
+ }
188
+ ) }),
189
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
190
+ designSystem.TimePicker,
191
+ {
192
+ label: formatMessage({
193
+ id: "content-releases.modal.form.input.label.time",
194
+ defaultMessage: "Time"
195
+ }),
196
+ name: "time",
197
+ error: errors.time,
198
+ onChange: (time) => {
199
+ setFieldValue("time", time);
200
+ },
201
+ clearLabel: formatMessage({
202
+ id: "content-releases.modal.form.input.clearLabel",
203
+ defaultMessage: "Clear"
204
+ }),
205
+ onClear: () => {
206
+ setFieldValue("time", "");
207
+ },
208
+ value: values.time || void 0,
209
+ required: true
210
+ }
211
+ ) })
212
+ ] }),
213
+ /* @__PURE__ */ jsxRuntime.jsx(TimezoneComponent, { timezoneOptions: timezoneList })
214
+ ] })
215
+ ] }) }),
84
216
  /* @__PURE__ */ jsxRuntime.jsx(
85
217
  designSystem.ModalFooter,
86
218
  {
87
219
  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,
220
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
90
221
  {
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
- )
222
+ id: "content-releases.modal.form.button.submit",
223
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
224
+ },
225
+ { isCreatingRelease }
226
+ ) })
104
227
  }
105
228
  )
106
229
  ] })
@@ -108,33 +231,396 @@ const ReleaseModal = ({
108
231
  )
109
232
  ] });
110
233
  };
234
+ const getTimezones = (selectedDate) => {
235
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
236
+ const utcOffset = index.getTimezoneOffset(timezone, selectedDate);
237
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
238
+ });
239
+ const systemTimezone = timezoneList.find(
240
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
241
+ );
242
+ return { timezoneList, systemTimezone };
243
+ };
244
+ const TimezoneComponent = ({ timezoneOptions }) => {
245
+ const { values, errors, setFieldValue } = formik.useFormikContext();
246
+ const { formatMessage } = reactIntl.useIntl();
247
+ const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
248
+ React__namespace.useEffect(() => {
249
+ if (values.date) {
250
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
251
+ setTimezoneList(timezoneList2);
252
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
253
+ if (updatedTimezone) {
254
+ setFieldValue("timezone", updatedTimezone.value);
255
+ }
256
+ }
257
+ }, [setFieldValue, values.date, values.timezone]);
258
+ return /* @__PURE__ */ jsxRuntime.jsx(
259
+ designSystem.Combobox,
260
+ {
261
+ label: formatMessage({
262
+ id: "content-releases.modal.form.input.label.timezone",
263
+ defaultMessage: "Timezone"
264
+ }),
265
+ autocomplete: { type: "list", filter: "contains" },
266
+ name: "timezone",
267
+ value: values.timezone || void 0,
268
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
269
+ onChange: (timezone) => {
270
+ setFieldValue("timezone", timezone);
271
+ },
272
+ onTextValueChange: (timezone) => {
273
+ setFieldValue("timezone", timezone);
274
+ },
275
+ onClear: () => {
276
+ setFieldValue("timezone", "");
277
+ },
278
+ error: errors.timezone,
279
+ required: true,
280
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
281
+ }
282
+ );
283
+ };
284
+ const LinkCard = styled__default.default(v2.Link)`
285
+ display: block;
286
+ `;
287
+ const CapitalizeRelativeTime = styled__default.default(helperPlugin.RelativeTime)`
288
+ text-transform: capitalize;
289
+ `;
290
+ const getBadgeProps = (status) => {
291
+ let color;
292
+ switch (status) {
293
+ case "ready":
294
+ color = "success";
295
+ break;
296
+ case "blocked":
297
+ color = "warning";
298
+ break;
299
+ case "failed":
300
+ color = "danger";
301
+ break;
302
+ case "done":
303
+ color = "primary";
304
+ break;
305
+ case "empty":
306
+ default:
307
+ color = "neutral";
308
+ }
309
+ return {
310
+ textColor: `${color}600`,
311
+ backgroundColor: `${color}100`,
312
+ borderColor: `${color}200`
313
+ };
314
+ };
315
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
316
+ const { formatMessage } = reactIntl.useIntl();
317
+ if (isError) {
318
+ return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
319
+ }
320
+ if (releases?.length === 0) {
321
+ return /* @__PURE__ */ jsxRuntime.jsx(
322
+ designSystem.EmptyStateLayout,
323
+ {
324
+ content: formatMessage(
325
+ {
326
+ id: "content-releases.page.Releases.tab.emptyEntries",
327
+ defaultMessage: "No releases"
328
+ },
329
+ {
330
+ target: sectionTitle
331
+ }
332
+ ),
333
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
334
+ }
335
+ );
336
+ }
337
+ 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(
338
+ designSystem.Flex,
339
+ {
340
+ direction: "column",
341
+ justifyContent: "space-between",
342
+ padding: 4,
343
+ hasRadius: true,
344
+ background: "neutral0",
345
+ shadow: "tableShadow",
346
+ height: "100%",
347
+ width: "100%",
348
+ alignItems: "start",
349
+ gap: 4,
350
+ children: [
351
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "start", gap: 1, children: [
352
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
353
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsxRuntime.jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
354
+ id: "content-releases.pages.Releases.not-scheduled",
355
+ defaultMessage: "Not scheduled"
356
+ }) })
357
+ ] }),
358
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(status), children: status })
359
+ ]
360
+ }
361
+ ) }) }, id)) });
362
+ };
363
+ const StyledAlert = styled__default.default(designSystem.Alert)`
364
+ button {
365
+ display: none;
366
+ }
367
+ p + div {
368
+ margin-left: auto;
369
+ }
370
+ `;
371
+ const INITIAL_FORM_VALUES = {
372
+ name: "",
373
+ date: null,
374
+ time: "",
375
+ isScheduled: true,
376
+ scheduledAt: null,
377
+ timezone: null
378
+ };
379
+ const ReleasesPage = () => {
380
+ const tabRef = React__namespace.useRef(null);
381
+ const location = reactRouterDom.useLocation();
382
+ const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
383
+ const toggleNotification = helperPlugin.useNotification();
384
+ const { formatMessage } = reactIntl.useIntl();
385
+ const { push, replace } = reactRouterDom.useHistory();
386
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
387
+ const [{ query }, setQuery] = helperPlugin.useQueryParams();
388
+ const response = index.useGetReleasesQuery(query);
389
+ const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
390
+ const { getFeature } = strapiAdmin.useLicenseLimits();
391
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
392
+ const { trackUsage } = helperPlugin.useTracking();
393
+ const { isLoading, isSuccess, isError } = response;
394
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
395
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
396
+ React__namespace.useEffect(() => {
397
+ if (location?.state?.errors) {
398
+ toggleNotification({
399
+ type: "warning",
400
+ title: formatMessage({
401
+ id: "content-releases.pages.Releases.notification.error.title",
402
+ defaultMessage: "Your request could not be processed."
403
+ }),
404
+ message: formatMessage({
405
+ id: "content-releases.pages.Releases.notification.error.message",
406
+ defaultMessage: "Please try again or open another release."
407
+ })
408
+ });
409
+ replace({ state: null });
410
+ }
411
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
412
+ React__namespace.useEffect(() => {
413
+ if (tabRef.current) {
414
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
415
+ }
416
+ }, [activeTabIndex]);
417
+ const toggleAddReleaseModal = () => {
418
+ setReleaseModalShown((prev) => !prev);
419
+ };
420
+ if (isLoading) {
421
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
422
+ }
423
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
424
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
425
+ const handleTabChange = (index2) => {
426
+ setQuery({
427
+ ...query,
428
+ page: 1,
429
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
430
+ filters: {
431
+ releasedAt: {
432
+ $notNull: index2 === 0 ? false : true
433
+ }
434
+ }
435
+ });
436
+ };
437
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
438
+ const response2 = await createRelease({
439
+ name,
440
+ scheduledAt,
441
+ timezone
442
+ });
443
+ if ("data" in response2) {
444
+ toggleNotification({
445
+ type: "success",
446
+ message: formatMessage({
447
+ id: "content-releases.modal.release-created-notification-success",
448
+ defaultMessage: "Release created."
449
+ })
450
+ });
451
+ trackUsage("didCreateRelease");
452
+ push(`/plugins/content-releases/${response2.data.data.id}`);
453
+ } else if (index.isAxiosError(response2.error)) {
454
+ toggleNotification({
455
+ type: "warning",
456
+ message: formatAPIError(response2.error)
457
+ });
458
+ } else {
459
+ toggleNotification({
460
+ type: "warning",
461
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
462
+ });
463
+ }
464
+ };
465
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
466
+ /* @__PURE__ */ jsxRuntime.jsx(
467
+ designSystem.HeaderLayout,
468
+ {
469
+ title: formatMessage({
470
+ id: "content-releases.pages.Releases.title",
471
+ defaultMessage: "Releases"
472
+ }),
473
+ subtitle: formatMessage({
474
+ id: "content-releases.pages.Releases.header-subtitle",
475
+ defaultMessage: "Create and manage content updates"
476
+ }),
477
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
478
+ designSystem.Button,
479
+ {
480
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
481
+ onClick: toggleAddReleaseModal,
482
+ disabled: hasReachedMaximumPendingReleases,
483
+ children: formatMessage({
484
+ id: "content-releases.header.actions.add-release",
485
+ defaultMessage: "New release"
486
+ })
487
+ }
488
+ ) })
489
+ }
490
+ ),
491
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
492
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
493
+ StyledAlert,
494
+ {
495
+ marginBottom: 6,
496
+ action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
497
+ id: "content-releases.pages.Releases.max-limit-reached.action",
498
+ defaultMessage: "Explore plans"
499
+ }) }),
500
+ title: formatMessage(
501
+ {
502
+ id: "content-releases.pages.Releases.max-limit-reached.title",
503
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
504
+ },
505
+ { number: maximumReleases }
506
+ ),
507
+ onClose: () => {
508
+ },
509
+ closeLabel: "",
510
+ children: formatMessage({
511
+ id: "content-releases.pages.Releases.max-limit-reached.message",
512
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
513
+ })
514
+ }
515
+ ),
516
+ /* @__PURE__ */ jsxRuntime.jsxs(
517
+ designSystem.TabGroup,
518
+ {
519
+ label: formatMessage({
520
+ id: "content-releases.pages.Releases.tab-group.label",
521
+ defaultMessage: "Releases list"
522
+ }),
523
+ variant: "simple",
524
+ initialSelectedTabIndex: activeTabIndex,
525
+ onTabChange: handleTabChange,
526
+ ref: tabRef,
527
+ children: [
528
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
529
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
530
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage(
531
+ {
532
+ id: "content-releases.pages.Releases.tab.pending",
533
+ defaultMessage: "Pending ({count})"
534
+ },
535
+ {
536
+ count: totalPendingReleases
537
+ }
538
+ ) }),
539
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
540
+ id: "content-releases.pages.Releases.tab.done",
541
+ defaultMessage: "Done"
542
+ }) })
543
+ ] }),
544
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
545
+ ] }),
546
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
547
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
548
+ ReleasesGrid,
549
+ {
550
+ sectionTitle: "pending",
551
+ releases: response?.currentData?.data,
552
+ isError
553
+ }
554
+ ) }),
555
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
556
+ ReleasesGrid,
557
+ {
558
+ sectionTitle: "done",
559
+ releases: response?.currentData?.data,
560
+ isError
561
+ }
562
+ ) })
563
+ ] })
564
+ ]
565
+ }
566
+ ),
567
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
568
+ /* @__PURE__ */ jsxRuntime.jsx(
569
+ helperPlugin.PageSizeURLQuery,
570
+ {
571
+ options: ["8", "16", "32", "64"],
572
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
573
+ }
574
+ ),
575
+ /* @__PURE__ */ jsxRuntime.jsx(
576
+ helperPlugin.PaginationURLQuery,
577
+ {
578
+ pagination: {
579
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
580
+ }
581
+ }
582
+ )
583
+ ] }) : null
584
+ ] }) }),
585
+ releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
586
+ ReleaseModal,
587
+ {
588
+ handleClose: toggleAddReleaseModal,
589
+ handleSubmit: handleAddRelease,
590
+ isLoading: isSubmittingForm,
591
+ initialValues: INITIAL_FORM_VALUES
592
+ }
593
+ )
594
+ ] });
595
+ };
111
596
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
112
597
  align-self: stretch;
113
598
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
114
599
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
115
600
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
116
601
  `;
117
- const StyledFlex = styled__default.default(designSystem.Flex)`
118
- align-self: stretch;
119
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
120
-
602
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
121
603
  svg path {
122
604
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
123
605
  }
124
606
  span {
125
607
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
126
608
  }
609
+
610
+ &:hover {
611
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
612
+ }
127
613
  `;
128
614
  const PencilIcon = styled__default.default(icons.Pencil)`
129
- width: ${({ theme }) => theme.spaces[4]};
130
- height: ${({ theme }) => theme.spaces[4]};
615
+ width: ${({ theme }) => theme.spaces[3]};
616
+ height: ${({ theme }) => theme.spaces[3]};
131
617
  path {
132
618
  fill: ${({ theme }) => theme.colors.neutral600};
133
619
  }
134
620
  `;
135
621
  const TrashIcon = styled__default.default(icons.Trash)`
136
- width: ${({ theme }) => theme.spaces[4]};
137
- height: ${({ theme }) => theme.spaces[4]};
622
+ width: ${({ theme }) => theme.spaces[3]};
623
+ height: ${({ theme }) => theme.spaces[3]};
138
624
  path {
139
625
  fill: ${({ theme }) => theme.colors.danger600};
140
626
  }
@@ -142,24 +628,6 @@ const TrashIcon = styled__default.default(icons.Trash)`
142
628
  const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
143
629
  max-width: 300px;
144
630
  `;
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
631
  const EntryValidationText = ({ action, schema, components, entry }) => {
164
632
  const { formatMessage } = reactIntl.useIntl();
165
633
  const { validate } = strapiAdmin.unstable_useDocument();
@@ -208,10 +676,8 @@ const ReleaseDetailsLayout = ({
208
676
  toggleWarningSubmit,
209
677
  children
210
678
  }) => {
211
- const { formatMessage } = reactIntl.useIntl();
679
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
212
680
  const { releaseId } = reactRouterDom.useParams();
213
- const [isPopoverVisible, setIsPopoverVisible] = React__namespace.useState(false);
214
- const moreButtonRef = React__namespace.useRef(null);
215
681
  const {
216
682
  data,
217
683
  isLoading: isLoadingDetails,
@@ -227,13 +693,6 @@ const ReleaseDetailsLayout = ({
227
693
  const dispatch = index.useTypedDispatch();
228
694
  const { trackUsage } = helperPlugin.useTracking();
229
695
  const release = data?.data;
230
- const handleTogglePopover = () => {
231
- setIsPopoverVisible((prev) => !prev);
232
- };
233
- const openReleaseModal = () => {
234
- toggleEditReleaseModal();
235
- handleTogglePopover();
236
- };
237
696
  const handlePublishRelease = async () => {
238
697
  const response = await publishRelease({ id: releaseId });
239
698
  if ("data" in response) {
@@ -262,12 +721,25 @@ const ReleaseDetailsLayout = ({
262
721
  });
263
722
  }
264
723
  };
265
- const openWarningConfirmDialog = () => {
266
- toggleWarningSubmit();
267
- handleTogglePopover();
268
- };
269
724
  const handleRefresh = () => {
270
- dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
725
+ dispatch(
726
+ index.releaseApi.util.invalidateTags([
727
+ { type: "ReleaseAction", id: "LIST" },
728
+ { type: "Release", id: releaseId }
729
+ ])
730
+ );
731
+ };
732
+ const getCreatedByUser = () => {
733
+ if (!release?.createdBy) {
734
+ return null;
735
+ }
736
+ if (release.createdBy.username) {
737
+ return release.createdBy.username;
738
+ }
739
+ if (release.createdBy.firstname) {
740
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
741
+ }
742
+ return release.createdBy.email;
271
743
  };
272
744
  if (isLoadingDetails) {
273
745
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
@@ -290,90 +762,127 @@ const ReleaseDetailsLayout = ({
290
762
  );
291
763
  }
292
764
  const totalEntries = release.actions.meta.count || 0;
293
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
765
+ const hasCreatedByUser = Boolean(getCreatedByUser());
766
+ const isScheduled = release.scheduledAt && release.timezone;
767
+ const numberOfEntriesText = formatMessage(
768
+ {
769
+ id: "content-releases.pages.Details.header-subtitle",
770
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
771
+ },
772
+ { number: totalEntries }
773
+ );
774
+ const scheduledText = isScheduled ? formatMessage(
775
+ {
776
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
777
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
778
+ },
779
+ {
780
+ date: formatDate(new Date(release.scheduledAt), {
781
+ weekday: "long",
782
+ day: "numeric",
783
+ month: "long",
784
+ year: "numeric",
785
+ timeZone: release.timezone
786
+ }),
787
+ time: formatTime(new Date(release.scheduledAt), {
788
+ timeZone: release.timezone,
789
+ hourCycle: "h23"
790
+ }),
791
+ offset: index.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
792
+ }
793
+ ) : "";
294
794
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
295
795
  /* @__PURE__ */ jsxRuntime.jsx(
296
796
  designSystem.HeaderLayout,
297
797
  {
298
798
  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
- ),
799
+ subtitle: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, lineHeight: 6, children: [
800
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
801
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(release.status), children: release.status })
802
+ ] }),
306
803
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
307
804
  id: "global.back",
308
805
  defaultMessage: "Back"
309
806
  }) }),
310
807
  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
- ),
808
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Root, { children: [
809
+ /* @__PURE__ */ jsxRuntime.jsx(
810
+ v2.Menu.Trigger,
811
+ {
812
+ as: designSystem.IconButton,
813
+ paddingLeft: 2,
814
+ paddingRight: 2,
815
+ "aria-label": formatMessage({
816
+ id: "content-releases.header.actions.open-release-actions",
817
+ defaultMessage: "Release edit and delete menu"
818
+ }),
819
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {}),
820
+ variant: "tertiary"
821
+ }
822
+ ),
823
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
824
+ /* @__PURE__ */ jsxRuntime.jsxs(
825
+ designSystem.Flex,
826
+ {
827
+ alignItems: "center",
828
+ justifyContent: "center",
829
+ direction: "column",
830
+ padding: 1,
831
+ width: "100%",
832
+ children: [
833
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
834
+ /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
835
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
836
+ id: "content-releases.header.actions.edit",
837
+ defaultMessage: "Edit"
838
+ }) })
839
+ ] }) }),
840
+ /* @__PURE__ */ jsxRuntime.jsx(
841
+ StyledMenuItem,
842
+ {
843
+ disabled: !canDelete,
844
+ onSelect: toggleWarningSubmit,
845
+ variant: "danger",
846
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
847
+ /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
848
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
849
+ id: "content-releases.header.actions.delete",
850
+ defaultMessage: "Delete"
851
+ }) })
852
+ ] })
853
+ }
854
+ )
855
+ ]
856
+ }
857
+ ),
858
+ /* @__PURE__ */ jsxRuntime.jsxs(
859
+ ReleaseInfoWrapper,
860
+ {
861
+ direction: "column",
862
+ justifyContent: "center",
863
+ alignItems: "flex-start",
864
+ gap: 1,
865
+ padding: 5,
866
+ children: [
867
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
868
+ id: "content-releases.header.actions.created",
869
+ defaultMessage: "Created"
870
+ }) }),
871
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
872
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
873
+ formatMessage(
874
+ {
875
+ id: "content-releases.header.actions.created.description",
876
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
877
+ },
878
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
879
+ )
880
+ ] })
881
+ ]
882
+ }
883
+ )
884
+ ] })
885
+ ] }),
377
886
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
378
887
  id: "content-releases.header.actions.refresh",
379
888
  defaultMessage: "Refresh"
@@ -429,6 +938,9 @@ const ReleaseDetailsBody = () => {
429
938
  isError: isReleaseError,
430
939
  error: releaseError
431
940
  } = index.useGetReleaseQuery({ id: releaseId });
941
+ const {
942
+ allowedActions: { canUpdate }
943
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
432
944
  const release = releaseData?.data;
433
945
  const selectedGroupBy = query?.groupBy || "contentType";
434
946
  const {
@@ -535,7 +1047,7 @@ const ReleaseDetailsBody = () => {
535
1047
  designSystem.SingleSelect,
536
1048
  {
537
1049
  "aria-label": formatMessage({
538
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1050
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
539
1051
  defaultMessage: "Group by"
540
1052
  }),
541
1053
  customizeContent: (value) => formatMessage(
@@ -553,7 +1065,7 @@ const ReleaseDetailsBody = () => {
553
1065
  }
554
1066
  ) }),
555
1067
  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 }) }),
1068
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
557
1069
  /* @__PURE__ */ jsxRuntime.jsx(
558
1070
  helperPlugin.Table.Root,
559
1071
  {
@@ -642,7 +1154,8 @@ const ReleaseDetailsBody = () => {
642
1154
  {
643
1155
  selected: type,
644
1156
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
645
- name: `release-action-${id}-type`
1157
+ name: `release-action-${id}-type`,
1158
+ disabled: !canUpdate
646
1159
  }
647
1160
  ) }),
648
1161
  !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -697,7 +1210,7 @@ const ReleaseDetailsPage = () => {
697
1210
  const { releaseId } = reactRouterDom.useParams();
698
1211
  const toggleNotification = helperPlugin.useNotification();
699
1212
  const { formatAPIError } = helperPlugin.useAPIErrorHandler();
700
- const { push } = reactRouterDom.useHistory();
1213
+ const { replace } = reactRouterDom.useHistory();
701
1214
  const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
702
1215
  const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
703
1216
  const {
@@ -721,11 +1234,18 @@ const ReleaseDetailsPage = () => {
721
1234
  }
722
1235
  );
723
1236
  }
724
- const title = isSuccessDetails && data?.data?.name || "";
1237
+ const releaseData = isSuccessDetails && data?.data || null;
1238
+ const title = releaseData?.name || "";
1239
+ const timezone = releaseData?.timezone ?? null;
1240
+ const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1241
+ const date = scheduledAt ? new Date(format__default.default(scheduledAt, "yyyy-MM-dd")) : null;
1242
+ const time = scheduledAt ? format__default.default(scheduledAt, "HH:mm") : "";
725
1243
  const handleEditRelease = async (values) => {
726
1244
  const response = await updateRelease({
727
1245
  id: releaseId,
728
- name: values.name
1246
+ name: values.name,
1247
+ scheduledAt: values.scheduledAt,
1248
+ timezone: values.timezone
729
1249
  });
730
1250
  if ("data" in response) {
731
1251
  toggleNotification({
@@ -753,7 +1273,7 @@ const ReleaseDetailsPage = () => {
753
1273
  id: releaseId
754
1274
  });
755
1275
  if ("data" in response) {
756
- push("/plugins/content-releases");
1276
+ replace("/plugins/content-releases");
757
1277
  } else if (index.isAxiosError(response.error)) {
758
1278
  toggleNotification({
759
1279
  type: "warning",
@@ -779,7 +1299,14 @@ const ReleaseDetailsPage = () => {
779
1299
  handleClose: toggleEditReleaseModal,
780
1300
  handleSubmit: handleEditRelease,
781
1301
  isLoading: isLoadingDetails || isSubmittingForm,
782
- initialValues: { name: title || "" }
1302
+ initialValues: {
1303
+ name: title || "",
1304
+ scheduledAt,
1305
+ date,
1306
+ time,
1307
+ isScheduled: Boolean(scheduledAt),
1308
+ timezone
1309
+ }
783
1310
  }
784
1311
  ),
785
1312
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -799,281 +1326,6 @@ const ReleaseDetailsPage = () => {
799
1326
  }
800
1327
  );
801
1328
  };
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
1329
  const App = () => {
1078
1330
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPagePermissions, { permissions: index.PERMISSIONS.main, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Switch, { children: [
1079
1331
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}`, component: ReleasesPage }),
@@ -1081,4 +1333,4 @@ const App = () => {
1081
1333
  ] }) });
1082
1334
  };
1083
1335
  exports.App = App;
1084
- //# sourceMappingURL=App-5PsAyVt2.js.map
1336
+ //# sourceMappingURL=App-l62gIUTX.js.map