@strapi/content-releases 0.0.0-next.e1ede8c55a0e1e22ce20137bf238fc374bd5dd51 → 0.0.0-next.f4ff842a3cb7b83db540bee67554b704e042b042

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 (33) hide show
  1. package/dist/_chunks/App-dLXY5ei3.js +1353 -0
  2. package/dist/_chunks/App-dLXY5ei3.js.map +1 -0
  3. package/dist/_chunks/App-jrh58sXY.mjs +1330 -0
  4. package/dist/_chunks/App-jrh58sXY.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-haKSQIo8.js → en-HrREghh3.js} +31 -7
  10. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  11. package/dist/_chunks/{en-ngTk74JV.mjs → en-ltT1TlKQ.mjs} +31 -7
  12. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  13. package/dist/_chunks/{index-EdBmRHRU.js → index-CVO0Rqdm.js} +551 -64
  14. package/dist/_chunks/index-CVO0Rqdm.js.map +1 -0
  15. package/dist/_chunks/{index-XAQOX_IB.mjs → index-PiOGBETy.mjs} +570 -83
  16. package/dist/_chunks/index-PiOGBETy.mjs.map +1 -0
  17. package/dist/admin/index.js +2 -1
  18. package/dist/admin/index.js.map +1 -1
  19. package/dist/admin/index.mjs +3 -2
  20. package/dist/admin/index.mjs.map +1 -1
  21. package/dist/server/index.js +1192 -299
  22. package/dist/server/index.js.map +1 -1
  23. package/dist/server/index.mjs +1191 -300
  24. package/dist/server/index.mjs.map +1 -1
  25. package/package.json +16 -12
  26. package/dist/_chunks/App-g2P5kbSm.mjs +0 -945
  27. package/dist/_chunks/App-g2P5kbSm.mjs.map +0 -1
  28. package/dist/_chunks/App-o5_WfqR-.js +0 -967
  29. package/dist/_chunks/App-o5_WfqR-.js.map +0 -1
  30. package/dist/_chunks/en-haKSQIo8.js.map +0 -1
  31. package/dist/_chunks/en-ngTk74JV.mjs.map +0 -1
  32. package/dist/_chunks/index-EdBmRHRU.js.map +0 -1
  33. package/dist/_chunks/index-XAQOX_IB.mjs.map +0 -1
@@ -0,0 +1,1353 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const helperPlugin = require("@strapi/helper-plugin");
5
+ const reactRouterDom = require("react-router-dom");
6
+ const index = require("./index-CVO0Rqdm.js");
7
+ const React = require("react");
8
+ const strapiAdmin = require("@strapi/admin/strapi-admin");
9
+ const designSystem = require("@strapi/design-system");
10
+ const v2 = require("@strapi/design-system/v2");
11
+ const icons = require("@strapi/icons");
12
+ const format = require("date-fns/format");
13
+ const dateFnsTz = require("date-fns-tz");
14
+ const reactIntl = require("react-intl");
15
+ const styled = require("styled-components");
16
+ const dateFns = require("date-fns");
17
+ const formik = require("formik");
18
+ const yup = require("yup");
19
+ require("@reduxjs/toolkit/query");
20
+ require("axios");
21
+ require("@reduxjs/toolkit/query/react");
22
+ require("react-redux");
23
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
24
+ function _interopNamespace(e) {
25
+ if (e && e.__esModule)
26
+ return e;
27
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
28
+ if (e) {
29
+ for (const k in e) {
30
+ if (k !== "default") {
31
+ const d = Object.getOwnPropertyDescriptor(e, k);
32
+ Object.defineProperty(n, k, d.get ? d : {
33
+ enumerable: true,
34
+ get: () => e[k]
35
+ });
36
+ }
37
+ }
38
+ }
39
+ n.default = e;
40
+ return Object.freeze(n);
41
+ }
42
+ const React__namespace = /* @__PURE__ */ _interopNamespace(React);
43
+ const format__default = /* @__PURE__ */ _interopDefault(format);
44
+ const styled__default = /* @__PURE__ */ _interopDefault(styled);
45
+ const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
46
+ const RELEASE_SCHEMA = yup__namespace.object().shape({
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
+ })
65
+ }).required().noUnknown();
66
+ const ReleaseModal = ({
67
+ handleClose,
68
+ handleSubmit,
69
+ initialValues,
70
+ isLoading = false
71
+ }) => {
72
+ const { formatMessage } = reactIntl.useIntl();
73
+ const { pathname } = reactRouterDom.useLocation();
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
+ };
91
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
92
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
93
+ {
94
+ id: "content-releases.modal.title",
95
+ defaultMessage: "{isCreatingRelease, select, true {New release} other {Edit release}}"
96
+ },
97
+ { isCreatingRelease }
98
+ ) }) }),
99
+ /* @__PURE__ */ jsxRuntime.jsx(
100
+ formik.Formik,
101
+ {
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
+ },
113
+ validationSchema: RELEASE_SCHEMA,
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
+ ] }) }),
215
+ /* @__PURE__ */ jsxRuntime.jsx(
216
+ designSystem.ModalFooter,
217
+ {
218
+ startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
219
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
220
+ {
221
+ id: "content-releases.modal.form.button.submit",
222
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
223
+ },
224
+ { isCreatingRelease }
225
+ ) })
226
+ }
227
+ )
228
+ ] })
229
+ }
230
+ )
231
+ ] });
232
+ };
233
+ const getTimezones = (selectedDate) => {
234
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
235
+ const utcOffset = index.getTimezoneOffset(timezone, selectedDate);
236
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
237
+ });
238
+ const systemTimezone = timezoneList.find(
239
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
240
+ );
241
+ return { timezoneList, systemTimezone };
242
+ };
243
+ const TimezoneComponent = ({ timezoneOptions }) => {
244
+ const { values, errors, setFieldValue } = formik.useFormikContext();
245
+ const { formatMessage } = reactIntl.useIntl();
246
+ const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
247
+ React__namespace.useEffect(() => {
248
+ if (values.date) {
249
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
250
+ setTimezoneList(timezoneList2);
251
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
252
+ if (updatedTimezone) {
253
+ setFieldValue("timezone", updatedTimezone.value);
254
+ }
255
+ }
256
+ }, [setFieldValue, values.date, values.timezone]);
257
+ return /* @__PURE__ */ jsxRuntime.jsx(
258
+ designSystem.Combobox,
259
+ {
260
+ label: formatMessage({
261
+ id: "content-releases.modal.form.input.label.timezone",
262
+ defaultMessage: "Timezone"
263
+ }),
264
+ autocomplete: { type: "list", filter: "contains" },
265
+ name: "timezone",
266
+ value: values.timezone || void 0,
267
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
268
+ onChange: (timezone) => {
269
+ setFieldValue("timezone", timezone);
270
+ },
271
+ onTextValueChange: (timezone) => {
272
+ setFieldValue("timezone", timezone);
273
+ },
274
+ onClear: () => {
275
+ setFieldValue("timezone", "");
276
+ },
277
+ error: errors.timezone,
278
+ required: true,
279
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
280
+ }
281
+ );
282
+ };
283
+ const LinkCard = styled__default.default(v2.Link)`
284
+ display: block;
285
+ `;
286
+ const RelativeTime = styled__default.default(helperPlugin.RelativeTime)`
287
+ display: inline-block;
288
+ &::first-letter {
289
+ text-transform: uppercase;
290
+ }
291
+ `;
292
+ const getBadgeProps = (status) => {
293
+ let color;
294
+ switch (status) {
295
+ case "ready":
296
+ color = "success";
297
+ break;
298
+ case "blocked":
299
+ color = "warning";
300
+ break;
301
+ case "failed":
302
+ color = "danger";
303
+ break;
304
+ case "done":
305
+ color = "primary";
306
+ break;
307
+ case "empty":
308
+ default:
309
+ color = "neutral";
310
+ }
311
+ return {
312
+ textColor: `${color}600`,
313
+ backgroundColor: `${color}100`,
314
+ borderColor: `${color}200`
315
+ };
316
+ };
317
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
318
+ const { formatMessage } = reactIntl.useIntl();
319
+ if (isError) {
320
+ return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
321
+ }
322
+ if (releases?.length === 0) {
323
+ return /* @__PURE__ */ jsxRuntime.jsx(
324
+ designSystem.EmptyStateLayout,
325
+ {
326
+ content: formatMessage(
327
+ {
328
+ id: "content-releases.page.Releases.tab.emptyEntries",
329
+ defaultMessage: "No releases"
330
+ },
331
+ {
332
+ target: sectionTitle
333
+ }
334
+ ),
335
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
336
+ }
337
+ );
338
+ }
339
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid, { gap: 4, children: releases.map(({ id, name, scheduledAt, status }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxRuntime.jsxs(
340
+ designSystem.Flex,
341
+ {
342
+ direction: "column",
343
+ justifyContent: "space-between",
344
+ padding: 4,
345
+ hasRadius: true,
346
+ background: "neutral0",
347
+ shadow: "tableShadow",
348
+ height: "100%",
349
+ width: "100%",
350
+ alignItems: "start",
351
+ gap: 4,
352
+ children: [
353
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "start", gap: 1, children: [
354
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
355
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsxRuntime.jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
356
+ id: "content-releases.pages.Releases.not-scheduled",
357
+ defaultMessage: "Not scheduled"
358
+ }) })
359
+ ] }),
360
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(status), children: status })
361
+ ]
362
+ }
363
+ ) }) }, id)) });
364
+ };
365
+ const StyledAlert = styled__default.default(designSystem.Alert)`
366
+ button {
367
+ display: none;
368
+ }
369
+ p + div {
370
+ margin-left: auto;
371
+ }
372
+ `;
373
+ const INITIAL_FORM_VALUES = {
374
+ name: "",
375
+ date: null,
376
+ time: "",
377
+ isScheduled: true,
378
+ scheduledAt: null,
379
+ timezone: null
380
+ };
381
+ const ReleasesPage = () => {
382
+ const tabRef = React__namespace.useRef(null);
383
+ const location = reactRouterDom.useLocation();
384
+ const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
385
+ const toggleNotification = helperPlugin.useNotification();
386
+ const { formatMessage } = reactIntl.useIntl();
387
+ const { push, replace } = reactRouterDom.useHistory();
388
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
389
+ const [{ query }, setQuery] = helperPlugin.useQueryParams();
390
+ const response = index.useGetReleasesQuery(query);
391
+ const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
392
+ const { getFeature } = strapiAdmin.useLicenseLimits();
393
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
394
+ const { trackUsage } = helperPlugin.useTracking();
395
+ const { isLoading, isSuccess, isError } = response;
396
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
397
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
398
+ React__namespace.useEffect(() => {
399
+ if (location?.state?.errors) {
400
+ toggleNotification({
401
+ type: "warning",
402
+ title: formatMessage({
403
+ id: "content-releases.pages.Releases.notification.error.title",
404
+ defaultMessage: "Your request could not be processed."
405
+ }),
406
+ message: formatMessage({
407
+ id: "content-releases.pages.Releases.notification.error.message",
408
+ defaultMessage: "Please try again or open another release."
409
+ })
410
+ });
411
+ replace({ state: null });
412
+ }
413
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
414
+ React__namespace.useEffect(() => {
415
+ if (tabRef.current) {
416
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
417
+ }
418
+ }, [activeTabIndex]);
419
+ const toggleAddReleaseModal = () => {
420
+ setReleaseModalShown((prev) => !prev);
421
+ };
422
+ if (isLoading) {
423
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
424
+ }
425
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
426
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
427
+ const handleTabChange = (index2) => {
428
+ setQuery({
429
+ ...query,
430
+ page: 1,
431
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
432
+ filters: {
433
+ releasedAt: {
434
+ $notNull: index2 === 0 ? false : true
435
+ }
436
+ }
437
+ });
438
+ };
439
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
440
+ const response2 = await createRelease({
441
+ name,
442
+ scheduledAt,
443
+ timezone
444
+ });
445
+ if ("data" in response2) {
446
+ toggleNotification({
447
+ type: "success",
448
+ message: formatMessage({
449
+ id: "content-releases.modal.release-created-notification-success",
450
+ defaultMessage: "Release created."
451
+ })
452
+ });
453
+ trackUsage("didCreateRelease");
454
+ push(`/plugins/content-releases/${response2.data.data.id}`);
455
+ } else if (index.isAxiosError(response2.error)) {
456
+ toggleNotification({
457
+ type: "warning",
458
+ message: formatAPIError(response2.error)
459
+ });
460
+ } else {
461
+ toggleNotification({
462
+ type: "warning",
463
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
464
+ });
465
+ }
466
+ };
467
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
468
+ /* @__PURE__ */ jsxRuntime.jsx(
469
+ designSystem.HeaderLayout,
470
+ {
471
+ title: formatMessage({
472
+ id: "content-releases.pages.Releases.title",
473
+ defaultMessage: "Releases"
474
+ }),
475
+ subtitle: formatMessage({
476
+ id: "content-releases.pages.Releases.header-subtitle",
477
+ defaultMessage: "Create and manage content updates"
478
+ }),
479
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
480
+ designSystem.Button,
481
+ {
482
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
483
+ onClick: toggleAddReleaseModal,
484
+ disabled: hasReachedMaximumPendingReleases,
485
+ children: formatMessage({
486
+ id: "content-releases.header.actions.add-release",
487
+ defaultMessage: "New release"
488
+ })
489
+ }
490
+ ) })
491
+ }
492
+ ),
493
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
494
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
495
+ StyledAlert,
496
+ {
497
+ marginBottom: 6,
498
+ action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
499
+ id: "content-releases.pages.Releases.max-limit-reached.action",
500
+ defaultMessage: "Explore plans"
501
+ }) }),
502
+ title: formatMessage(
503
+ {
504
+ id: "content-releases.pages.Releases.max-limit-reached.title",
505
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
506
+ },
507
+ { number: maximumReleases }
508
+ ),
509
+ onClose: () => {
510
+ },
511
+ closeLabel: "",
512
+ children: formatMessage({
513
+ id: "content-releases.pages.Releases.max-limit-reached.message",
514
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
515
+ })
516
+ }
517
+ ),
518
+ /* @__PURE__ */ jsxRuntime.jsxs(
519
+ designSystem.TabGroup,
520
+ {
521
+ label: formatMessage({
522
+ id: "content-releases.pages.Releases.tab-group.label",
523
+ defaultMessage: "Releases list"
524
+ }),
525
+ variant: "simple",
526
+ initialSelectedTabIndex: activeTabIndex,
527
+ onTabChange: handleTabChange,
528
+ ref: tabRef,
529
+ children: [
530
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
531
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
532
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage(
533
+ {
534
+ id: "content-releases.pages.Releases.tab.pending",
535
+ defaultMessage: "Pending ({count})"
536
+ },
537
+ {
538
+ count: totalPendingReleases
539
+ }
540
+ ) }),
541
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
542
+ id: "content-releases.pages.Releases.tab.done",
543
+ defaultMessage: "Done"
544
+ }) })
545
+ ] }),
546
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
547
+ ] }),
548
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
549
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
550
+ ReleasesGrid,
551
+ {
552
+ sectionTitle: "pending",
553
+ releases: response?.currentData?.data,
554
+ isError
555
+ }
556
+ ) }),
557
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
558
+ ReleasesGrid,
559
+ {
560
+ sectionTitle: "done",
561
+ releases: response?.currentData?.data,
562
+ isError
563
+ }
564
+ ) })
565
+ ] })
566
+ ]
567
+ }
568
+ ),
569
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
570
+ /* @__PURE__ */ jsxRuntime.jsx(
571
+ helperPlugin.PageSizeURLQuery,
572
+ {
573
+ options: ["8", "16", "32", "64"],
574
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
575
+ }
576
+ ),
577
+ /* @__PURE__ */ jsxRuntime.jsx(
578
+ helperPlugin.PaginationURLQuery,
579
+ {
580
+ pagination: {
581
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
582
+ }
583
+ }
584
+ )
585
+ ] }) : null
586
+ ] }) }),
587
+ releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
588
+ ReleaseModal,
589
+ {
590
+ handleClose: toggleAddReleaseModal,
591
+ handleSubmit: handleAddRelease,
592
+ isLoading: isSubmittingForm,
593
+ initialValues: INITIAL_FORM_VALUES
594
+ }
595
+ )
596
+ ] });
597
+ };
598
+ const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
599
+ align-self: stretch;
600
+ border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
601
+ border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
602
+ border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
603
+ `;
604
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
605
+ svg path {
606
+ fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
607
+ }
608
+ span {
609
+ color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
610
+ }
611
+
612
+ &:hover {
613
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
614
+ }
615
+ `;
616
+ const PencilIcon = styled__default.default(icons.Pencil)`
617
+ width: ${({ theme }) => theme.spaces[3]};
618
+ height: ${({ theme }) => theme.spaces[3]};
619
+ path {
620
+ fill: ${({ theme }) => theme.colors.neutral600};
621
+ }
622
+ `;
623
+ const TrashIcon = styled__default.default(icons.Trash)`
624
+ width: ${({ theme }) => theme.spaces[3]};
625
+ height: ${({ theme }) => theme.spaces[3]};
626
+ path {
627
+ fill: ${({ theme }) => theme.colors.danger600};
628
+ }
629
+ `;
630
+ const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
631
+ max-width: 300px;
632
+ `;
633
+ const EntryValidationText = ({ action, schema, components, entry }) => {
634
+ const { formatMessage } = reactIntl.useIntl();
635
+ const { validate } = strapiAdmin.unstable_useDocument();
636
+ const { errors } = validate(entry, {
637
+ contentType: schema,
638
+ components,
639
+ isCreatingEntry: false
640
+ });
641
+ if (Object.keys(errors).length > 0) {
642
+ const validationErrorsMessages = Object.entries(errors).map(
643
+ ([key, value]) => formatMessage(
644
+ { id: `${value.id}.withField`, defaultMessage: value.defaultMessage },
645
+ { field: key }
646
+ )
647
+ ).join(" ");
648
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
649
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "danger600", as: icons.CrossCircle }),
650
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { description: validationErrorsMessages, children: /* @__PURE__ */ jsxRuntime.jsx(TypographyMaxWidth, { textColor: "danger600", variant: "omega", fontWeight: "semiBold", ellipsis: true, children: validationErrorsMessages }) })
651
+ ] });
652
+ }
653
+ if (action == "publish") {
654
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
655
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "success600", as: icons.CheckCircle }),
656
+ entry.publishedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
657
+ id: "content-releases.pages.ReleaseDetails.entry-validation.already-published",
658
+ defaultMessage: "Already published"
659
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
660
+ id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish",
661
+ defaultMessage: "Ready to publish"
662
+ }) })
663
+ ] });
664
+ }
665
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
666
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "success600", as: icons.CheckCircle }),
667
+ !entry.publishedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
668
+ id: "content-releases.pages.ReleaseDetails.entry-validation.already-unpublished",
669
+ defaultMessage: "Already unpublished"
670
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
671
+ id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish",
672
+ defaultMessage: "Ready to unpublish"
673
+ }) })
674
+ ] });
675
+ };
676
+ const ReleaseDetailsLayout = ({
677
+ toggleEditReleaseModal,
678
+ toggleWarningSubmit,
679
+ children
680
+ }) => {
681
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
682
+ const { releaseId } = reactRouterDom.useParams();
683
+ const {
684
+ data,
685
+ isLoading: isLoadingDetails,
686
+ isError,
687
+ error
688
+ } = index.useGetReleaseQuery({ id: releaseId });
689
+ const [publishRelease, { isLoading: isPublishing }] = index.usePublishReleaseMutation();
690
+ const toggleNotification = helperPlugin.useNotification();
691
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
692
+ const {
693
+ allowedActions: { canUpdate, canDelete }
694
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
695
+ const dispatch = index.useTypedDispatch();
696
+ const { trackUsage } = helperPlugin.useTracking();
697
+ const release = data?.data;
698
+ const handlePublishRelease = async () => {
699
+ const response = await publishRelease({ id: releaseId });
700
+ if ("data" in response) {
701
+ toggleNotification({
702
+ type: "success",
703
+ message: formatMessage({
704
+ id: "content-releases.pages.ReleaseDetails.publish-notification-success",
705
+ defaultMessage: "Release was published successfully."
706
+ })
707
+ });
708
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
709
+ trackUsage("didPublishRelease", {
710
+ totalEntries: totalEntries2,
711
+ totalPublishedEntries,
712
+ totalUnpublishedEntries
713
+ });
714
+ } else if (index.isAxiosError(response.error)) {
715
+ toggleNotification({
716
+ type: "warning",
717
+ message: formatAPIError(response.error)
718
+ });
719
+ } else {
720
+ toggleNotification({
721
+ type: "warning",
722
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
723
+ });
724
+ }
725
+ };
726
+ const handleRefresh = () => {
727
+ dispatch(
728
+ index.releaseApi.util.invalidateTags([
729
+ { type: "ReleaseAction", id: "LIST" },
730
+ { type: "Release", id: releaseId }
731
+ ])
732
+ );
733
+ };
734
+ const getCreatedByUser = () => {
735
+ if (!release?.createdBy) {
736
+ return null;
737
+ }
738
+ if (release.createdBy.username) {
739
+ return release.createdBy.username;
740
+ }
741
+ if (release.createdBy.firstname) {
742
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
743
+ }
744
+ return release.createdBy.email;
745
+ };
746
+ if (isLoadingDetails) {
747
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
748
+ }
749
+ if (isError || !release) {
750
+ return /* @__PURE__ */ jsxRuntime.jsx(
751
+ reactRouterDom.Redirect,
752
+ {
753
+ to: {
754
+ pathname: "/plugins/content-releases",
755
+ state: {
756
+ errors: [
757
+ {
758
+ code: error?.code
759
+ }
760
+ ]
761
+ }
762
+ }
763
+ }
764
+ );
765
+ }
766
+ const totalEntries = release.actions.meta.count || 0;
767
+ const hasCreatedByUser = Boolean(getCreatedByUser());
768
+ const isScheduled = release.scheduledAt && release.timezone;
769
+ const numberOfEntriesText = formatMessage(
770
+ {
771
+ id: "content-releases.pages.Details.header-subtitle",
772
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
773
+ },
774
+ { number: totalEntries }
775
+ );
776
+ const scheduledText = isScheduled ? formatMessage(
777
+ {
778
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
779
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
780
+ },
781
+ {
782
+ date: formatDate(new Date(release.scheduledAt), {
783
+ weekday: "long",
784
+ day: "numeric",
785
+ month: "long",
786
+ year: "numeric",
787
+ timeZone: release.timezone
788
+ }),
789
+ time: formatTime(new Date(release.scheduledAt), {
790
+ timeZone: release.timezone,
791
+ hourCycle: "h23"
792
+ }),
793
+ offset: index.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
794
+ }
795
+ ) : "";
796
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
797
+ /* @__PURE__ */ jsxRuntime.jsx(
798
+ designSystem.HeaderLayout,
799
+ {
800
+ title: release.name,
801
+ subtitle: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, lineHeight: 6, children: [
802
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
803
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(release.status), children: release.status })
804
+ ] }),
805
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
806
+ id: "global.back",
807
+ defaultMessage: "Back"
808
+ }) }),
809
+ primaryAction: !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
810
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Root, { children: [
811
+ /* @__PURE__ */ jsxRuntime.jsx(
812
+ v2.Menu.Trigger,
813
+ {
814
+ as: designSystem.IconButton,
815
+ paddingLeft: 2,
816
+ paddingRight: 2,
817
+ "aria-label": formatMessage({
818
+ id: "content-releases.header.actions.open-release-actions",
819
+ defaultMessage: "Release edit and delete menu"
820
+ }),
821
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {}),
822
+ variant: "tertiary"
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
826
+ /* @__PURE__ */ jsxRuntime.jsxs(
827
+ designSystem.Flex,
828
+ {
829
+ alignItems: "center",
830
+ justifyContent: "center",
831
+ direction: "column",
832
+ padding: 1,
833
+ width: "100%",
834
+ children: [
835
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
836
+ /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
837
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
838
+ id: "content-releases.header.actions.edit",
839
+ defaultMessage: "Edit"
840
+ }) })
841
+ ] }) }),
842
+ /* @__PURE__ */ jsxRuntime.jsx(
843
+ StyledMenuItem,
844
+ {
845
+ disabled: !canDelete,
846
+ onSelect: toggleWarningSubmit,
847
+ variant: "danger",
848
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
849
+ /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
850
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
851
+ id: "content-releases.header.actions.delete",
852
+ defaultMessage: "Delete"
853
+ }) })
854
+ ] })
855
+ }
856
+ )
857
+ ]
858
+ }
859
+ ),
860
+ /* @__PURE__ */ jsxRuntime.jsxs(
861
+ ReleaseInfoWrapper,
862
+ {
863
+ direction: "column",
864
+ justifyContent: "center",
865
+ alignItems: "flex-start",
866
+ gap: 1,
867
+ padding: 5,
868
+ children: [
869
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
870
+ id: "content-releases.header.actions.created",
871
+ defaultMessage: "Created"
872
+ }) }),
873
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
874
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
875
+ formatMessage(
876
+ {
877
+ id: "content-releases.header.actions.created.description",
878
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
879
+ },
880
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
881
+ )
882
+ ] })
883
+ ]
884
+ }
885
+ )
886
+ ] })
887
+ ] }),
888
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
889
+ id: "content-releases.header.actions.refresh",
890
+ defaultMessage: "Refresh"
891
+ }) }),
892
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.publish, children: /* @__PURE__ */ jsxRuntime.jsx(
893
+ designSystem.Button,
894
+ {
895
+ size: "S",
896
+ variant: "default",
897
+ onClick: handlePublishRelease,
898
+ loading: isPublishing,
899
+ disabled: release.actions.meta.count === 0,
900
+ children: formatMessage({
901
+ id: "content-releases.header.actions.publish",
902
+ defaultMessage: "Publish"
903
+ })
904
+ }
905
+ ) })
906
+ ] })
907
+ }
908
+ ),
909
+ children
910
+ ] });
911
+ };
912
+ const GROUP_BY_OPTIONS = ["contentType", "locale", "action"];
913
+ const GROUP_BY_OPTIONS_NO_LOCALE = ["contentType", "action"];
914
+ const getGroupByOptionLabel = (value) => {
915
+ if (value === "locale") {
916
+ return {
917
+ id: "content-releases.pages.ReleaseDetails.groupBy.option.locales",
918
+ defaultMessage: "Locales"
919
+ };
920
+ }
921
+ if (value === "action") {
922
+ return {
923
+ id: "content-releases.pages.ReleaseDetails.groupBy.option.actions",
924
+ defaultMessage: "Actions"
925
+ };
926
+ }
927
+ return {
928
+ id: "content-releases.pages.ReleaseDetails.groupBy.option.content-type",
929
+ defaultMessage: "Content-Types"
930
+ };
931
+ };
932
+ const DEFAULT_RELEASE_DETAILS_HEADER = [
933
+ {
934
+ key: "__name__",
935
+ fieldSchema: { type: "string" },
936
+ metadatas: {
937
+ label: {
938
+ id: "content-releases.page.ReleaseDetails.table.header.label.name",
939
+ defaultMessage: "name"
940
+ },
941
+ searchable: false,
942
+ sortable: false
943
+ },
944
+ name: "name"
945
+ }
946
+ ];
947
+ const ReleaseDetailsBody = () => {
948
+ const { formatMessage } = reactIntl.useIntl();
949
+ const { releaseId } = reactRouterDom.useParams();
950
+ const [{ query }, setQuery] = helperPlugin.useQueryParams();
951
+ const toggleNotification = helperPlugin.useNotification();
952
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
953
+ const {
954
+ data: releaseData,
955
+ isLoading: isReleaseLoading,
956
+ isError: isReleaseError,
957
+ error: releaseError
958
+ } = index.useGetReleaseQuery({ id: releaseId });
959
+ const {
960
+ allowedActions: { canUpdate }
961
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
962
+ const { runHookWaterfall } = helperPlugin.useStrapiApp();
963
+ const {
964
+ displayedHeaders,
965
+ hasI18nEnabled
966
+ } = runHookWaterfall(
967
+ "ContentReleases/pages/ReleaseDetails/add-locale-in-releases",
968
+ {
969
+ displayedHeaders: DEFAULT_RELEASE_DETAILS_HEADER,
970
+ hasI18nEnabled: false
971
+ }
972
+ );
973
+ const release = releaseData?.data;
974
+ const selectedGroupBy = query?.groupBy || "contentType";
975
+ const {
976
+ isLoading,
977
+ isFetching,
978
+ isError,
979
+ data,
980
+ error: releaseActionsError
981
+ } = index.useGetReleaseActionsQuery({
982
+ ...query,
983
+ releaseId
984
+ });
985
+ const [updateReleaseAction] = index.useUpdateReleaseActionMutation();
986
+ const handleChangeType = async (e, actionId, actionPath) => {
987
+ const response = await updateReleaseAction({
988
+ params: {
989
+ releaseId,
990
+ actionId
991
+ },
992
+ body: {
993
+ type: e.target.value
994
+ },
995
+ query,
996
+ // We are passing the query params to make optimistic updates
997
+ actionPath
998
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
999
+ });
1000
+ if ("error" in response) {
1001
+ if (index.isAxiosError(response.error)) {
1002
+ toggleNotification({
1003
+ type: "warning",
1004
+ message: formatAPIError(response.error)
1005
+ });
1006
+ } else {
1007
+ toggleNotification({
1008
+ type: "warning",
1009
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1010
+ });
1011
+ }
1012
+ }
1013
+ };
1014
+ if (isLoading || isReleaseLoading) {
1015
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
1016
+ }
1017
+ const releaseActions = data?.data;
1018
+ const releaseMeta = data?.meta;
1019
+ const contentTypes = releaseMeta?.contentTypes || {};
1020
+ const components = releaseMeta?.components || {};
1021
+ if (isReleaseError || !release) {
1022
+ const errorsArray = [];
1023
+ if (releaseError) {
1024
+ errorsArray.push({
1025
+ code: releaseError.code
1026
+ });
1027
+ }
1028
+ if (releaseActionsError) {
1029
+ errorsArray.push({
1030
+ code: releaseActionsError.code
1031
+ });
1032
+ }
1033
+ return /* @__PURE__ */ jsxRuntime.jsx(
1034
+ reactRouterDom.Redirect,
1035
+ {
1036
+ to: {
1037
+ pathname: "/plugins/content-releases",
1038
+ state: {
1039
+ errors: errorsArray
1040
+ }
1041
+ }
1042
+ }
1043
+ );
1044
+ }
1045
+ if (isError || !releaseActions) {
1046
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {}) });
1047
+ }
1048
+ if (Object.keys(releaseActions).length === 0) {
1049
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(
1050
+ helperPlugin.NoContent,
1051
+ {
1052
+ content: {
1053
+ id: "content-releases.pages.Details.tab.emptyEntries",
1054
+ defaultMessage: "This release is empty. Open the Content Manager, select an entry and add it to the release."
1055
+ },
1056
+ action: /* @__PURE__ */ jsxRuntime.jsx(
1057
+ v2.LinkButton,
1058
+ {
1059
+ as: reactRouterDom.Link,
1060
+ to: {
1061
+ pathname: "/content-manager"
1062
+ },
1063
+ style: { textDecoration: "none" },
1064
+ variant: "secondary",
1065
+ children: formatMessage({
1066
+ id: "content-releases.page.Details.button.openContentManager",
1067
+ defaultMessage: "Open the Content Manager"
1068
+ })
1069
+ }
1070
+ )
1071
+ }
1072
+ ) });
1073
+ }
1074
+ const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
1075
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 8, direction: "column", alignItems: "stretch", children: [
1076
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(
1077
+ designSystem.SingleSelect,
1078
+ {
1079
+ "aria-label": formatMessage({
1080
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
1081
+ defaultMessage: "Group by"
1082
+ }),
1083
+ customizeContent: (value) => formatMessage(
1084
+ {
1085
+ id: `content-releases.pages.ReleaseDetails.groupBy.label`,
1086
+ defaultMessage: `Group by {groupBy}`
1087
+ },
1088
+ {
1089
+ groupBy: value
1090
+ }
1091
+ ),
1092
+ value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
1093
+ onChange: (value) => setQuery({ groupBy: value }),
1094
+ children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: option, children: formatMessage(getGroupByOptionLabel(option)) }, option))
1095
+ }
1096
+ ) }),
1097
+ Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
1098
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
1099
+ /* @__PURE__ */ jsxRuntime.jsx(
1100
+ helperPlugin.Table.Root,
1101
+ {
1102
+ rows: releaseActions[key].map((item) => ({
1103
+ ...item,
1104
+ id: Number(item.entry.id)
1105
+ })),
1106
+ colCount: releaseActions[key].length,
1107
+ isLoading,
1108
+ isFetching,
1109
+ children: /* @__PURE__ */ jsxRuntime.jsxs(helperPlugin.Table.Content, { children: [
1110
+ /* @__PURE__ */ jsxRuntime.jsxs(helperPlugin.Table.Head, { children: [
1111
+ displayedHeaders.map(({ key: key2, fieldSchema, metadatas, name }) => /* @__PURE__ */ jsxRuntime.jsx(
1112
+ helperPlugin.Table.HeaderCell,
1113
+ {
1114
+ fieldSchemaType: fieldSchema.type,
1115
+ label: formatMessage(metadatas.label),
1116
+ name
1117
+ },
1118
+ key2
1119
+ )),
1120
+ /* @__PURE__ */ jsxRuntime.jsx(
1121
+ helperPlugin.Table.HeaderCell,
1122
+ {
1123
+ fieldSchemaType: "string",
1124
+ label: formatMessage({
1125
+ id: "content-releases.page.ReleaseDetails.table.header.label.content-type",
1126
+ defaultMessage: "content-type"
1127
+ }),
1128
+ name: "content-type"
1129
+ }
1130
+ ),
1131
+ /* @__PURE__ */ jsxRuntime.jsx(
1132
+ helperPlugin.Table.HeaderCell,
1133
+ {
1134
+ fieldSchemaType: "string",
1135
+ label: formatMessage({
1136
+ id: "content-releases.page.ReleaseDetails.table.header.label.action",
1137
+ defaultMessage: "action"
1138
+ }),
1139
+ name: "action"
1140
+ }
1141
+ ),
1142
+ !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsx(
1143
+ helperPlugin.Table.HeaderCell,
1144
+ {
1145
+ fieldSchemaType: "string",
1146
+ label: formatMessage({
1147
+ id: "content-releases.page.ReleaseDetails.table.header.label.status",
1148
+ defaultMessage: "status"
1149
+ }),
1150
+ name: "status"
1151
+ }
1152
+ )
1153
+ ] }),
1154
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.LoadingBody, {}),
1155
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(
1156
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1157
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
1158
+ hasI18nEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
1159
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
1160
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
1161
+ {
1162
+ id: "content-releases.page.ReleaseDetails.table.action-published",
1163
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
1164
+ },
1165
+ {
1166
+ isPublish: type === "publish",
1167
+ b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
1168
+ }
1169
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
1170
+ index.ReleaseActionOptions,
1171
+ {
1172
+ selected: type,
1173
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
1174
+ name: `release-action-${id}-type`,
1175
+ disabled: !canUpdate
1176
+ }
1177
+ ) }),
1178
+ !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1179
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(
1180
+ EntryValidationText,
1181
+ {
1182
+ action: type,
1183
+ schema: contentTypes?.[contentType.uid],
1184
+ components,
1185
+ entry
1186
+ }
1187
+ ) }),
1188
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(index.ReleaseActionMenu.Root, { children: [
1189
+ /* @__PURE__ */ jsxRuntime.jsx(
1190
+ index.ReleaseActionMenu.ReleaseActionEntryLinkItem,
1191
+ {
1192
+ contentTypeUid: contentType.uid,
1193
+ entryId: entry.id,
1194
+ locale: locale?.code
1195
+ }
1196
+ ),
1197
+ /* @__PURE__ */ jsxRuntime.jsx(
1198
+ index.ReleaseActionMenu.DeleteReleaseActionItem,
1199
+ {
1200
+ releaseId: release.id,
1201
+ actionId: id
1202
+ }
1203
+ )
1204
+ ] }) }) })
1205
+ ] })
1206
+ ] }, id)
1207
+ ) })
1208
+ ] })
1209
+ }
1210
+ )
1211
+ ] }, `releases-group-${key}`)),
1212
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1213
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.PageSizeURLQuery, { defaultValue: releaseMeta?.pagination?.pageSize.toString() }),
1214
+ /* @__PURE__ */ jsxRuntime.jsx(
1215
+ helperPlugin.PaginationURLQuery,
1216
+ {
1217
+ pagination: {
1218
+ pageCount: releaseMeta?.pagination?.pageCount || 0
1219
+ }
1220
+ }
1221
+ )
1222
+ ] })
1223
+ ] }) });
1224
+ };
1225
+ const ReleaseDetailsPage = () => {
1226
+ const { formatMessage } = reactIntl.useIntl();
1227
+ const { releaseId } = reactRouterDom.useParams();
1228
+ const toggleNotification = helperPlugin.useNotification();
1229
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
1230
+ const { replace } = reactRouterDom.useHistory();
1231
+ const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
1232
+ const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
1233
+ const {
1234
+ isLoading: isLoadingDetails,
1235
+ data,
1236
+ isSuccess: isSuccessDetails
1237
+ } = index.useGetReleaseQuery({ id: releaseId });
1238
+ const [updateRelease, { isLoading: isSubmittingForm }] = index.useUpdateReleaseMutation();
1239
+ const [deleteRelease, { isLoading: isDeletingRelease }] = index.useDeleteReleaseMutation();
1240
+ const toggleEditReleaseModal = () => {
1241
+ setReleaseModalShown((prev) => !prev);
1242
+ };
1243
+ const toggleWarningSubmit = () => setWarningSubmit((prevState) => !prevState);
1244
+ if (isLoadingDetails) {
1245
+ return /* @__PURE__ */ jsxRuntime.jsx(
1246
+ ReleaseDetailsLayout,
1247
+ {
1248
+ toggleEditReleaseModal,
1249
+ toggleWarningSubmit,
1250
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) })
1251
+ }
1252
+ );
1253
+ }
1254
+ const releaseData = isSuccessDetails && data?.data || null;
1255
+ const title = releaseData?.name || "";
1256
+ const timezone = releaseData?.timezone ?? null;
1257
+ const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1258
+ const date = scheduledAt ? format__default.default(scheduledAt, "yyyy-MM-dd") : null;
1259
+ const time = scheduledAt ? format__default.default(scheduledAt, "HH:mm") : "";
1260
+ const handleEditRelease = async (values) => {
1261
+ const response = await updateRelease({
1262
+ id: releaseId,
1263
+ name: values.name,
1264
+ scheduledAt: values.scheduledAt,
1265
+ timezone: values.timezone
1266
+ });
1267
+ if ("data" in response) {
1268
+ toggleNotification({
1269
+ type: "success",
1270
+ message: formatMessage({
1271
+ id: "content-releases.modal.release-updated-notification-success",
1272
+ defaultMessage: "Release updated."
1273
+ })
1274
+ });
1275
+ toggleEditReleaseModal();
1276
+ } else if (index.isAxiosError(response.error)) {
1277
+ toggleNotification({
1278
+ type: "warning",
1279
+ message: formatAPIError(response.error)
1280
+ });
1281
+ } else {
1282
+ toggleNotification({
1283
+ type: "warning",
1284
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1285
+ });
1286
+ }
1287
+ };
1288
+ const handleDeleteRelease = async () => {
1289
+ const response = await deleteRelease({
1290
+ id: releaseId
1291
+ });
1292
+ if ("data" in response) {
1293
+ replace("/plugins/content-releases");
1294
+ } else if (index.isAxiosError(response.error)) {
1295
+ toggleNotification({
1296
+ type: "warning",
1297
+ message: formatAPIError(response.error)
1298
+ });
1299
+ } else {
1300
+ toggleNotification({
1301
+ type: "warning",
1302
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1303
+ });
1304
+ }
1305
+ };
1306
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1307
+ ReleaseDetailsLayout,
1308
+ {
1309
+ toggleEditReleaseModal,
1310
+ toggleWarningSubmit,
1311
+ children: [
1312
+ /* @__PURE__ */ jsxRuntime.jsx(ReleaseDetailsBody, {}),
1313
+ releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
1314
+ ReleaseModal,
1315
+ {
1316
+ handleClose: toggleEditReleaseModal,
1317
+ handleSubmit: handleEditRelease,
1318
+ isLoading: isLoadingDetails || isSubmittingForm,
1319
+ initialValues: {
1320
+ name: title || "",
1321
+ scheduledAt,
1322
+ date,
1323
+ time,
1324
+ isScheduled: Boolean(scheduledAt),
1325
+ timezone
1326
+ }
1327
+ }
1328
+ ),
1329
+ /* @__PURE__ */ jsxRuntime.jsx(
1330
+ helperPlugin.ConfirmDialog,
1331
+ {
1332
+ bodyText: {
1333
+ id: "content-releases.dialog.confirmation-message",
1334
+ defaultMessage: "Are you sure you want to delete this release?"
1335
+ },
1336
+ isOpen: showWarningSubmit,
1337
+ isConfirmButtonLoading: isDeletingRelease,
1338
+ onToggleDialog: toggleWarningSubmit,
1339
+ onConfirm: handleDeleteRelease
1340
+ }
1341
+ )
1342
+ ]
1343
+ }
1344
+ );
1345
+ };
1346
+ const App = () => {
1347
+ return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPagePermissions, { permissions: index.PERMISSIONS.main, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Switch, { children: [
1348
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}`, component: ReleasesPage }),
1349
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}/:releaseId`, component: ReleaseDetailsPage })
1350
+ ] }) });
1351
+ };
1352
+ exports.App = App;
1353
+ //# sourceMappingURL=App-dLXY5ei3.js.map