@strapi/content-releases 0.0.0-next.d470b4f75cf00f24f440b80300f1c833c322b871 → 0.0.0-next.e09d30edcbd16960a838997778a31d50e9c60bc4

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.
@@ -1,15 +1,18 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { useNotification, useAPIErrorHandler, LoadingIndicatorPage, ConfirmDialog, useRBAC, useTracking, RelativeTime, CheckPermissions, useQueryParams, AnErrorOccurred, NoContent, Table, PageSizeURLQuery, PaginationURLQuery, CheckPagePermissions } from "@strapi/helper-plugin";
3
- import { useLocation, useParams, useHistory, Redirect, Link as Link$1, Switch, Route } from "react-router-dom";
4
- import { p as pluginId, u as useGetReleaseQuery, a as useUpdateReleaseMutation, b as useDeleteReleaseMutation, c as usePublishReleaseMutation, P as PERMISSIONS, d as useTypedDispatch, e as useGetReleaseActionsQuery, f as useUpdateReleaseActionMutation, R as ReleaseActionOptions, g as ReleaseActionMenu, i as isAxiosError, r as releaseApi, h as useGetReleasesQuery, j as useCreateReleaseMutation } from "./index-gkExFBa0.mjs";
2
+ import { RelativeTime, useNotification, useAPIErrorHandler, useQueryParams, useTracking, LoadingIndicatorPage, CheckPermissions, PageSizeURLQuery, PaginationURLQuery, AnErrorOccurred, ConfirmDialog, useRBAC, NoContent, Table, CheckPagePermissions } from "@strapi/helper-plugin";
3
+ import { useLocation, useHistory, useParams, Redirect, Link as Link$2, Switch, Route } from "react-router-dom";
4
+ import { g as getTimezoneOffset, p as pluginId, u as useGetReleasesQuery, a as useCreateReleaseMutation, P as PERMISSIONS, i as isAxiosError, b as useGetReleaseQuery, c as useUpdateReleaseMutation, d as useDeleteReleaseMutation, e as usePublishReleaseMutation, f as useTypedDispatch, h as useGetReleaseActionsQuery, j as useUpdateReleaseActionMutation, R as ReleaseActionOptions, k as ReleaseActionMenu, r as releaseApi } from "./index-LaZVYUlg.mjs";
5
5
  import * as React from "react";
6
- import { unstable_useDocument, useLicenseLimits } from "@strapi/admin/strapi-admin";
7
- import { ModalLayout, ModalHeader, Typography, ModalBody, TextInput, ModalFooter, Button, Flex, ContentLayout, Main, HeaderLayout, Link, IconButton, SingleSelect, SingleSelectOption, Badge, Tr, Td, Icon, Tooltip, Alert, TabGroup, Box, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem } from "@strapi/design-system";
8
- import { Menu, LinkButton, Link as Link$2 } from "@strapi/design-system/v2";
9
- import { Pencil, Trash, ArrowLeft, More, CrossCircle, CheckCircle, Plus, EmptyDocuments } from "@strapi/icons";
6
+ import { useLicenseLimits, unstable_useDocument } from "@strapi/admin/strapi-admin";
7
+ import { ModalLayout, ModalHeader, Typography, ModalBody, Flex, TextInput, Box, Checkbox, DatePicker, TimePicker, ModalFooter, Button, Combobox, ComboboxOption, Alert, Main, HeaderLayout, ContentLayout, TabGroup, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem, Badge, Link as Link$1, IconButton, SingleSelect, SingleSelectOption, Tr, Td, Icon, Tooltip } from "@strapi/design-system";
8
+ import { Link, Menu, LinkButton } from "@strapi/design-system/v2";
9
+ import { Plus, EmptyDocuments, Pencil, Trash, ArrowLeft, More, CrossCircle, CheckCircle } from "@strapi/icons";
10
+ import format from "date-fns/format";
11
+ import { zonedTimeToUtc, utcToZonedTime } from "date-fns-tz";
10
12
  import { useIntl } from "react-intl";
11
13
  import styled from "styled-components";
12
- import { Formik, Form } from "formik";
14
+ import { formatISO, parse } from "date-fns";
15
+ import { Formik, Form, useFormikContext } from "formik";
13
16
  import * as yup from "yup";
14
17
  import "@reduxjs/toolkit/query";
15
18
  import "axios";
@@ -17,11 +20,21 @@ import "@reduxjs/toolkit/query/react";
17
20
  import "react-redux";
18
21
  const RELEASE_SCHEMA = yup.object().shape({
19
22
  name: yup.string().trim().required(),
20
- // scheduledAt is a date, but we always receive strings from the client
21
23
  scheduledAt: yup.string().nullable(),
22
- timezone: yup.string().when("scheduledAt", {
23
- is: (scheduledAt) => !!scheduledAt,
24
- then: yup.string().required(),
24
+ isScheduled: yup.boolean().optional(),
25
+ time: yup.string().when("isScheduled", {
26
+ is: true,
27
+ then: yup.string().trim().required(),
28
+ otherwise: yup.string().nullable()
29
+ }),
30
+ timezone: yup.string().when("isScheduled", {
31
+ is: true,
32
+ then: yup.string().required().nullable(),
33
+ otherwise: yup.string().nullable()
34
+ }),
35
+ date: yup.string().when("isScheduled", {
36
+ is: true,
37
+ then: yup.string().required().nullable(),
25
38
  otherwise: yup.string().nullable()
26
39
  })
27
40
  }).required().noUnknown();
@@ -34,6 +47,24 @@ const ReleaseModal = ({
34
47
  const { formatMessage } = useIntl();
35
48
  const { pathname } = useLocation();
36
49
  const isCreatingRelease = pathname === `/plugins/${pluginId}`;
50
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
51
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
52
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
53
+ );
54
+ const getScheduledTimestamp = (values) => {
55
+ const { date, time, timezone } = values;
56
+ if (!date || !time || !timezone)
57
+ return null;
58
+ const formattedDate = parse(time, "HH:mm", new Date(date));
59
+ const timezoneWithoutOffset = timezone.split("_")[1];
60
+ return zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
61
+ };
62
+ const getTimezoneWithOffset = () => {
63
+ const currentTimezone = timezoneList.find(
64
+ (timezone) => timezone.value.split("_")[1] === initialValues.timezone
65
+ );
66
+ return currentTimezone?.value || systemTimezone.value;
67
+ };
37
68
  return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
38
69
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
39
70
  {
@@ -45,45 +76,134 @@ const ReleaseModal = ({
45
76
  /* @__PURE__ */ jsx(
46
77
  Formik,
47
78
  {
48
- validateOnChange: false,
49
- onSubmit: handleSubmit,
50
- initialValues,
79
+ onSubmit: (values) => {
80
+ handleSubmit({
81
+ ...values,
82
+ timezone: values.timezone ? values.timezone.split("_")[1] : null,
83
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
84
+ });
85
+ },
86
+ initialValues: {
87
+ ...initialValues,
88
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
89
+ },
51
90
  validationSchema: RELEASE_SCHEMA,
52
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxs(Form, { children: [
53
- /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx(
54
- TextInput,
55
- {
56
- label: formatMessage({
57
- id: "content-releases.modal.form.input.label.release-name",
58
- defaultMessage: "Name"
59
- }),
60
- name: "name",
61
- value: values.name,
62
- error: errors.name,
63
- onChange: handleChange,
64
- required: true
65
- }
66
- ) }),
91
+ validateOnChange: false,
92
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxs(Form, { children: [
93
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
94
+ /* @__PURE__ */ jsx(
95
+ TextInput,
96
+ {
97
+ label: formatMessage({
98
+ id: "content-releases.modal.form.input.label.release-name",
99
+ defaultMessage: "Name"
100
+ }),
101
+ name: "name",
102
+ value: values.name,
103
+ error: errors.name,
104
+ onChange: handleChange,
105
+ required: true
106
+ }
107
+ ),
108
+ IsSchedulingEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
109
+ /* @__PURE__ */ jsx(Box, { width: "max-content", children: /* @__PURE__ */ jsx(
110
+ Checkbox,
111
+ {
112
+ name: "isScheduled",
113
+ value: values.isScheduled,
114
+ onChange: (event) => {
115
+ setFieldValue("isScheduled", event.target.checked);
116
+ if (!event.target.checked) {
117
+ setFieldValue("date", null);
118
+ setFieldValue("time", "");
119
+ setFieldValue("timezone", null);
120
+ } else {
121
+ setFieldValue("date", initialValues.date);
122
+ setFieldValue("time", initialValues.time);
123
+ setFieldValue(
124
+ "timezone",
125
+ initialValues.timezone ?? systemTimezone?.value
126
+ );
127
+ }
128
+ },
129
+ children: /* @__PURE__ */ jsx(
130
+ Typography,
131
+ {
132
+ textColor: values.isScheduled ? "primary600" : "neutral800",
133
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
134
+ children: formatMessage({
135
+ id: "modal.form.input.label.schedule-release",
136
+ defaultMessage: "Schedule release"
137
+ })
138
+ }
139
+ )
140
+ }
141
+ ) }),
142
+ values.isScheduled && /* @__PURE__ */ jsxs(Fragment, { children: [
143
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "start", children: [
144
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
145
+ DatePicker,
146
+ {
147
+ label: formatMessage({
148
+ id: "content-releases.modal.form.input.label.date",
149
+ defaultMessage: "Date"
150
+ }),
151
+ name: "date",
152
+ error: errors.date,
153
+ onChange: (date) => {
154
+ const isoFormatDate = date ? formatISO(date, { representation: "date" }) : null;
155
+ setFieldValue("date", isoFormatDate);
156
+ },
157
+ clearLabel: formatMessage({
158
+ id: "content-releases.modal.form.input.clearLabel",
159
+ defaultMessage: "Clear"
160
+ }),
161
+ onClear: () => {
162
+ setFieldValue("date", null);
163
+ },
164
+ selectedDate: values.date || void 0,
165
+ required: true
166
+ }
167
+ ) }),
168
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
169
+ TimePicker,
170
+ {
171
+ label: formatMessage({
172
+ id: "content-releases.modal.form.input.label.time",
173
+ defaultMessage: "Time"
174
+ }),
175
+ name: "time",
176
+ error: errors.time,
177
+ onChange: (time) => {
178
+ setFieldValue("time", time);
179
+ },
180
+ clearLabel: formatMessage({
181
+ id: "content-releases.modal.form.input.clearLabel",
182
+ defaultMessage: "Clear"
183
+ }),
184
+ onClear: () => {
185
+ setFieldValue("time", "");
186
+ },
187
+ value: values.time || void 0,
188
+ required: true
189
+ }
190
+ ) })
191
+ ] }),
192
+ /* @__PURE__ */ jsx(TimezoneComponent, { timezoneOptions: timezoneList })
193
+ ] })
194
+ ] })
195
+ ] }) }),
67
196
  /* @__PURE__ */ jsx(
68
197
  ModalFooter,
69
198
  {
70
199
  startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
71
- endActions: /* @__PURE__ */ jsx(
72
- Button,
200
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
73
201
  {
74
- name: "submit",
75
- loading: isLoading,
76
- disabled: !values.name || values.name === initialValues.name,
77
- type: "submit",
78
- children: formatMessage(
79
- {
80
- id: "content-releases.modal.form.button.submit",
81
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
82
- },
83
- { isCreatingRelease }
84
- )
85
- }
86
- )
202
+ id: "content-releases.modal.form.button.submit",
203
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
204
+ },
205
+ { isCreatingRelease }
206
+ ) })
87
207
  }
88
208
  )
89
209
  ] })
@@ -91,6 +211,376 @@ const ReleaseModal = ({
91
211
  )
92
212
  ] });
93
213
  };
214
+ const getTimezones = (selectedDate) => {
215
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
216
+ const utcOffset = getTimezoneOffset(timezone, selectedDate);
217
+ return { offset: utcOffset, value: `${utcOffset}_${timezone}` };
218
+ });
219
+ const systemTimezone = timezoneList.find(
220
+ (timezone) => timezone.value.split("_")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
221
+ );
222
+ return { timezoneList, systemTimezone };
223
+ };
224
+ const TimezoneComponent = ({ timezoneOptions }) => {
225
+ const { values, errors, setFieldValue } = useFormikContext();
226
+ const { formatMessage } = useIntl();
227
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
228
+ React.useEffect(() => {
229
+ if (values.date) {
230
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
231
+ setTimezoneList(timezoneList2);
232
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("_")[1] === values.timezone.split("_")[1]);
233
+ if (updatedTimezone) {
234
+ setFieldValue("timezone", updatedTimezone.value);
235
+ }
236
+ }
237
+ }, [setFieldValue, values.date, values.timezone]);
238
+ return /* @__PURE__ */ jsx(
239
+ Combobox,
240
+ {
241
+ label: formatMessage({
242
+ id: "content-releases.modal.form.input.label.timezone",
243
+ defaultMessage: "Timezone"
244
+ }),
245
+ autocomplete: { type: "list", filter: "contains" },
246
+ name: "timezone",
247
+ value: values.timezone || void 0,
248
+ textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
249
+ onChange: (timezone) => {
250
+ setFieldValue("timezone", timezone);
251
+ },
252
+ onTextValueChange: (timezone) => {
253
+ setFieldValue("timezone", timezone);
254
+ },
255
+ onClear: () => {
256
+ setFieldValue("timezone", "");
257
+ },
258
+ error: errors.timezone,
259
+ required: true,
260
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
261
+ }
262
+ );
263
+ };
264
+ const LinkCard = styled(Link)`
265
+ display: block;
266
+ `;
267
+ const CapitalizeRelativeTime = styled(RelativeTime)`
268
+ text-transform: capitalize;
269
+ `;
270
+ const getBadgeProps = (status) => {
271
+ let color;
272
+ switch (status) {
273
+ case "ready":
274
+ color = "success";
275
+ break;
276
+ case "blocked":
277
+ color = "warning";
278
+ break;
279
+ case "failed":
280
+ color = "danger";
281
+ break;
282
+ case "done":
283
+ color = "primary";
284
+ break;
285
+ case "empty":
286
+ default:
287
+ color = "neutral";
288
+ }
289
+ return {
290
+ textColor: `${color}600`,
291
+ backgroundColor: `${color}100`,
292
+ borderColor: `${color}200`
293
+ };
294
+ };
295
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
296
+ const { formatMessage } = useIntl();
297
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
298
+ if (isError) {
299
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
300
+ }
301
+ if (releases?.length === 0) {
302
+ return /* @__PURE__ */ jsx(
303
+ EmptyStateLayout,
304
+ {
305
+ content: formatMessage(
306
+ {
307
+ id: "content-releases.page.Releases.tab.emptyEntries",
308
+ defaultMessage: "No releases"
309
+ },
310
+ {
311
+ target: sectionTitle
312
+ }
313
+ ),
314
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
315
+ }
316
+ );
317
+ }
318
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt, status }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
319
+ Flex,
320
+ {
321
+ direction: "column",
322
+ justifyContent: "space-between",
323
+ padding: 4,
324
+ hasRadius: true,
325
+ background: "neutral0",
326
+ shadow: "tableShadow",
327
+ height: "100%",
328
+ width: "100%",
329
+ alignItems: "start",
330
+ gap: 4,
331
+ children: [
332
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
333
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
334
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
335
+ id: "content-releases.pages.Releases.not-scheduled",
336
+ defaultMessage: "Not scheduled"
337
+ }) : formatMessage(
338
+ {
339
+ id: "content-releases.page.Releases.release-item.entries",
340
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
341
+ },
342
+ { number: actions.meta.count }
343
+ ) })
344
+ ] }),
345
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
346
+ ]
347
+ }
348
+ ) }) }, id)) });
349
+ };
350
+ const StyledAlert = styled(Alert)`
351
+ button {
352
+ display: none;
353
+ }
354
+ p + div {
355
+ margin-left: auto;
356
+ }
357
+ `;
358
+ const INITIAL_FORM_VALUES = {
359
+ name: "",
360
+ date: null,
361
+ time: "",
362
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
363
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
364
+ scheduledAt: null,
365
+ timezone: null
366
+ };
367
+ const ReleasesPage = () => {
368
+ const tabRef = React.useRef(null);
369
+ const location = useLocation();
370
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
371
+ const toggleNotification = useNotification();
372
+ const { formatMessage } = useIntl();
373
+ const { push, replace } = useHistory();
374
+ const { formatAPIError } = useAPIErrorHandler();
375
+ const [{ query }, setQuery] = useQueryParams();
376
+ const response = useGetReleasesQuery(query);
377
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
378
+ const { getFeature } = useLicenseLimits();
379
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
380
+ const { trackUsage } = useTracking();
381
+ const { isLoading, isSuccess, isError } = response;
382
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
383
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
384
+ React.useEffect(() => {
385
+ if (location?.state?.errors) {
386
+ toggleNotification({
387
+ type: "warning",
388
+ title: formatMessage({
389
+ id: "content-releases.pages.Releases.notification.error.title",
390
+ defaultMessage: "Your request could not be processed."
391
+ }),
392
+ message: formatMessage({
393
+ id: "content-releases.pages.Releases.notification.error.message",
394
+ defaultMessage: "Please try again or open another release."
395
+ })
396
+ });
397
+ replace({ state: null });
398
+ }
399
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
400
+ React.useEffect(() => {
401
+ if (tabRef.current) {
402
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
403
+ }
404
+ }, [activeTabIndex]);
405
+ const toggleAddReleaseModal = () => {
406
+ setReleaseModalShown((prev) => !prev);
407
+ };
408
+ if (isLoading) {
409
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
410
+ }
411
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
412
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
413
+ const handleTabChange = (index) => {
414
+ setQuery({
415
+ ...query,
416
+ page: 1,
417
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
418
+ filters: {
419
+ releasedAt: {
420
+ $notNull: index === 0 ? false : true
421
+ }
422
+ }
423
+ });
424
+ };
425
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
426
+ const response2 = await createRelease({
427
+ name,
428
+ scheduledAt,
429
+ timezone
430
+ });
431
+ if ("data" in response2) {
432
+ toggleNotification({
433
+ type: "success",
434
+ message: formatMessage({
435
+ id: "content-releases.modal.release-created-notification-success",
436
+ defaultMessage: "Release created."
437
+ })
438
+ });
439
+ trackUsage("didCreateRelease");
440
+ push(`/plugins/content-releases/${response2.data.data.id}`);
441
+ } else if (isAxiosError(response2.error)) {
442
+ toggleNotification({
443
+ type: "warning",
444
+ message: formatAPIError(response2.error)
445
+ });
446
+ } else {
447
+ toggleNotification({
448
+ type: "warning",
449
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
450
+ });
451
+ }
452
+ };
453
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
454
+ /* @__PURE__ */ jsx(
455
+ HeaderLayout,
456
+ {
457
+ title: formatMessage({
458
+ id: "content-releases.pages.Releases.title",
459
+ defaultMessage: "Releases"
460
+ }),
461
+ subtitle: formatMessage({
462
+ id: "content-releases.pages.Releases.header-subtitle",
463
+ defaultMessage: "Create and manage content updates"
464
+ }),
465
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
466
+ Button,
467
+ {
468
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
469
+ onClick: toggleAddReleaseModal,
470
+ disabled: hasReachedMaximumPendingReleases,
471
+ children: formatMessage({
472
+ id: "content-releases.header.actions.add-release",
473
+ defaultMessage: "New release"
474
+ })
475
+ }
476
+ ) })
477
+ }
478
+ ),
479
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
480
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
481
+ StyledAlert,
482
+ {
483
+ marginBottom: 6,
484
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
485
+ id: "content-releases.pages.Releases.max-limit-reached.action",
486
+ defaultMessage: "Explore plans"
487
+ }) }),
488
+ title: formatMessage(
489
+ {
490
+ id: "content-releases.pages.Releases.max-limit-reached.title",
491
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
492
+ },
493
+ { number: maximumReleases }
494
+ ),
495
+ onClose: () => {
496
+ },
497
+ closeLabel: "",
498
+ children: formatMessage({
499
+ id: "content-releases.pages.Releases.max-limit-reached.message",
500
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
501
+ })
502
+ }
503
+ ),
504
+ /* @__PURE__ */ jsxs(
505
+ TabGroup,
506
+ {
507
+ label: formatMessage({
508
+ id: "content-releases.pages.Releases.tab-group.label",
509
+ defaultMessage: "Releases list"
510
+ }),
511
+ variant: "simple",
512
+ initialSelectedTabIndex: activeTabIndex,
513
+ onTabChange: handleTabChange,
514
+ ref: tabRef,
515
+ children: [
516
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
517
+ /* @__PURE__ */ jsxs(Tabs, { children: [
518
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
519
+ {
520
+ id: "content-releases.pages.Releases.tab.pending",
521
+ defaultMessage: "Pending ({count})"
522
+ },
523
+ {
524
+ count: totalPendingReleases
525
+ }
526
+ ) }),
527
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
528
+ id: "content-releases.pages.Releases.tab.done",
529
+ defaultMessage: "Done"
530
+ }) })
531
+ ] }),
532
+ /* @__PURE__ */ jsx(Divider, {})
533
+ ] }),
534
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
535
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
536
+ ReleasesGrid,
537
+ {
538
+ sectionTitle: "pending",
539
+ releases: response?.currentData?.data,
540
+ isError
541
+ }
542
+ ) }),
543
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
544
+ ReleasesGrid,
545
+ {
546
+ sectionTitle: "done",
547
+ releases: response?.currentData?.data,
548
+ isError
549
+ }
550
+ ) })
551
+ ] })
552
+ ]
553
+ }
554
+ ),
555
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
556
+ /* @__PURE__ */ jsx(
557
+ PageSizeURLQuery,
558
+ {
559
+ options: ["8", "16", "32", "64"],
560
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
561
+ }
562
+ ),
563
+ /* @__PURE__ */ jsx(
564
+ PaginationURLQuery,
565
+ {
566
+ pagination: {
567
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
568
+ }
569
+ }
570
+ )
571
+ ] }) : null
572
+ ] }) }),
573
+ releaseModalShown && /* @__PURE__ */ jsx(
574
+ ReleaseModal,
575
+ {
576
+ handleClose: toggleAddReleaseModal,
577
+ handleSubmit: handleAddRelease,
578
+ isLoading: isSubmittingForm,
579
+ initialValues: INITIAL_FORM_VALUES
580
+ }
581
+ )
582
+ ] });
583
+ };
94
584
  const ReleaseInfoWrapper = styled(Flex)`
95
585
  align-self: stretch;
96
586
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
@@ -104,6 +594,10 @@ const StyledMenuItem = styled(Menu.Item)`
104
594
  span {
105
595
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
106
596
  }
597
+
598
+ &:hover {
599
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
600
+ }
107
601
  `;
108
602
  const PencilIcon = styled(Pencil)`
109
603
  width: ${({ theme }) => theme.spaces[3]};
@@ -170,7 +664,7 @@ const ReleaseDetailsLayout = ({
170
664
  toggleWarningSubmit,
171
665
  children
172
666
  }) => {
173
- const { formatMessage } = useIntl();
667
+ const { formatMessage, formatDate, formatTime } = useIntl();
174
668
  const { releaseId } = useParams();
175
669
  const {
176
670
  data,
@@ -252,19 +746,45 @@ const ReleaseDetailsLayout = ({
252
746
  }
253
747
  const totalEntries = release.actions.meta.count || 0;
254
748
  const hasCreatedByUser = Boolean(getCreatedByUser());
749
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
750
+ const isScheduled = release.scheduledAt && release.timezone;
751
+ const numberOfEntriesText = formatMessage(
752
+ {
753
+ id: "content-releases.pages.Details.header-subtitle",
754
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
755
+ },
756
+ { number: totalEntries }
757
+ );
758
+ const scheduledText = isScheduled ? formatMessage(
759
+ {
760
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
761
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
762
+ },
763
+ {
764
+ date: formatDate(new Date(release.scheduledAt), {
765
+ weekday: "long",
766
+ day: "numeric",
767
+ month: "long",
768
+ year: "numeric",
769
+ timeZone: release.timezone
770
+ }),
771
+ time: formatTime(new Date(release.scheduledAt), {
772
+ timeZone: release.timezone,
773
+ hourCycle: "h23"
774
+ }),
775
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
776
+ }
777
+ ) : "";
255
778
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
256
779
  /* @__PURE__ */ jsx(
257
780
  HeaderLayout,
258
781
  {
259
782
  title: release.name,
260
- subtitle: formatMessage(
261
- {
262
- id: "content-releases.pages.Details.header-subtitle",
263
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
264
- },
265
- { number: totalEntries }
266
- ),
267
- navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
783
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
784
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : "") }),
785
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
786
+ ] }),
787
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
268
788
  id: "global.back",
269
789
  defaultMessage: "Back"
270
790
  }) }),
@@ -292,44 +812,30 @@ const ReleaseDetailsLayout = ({
292
812
  justifyContent: "center",
293
813
  direction: "column",
294
814
  padding: 1,
295
- width: "100%",
296
- children: [
297
- /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(
298
- Flex,
299
- {
300
- paddingTop: 2,
301
- paddingBottom: 2,
302
- alignItems: "center",
303
- gap: 2,
304
- hasRadius: true,
305
- width: "100%",
306
- children: [
307
- /* @__PURE__ */ jsx(PencilIcon, {}),
308
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
309
- id: "content-releases.header.actions.edit",
310
- defaultMessage: "Edit"
311
- }) })
312
- ]
313
- }
314
- ) }),
315
- /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canDelete, onSelect: toggleWarningSubmit, children: /* @__PURE__ */ jsxs(
316
- Flex,
815
+ width: "100%",
816
+ children: [
817
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
818
+ /* @__PURE__ */ jsx(PencilIcon, {}),
819
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
820
+ id: "content-releases.header.actions.edit",
821
+ defaultMessage: "Edit"
822
+ }) })
823
+ ] }) }),
824
+ /* @__PURE__ */ jsx(
825
+ StyledMenuItem,
317
826
  {
318
- paddingTop: 2,
319
- paddingBottom: 2,
320
- alignItems: "center",
321
- gap: 2,
322
- hasRadius: true,
323
- width: "100%",
324
- children: [
827
+ disabled: !canDelete,
828
+ onSelect: toggleWarningSubmit,
829
+ variant: "danger",
830
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
325
831
  /* @__PURE__ */ jsx(TrashIcon, {}),
326
832
  /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
327
833
  id: "content-releases.header.actions.delete",
328
834
  defaultMessage: "Delete"
329
835
  }) })
330
- ]
836
+ ] })
331
837
  }
332
- ) })
838
+ )
333
839
  ]
334
840
  }
335
841
  ),
@@ -505,7 +1011,7 @@ const ReleaseDetailsBody = () => {
505
1011
  action: /* @__PURE__ */ jsx(
506
1012
  LinkButton,
507
1013
  {
508
- as: Link$1,
1014
+ as: Link$2,
509
1015
  to: {
510
1016
  pathname: "/content-manager"
511
1017
  },
@@ -712,11 +1218,18 @@ const ReleaseDetailsPage = () => {
712
1218
  }
713
1219
  );
714
1220
  }
715
- const title = isSuccessDetails && data?.data?.name || "";
1221
+ const releaseData = isSuccessDetails && data?.data || null;
1222
+ const title = releaseData?.name || "";
1223
+ const timezone = releaseData?.timezone ?? null;
1224
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1225
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
1226
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
716
1227
  const handleEditRelease = async (values) => {
717
1228
  const response = await updateRelease({
718
1229
  id: releaseId,
719
- name: values.name
1230
+ name: values.name,
1231
+ scheduledAt: values.scheduledAt,
1232
+ timezone: values.timezone
720
1233
  });
721
1234
  if ("data" in response) {
722
1235
  toggleNotification({
@@ -770,7 +1283,14 @@ const ReleaseDetailsPage = () => {
770
1283
  handleClose: toggleEditReleaseModal,
771
1284
  handleSubmit: handleEditRelease,
772
1285
  isLoading: isLoadingDetails || isSubmittingForm,
773
- initialValues: { name: title || "" }
1286
+ initialValues: {
1287
+ name: title || "",
1288
+ scheduledAt,
1289
+ date,
1290
+ time,
1291
+ isScheduled: Boolean(scheduledAt),
1292
+ timezone
1293
+ }
774
1294
  }
775
1295
  ),
776
1296
  /* @__PURE__ */ jsx(
@@ -790,281 +1310,6 @@ const ReleaseDetailsPage = () => {
790
1310
  }
791
1311
  );
792
1312
  };
793
- const LinkCard = styled(Link$2)`
794
- display: block;
795
- `;
796
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
797
- const { formatMessage } = useIntl();
798
- if (isError) {
799
- return /* @__PURE__ */ jsx(AnErrorOccurred, {});
800
- }
801
- if (releases?.length === 0) {
802
- return /* @__PURE__ */ jsx(
803
- EmptyStateLayout,
804
- {
805
- content: formatMessage(
806
- {
807
- id: "content-releases.page.Releases.tab.emptyEntries",
808
- defaultMessage: "No releases"
809
- },
810
- {
811
- target: sectionTitle
812
- }
813
- ),
814
- icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
815
- }
816
- );
817
- }
818
- return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
819
- Flex,
820
- {
821
- direction: "column",
822
- justifyContent: "space-between",
823
- padding: 4,
824
- hasRadius: true,
825
- background: "neutral0",
826
- shadow: "tableShadow",
827
- height: "100%",
828
- width: "100%",
829
- alignItems: "start",
830
- gap: 2,
831
- children: [
832
- /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
833
- /* @__PURE__ */ jsx(Typography, { variant: "pi", children: formatMessage(
834
- {
835
- id: "content-releases.page.Releases.release-item.entries",
836
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
837
- },
838
- { number: actions.meta.count }
839
- ) })
840
- ]
841
- }
842
- ) }) }, id)) });
843
- };
844
- const StyledAlert = styled(Alert)`
845
- button {
846
- display: none;
847
- }
848
- p + div {
849
- margin-left: auto;
850
- }
851
- `;
852
- const INITIAL_FORM_VALUES = {
853
- name: ""
854
- };
855
- const ReleasesPage = () => {
856
- const tabRef = React.useRef(null);
857
- const location = useLocation();
858
- const [releaseModalShown, setReleaseModalShown] = React.useState(false);
859
- const toggleNotification = useNotification();
860
- const { formatMessage } = useIntl();
861
- const { push, replace } = useHistory();
862
- const { formatAPIError } = useAPIErrorHandler();
863
- const [{ query }, setQuery] = useQueryParams();
864
- const response = useGetReleasesQuery(query);
865
- const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
866
- const { getFeature } = useLicenseLimits();
867
- const { maximumReleases = 3 } = getFeature("cms-content-releases");
868
- const { trackUsage } = useTracking();
869
- const { isLoading, isSuccess, isError } = response;
870
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
871
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
872
- React.useEffect(() => {
873
- if (location?.state?.errors) {
874
- toggleNotification({
875
- type: "warning",
876
- title: formatMessage({
877
- id: "content-releases.pages.Releases.notification.error.title",
878
- defaultMessage: "Your request could not be processed."
879
- }),
880
- message: formatMessage({
881
- id: "content-releases.pages.Releases.notification.error.message",
882
- defaultMessage: "Please try again or open another release."
883
- })
884
- });
885
- replace({ state: null });
886
- }
887
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
888
- React.useEffect(() => {
889
- if (tabRef.current) {
890
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
891
- }
892
- }, [activeTabIndex]);
893
- const toggleAddReleaseModal = () => {
894
- setReleaseModalShown((prev) => !prev);
895
- };
896
- if (isLoading) {
897
- return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
898
- }
899
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
900
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
901
- const handleTabChange = (index) => {
902
- setQuery({
903
- ...query,
904
- page: 1,
905
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
906
- filters: {
907
- releasedAt: {
908
- $notNull: index === 0 ? false : true
909
- }
910
- }
911
- });
912
- };
913
- const handleAddRelease = async (values) => {
914
- const response2 = await createRelease({
915
- name: values.name
916
- });
917
- if ("data" in response2) {
918
- toggleNotification({
919
- type: "success",
920
- message: formatMessage({
921
- id: "content-releases.modal.release-created-notification-success",
922
- defaultMessage: "Release created."
923
- })
924
- });
925
- trackUsage("didCreateRelease");
926
- push(`/plugins/content-releases/${response2.data.data.id}`);
927
- } else if (isAxiosError(response2.error)) {
928
- toggleNotification({
929
- type: "warning",
930
- message: formatAPIError(response2.error)
931
- });
932
- } else {
933
- toggleNotification({
934
- type: "warning",
935
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
936
- });
937
- }
938
- };
939
- return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
940
- /* @__PURE__ */ jsx(
941
- HeaderLayout,
942
- {
943
- title: formatMessage({
944
- id: "content-releases.pages.Releases.title",
945
- defaultMessage: "Releases"
946
- }),
947
- subtitle: formatMessage(
948
- {
949
- id: "content-releases.pages.Releases.header-subtitle",
950
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
951
- },
952
- { number: totalReleases }
953
- ),
954
- primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
955
- Button,
956
- {
957
- startIcon: /* @__PURE__ */ jsx(Plus, {}),
958
- onClick: toggleAddReleaseModal,
959
- disabled: hasReachedMaximumPendingReleases,
960
- children: formatMessage({
961
- id: "content-releases.header.actions.add-release",
962
- defaultMessage: "New release"
963
- })
964
- }
965
- ) })
966
- }
967
- ),
968
- /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
969
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
970
- StyledAlert,
971
- {
972
- marginBottom: 6,
973
- action: /* @__PURE__ */ jsx(Link$2, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
974
- id: "content-releases.pages.Releases.max-limit-reached.action",
975
- defaultMessage: "Explore plans"
976
- }) }),
977
- title: formatMessage(
978
- {
979
- id: "content-releases.pages.Releases.max-limit-reached.title",
980
- defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
981
- },
982
- { number: maximumReleases }
983
- ),
984
- onClose: () => {
985
- },
986
- closeLabel: "",
987
- children: formatMessage({
988
- id: "content-releases.pages.Releases.max-limit-reached.message",
989
- defaultMessage: "Upgrade to manage an unlimited number of releases."
990
- })
991
- }
992
- ),
993
- /* @__PURE__ */ jsxs(
994
- TabGroup,
995
- {
996
- label: formatMessage({
997
- id: "content-releases.pages.Releases.tab-group.label",
998
- defaultMessage: "Releases list"
999
- }),
1000
- variant: "simple",
1001
- initialSelectedTabIndex: activeTabIndex,
1002
- onTabChange: handleTabChange,
1003
- ref: tabRef,
1004
- children: [
1005
- /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
1006
- /* @__PURE__ */ jsxs(Tabs, { children: [
1007
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
1008
- id: "content-releases.pages.Releases.tab.pending",
1009
- defaultMessage: "Pending"
1010
- }) }),
1011
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
1012
- id: "content-releases.pages.Releases.tab.done",
1013
- defaultMessage: "Done"
1014
- }) })
1015
- ] }),
1016
- /* @__PURE__ */ jsx(Divider, {})
1017
- ] }),
1018
- /* @__PURE__ */ jsxs(TabPanels, { children: [
1019
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1020
- ReleasesGrid,
1021
- {
1022
- sectionTitle: "pending",
1023
- releases: response?.currentData?.data,
1024
- isError
1025
- }
1026
- ) }),
1027
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1028
- ReleasesGrid,
1029
- {
1030
- sectionTitle: "done",
1031
- releases: response?.currentData?.data,
1032
- isError
1033
- }
1034
- ) })
1035
- ] })
1036
- ]
1037
- }
1038
- ),
1039
- totalReleases > 0 && /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1040
- /* @__PURE__ */ jsx(
1041
- PageSizeURLQuery,
1042
- {
1043
- options: ["8", "16", "32", "64"],
1044
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1045
- }
1046
- ),
1047
- /* @__PURE__ */ jsx(
1048
- PaginationURLQuery,
1049
- {
1050
- pagination: {
1051
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1052
- }
1053
- }
1054
- )
1055
- ] })
1056
- ] }) }),
1057
- releaseModalShown && /* @__PURE__ */ jsx(
1058
- ReleaseModal,
1059
- {
1060
- handleClose: toggleAddReleaseModal,
1061
- handleSubmit: handleAddRelease,
1062
- isLoading: isSubmittingForm,
1063
- initialValues: INITIAL_FORM_VALUES
1064
- }
1065
- )
1066
- ] });
1067
- };
1068
1313
  const App = () => {
1069
1314
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
1070
1315
  /* @__PURE__ */ jsx(Route, { exact: true, path: `/plugins/${pluginId}`, component: ReleasesPage }),
@@ -1074,4 +1319,4 @@ const App = () => {
1074
1319
  export {
1075
1320
  App
1076
1321
  };
1077
- //# sourceMappingURL=App-U6GbyLIE.mjs.map
1322
+ //# sourceMappingURL=App-0juWMfve.mjs.map