@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 0.0.0-next.734763e5757af27ff96ad1c9662161f3f677052a

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