@strapi/content-releases 0.0.0-next.37dd1e3ff22e1635b69683abadd444912ae0dbff → 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 (32) hide show
  1. package/dist/_chunks/App-R-kksTW7.mjs +1308 -0
  2. package/dist/_chunks/App-R-kksTW7.mjs.map +1 -0
  3. package/dist/_chunks/{App-5PRKHpa2.js → App-WZHc_d3m.js} +779 -420
  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-ngTk74JV.mjs → en-RdapH-9X.mjs} +22 -7
  10. package/dist/_chunks/en-RdapH-9X.mjs.map +1 -0
  11. package/dist/_chunks/{en-haKSQIo8.js → en-faJDuv3q.js} +22 -7
  12. package/dist/_chunks/en-faJDuv3q.js.map +1 -0
  13. package/dist/_chunks/{index-PEkKIRyJ.js → index-k6fw6RYi.js} +240 -56
  14. package/dist/_chunks/index-k6fw6RYi.js.map +1 -0
  15. package/dist/_chunks/{index-_Zsj8MUA.mjs → index-vpSczx8v.mjs} +255 -71
  16. package/dist/_chunks/index-vpSczx8v.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 +1078 -371
  22. package/dist/server/index.js.map +1 -1
  23. package/dist/server/index.mjs +1077 -372
  24. package/dist/server/index.mjs.map +1 -1
  25. package/package.json +15 -11
  26. package/dist/_chunks/App-5PRKHpa2.js.map +0 -1
  27. package/dist/_chunks/App-J4jrthEu.mjs +0 -950
  28. package/dist/_chunks/App-J4jrthEu.mjs.map +0 -1
  29. package/dist/_chunks/en-haKSQIo8.js.map +0 -1
  30. package/dist/_chunks/en-ngTk74JV.mjs.map +0 -1
  31. package/dist/_chunks/index-PEkKIRyJ.js.map +0 -1
  32. package/dist/_chunks/index-_Zsj8MUA.mjs.map +0 -1
@@ -3,18 +3,23 @@ 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-PEkKIRyJ.js");
6
+ const index = require("./index-k6fw6RYi.js");
7
7
  const React = require("react");
8
+ const strapiAdmin = require("@strapi/admin/strapi-admin");
8
9
  const designSystem = require("@strapi/design-system");
9
10
  const v2 = require("@strapi/design-system/v2");
10
11
  const icons = require("@strapi/icons");
12
+ const format = require("date-fns/format");
13
+ const dateFnsTz = require("date-fns-tz");
11
14
  const reactIntl = require("react-intl");
12
15
  const styled = require("styled-components");
16
+ const dateFns = require("date-fns");
13
17
  const formik = require("formik");
14
18
  const yup = require("yup");
15
19
  require("@reduxjs/toolkit/query");
16
20
  require("axios");
17
21
  require("@reduxjs/toolkit/query/react");
22
+ require("react-redux");
18
23
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
19
24
  function _interopNamespace(e) {
20
25
  if (e && e.__esModule)
@@ -35,10 +40,28 @@ function _interopNamespace(e) {
35
40
  return Object.freeze(n);
36
41
  }
37
42
  const React__namespace = /* @__PURE__ */ _interopNamespace(React);
43
+ const format__default = /* @__PURE__ */ _interopDefault(format);
38
44
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
39
45
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
40
46
  const RELEASE_SCHEMA = yup__namespace.object().shape({
41
- 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
+ })
42
65
  }).required().noUnknown();
43
66
  const ReleaseModal = ({
44
67
  handleClose,
@@ -49,6 +72,23 @@ const ReleaseModal = ({
49
72
  const { formatMessage } = reactIntl.useIntl();
50
73
  const { pathname } = reactRouterDom.useLocation();
51
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
+ };
52
92
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
53
93
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
54
94
  {
@@ -60,45 +100,130 @@ const ReleaseModal = ({
60
100
  /* @__PURE__ */ jsxRuntime.jsx(
61
101
  formik.Formik,
62
102
  {
63
- validateOnChange: false,
64
- onSubmit: handleSubmit,
65
- 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
+ },
66
114
  validationSchema: RELEASE_SCHEMA,
67
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
68
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
69
- designSystem.TextInput,
70
- {
71
- label: formatMessage({
72
- id: "content-releases.modal.form.input.label.release-name",
73
- defaultMessage: "Name"
74
- }),
75
- name: "name",
76
- value: values.name,
77
- error: errors.name,
78
- onChange: handleChange,
79
- required: true
80
- }
81
- ) }),
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
+ ] }) }),
82
216
  /* @__PURE__ */ jsxRuntime.jsx(
83
217
  designSystem.ModalFooter,
84
218
  {
85
219
  startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
86
- endActions: /* @__PURE__ */ jsxRuntime.jsx(
87
- designSystem.Button,
220
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
88
221
  {
89
- name: "submit",
90
- loading: isLoading,
91
- disabled: !values.name || values.name === initialValues.name,
92
- type: "submit",
93
- children: formatMessage(
94
- {
95
- id: "content-releases.modal.form.button.submit",
96
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
97
- },
98
- { isCreatingRelease }
99
- )
100
- }
101
- )
222
+ id: "content-releases.modal.form.button.submit",
223
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
224
+ },
225
+ { isCreatingRelease }
226
+ ) })
102
227
  }
103
228
  )
104
229
  ] })
@@ -106,61 +231,427 @@ const ReleaseModal = ({
106
231
  )
107
232
  ] });
108
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
+ };
109
596
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
110
597
  align-self: stretch;
111
598
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
112
599
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
113
600
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
114
601
  `;
115
- const StyledFlex = styled__default.default(designSystem.Flex)`
116
- align-self: stretch;
117
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
118
-
602
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
119
603
  svg path {
120
604
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
121
605
  }
122
606
  span {
123
607
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
124
608
  }
609
+
610
+ &:hover {
611
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
612
+ }
125
613
  `;
126
614
  const PencilIcon = styled__default.default(icons.Pencil)`
127
- width: ${({ theme }) => theme.spaces[4]};
128
- height: ${({ theme }) => theme.spaces[4]};
615
+ width: ${({ theme }) => theme.spaces[3]};
616
+ height: ${({ theme }) => theme.spaces[3]};
129
617
  path {
130
618
  fill: ${({ theme }) => theme.colors.neutral600};
131
619
  }
132
620
  `;
133
621
  const TrashIcon = styled__default.default(icons.Trash)`
134
- width: ${({ theme }) => theme.spaces[4]};
135
- height: ${({ theme }) => theme.spaces[4]};
622
+ width: ${({ theme }) => theme.spaces[3]};
623
+ height: ${({ theme }) => theme.spaces[3]};
136
624
  path {
137
625
  fill: ${({ theme }) => theme.colors.danger600};
138
626
  }
139
627
  `;
140
- const PopoverButton = ({ onClick, disabled, children }) => {
141
- return /* @__PURE__ */ jsxRuntime.jsx(
142
- StyledFlex,
143
- {
144
- paddingTop: 2,
145
- paddingBottom: 2,
146
- paddingLeft: 4,
147
- paddingRight: 4,
148
- alignItems: "center",
149
- gap: 2,
150
- as: "button",
151
- hasRadius: true,
152
- onClick,
153
- disabled,
154
- children
155
- }
156
- );
157
- };
158
- const EntryValidationText = ({ status, action }) => {
628
+ const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
629
+ max-width: 300px;
630
+ `;
631
+ const EntryValidationText = ({ action, schema, components, entry }) => {
159
632
  const { formatMessage } = reactIntl.useIntl();
633
+ const { validate } = strapiAdmin.unstable_useDocument();
634
+ const { errors } = validate(entry, {
635
+ contentType: schema,
636
+ components,
637
+ isCreatingEntry: false
638
+ });
639
+ if (Object.keys(errors).length > 0) {
640
+ const validationErrorsMessages = Object.entries(errors).map(
641
+ ([key, value]) => formatMessage(
642
+ { id: `${value.id}.withField`, defaultMessage: value.defaultMessage },
643
+ { field: key }
644
+ )
645
+ ).join(" ");
646
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
647
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "danger600", as: icons.CrossCircle }),
648
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { description: validationErrorsMessages, children: /* @__PURE__ */ jsxRuntime.jsx(TypographyMaxWidth, { textColor: "danger600", variant: "omega", fontWeight: "semiBold", ellipsis: true, children: validationErrorsMessages }) })
649
+ ] });
650
+ }
160
651
  if (action == "publish") {
161
652
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
162
653
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "success600", as: icons.CheckCircle }),
163
- status === "published" ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
654
+ entry.publishedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
164
655
  id: "content-releases.pages.ReleaseDetails.entry-validation.already-published",
165
656
  defaultMessage: "Already published"
166
657
  }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
@@ -171,7 +662,7 @@ const EntryValidationText = ({ status, action }) => {
171
662
  }
172
663
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
173
664
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Icon, { color: "success600", as: icons.CheckCircle }),
174
- status === "draft" ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
665
+ !entry.publishedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
175
666
  id: "content-releases.pages.ReleaseDetails.entry-validation.already-unpublished",
176
667
  defaultMessage: "Already unpublished"
177
668
  }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
@@ -185,10 +676,8 @@ const ReleaseDetailsLayout = ({
185
676
  toggleWarningSubmit,
186
677
  children
187
678
  }) => {
188
- const { formatMessage } = reactIntl.useIntl();
679
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
189
680
  const { releaseId } = reactRouterDom.useParams();
190
- const [isPopoverVisible, setIsPopoverVisible] = React__namespace.useState(false);
191
- const moreButtonRef = React__namespace.useRef(null);
192
681
  const {
193
682
  data,
194
683
  isLoading: isLoadingDetails,
@@ -201,14 +690,9 @@ const ReleaseDetailsLayout = ({
201
690
  const {
202
691
  allowedActions: { canUpdate, canDelete }
203
692
  } = helperPlugin.useRBAC(index.PERMISSIONS);
693
+ const dispatch = index.useTypedDispatch();
694
+ const { trackUsage } = helperPlugin.useTracking();
204
695
  const release = data?.data;
205
- const handleTogglePopover = () => {
206
- setIsPopoverVisible((prev) => !prev);
207
- };
208
- const openReleaseModal = () => {
209
- toggleEditReleaseModal();
210
- handleTogglePopover();
211
- };
212
696
  const handlePublishRelease = async () => {
213
697
  const response = await publishRelease({ id: releaseId });
214
698
  if ("data" in response) {
@@ -219,6 +703,12 @@ const ReleaseDetailsLayout = ({
219
703
  defaultMessage: "Release was published successfully."
220
704
  })
221
705
  });
706
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
707
+ trackUsage("didPublishRelease", {
708
+ totalEntries: totalEntries2,
709
+ totalPublishedEntries,
710
+ totalUnpublishedEntries
711
+ });
222
712
  } else if (index.isAxiosError(response.error)) {
223
713
  toggleNotification({
224
714
  type: "warning",
@@ -231,9 +721,20 @@ const ReleaseDetailsLayout = ({
231
721
  });
232
722
  }
233
723
  };
234
- const openWarningConfirmDialog = () => {
235
- toggleWarningSubmit();
236
- handleTogglePopover();
724
+ const handleRefresh = () => {
725
+ dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
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;
237
738
  };
238
739
  if (isLoadingDetails) {
239
740
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
@@ -256,90 +757,131 @@ const ReleaseDetailsLayout = ({
256
757
  );
257
758
  }
258
759
  const totalEntries = release.actions.meta.count || 0;
259
- 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
+ ) : "";
260
789
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
261
790
  /* @__PURE__ */ jsxRuntime.jsx(
262
791
  designSystem.HeaderLayout,
263
792
  {
264
793
  title: release.name,
265
- subtitle: formatMessage(
266
- {
267
- id: "content-releases.pages.Details.header-subtitle",
268
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
269
- },
270
- { number: totalEntries }
271
- ),
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
+ ] }),
272
798
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
273
799
  id: "global.back",
274
800
  defaultMessage: "Back"
275
801
  }) }),
276
802
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
277
- /* @__PURE__ */ jsxRuntime.jsx(
278
- designSystem.IconButton,
279
- {
280
- label: formatMessage({
281
- id: "content-releases.header.actions.open-release-actions",
282
- defaultMessage: "Release actions"
283
- }),
284
- ref: moreButtonRef,
285
- onClick: handleTogglePopover,
286
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {})
287
- }
288
- ),
289
- isPopoverVisible && /* @__PURE__ */ jsxRuntime.jsxs(
290
- designSystem.Popover,
291
- {
292
- source: moreButtonRef,
293
- placement: "bottom-end",
294
- onDismiss: handleTogglePopover,
295
- spacing: 4,
296
- minWidth: "242px",
297
- children: [
298
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
299
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
300
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
301
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
302
- id: "content-releases.header.actions.edit",
303
- defaultMessage: "Edit"
304
- }) })
305
- ] }),
306
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
307
- /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
308
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
309
- id: "content-releases.header.actions.delete",
310
- defaultMessage: "Delete"
311
- }) })
312
- ] })
313
- ] }),
314
- /* @__PURE__ */ jsxRuntime.jsxs(
315
- ReleaseInfoWrapper,
316
- {
317
- direction: "column",
318
- justifyContent: "center",
319
- alignItems: "flex-start",
320
- gap: 1,
321
- padding: 5,
322
- children: [
323
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
324
- id: "content-releases.header.actions.created",
325
- defaultMessage: "Created"
326
- }) }),
327
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
328
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
329
- formatMessage(
330
- {
331
- id: "content-releases.header.actions.created.description",
332
- defaultMessage: " by {createdBy}"
333
- },
334
- { createdBy }
335
- )
336
- ] })
337
- ]
338
- }
339
- )
340
- ]
341
- }
342
- ),
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
+ ] }),
881
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
882
+ id: "content-releases.header.actions.refresh",
883
+ defaultMessage: "Refresh"
884
+ }) }),
343
885
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.publish, children: /* @__PURE__ */ jsxRuntime.jsx(
344
886
  designSystem.Button,
345
887
  {
@@ -391,6 +933,9 @@ const ReleaseDetailsBody = () => {
391
933
  isError: isReleaseError,
392
934
  error: releaseError
393
935
  } = index.useGetReleaseQuery({ id: releaseId });
936
+ const {
937
+ allowedActions: { canUpdate }
938
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
394
939
  const release = releaseData?.data;
395
940
  const selectedGroupBy = query?.groupBy || "contentType";
396
941
  const {
@@ -404,7 +949,7 @@ const ReleaseDetailsBody = () => {
404
949
  releaseId
405
950
  });
406
951
  const [updateReleaseAction] = index.useUpdateReleaseActionMutation();
407
- const handleChangeType = async (e, actionId) => {
952
+ const handleChangeType = async (e, actionId, actionPath) => {
408
953
  const response = await updateReleaseAction({
409
954
  params: {
410
955
  releaseId,
@@ -412,7 +957,11 @@ const ReleaseDetailsBody = () => {
412
957
  },
413
958
  body: {
414
959
  type: e.target.value
415
- }
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
416
965
  });
417
966
  if ("error" in response) {
418
967
  if (index.isAxiosError(response.error)) {
@@ -433,6 +982,8 @@ const ReleaseDetailsBody = () => {
433
982
  }
434
983
  const releaseActions = data?.data;
435
984
  const releaseMeta = data?.meta;
985
+ const contentTypes = releaseMeta?.contentTypes || {};
986
+ const components = releaseMeta?.components || {};
436
987
  if (isReleaseError || !release) {
437
988
  const errorsArray = [];
438
989
  if (releaseError) {
@@ -491,7 +1042,7 @@ const ReleaseDetailsBody = () => {
491
1042
  designSystem.SingleSelect,
492
1043
  {
493
1044
  "aria-label": formatMessage({
494
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1045
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
495
1046
  defaultMessage: "Group by"
496
1047
  }),
497
1048
  customizeContent: (value) => formatMessage(
@@ -509,7 +1060,7 @@ const ReleaseDetailsBody = () => {
509
1060
  }
510
1061
  ) }),
511
1062
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
512
- /* @__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 }) }),
513
1064
  /* @__PURE__ */ jsxRuntime.jsx(
514
1065
  helperPlugin.Table.Root,
515
1066
  {
@@ -579,32 +1130,59 @@ const ReleaseDetailsBody = () => {
579
1130
  )
580
1131
  ] }),
581
1132
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.LoadingBody, {}),
582
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(({ id, type, entry }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
583
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${entry.contentType.mainFieldValue || entry.id}` }) }),
584
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${entry?.locale?.name ? entry.locale.name : "-"}` }) }),
585
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: entry.contentType.displayName || "" }) }),
586
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
587
- {
588
- id: "content-releases.page.ReleaseDetails.table.action-published",
589
- defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
590
- },
591
- {
592
- isPublish: type === "publish",
593
- b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
594
- }
595
- ) }) : /* @__PURE__ */ jsxRuntime.jsx(
596
- index.ReleaseActionOptions,
597
- {
598
- selected: type,
599
- handleChange: (e) => handleChangeType(e, id),
600
- name: `release-action-${id}-type`
601
- }
602
- ) }),
603
- !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
604
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(EntryValidationText, { status: entry.status, action: type }) }),
605
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(index.ReleaseActionMenu, { releaseId, actionId: id }) }) })
606
- ] })
607
- ] }, id)) })
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
+ },
1143
+ {
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
1154
+ }
1155
+ ) }),
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,
1159
+ {
1160
+ action: type,
1161
+ schema: contentTypes?.[contentType.uid],
1162
+ components,
1163
+ entry
1164
+ }
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
+ ) })
608
1186
  ] })
609
1187
  }
610
1188
  )
@@ -627,7 +1205,7 @@ const ReleaseDetailsPage = () => {
627
1205
  const { releaseId } = reactRouterDom.useParams();
628
1206
  const toggleNotification = helperPlugin.useNotification();
629
1207
  const { formatAPIError } = helperPlugin.useAPIErrorHandler();
630
- const { push } = reactRouterDom.useHistory();
1208
+ const { replace } = reactRouterDom.useHistory();
631
1209
  const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
632
1210
  const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
633
1211
  const {
@@ -651,11 +1229,18 @@ const ReleaseDetailsPage = () => {
651
1229
  }
652
1230
  );
653
1231
  }
654
- 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") : "";
655
1238
  const handleEditRelease = async (values) => {
656
1239
  const response = await updateRelease({
657
1240
  id: releaseId,
658
- name: values.name
1241
+ name: values.name,
1242
+ scheduledAt: values.scheduledAt,
1243
+ timezone: values.timezone
659
1244
  });
660
1245
  if ("data" in response) {
661
1246
  toggleNotification({
@@ -683,7 +1268,7 @@ const ReleaseDetailsPage = () => {
683
1268
  id: releaseId
684
1269
  });
685
1270
  if ("data" in response) {
686
- push("/plugins/content-releases");
1271
+ replace("/plugins/content-releases");
687
1272
  } else if (index.isAxiosError(response.error)) {
688
1273
  toggleNotification({
689
1274
  type: "warning",
@@ -709,7 +1294,14 @@ const ReleaseDetailsPage = () => {
709
1294
  handleClose: toggleEditReleaseModal,
710
1295
  handleSubmit: handleEditRelease,
711
1296
  isLoading: isLoadingDetails || isSubmittingForm,
712
- initialValues: { name: title || "" }
1297
+ initialValues: {
1298
+ name: title || "",
1299
+ scheduledAt,
1300
+ date,
1301
+ time,
1302
+ isScheduled: Boolean(scheduledAt),
1303
+ timezone
1304
+ }
713
1305
  }
714
1306
  ),
715
1307
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -729,239 +1321,6 @@ const ReleaseDetailsPage = () => {
729
1321
  }
730
1322
  );
731
1323
  };
732
- const ReleasesLayout = ({
733
- isLoading,
734
- totalReleases,
735
- onClickAddRelease,
736
- children
737
- }) => {
738
- const { formatMessage } = reactIntl.useIntl();
739
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
740
- /* @__PURE__ */ jsxRuntime.jsx(
741
- designSystem.HeaderLayout,
742
- {
743
- title: formatMessage({
744
- id: "content-releases.pages.Releases.title",
745
- defaultMessage: "Releases"
746
- }),
747
- subtitle: !isLoading && formatMessage(
748
- {
749
- id: "content-releases.pages.Releases.header-subtitle",
750
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
751
- },
752
- { number: totalReleases }
753
- ),
754
- 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({
755
- id: "content-releases.header.actions.add-release",
756
- defaultMessage: "New release"
757
- }) }) })
758
- }
759
- ),
760
- children
761
- ] });
762
- };
763
- const LinkCard = styled__default.default(v2.Link)`
764
- display: block;
765
- `;
766
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
767
- const { formatMessage } = reactIntl.useIntl();
768
- if (isError) {
769
- return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
770
- }
771
- if (releases?.length === 0) {
772
- return /* @__PURE__ */ jsxRuntime.jsx(
773
- designSystem.EmptyStateLayout,
774
- {
775
- content: formatMessage(
776
- {
777
- id: "content-releases.page.Releases.tab.emptyEntries",
778
- defaultMessage: "No releases"
779
- },
780
- {
781
- target: sectionTitle
782
- }
783
- ),
784
- icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
785
- }
786
- );
787
- }
788
- 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(
789
- designSystem.Flex,
790
- {
791
- direction: "column",
792
- justifyContent: "space-between",
793
- padding: 4,
794
- hasRadius: true,
795
- background: "neutral0",
796
- shadow: "tableShadow",
797
- height: "100%",
798
- width: "100%",
799
- alignItems: "start",
800
- gap: 2,
801
- children: [
802
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
803
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: formatMessage(
804
- {
805
- id: "content-releases.page.Releases.release-item.entries",
806
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
807
- },
808
- { number: actions.meta.count }
809
- ) })
810
- ]
811
- }
812
- ) }) }, id)) });
813
- };
814
- const INITIAL_FORM_VALUES = {
815
- name: ""
816
- };
817
- const ReleasesPage = () => {
818
- const location = reactRouterDom.useLocation();
819
- const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
820
- const toggleNotification = helperPlugin.useNotification();
821
- const { formatMessage } = reactIntl.useIntl();
822
- const { push, replace } = reactRouterDom.useHistory();
823
- const { formatAPIError } = helperPlugin.useAPIErrorHandler();
824
- const [{ query }, setQuery] = helperPlugin.useQueryParams();
825
- const response = index.useGetReleasesQuery(query);
826
- const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
827
- const { isLoading, isSuccess, isError } = response;
828
- React__namespace.useEffect(() => {
829
- if (location?.state?.errors) {
830
- toggleNotification({
831
- type: "warning",
832
- title: formatMessage({
833
- id: "content-releases.pages.Releases.notification.error.title",
834
- defaultMessage: "Your request could not be processed."
835
- }),
836
- message: formatMessage({
837
- id: "content-releases.pages.Releases.notification.error.message",
838
- defaultMessage: "Please try again or open another release."
839
- })
840
- });
841
- replace({ state: null });
842
- }
843
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
844
- const toggleAddReleaseModal = () => {
845
- setReleaseModalShown((prev) => !prev);
846
- };
847
- if (isLoading) {
848
- return /* @__PURE__ */ jsxRuntime.jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) }) });
849
- }
850
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
851
- const handleTabChange = (index2) => {
852
- setQuery({
853
- ...query,
854
- page: 1,
855
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
856
- filters: {
857
- releasedAt: {
858
- $notNull: index2 === 0 ? false : true
859
- }
860
- }
861
- });
862
- };
863
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
864
- const handleAddRelease = async (values) => {
865
- const response2 = await createRelease({
866
- name: values.name
867
- });
868
- if ("data" in response2) {
869
- toggleNotification({
870
- type: "success",
871
- message: formatMessage({
872
- id: "content-releases.modal.release-created-notification-success",
873
- defaultMessage: "Release created."
874
- })
875
- });
876
- push(`/plugins/content-releases/${response2.data.data.id}`);
877
- } else if (index.isAxiosError(response2.error)) {
878
- toggleNotification({
879
- type: "warning",
880
- message: formatAPIError(response2.error)
881
- });
882
- } else {
883
- toggleNotification({
884
- type: "warning",
885
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
886
- });
887
- }
888
- };
889
- return /* @__PURE__ */ jsxRuntime.jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
890
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
891
- /* @__PURE__ */ jsxRuntime.jsxs(
892
- designSystem.TabGroup,
893
- {
894
- label: formatMessage({
895
- id: "content-releases.pages.Releases.tab-group.label",
896
- defaultMessage: "Releases list"
897
- }),
898
- variant: "simple",
899
- initialSelectedTabIndex: ["pending", "done"].indexOf(activeTab),
900
- onTabChange: handleTabChange,
901
- children: [
902
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
903
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
904
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
905
- id: "content-releases.pages.Releases.tab.pending",
906
- defaultMessage: "Pending"
907
- }) }),
908
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
909
- id: "content-releases.pages.Releases.tab.done",
910
- defaultMessage: "Done"
911
- }) })
912
- ] }),
913
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
914
- ] }),
915
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
916
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
917
- ReleasesGrid,
918
- {
919
- sectionTitle: "pending",
920
- releases: response?.currentData?.data,
921
- isError
922
- }
923
- ) }),
924
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
925
- ReleasesGrid,
926
- {
927
- sectionTitle: "done",
928
- releases: response?.currentData?.data,
929
- isError
930
- }
931
- ) })
932
- ] })
933
- ]
934
- }
935
- ),
936
- totalReleases > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
937
- /* @__PURE__ */ jsxRuntime.jsx(
938
- helperPlugin.PageSizeURLQuery,
939
- {
940
- options: ["8", "16", "32", "64"],
941
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
942
- }
943
- ),
944
- /* @__PURE__ */ jsxRuntime.jsx(
945
- helperPlugin.PaginationURLQuery,
946
- {
947
- pagination: {
948
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
949
- }
950
- }
951
- )
952
- ] })
953
- ] }) }),
954
- releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
955
- ReleaseModal,
956
- {
957
- handleClose: toggleAddReleaseModal,
958
- handleSubmit: handleAddRelease,
959
- isLoading: isSubmittingForm,
960
- initialValues: INITIAL_FORM_VALUES
961
- }
962
- )
963
- ] });
964
- };
965
1324
  const App = () => {
966
1325
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPagePermissions, { permissions: index.PERMISSIONS.main, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Switch, { children: [
967
1326
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}`, component: ReleasesPage }),
@@ -969,4 +1328,4 @@ const App = () => {
969
1328
  ] }) });
970
1329
  };
971
1330
  exports.App = App;
972
- //# sourceMappingURL=App-5PRKHpa2.js.map
1331
+ //# sourceMappingURL=App-WZHc_d3m.js.map