@strapi/content-releases 0.0.0-next.56199ab7a5f3320e0debcbe4a24fe0b8cd599e21 → 0.0.0-next.583e758623dc82206a4b2758d01dd5948b6e3f6a

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-8J9a-MD5.mjs → App-6ugQxqYE.mjs} +758 -452
  2. package/dist/_chunks/App-6ugQxqYE.mjs.map +1 -0
  3. package/dist/_chunks/{App-YFvVMqB8.js → App-P1kyM3gT.js} +750 -443
  4. package/dist/_chunks/App-P1kyM3gT.js.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
  9. package/dist/_chunks/{en-MyLPoISH.mjs → en-WuuhP6Bn.mjs} +21 -6
  10. package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
  11. package/dist/_chunks/{en-gYDqKYFd.js → en-gcJJ5htG.js} +21 -6
  12. package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
  13. package/dist/_chunks/{index-vxli-E-l.js → index-2xzbhaQP.js} +159 -33
  14. package/dist/_chunks/index-2xzbhaQP.js.map +1 -0
  15. package/dist/_chunks/{index-ej8MzbQl.mjs → index-_eBuegHN.mjs} +171 -45
  16. package/dist/_chunks/index-_eBuegHN.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 +928 -402
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +928 -403
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -11
  24. package/dist/_chunks/App-8J9a-MD5.mjs.map +0 -1
  25. package/dist/_chunks/App-YFvVMqB8.js.map +0 -1
  26. package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
  27. package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
  28. package/dist/_chunks/index-ej8MzbQl.mjs.map +0 -1
  29. package/dist/_chunks/index-vxli-E-l.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-vxli-E-l.js");
6
+ const index = require("./index-2xzbhaQP.js");
7
7
  const React = require("react");
8
8
  const strapiAdmin = require("@strapi/admin/strapi-admin");
9
9
  const designSystem = require("@strapi/design-system");
10
10
  const v2 = require("@strapi/design-system/v2");
11
11
  const icons = require("@strapi/icons");
12
+ const format = require("date-fns/format");
13
+ const dateFnsTz = require("date-fns-tz");
12
14
  const reactIntl = require("react-intl");
13
15
  const styled = require("styled-components");
16
+ const dateFns = require("date-fns");
14
17
  const formik = require("formik");
15
18
  const yup = require("yup");
16
19
  require("@reduxjs/toolkit/query");
@@ -37,10 +40,28 @@ function _interopNamespace(e) {
37
40
  return Object.freeze(n);
38
41
  }
39
42
  const React__namespace = /* @__PURE__ */ _interopNamespace(React);
43
+ const format__default = /* @__PURE__ */ _interopDefault(format);
40
44
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
41
45
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
42
46
  const RELEASE_SCHEMA = yup__namespace.object().shape({
43
- name: yup__namespace.string().trim().required()
47
+ name: yup__namespace.string().trim().required(),
48
+ scheduledAt: yup__namespace.string().nullable(),
49
+ isScheduled: yup__namespace.boolean().optional(),
50
+ time: yup__namespace.string().when("isScheduled", {
51
+ is: true,
52
+ then: yup__namespace.string().trim().required(),
53
+ otherwise: yup__namespace.string().nullable()
54
+ }),
55
+ timezone: yup__namespace.string().when("isScheduled", {
56
+ is: true,
57
+ then: yup__namespace.string().required().nullable(),
58
+ otherwise: yup__namespace.string().nullable()
59
+ }),
60
+ date: yup__namespace.string().when("isScheduled", {
61
+ is: true,
62
+ then: yup__namespace.string().required().nullable(),
63
+ otherwise: yup__namespace.string().nullable()
64
+ })
44
65
  }).required().noUnknown();
45
66
  const ReleaseModal = ({
46
67
  handleClose,
@@ -51,6 +72,24 @@ const ReleaseModal = ({
51
72
  const { formatMessage } = reactIntl.useIntl();
52
73
  const { pathname } = reactRouterDom.useLocation();
53
74
  const isCreatingRelease = pathname === `/plugins/${index.pluginId}`;
75
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
76
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
77
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
78
+ );
79
+ const getScheduledTimestamp = (values) => {
80
+ const { date, time, timezone } = values;
81
+ if (!date || !time || !timezone)
82
+ return null;
83
+ const formattedDate = dateFns.parse(time, "HH:mm", new Date(date));
84
+ const timezoneWithoutOffset = timezone.split("_")[1];
85
+ return dateFnsTz.zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
86
+ };
87
+ const getTimezoneWithOffset = () => {
88
+ const currentTimezone = timezoneList.find(
89
+ (timezone) => timezone.value.split("_")[1] === initialValues.timezone
90
+ );
91
+ return currentTimezone?.value || systemTimezone.value;
92
+ };
54
93
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
55
94
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
56
95
  {
@@ -62,45 +101,134 @@ const ReleaseModal = ({
62
101
  /* @__PURE__ */ jsxRuntime.jsx(
63
102
  formik.Formik,
64
103
  {
65
- validateOnChange: false,
66
- onSubmit: handleSubmit,
67
- initialValues,
104
+ onSubmit: (values) => {
105
+ handleSubmit({
106
+ ...values,
107
+ timezone: values.timezone ? values.timezone.split("_")[1] : null,
108
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
109
+ });
110
+ },
111
+ initialValues: {
112
+ ...initialValues,
113
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
114
+ },
68
115
  validationSchema: RELEASE_SCHEMA,
69
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
70
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
71
- designSystem.TextInput,
72
- {
73
- label: formatMessage({
74
- id: "content-releases.modal.form.input.label.release-name",
75
- defaultMessage: "Name"
76
- }),
77
- name: "name",
78
- value: values.name,
79
- error: errors.name,
80
- onChange: handleChange,
81
- required: true
82
- }
83
- ) }),
116
+ validateOnChange: false,
117
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx(
120
+ designSystem.TextInput,
121
+ {
122
+ label: formatMessage({
123
+ id: "content-releases.modal.form.input.label.release-name",
124
+ defaultMessage: "Name"
125
+ }),
126
+ name: "name",
127
+ value: values.name,
128
+ error: errors.name,
129
+ onChange: handleChange,
130
+ required: true
131
+ }
132
+ ),
133
+ IsSchedulingEnabled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
134
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "max-content", children: /* @__PURE__ */ jsxRuntime.jsx(
135
+ designSystem.Checkbox,
136
+ {
137
+ name: "isScheduled",
138
+ value: values.isScheduled,
139
+ onChange: (event) => {
140
+ setFieldValue("isScheduled", event.target.checked);
141
+ if (!event.target.checked) {
142
+ setFieldValue("date", null);
143
+ setFieldValue("time", "");
144
+ setFieldValue("timezone", null);
145
+ } else {
146
+ setFieldValue("date", initialValues.date);
147
+ setFieldValue("time", initialValues.time);
148
+ setFieldValue(
149
+ "timezone",
150
+ initialValues.timezone ?? systemTimezone?.value
151
+ );
152
+ }
153
+ },
154
+ children: /* @__PURE__ */ jsxRuntime.jsx(
155
+ designSystem.Typography,
156
+ {
157
+ textColor: values.isScheduled ? "primary600" : "neutral800",
158
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
159
+ children: formatMessage({
160
+ id: "modal.form.input.label.schedule-release",
161
+ defaultMessage: "Schedule release"
162
+ })
163
+ }
164
+ )
165
+ }
166
+ ) }),
167
+ values.isScheduled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
168
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "start", children: [
169
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
170
+ designSystem.DatePicker,
171
+ {
172
+ label: formatMessage({
173
+ id: "content-releases.modal.form.input.label.date",
174
+ defaultMessage: "Date"
175
+ }),
176
+ name: "date",
177
+ error: errors.date,
178
+ onChange: (date) => {
179
+ const isoFormatDate = date ? dateFns.formatISO(date, { representation: "date" }) : null;
180
+ setFieldValue("date", isoFormatDate);
181
+ },
182
+ clearLabel: formatMessage({
183
+ id: "content-releases.modal.form.input.clearLabel",
184
+ defaultMessage: "Clear"
185
+ }),
186
+ onClear: () => {
187
+ setFieldValue("date", null);
188
+ },
189
+ selectedDate: values.date || void 0,
190
+ required: true
191
+ }
192
+ ) }),
193
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
194
+ designSystem.TimePicker,
195
+ {
196
+ label: formatMessage({
197
+ id: "content-releases.modal.form.input.label.time",
198
+ defaultMessage: "Time"
199
+ }),
200
+ name: "time",
201
+ error: errors.time,
202
+ onChange: (time) => {
203
+ setFieldValue("time", time);
204
+ },
205
+ clearLabel: formatMessage({
206
+ id: "content-releases.modal.form.input.clearLabel",
207
+ defaultMessage: "Clear"
208
+ }),
209
+ onClear: () => {
210
+ setFieldValue("time", "");
211
+ },
212
+ value: values.time || void 0,
213
+ required: true
214
+ }
215
+ ) })
216
+ ] }),
217
+ /* @__PURE__ */ jsxRuntime.jsx(TimezoneComponent, { timezoneOptions: timezoneList })
218
+ ] })
219
+ ] })
220
+ ] }) }),
84
221
  /* @__PURE__ */ jsxRuntime.jsx(
85
222
  designSystem.ModalFooter,
86
223
  {
87
224
  startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
88
- endActions: /* @__PURE__ */ jsxRuntime.jsx(
89
- designSystem.Button,
225
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
90
226
  {
91
- name: "submit",
92
- loading: isLoading,
93
- disabled: !values.name || values.name === initialValues.name,
94
- type: "submit",
95
- children: formatMessage(
96
- {
97
- id: "content-releases.modal.form.button.submit",
98
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
99
- },
100
- { isCreatingRelease }
101
- )
102
- }
103
- )
227
+ id: "content-releases.modal.form.button.submit",
228
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
229
+ },
230
+ { isCreatingRelease }
231
+ ) })
104
232
  }
105
233
  )
106
234
  ] })
@@ -108,33 +236,403 @@ const ReleaseModal = ({
108
236
  )
109
237
  ] });
110
238
  };
239
+ const getTimezones = (selectedDate) => {
240
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
241
+ const utcOffset = index.getTimezoneOffset(timezone, selectedDate);
242
+ return { offset: utcOffset, value: `${utcOffset}_${timezone}` };
243
+ });
244
+ const systemTimezone = timezoneList.find(
245
+ (timezone) => timezone.value.split("_")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
246
+ );
247
+ return { timezoneList, systemTimezone };
248
+ };
249
+ const TimezoneComponent = ({ timezoneOptions }) => {
250
+ const { values, errors, setFieldValue } = formik.useFormikContext();
251
+ const { formatMessage } = reactIntl.useIntl();
252
+ const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
253
+ React__namespace.useEffect(() => {
254
+ if (values.date) {
255
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
256
+ setTimezoneList(timezoneList2);
257
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("_")[1] === values.timezone.split("_")[1]);
258
+ if (updatedTimezone) {
259
+ setFieldValue("timezone", updatedTimezone.value);
260
+ }
261
+ }
262
+ }, [setFieldValue, values.date, values.timezone]);
263
+ return /* @__PURE__ */ jsxRuntime.jsx(
264
+ designSystem.Combobox,
265
+ {
266
+ label: formatMessage({
267
+ id: "content-releases.modal.form.input.label.timezone",
268
+ defaultMessage: "Timezone"
269
+ }),
270
+ name: "timezone",
271
+ value: values.timezone || void 0,
272
+ textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
273
+ onChange: (timezone) => {
274
+ setFieldValue("timezone", timezone);
275
+ },
276
+ onTextValueChange: (timezone) => {
277
+ setFieldValue("timezone", timezone);
278
+ },
279
+ onClear: () => {
280
+ setFieldValue("timezone", "");
281
+ },
282
+ error: errors.timezone,
283
+ required: true,
284
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
285
+ }
286
+ );
287
+ };
288
+ const LinkCard = styled__default.default(v2.Link)`
289
+ display: block;
290
+ `;
291
+ const CapitalizeRelativeTime = styled__default.default(helperPlugin.RelativeTime)`
292
+ text-transform: capitalize;
293
+ `;
294
+ const getBadgeProps = (status) => {
295
+ let color;
296
+ switch (status) {
297
+ case "ready":
298
+ color = "success";
299
+ break;
300
+ case "blocked":
301
+ color = "warning";
302
+ break;
303
+ case "failed":
304
+ color = "danger";
305
+ break;
306
+ case "done":
307
+ color = "primary";
308
+ break;
309
+ case "empty":
310
+ default:
311
+ color = "neutral";
312
+ }
313
+ return {
314
+ textColor: `${color}600`,
315
+ backgroundColor: `${color}100`,
316
+ borderColor: `${color}200`
317
+ };
318
+ };
319
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
320
+ const { formatMessage } = reactIntl.useIntl();
321
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
322
+ if (isError) {
323
+ return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
324
+ }
325
+ if (releases?.length === 0) {
326
+ return /* @__PURE__ */ jsxRuntime.jsx(
327
+ designSystem.EmptyStateLayout,
328
+ {
329
+ content: formatMessage(
330
+ {
331
+ id: "content-releases.page.Releases.tab.emptyEntries",
332
+ defaultMessage: "No releases"
333
+ },
334
+ {
335
+ target: sectionTitle
336
+ }
337
+ ),
338
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
339
+ }
340
+ );
341
+ }
342
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid, { gap: 4, children: releases.map(({ id, name, actions, 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(
343
+ designSystem.Flex,
344
+ {
345
+ direction: "column",
346
+ justifyContent: "space-between",
347
+ padding: 4,
348
+ hasRadius: true,
349
+ background: "neutral0",
350
+ shadow: "tableShadow",
351
+ height: "100%",
352
+ width: "100%",
353
+ alignItems: "start",
354
+ gap: 4,
355
+ children: [
356
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "start", gap: 1, children: [
357
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
358
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsxRuntime.jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
359
+ id: "content-releases.pages.Releases.not-scheduled",
360
+ defaultMessage: "Not scheduled"
361
+ }) : formatMessage(
362
+ {
363
+ id: "content-releases.page.Releases.release-item.entries",
364
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
365
+ },
366
+ { number: actions.meta.count }
367
+ ) })
368
+ ] }),
369
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(status), children: status })
370
+ ]
371
+ }
372
+ ) }) }, id)) });
373
+ };
374
+ const StyledAlert = styled__default.default(designSystem.Alert)`
375
+ button {
376
+ display: none;
377
+ }
378
+ p + div {
379
+ margin-left: auto;
380
+ }
381
+ `;
382
+ const INITIAL_FORM_VALUES = {
383
+ name: "",
384
+ date: null,
385
+ time: "",
386
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
387
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
388
+ scheduledAt: null,
389
+ timezone: null
390
+ };
391
+ const ReleasesPage = () => {
392
+ const tabRef = React__namespace.useRef(null);
393
+ const location = reactRouterDom.useLocation();
394
+ const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
395
+ const toggleNotification = helperPlugin.useNotification();
396
+ const { formatMessage } = reactIntl.useIntl();
397
+ const { push, replace } = reactRouterDom.useHistory();
398
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
399
+ const [{ query }, setQuery] = helperPlugin.useQueryParams();
400
+ const response = index.useGetReleasesQuery(query);
401
+ const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
402
+ const { getFeature } = strapiAdmin.useLicenseLimits();
403
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
404
+ const { trackUsage } = helperPlugin.useTracking();
405
+ const { isLoading, isSuccess, isError } = response;
406
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
407
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
408
+ React__namespace.useEffect(() => {
409
+ if (location?.state?.errors) {
410
+ toggleNotification({
411
+ type: "warning",
412
+ title: formatMessage({
413
+ id: "content-releases.pages.Releases.notification.error.title",
414
+ defaultMessage: "Your request could not be processed."
415
+ }),
416
+ message: formatMessage({
417
+ id: "content-releases.pages.Releases.notification.error.message",
418
+ defaultMessage: "Please try again or open another release."
419
+ })
420
+ });
421
+ replace({ state: null });
422
+ }
423
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
424
+ React__namespace.useEffect(() => {
425
+ if (tabRef.current) {
426
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
427
+ }
428
+ }, [activeTabIndex]);
429
+ const toggleAddReleaseModal = () => {
430
+ setReleaseModalShown((prev) => !prev);
431
+ };
432
+ if (isLoading) {
433
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
434
+ }
435
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
436
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
437
+ const handleTabChange = (index2) => {
438
+ setQuery({
439
+ ...query,
440
+ page: 1,
441
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
442
+ filters: {
443
+ releasedAt: {
444
+ $notNull: index2 === 0 ? false : true
445
+ }
446
+ }
447
+ });
448
+ };
449
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
450
+ const response2 = await createRelease({
451
+ name,
452
+ scheduledAt,
453
+ timezone
454
+ });
455
+ if ("data" in response2) {
456
+ toggleNotification({
457
+ type: "success",
458
+ message: formatMessage({
459
+ id: "content-releases.modal.release-created-notification-success",
460
+ defaultMessage: "Release created."
461
+ })
462
+ });
463
+ trackUsage("didCreateRelease");
464
+ push(`/plugins/content-releases/${response2.data.data.id}`);
465
+ } else if (index.isAxiosError(response2.error)) {
466
+ toggleNotification({
467
+ type: "warning",
468
+ message: formatAPIError(response2.error)
469
+ });
470
+ } else {
471
+ toggleNotification({
472
+ type: "warning",
473
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
474
+ });
475
+ }
476
+ };
477
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
478
+ /* @__PURE__ */ jsxRuntime.jsx(
479
+ designSystem.HeaderLayout,
480
+ {
481
+ title: formatMessage({
482
+ id: "content-releases.pages.Releases.title",
483
+ defaultMessage: "Releases"
484
+ }),
485
+ subtitle: formatMessage({
486
+ id: "content-releases.pages.Releases.header-subtitle",
487
+ defaultMessage: "Create and manage content updates"
488
+ }),
489
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
490
+ designSystem.Button,
491
+ {
492
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
493
+ onClick: toggleAddReleaseModal,
494
+ disabled: hasReachedMaximumPendingReleases,
495
+ children: formatMessage({
496
+ id: "content-releases.header.actions.add-release",
497
+ defaultMessage: "New release"
498
+ })
499
+ }
500
+ ) })
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
504
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
505
+ StyledAlert,
506
+ {
507
+ marginBottom: 6,
508
+ action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
509
+ id: "content-releases.pages.Releases.max-limit-reached.action",
510
+ defaultMessage: "Explore plans"
511
+ }) }),
512
+ title: formatMessage(
513
+ {
514
+ id: "content-releases.pages.Releases.max-limit-reached.title",
515
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
516
+ },
517
+ { number: maximumReleases }
518
+ ),
519
+ onClose: () => {
520
+ },
521
+ closeLabel: "",
522
+ children: formatMessage({
523
+ id: "content-releases.pages.Releases.max-limit-reached.message",
524
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
525
+ })
526
+ }
527
+ ),
528
+ /* @__PURE__ */ jsxRuntime.jsxs(
529
+ designSystem.TabGroup,
530
+ {
531
+ label: formatMessage({
532
+ id: "content-releases.pages.Releases.tab-group.label",
533
+ defaultMessage: "Releases list"
534
+ }),
535
+ variant: "simple",
536
+ initialSelectedTabIndex: activeTabIndex,
537
+ onTabChange: handleTabChange,
538
+ ref: tabRef,
539
+ children: [
540
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
541
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
542
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage(
543
+ {
544
+ id: "content-releases.pages.Releases.tab.pending",
545
+ defaultMessage: "Pending ({count})"
546
+ },
547
+ {
548
+ count: totalPendingReleases
549
+ }
550
+ ) }),
551
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
552
+ id: "content-releases.pages.Releases.tab.done",
553
+ defaultMessage: "Done"
554
+ }) })
555
+ ] }),
556
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
557
+ ] }),
558
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
559
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
560
+ ReleasesGrid,
561
+ {
562
+ sectionTitle: "pending",
563
+ releases: response?.currentData?.data,
564
+ isError
565
+ }
566
+ ) }),
567
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
568
+ ReleasesGrid,
569
+ {
570
+ sectionTitle: "done",
571
+ releases: response?.currentData?.data,
572
+ isError
573
+ }
574
+ ) })
575
+ ] })
576
+ ]
577
+ }
578
+ ),
579
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
580
+ /* @__PURE__ */ jsxRuntime.jsx(
581
+ helperPlugin.PageSizeURLQuery,
582
+ {
583
+ options: ["8", "16", "32", "64"],
584
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
585
+ }
586
+ ),
587
+ /* @__PURE__ */ jsxRuntime.jsx(
588
+ helperPlugin.PaginationURLQuery,
589
+ {
590
+ pagination: {
591
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
592
+ }
593
+ }
594
+ )
595
+ ] }) : null
596
+ ] }) }),
597
+ releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
598
+ ReleaseModal,
599
+ {
600
+ handleClose: toggleAddReleaseModal,
601
+ handleSubmit: handleAddRelease,
602
+ isLoading: isSubmittingForm,
603
+ initialValues: INITIAL_FORM_VALUES
604
+ }
605
+ )
606
+ ] });
607
+ };
111
608
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
112
609
  align-self: stretch;
113
610
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
114
611
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
115
612
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
116
613
  `;
117
- const StyledFlex = styled__default.default(designSystem.Flex)`
118
- align-self: stretch;
119
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
120
-
614
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
121
615
  svg path {
122
616
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
123
617
  }
124
618
  span {
125
619
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
126
620
  }
621
+
622
+ &:hover {
623
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
624
+ }
127
625
  `;
128
626
  const PencilIcon = styled__default.default(icons.Pencil)`
129
- width: ${({ theme }) => theme.spaces[4]};
130
- height: ${({ theme }) => theme.spaces[4]};
627
+ width: ${({ theme }) => theme.spaces[3]};
628
+ height: ${({ theme }) => theme.spaces[3]};
131
629
  path {
132
630
  fill: ${({ theme }) => theme.colors.neutral600};
133
631
  }
134
632
  `;
135
633
  const TrashIcon = styled__default.default(icons.Trash)`
136
- width: ${({ theme }) => theme.spaces[4]};
137
- height: ${({ theme }) => theme.spaces[4]};
634
+ width: ${({ theme }) => theme.spaces[3]};
635
+ height: ${({ theme }) => theme.spaces[3]};
138
636
  path {
139
637
  fill: ${({ theme }) => theme.colors.danger600};
140
638
  }
@@ -142,24 +640,6 @@ const TrashIcon = styled__default.default(icons.Trash)`
142
640
  const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
143
641
  max-width: 300px;
144
642
  `;
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
643
  const EntryValidationText = ({ action, schema, components, entry }) => {
164
644
  const { formatMessage } = reactIntl.useIntl();
165
645
  const { validate } = strapiAdmin.unstable_useDocument();
@@ -208,10 +688,8 @@ const ReleaseDetailsLayout = ({
208
688
  toggleWarningSubmit,
209
689
  children
210
690
  }) => {
211
- const { formatMessage } = reactIntl.useIntl();
691
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
212
692
  const { releaseId } = reactRouterDom.useParams();
213
- const [isPopoverVisible, setIsPopoverVisible] = React__namespace.useState(false);
214
- const moreButtonRef = React__namespace.useRef(null);
215
693
  const {
216
694
  data,
217
695
  isLoading: isLoadingDetails,
@@ -225,14 +703,8 @@ const ReleaseDetailsLayout = ({
225
703
  allowedActions: { canUpdate, canDelete }
226
704
  } = helperPlugin.useRBAC(index.PERMISSIONS);
227
705
  const dispatch = index.useTypedDispatch();
706
+ const { trackUsage } = helperPlugin.useTracking();
228
707
  const release = data?.data;
229
- const handleTogglePopover = () => {
230
- setIsPopoverVisible((prev) => !prev);
231
- };
232
- const openReleaseModal = () => {
233
- toggleEditReleaseModal();
234
- handleTogglePopover();
235
- };
236
708
  const handlePublishRelease = async () => {
237
709
  const response = await publishRelease({ id: releaseId });
238
710
  if ("data" in response) {
@@ -243,6 +715,12 @@ const ReleaseDetailsLayout = ({
243
715
  defaultMessage: "Release was published successfully."
244
716
  })
245
717
  });
718
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
719
+ trackUsage("didPublishRelease", {
720
+ totalEntries: totalEntries2,
721
+ totalPublishedEntries,
722
+ totalUnpublishedEntries
723
+ });
246
724
  } else if (index.isAxiosError(response.error)) {
247
725
  toggleNotification({
248
726
  type: "warning",
@@ -255,13 +733,21 @@ const ReleaseDetailsLayout = ({
255
733
  });
256
734
  }
257
735
  };
258
- const openWarningConfirmDialog = () => {
259
- toggleWarningSubmit();
260
- handleTogglePopover();
261
- };
262
736
  const handleRefresh = () => {
263
737
  dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
264
738
  };
739
+ const getCreatedByUser = () => {
740
+ if (!release?.createdBy) {
741
+ return null;
742
+ }
743
+ if (release.createdBy.username) {
744
+ return release.createdBy.username;
745
+ }
746
+ if (release.createdBy.firstname) {
747
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
748
+ }
749
+ return release.createdBy.email;
750
+ };
265
751
  if (isLoadingDetails) {
266
752
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
267
753
  }
@@ -283,90 +769,128 @@ const ReleaseDetailsLayout = ({
283
769
  );
284
770
  }
285
771
  const totalEntries = release.actions.meta.count || 0;
286
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
772
+ const hasCreatedByUser = Boolean(getCreatedByUser());
773
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
774
+ const isScheduled = release.scheduledAt && release.timezone;
775
+ const numberOfEntriesText = formatMessage(
776
+ {
777
+ id: "content-releases.pages.Details.header-subtitle",
778
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
779
+ },
780
+ { number: totalEntries }
781
+ );
782
+ const scheduledText = isScheduled ? formatMessage(
783
+ {
784
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
785
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
786
+ },
787
+ {
788
+ date: formatDate(new Date(release.scheduledAt), {
789
+ weekday: "long",
790
+ day: "numeric",
791
+ month: "long",
792
+ year: "numeric",
793
+ timeZone: release.timezone
794
+ }),
795
+ time: formatTime(new Date(release.scheduledAt), {
796
+ timeZone: release.timezone,
797
+ hourCycle: "h23"
798
+ }),
799
+ offset: index.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
800
+ }
801
+ ) : "";
287
802
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
288
803
  /* @__PURE__ */ jsxRuntime.jsx(
289
804
  designSystem.HeaderLayout,
290
805
  {
291
806
  title: release.name,
292
- subtitle: formatMessage(
293
- {
294
- id: "content-releases.pages.Details.header-subtitle",
295
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
296
- },
297
- { number: totalEntries }
298
- ),
807
+ subtitle: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, lineHeight: 6, children: [
808
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : "") }),
809
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(release.status), children: release.status })
810
+ ] }),
299
811
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
300
812
  id: "global.back",
301
813
  defaultMessage: "Back"
302
814
  }) }),
303
815
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
304
- /* @__PURE__ */ jsxRuntime.jsx(
305
- designSystem.IconButton,
306
- {
307
- label: formatMessage({
308
- id: "content-releases.header.actions.open-release-actions",
309
- defaultMessage: "Release actions"
310
- }),
311
- ref: moreButtonRef,
312
- onClick: handleTogglePopover,
313
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {})
314
- }
315
- ),
316
- isPopoverVisible && /* @__PURE__ */ jsxRuntime.jsxs(
317
- designSystem.Popover,
318
- {
319
- source: moreButtonRef,
320
- placement: "bottom-end",
321
- onDismiss: handleTogglePopover,
322
- spacing: 4,
323
- minWidth: "242px",
324
- children: [
325
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
326
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
327
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
328
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
329
- id: "content-releases.header.actions.edit",
330
- defaultMessage: "Edit"
331
- }) })
332
- ] }),
333
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
334
- /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
335
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
336
- id: "content-releases.header.actions.delete",
337
- defaultMessage: "Delete"
338
- }) })
339
- ] })
340
- ] }),
341
- /* @__PURE__ */ jsxRuntime.jsxs(
342
- ReleaseInfoWrapper,
343
- {
344
- direction: "column",
345
- justifyContent: "center",
346
- alignItems: "flex-start",
347
- gap: 1,
348
- padding: 5,
349
- children: [
350
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
351
- id: "content-releases.header.actions.created",
352
- defaultMessage: "Created"
353
- }) }),
354
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
355
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
356
- formatMessage(
357
- {
358
- id: "content-releases.header.actions.created.description",
359
- defaultMessage: " by {createdBy}"
360
- },
361
- { createdBy }
362
- )
363
- ] })
364
- ]
365
- }
366
- )
367
- ]
368
- }
369
- ),
816
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Root, { children: [
817
+ /* @__PURE__ */ jsxRuntime.jsx(
818
+ v2.Menu.Trigger,
819
+ {
820
+ as: designSystem.IconButton,
821
+ paddingLeft: 2,
822
+ paddingRight: 2,
823
+ "aria-label": formatMessage({
824
+ id: "content-releases.header.actions.open-release-actions",
825
+ defaultMessage: "Release edit and delete menu"
826
+ }),
827
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {}),
828
+ variant: "tertiary"
829
+ }
830
+ ),
831
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
832
+ /* @__PURE__ */ jsxRuntime.jsxs(
833
+ designSystem.Flex,
834
+ {
835
+ alignItems: "center",
836
+ justifyContent: "center",
837
+ direction: "column",
838
+ padding: 1,
839
+ width: "100%",
840
+ children: [
841
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
842
+ /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
843
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
844
+ id: "content-releases.header.actions.edit",
845
+ defaultMessage: "Edit"
846
+ }) })
847
+ ] }) }),
848
+ /* @__PURE__ */ jsxRuntime.jsx(
849
+ StyledMenuItem,
850
+ {
851
+ disabled: !canDelete,
852
+ onSelect: toggleWarningSubmit,
853
+ variant: "danger",
854
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
855
+ /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
856
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
857
+ id: "content-releases.header.actions.delete",
858
+ defaultMessage: "Delete"
859
+ }) })
860
+ ] })
861
+ }
862
+ )
863
+ ]
864
+ }
865
+ ),
866
+ /* @__PURE__ */ jsxRuntime.jsxs(
867
+ ReleaseInfoWrapper,
868
+ {
869
+ direction: "column",
870
+ justifyContent: "center",
871
+ alignItems: "flex-start",
872
+ gap: 1,
873
+ padding: 5,
874
+ children: [
875
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
876
+ id: "content-releases.header.actions.created",
877
+ defaultMessage: "Created"
878
+ }) }),
879
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
880
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
881
+ formatMessage(
882
+ {
883
+ id: "content-releases.header.actions.created.description",
884
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
885
+ },
886
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
887
+ )
888
+ ] })
889
+ ]
890
+ }
891
+ )
892
+ ] })
893
+ ] }),
370
894
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
371
895
  id: "content-releases.header.actions.refresh",
372
896
  defaultMessage: "Refresh"
@@ -422,6 +946,9 @@ const ReleaseDetailsBody = () => {
422
946
  isError: isReleaseError,
423
947
  error: releaseError
424
948
  } = index.useGetReleaseQuery({ id: releaseId });
949
+ const {
950
+ allowedActions: { canUpdate }
951
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
425
952
  const release = releaseData?.data;
426
953
  const selectedGroupBy = query?.groupBy || "contentType";
427
954
  const {
@@ -435,7 +962,7 @@ const ReleaseDetailsBody = () => {
435
962
  releaseId
436
963
  });
437
964
  const [updateReleaseAction] = index.useUpdateReleaseActionMutation();
438
- const handleChangeType = async (e, actionId) => {
965
+ const handleChangeType = async (e, actionId, actionPath) => {
439
966
  const response = await updateReleaseAction({
440
967
  params: {
441
968
  releaseId,
@@ -443,7 +970,11 @@ const ReleaseDetailsBody = () => {
443
970
  },
444
971
  body: {
445
972
  type: e.target.value
446
- }
973
+ },
974
+ query,
975
+ // We are passing the query params to make optimistic updates
976
+ actionPath
977
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
447
978
  });
448
979
  if ("error" in response) {
449
980
  if (index.isAxiosError(response.error)) {
@@ -524,7 +1055,7 @@ const ReleaseDetailsBody = () => {
524
1055
  designSystem.SingleSelect,
525
1056
  {
526
1057
  "aria-label": formatMessage({
527
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1058
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
528
1059
  defaultMessage: "Group by"
529
1060
  }),
530
1061
  customizeContent: (value) => formatMessage(
@@ -542,7 +1073,7 @@ const ReleaseDetailsBody = () => {
542
1073
  }
543
1074
  ) }),
544
1075
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
545
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
1076
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
546
1077
  /* @__PURE__ */ jsxRuntime.jsx(
547
1078
  helperPlugin.Table.Root,
548
1079
  {
@@ -612,56 +1143,59 @@ const ReleaseDetailsBody = () => {
612
1143
  )
613
1144
  ] }),
614
1145
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.LoadingBody, {}),
615
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(({ id, contentType, locale, type, entry }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
616
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
617
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
618
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
619
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
620
- {
621
- id: "content-releases.page.ReleaseDetails.table.action-published",
622
- defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
623
- },
624
- {
625
- isPublish: type === "publish",
626
- b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
627
- }
628
- ) }) : /* @__PURE__ */ jsxRuntime.jsx(
629
- index.ReleaseActionOptions,
630
- {
631
- selected: type,
632
- handleChange: (e) => handleChangeType(e, id),
633
- name: `release-action-${id}-type`
634
- }
635
- ) }),
636
- !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
637
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(
638
- EntryValidationText,
1146
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(
1147
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1148
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
1149
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
1150
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
1151
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
1152
+ {
1153
+ id: "content-releases.page.ReleaseDetails.table.action-published",
1154
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
1155
+ },
1156
+ {
1157
+ isPublish: type === "publish",
1158
+ b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
1159
+ }
1160
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
1161
+ index.ReleaseActionOptions,
639
1162
  {
640
- action: type,
641
- schema: contentTypes?.[contentType.uid],
642
- components,
643
- entry
1163
+ selected: type,
1164
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
1165
+ name: `release-action-${id}-type`,
1166
+ disabled: !canUpdate
644
1167
  }
645
1168
  ) }),
646
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(index.ReleaseActionMenu.Root, { children: [
647
- /* @__PURE__ */ jsxRuntime.jsx(
648
- index.ReleaseActionMenu.ReleaseActionEntryLinkItem,
1169
+ !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1170
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(
1171
+ EntryValidationText,
649
1172
  {
650
- contentTypeUid: contentType.uid,
651
- entryId: entry.id,
652
- locale: locale?.code
1173
+ action: type,
1174
+ schema: contentTypes?.[contentType.uid],
1175
+ components,
1176
+ entry
653
1177
  }
654
- ),
655
- /* @__PURE__ */ jsxRuntime.jsx(
656
- index.ReleaseActionMenu.DeleteReleaseActionItem,
657
- {
658
- releaseId: release.id,
659
- actionId: id
660
- }
661
- )
662
- ] }) }) })
663
- ] })
664
- ] }, id)) })
1178
+ ) }),
1179
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(index.ReleaseActionMenu.Root, { children: [
1180
+ /* @__PURE__ */ jsxRuntime.jsx(
1181
+ index.ReleaseActionMenu.ReleaseActionEntryLinkItem,
1182
+ {
1183
+ contentTypeUid: contentType.uid,
1184
+ entryId: entry.id,
1185
+ locale: locale?.code
1186
+ }
1187
+ ),
1188
+ /* @__PURE__ */ jsxRuntime.jsx(
1189
+ index.ReleaseActionMenu.DeleteReleaseActionItem,
1190
+ {
1191
+ releaseId: release.id,
1192
+ actionId: id
1193
+ }
1194
+ )
1195
+ ] }) }) })
1196
+ ] })
1197
+ ] }, id)
1198
+ ) })
665
1199
  ] })
666
1200
  }
667
1201
  )
@@ -708,11 +1242,18 @@ const ReleaseDetailsPage = () => {
708
1242
  }
709
1243
  );
710
1244
  }
711
- const title = isSuccessDetails && data?.data?.name || "";
1245
+ const releaseData = isSuccessDetails && data?.data || null;
1246
+ const title = releaseData?.name || "";
1247
+ const timezone = releaseData?.timezone ?? null;
1248
+ const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1249
+ const date = scheduledAt ? new Date(format__default.default(scheduledAt, "yyyy-MM-dd")) : null;
1250
+ const time = scheduledAt ? format__default.default(scheduledAt, "HH:mm") : "";
712
1251
  const handleEditRelease = async (values) => {
713
1252
  const response = await updateRelease({
714
1253
  id: releaseId,
715
- name: values.name
1254
+ name: values.name,
1255
+ scheduledAt: values.scheduledAt,
1256
+ timezone: values.timezone
716
1257
  });
717
1258
  if ("data" in response) {
718
1259
  toggleNotification({
@@ -766,7 +1307,14 @@ const ReleaseDetailsPage = () => {
766
1307
  handleClose: toggleEditReleaseModal,
767
1308
  handleSubmit: handleEditRelease,
768
1309
  isLoading: isLoadingDetails || isSubmittingForm,
769
- initialValues: { name: title || "" }
1310
+ initialValues: {
1311
+ name: title || "",
1312
+ scheduledAt,
1313
+ date,
1314
+ time,
1315
+ isScheduled: Boolean(scheduledAt),
1316
+ timezone
1317
+ }
770
1318
  }
771
1319
  ),
772
1320
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -786,247 +1334,6 @@ const ReleaseDetailsPage = () => {
786
1334
  }
787
1335
  );
788
1336
  };
789
- const ReleasesLayout = ({
790
- isLoading,
791
- totalReleases,
792
- onClickAddRelease,
793
- children
794
- }) => {
795
- const { formatMessage } = reactIntl.useIntl();
796
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
797
- /* @__PURE__ */ jsxRuntime.jsx(
798
- designSystem.HeaderLayout,
799
- {
800
- title: formatMessage({
801
- id: "content-releases.pages.Releases.title",
802
- defaultMessage: "Releases"
803
- }),
804
- subtitle: !isLoading && formatMessage(
805
- {
806
- id: "content-releases.pages.Releases.header-subtitle",
807
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
808
- },
809
- { number: totalReleases }
810
- ),
811
- primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}), onClick: onClickAddRelease, children: formatMessage({
812
- id: "content-releases.header.actions.add-release",
813
- defaultMessage: "New release"
814
- }) }) })
815
- }
816
- ),
817
- children
818
- ] });
819
- };
820
- const LinkCard = styled__default.default(v2.Link)`
821
- display: block;
822
- `;
823
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
824
- const { formatMessage } = reactIntl.useIntl();
825
- if (isError) {
826
- return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
827
- }
828
- if (releases?.length === 0) {
829
- return /* @__PURE__ */ jsxRuntime.jsx(
830
- designSystem.EmptyStateLayout,
831
- {
832
- content: formatMessage(
833
- {
834
- id: "content-releases.page.Releases.tab.emptyEntries",
835
- defaultMessage: "No releases"
836
- },
837
- {
838
- target: sectionTitle
839
- }
840
- ),
841
- icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
842
- }
843
- );
844
- }
845
- 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(
846
- designSystem.Flex,
847
- {
848
- direction: "column",
849
- justifyContent: "space-between",
850
- padding: 4,
851
- hasRadius: true,
852
- background: "neutral0",
853
- shadow: "tableShadow",
854
- height: "100%",
855
- width: "100%",
856
- alignItems: "start",
857
- gap: 2,
858
- children: [
859
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
860
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: formatMessage(
861
- {
862
- id: "content-releases.page.Releases.release-item.entries",
863
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
864
- },
865
- { number: actions.meta.count }
866
- ) })
867
- ]
868
- }
869
- ) }) }, id)) });
870
- };
871
- const INITIAL_FORM_VALUES = {
872
- name: ""
873
- };
874
- const ReleasesPage = () => {
875
- const tabRef = React__namespace.useRef(null);
876
- const location = reactRouterDom.useLocation();
877
- const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
878
- const toggleNotification = helperPlugin.useNotification();
879
- const { formatMessage } = reactIntl.useIntl();
880
- const { push, replace } = reactRouterDom.useHistory();
881
- const { formatAPIError } = helperPlugin.useAPIErrorHandler();
882
- const [{ query }, setQuery] = helperPlugin.useQueryParams();
883
- const response = index.useGetReleasesQuery(query);
884
- const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
885
- const { isLoading, isSuccess, isError } = response;
886
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
887
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
888
- React__namespace.useEffect(() => {
889
- if (location?.state?.errors) {
890
- toggleNotification({
891
- type: "warning",
892
- title: formatMessage({
893
- id: "content-releases.pages.Releases.notification.error.title",
894
- defaultMessage: "Your request could not be processed."
895
- }),
896
- message: formatMessage({
897
- id: "content-releases.pages.Releases.notification.error.message",
898
- defaultMessage: "Please try again or open another release."
899
- })
900
- });
901
- replace({ state: null });
902
- }
903
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
904
- React__namespace.useEffect(() => {
905
- if (tabRef.current) {
906
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
907
- }
908
- }, [activeTabIndex]);
909
- const toggleAddReleaseModal = () => {
910
- setReleaseModalShown((prev) => !prev);
911
- };
912
- if (isLoading) {
913
- return /* @__PURE__ */ jsxRuntime.jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) }) });
914
- }
915
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
916
- const handleTabChange = (index2) => {
917
- setQuery({
918
- ...query,
919
- page: 1,
920
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
921
- filters: {
922
- releasedAt: {
923
- $notNull: index2 === 0 ? false : true
924
- }
925
- }
926
- });
927
- };
928
- const handleAddRelease = async (values) => {
929
- const response2 = await createRelease({
930
- name: values.name
931
- });
932
- if ("data" in response2) {
933
- toggleNotification({
934
- type: "success",
935
- message: formatMessage({
936
- id: "content-releases.modal.release-created-notification-success",
937
- defaultMessage: "Release created."
938
- })
939
- });
940
- push(`/plugins/content-releases/${response2.data.data.id}`);
941
- } else if (index.isAxiosError(response2.error)) {
942
- toggleNotification({
943
- type: "warning",
944
- message: formatAPIError(response2.error)
945
- });
946
- } else {
947
- toggleNotification({
948
- type: "warning",
949
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
950
- });
951
- }
952
- };
953
- return /* @__PURE__ */ jsxRuntime.jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
954
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
955
- /* @__PURE__ */ jsxRuntime.jsxs(
956
- designSystem.TabGroup,
957
- {
958
- label: formatMessage({
959
- id: "content-releases.pages.Releases.tab-group.label",
960
- defaultMessage: "Releases list"
961
- }),
962
- variant: "simple",
963
- initialSelectedTabIndex: activeTabIndex,
964
- onTabChange: handleTabChange,
965
- ref: tabRef,
966
- children: [
967
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
968
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
969
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
970
- id: "content-releases.pages.Releases.tab.pending",
971
- defaultMessage: "Pending"
972
- }) }),
973
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
974
- id: "content-releases.pages.Releases.tab.done",
975
- defaultMessage: "Done"
976
- }) })
977
- ] }),
978
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
979
- ] }),
980
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
981
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
982
- ReleasesGrid,
983
- {
984
- sectionTitle: "pending",
985
- releases: response?.currentData?.data,
986
- isError
987
- }
988
- ) }),
989
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
990
- ReleasesGrid,
991
- {
992
- sectionTitle: "done",
993
- releases: response?.currentData?.data,
994
- isError
995
- }
996
- ) })
997
- ] })
998
- ]
999
- }
1000
- ),
1001
- totalReleases > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1002
- /* @__PURE__ */ jsxRuntime.jsx(
1003
- helperPlugin.PageSizeURLQuery,
1004
- {
1005
- options: ["8", "16", "32", "64"],
1006
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1007
- }
1008
- ),
1009
- /* @__PURE__ */ jsxRuntime.jsx(
1010
- helperPlugin.PaginationURLQuery,
1011
- {
1012
- pagination: {
1013
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1014
- }
1015
- }
1016
- )
1017
- ] })
1018
- ] }) }),
1019
- releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
1020
- ReleaseModal,
1021
- {
1022
- handleClose: toggleAddReleaseModal,
1023
- handleSubmit: handleAddRelease,
1024
- isLoading: isSubmittingForm,
1025
- initialValues: INITIAL_FORM_VALUES
1026
- }
1027
- )
1028
- ] });
1029
- };
1030
1337
  const App = () => {
1031
1338
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPagePermissions, { permissions: index.PERMISSIONS.main, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Switch, { children: [
1032
1339
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}`, component: ReleasesPage }),
@@ -1034,4 +1341,4 @@ const App = () => {
1034
1341
  ] }) });
1035
1342
  };
1036
1343
  exports.App = App;
1037
- //# sourceMappingURL=App-YFvVMqB8.js.map
1344
+ //# sourceMappingURL=App-P1kyM3gT.js.map