@strapi/content-releases 0.0.0-next.3844395bef7efa05c25c6d4337306935905bc653 → 0.0.0-next.3cc05002fb92029975799c3113971bb5b5198d7c

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