@strapi/content-releases 4.20.2 → 4.20.4

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-_eBuegHN.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,375 @@ 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
+ name: "timezone",
246
+ value: values.timezone || void 0,
247
+ textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
248
+ onChange: (timezone) => {
249
+ setFieldValue("timezone", timezone);
250
+ },
251
+ onTextValueChange: (timezone) => {
252
+ setFieldValue("timezone", timezone);
253
+ },
254
+ onClear: () => {
255
+ setFieldValue("timezone", "");
256
+ },
257
+ error: errors.timezone,
258
+ required: true,
259
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
260
+ }
261
+ );
262
+ };
263
+ const LinkCard = styled(Link)`
264
+ display: block;
265
+ `;
266
+ const CapitalizeRelativeTime = styled(RelativeTime)`
267
+ text-transform: capitalize;
268
+ `;
269
+ const getBadgeProps = (status) => {
270
+ let color;
271
+ switch (status) {
272
+ case "ready":
273
+ color = "success";
274
+ break;
275
+ case "blocked":
276
+ color = "warning";
277
+ break;
278
+ case "failed":
279
+ color = "danger";
280
+ break;
281
+ case "done":
282
+ color = "primary";
283
+ break;
284
+ case "empty":
285
+ default:
286
+ color = "neutral";
287
+ }
288
+ return {
289
+ textColor: `${color}600`,
290
+ backgroundColor: `${color}100`,
291
+ borderColor: `${color}200`
292
+ };
293
+ };
294
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
295
+ const { formatMessage } = useIntl();
296
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
297
+ if (isError) {
298
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
299
+ }
300
+ if (releases?.length === 0) {
301
+ return /* @__PURE__ */ jsx(
302
+ EmptyStateLayout,
303
+ {
304
+ content: formatMessage(
305
+ {
306
+ id: "content-releases.page.Releases.tab.emptyEntries",
307
+ defaultMessage: "No releases"
308
+ },
309
+ {
310
+ target: sectionTitle
311
+ }
312
+ ),
313
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
314
+ }
315
+ );
316
+ }
317
+ 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(
318
+ Flex,
319
+ {
320
+ direction: "column",
321
+ justifyContent: "space-between",
322
+ padding: 4,
323
+ hasRadius: true,
324
+ background: "neutral0",
325
+ shadow: "tableShadow",
326
+ height: "100%",
327
+ width: "100%",
328
+ alignItems: "start",
329
+ gap: 4,
330
+ children: [
331
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
332
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
333
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
334
+ id: "content-releases.pages.Releases.not-scheduled",
335
+ defaultMessage: "Not scheduled"
336
+ }) : formatMessage(
337
+ {
338
+ id: "content-releases.page.Releases.release-item.entries",
339
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
340
+ },
341
+ { number: actions.meta.count }
342
+ ) })
343
+ ] }),
344
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
345
+ ]
346
+ }
347
+ ) }) }, id)) });
348
+ };
349
+ const StyledAlert = styled(Alert)`
350
+ button {
351
+ display: none;
352
+ }
353
+ p + div {
354
+ margin-left: auto;
355
+ }
356
+ `;
357
+ const INITIAL_FORM_VALUES = {
358
+ name: "",
359
+ date: null,
360
+ time: "",
361
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
362
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
363
+ scheduledAt: null,
364
+ timezone: null
365
+ };
366
+ const ReleasesPage = () => {
367
+ const tabRef = React.useRef(null);
368
+ const location = useLocation();
369
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
370
+ const toggleNotification = useNotification();
371
+ const { formatMessage } = useIntl();
372
+ const { push, replace } = useHistory();
373
+ const { formatAPIError } = useAPIErrorHandler();
374
+ const [{ query }, setQuery] = useQueryParams();
375
+ const response = useGetReleasesQuery(query);
376
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
377
+ const { getFeature } = useLicenseLimits();
378
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
379
+ const { trackUsage } = useTracking();
380
+ const { isLoading, isSuccess, isError } = response;
381
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
382
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
383
+ React.useEffect(() => {
384
+ if (location?.state?.errors) {
385
+ toggleNotification({
386
+ type: "warning",
387
+ title: formatMessage({
388
+ id: "content-releases.pages.Releases.notification.error.title",
389
+ defaultMessage: "Your request could not be processed."
390
+ }),
391
+ message: formatMessage({
392
+ id: "content-releases.pages.Releases.notification.error.message",
393
+ defaultMessage: "Please try again or open another release."
394
+ })
395
+ });
396
+ replace({ state: null });
397
+ }
398
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
399
+ React.useEffect(() => {
400
+ if (tabRef.current) {
401
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
402
+ }
403
+ }, [activeTabIndex]);
404
+ const toggleAddReleaseModal = () => {
405
+ setReleaseModalShown((prev) => !prev);
406
+ };
407
+ if (isLoading) {
408
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
409
+ }
410
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
411
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
412
+ const handleTabChange = (index) => {
413
+ setQuery({
414
+ ...query,
415
+ page: 1,
416
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
417
+ filters: {
418
+ releasedAt: {
419
+ $notNull: index === 0 ? false : true
420
+ }
421
+ }
422
+ });
423
+ };
424
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
425
+ const response2 = await createRelease({
426
+ name,
427
+ scheduledAt,
428
+ timezone
429
+ });
430
+ if ("data" in response2) {
431
+ toggleNotification({
432
+ type: "success",
433
+ message: formatMessage({
434
+ id: "content-releases.modal.release-created-notification-success",
435
+ defaultMessage: "Release created."
436
+ })
437
+ });
438
+ trackUsage("didCreateRelease");
439
+ push(`/plugins/content-releases/${response2.data.data.id}`);
440
+ } else if (isAxiosError(response2.error)) {
441
+ toggleNotification({
442
+ type: "warning",
443
+ message: formatAPIError(response2.error)
444
+ });
445
+ } else {
446
+ toggleNotification({
447
+ type: "warning",
448
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
449
+ });
450
+ }
451
+ };
452
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
453
+ /* @__PURE__ */ jsx(
454
+ HeaderLayout,
455
+ {
456
+ title: formatMessage({
457
+ id: "content-releases.pages.Releases.title",
458
+ defaultMessage: "Releases"
459
+ }),
460
+ subtitle: formatMessage({
461
+ id: "content-releases.pages.Releases.header-subtitle",
462
+ defaultMessage: "Create and manage content updates"
463
+ }),
464
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
465
+ Button,
466
+ {
467
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
468
+ onClick: toggleAddReleaseModal,
469
+ disabled: hasReachedMaximumPendingReleases,
470
+ children: formatMessage({
471
+ id: "content-releases.header.actions.add-release",
472
+ defaultMessage: "New release"
473
+ })
474
+ }
475
+ ) })
476
+ }
477
+ ),
478
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
479
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
480
+ StyledAlert,
481
+ {
482
+ marginBottom: 6,
483
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
484
+ id: "content-releases.pages.Releases.max-limit-reached.action",
485
+ defaultMessage: "Explore plans"
486
+ }) }),
487
+ title: formatMessage(
488
+ {
489
+ id: "content-releases.pages.Releases.max-limit-reached.title",
490
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
491
+ },
492
+ { number: maximumReleases }
493
+ ),
494
+ onClose: () => {
495
+ },
496
+ closeLabel: "",
497
+ children: formatMessage({
498
+ id: "content-releases.pages.Releases.max-limit-reached.message",
499
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
500
+ })
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsxs(
504
+ TabGroup,
505
+ {
506
+ label: formatMessage({
507
+ id: "content-releases.pages.Releases.tab-group.label",
508
+ defaultMessage: "Releases list"
509
+ }),
510
+ variant: "simple",
511
+ initialSelectedTabIndex: activeTabIndex,
512
+ onTabChange: handleTabChange,
513
+ ref: tabRef,
514
+ children: [
515
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
516
+ /* @__PURE__ */ jsxs(Tabs, { children: [
517
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
518
+ {
519
+ id: "content-releases.pages.Releases.tab.pending",
520
+ defaultMessage: "Pending ({count})"
521
+ },
522
+ {
523
+ count: totalPendingReleases
524
+ }
525
+ ) }),
526
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
527
+ id: "content-releases.pages.Releases.tab.done",
528
+ defaultMessage: "Done"
529
+ }) })
530
+ ] }),
531
+ /* @__PURE__ */ jsx(Divider, {})
532
+ ] }),
533
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
534
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
535
+ ReleasesGrid,
536
+ {
537
+ sectionTitle: "pending",
538
+ releases: response?.currentData?.data,
539
+ isError
540
+ }
541
+ ) }),
542
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
543
+ ReleasesGrid,
544
+ {
545
+ sectionTitle: "done",
546
+ releases: response?.currentData?.data,
547
+ isError
548
+ }
549
+ ) })
550
+ ] })
551
+ ]
552
+ }
553
+ ),
554
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
555
+ /* @__PURE__ */ jsx(
556
+ PageSizeURLQuery,
557
+ {
558
+ options: ["8", "16", "32", "64"],
559
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
560
+ }
561
+ ),
562
+ /* @__PURE__ */ jsx(
563
+ PaginationURLQuery,
564
+ {
565
+ pagination: {
566
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
567
+ }
568
+ }
569
+ )
570
+ ] }) : null
571
+ ] }) }),
572
+ releaseModalShown && /* @__PURE__ */ jsx(
573
+ ReleaseModal,
574
+ {
575
+ handleClose: toggleAddReleaseModal,
576
+ handleSubmit: handleAddRelease,
577
+ isLoading: isSubmittingForm,
578
+ initialValues: INITIAL_FORM_VALUES
579
+ }
580
+ )
581
+ ] });
582
+ };
94
583
  const ReleaseInfoWrapper = styled(Flex)`
95
584
  align-self: stretch;
96
585
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
@@ -104,6 +593,10 @@ const StyledMenuItem = styled(Menu.Item)`
104
593
  span {
105
594
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
106
595
  }
596
+
597
+ &:hover {
598
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
599
+ }
107
600
  `;
108
601
  const PencilIcon = styled(Pencil)`
109
602
  width: ${({ theme }) => theme.spaces[3]};
@@ -170,7 +663,7 @@ const ReleaseDetailsLayout = ({
170
663
  toggleWarningSubmit,
171
664
  children
172
665
  }) => {
173
- const { formatMessage } = useIntl();
666
+ const { formatMessage, formatDate, formatTime } = useIntl();
174
667
  const { releaseId } = useParams();
175
668
  const {
176
669
  data,
@@ -252,19 +745,45 @@ const ReleaseDetailsLayout = ({
252
745
  }
253
746
  const totalEntries = release.actions.meta.count || 0;
254
747
  const hasCreatedByUser = Boolean(getCreatedByUser());
748
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
749
+ const isScheduled = release.scheduledAt && release.timezone;
750
+ const numberOfEntriesText = formatMessage(
751
+ {
752
+ id: "content-releases.pages.Details.header-subtitle",
753
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
754
+ },
755
+ { number: totalEntries }
756
+ );
757
+ const scheduledText = isScheduled ? formatMessage(
758
+ {
759
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
760
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
761
+ },
762
+ {
763
+ date: formatDate(new Date(release.scheduledAt), {
764
+ weekday: "long",
765
+ day: "numeric",
766
+ month: "long",
767
+ year: "numeric",
768
+ timeZone: release.timezone
769
+ }),
770
+ time: formatTime(new Date(release.scheduledAt), {
771
+ timeZone: release.timezone,
772
+ hourCycle: "h23"
773
+ }),
774
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
775
+ }
776
+ ) : "";
255
777
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
256
778
  /* @__PURE__ */ jsx(
257
779
  HeaderLayout,
258
780
  {
259
781
  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({
782
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
783
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : "") }),
784
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
785
+ ] }),
786
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
268
787
  id: "global.back",
269
788
  defaultMessage: "Back"
270
789
  }) }),
@@ -292,44 +811,30 @@ const ReleaseDetailsLayout = ({
292
811
  justifyContent: "center",
293
812
  direction: "column",
294
813
  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,
814
+ width: "100%",
815
+ children: [
816
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
817
+ /* @__PURE__ */ jsx(PencilIcon, {}),
818
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
819
+ id: "content-releases.header.actions.edit",
820
+ defaultMessage: "Edit"
821
+ }) })
822
+ ] }) }),
823
+ /* @__PURE__ */ jsx(
824
+ StyledMenuItem,
317
825
  {
318
- paddingTop: 2,
319
- paddingBottom: 2,
320
- alignItems: "center",
321
- gap: 2,
322
- hasRadius: true,
323
- width: "100%",
324
- children: [
826
+ disabled: !canDelete,
827
+ onSelect: toggleWarningSubmit,
828
+ variant: "danger",
829
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
325
830
  /* @__PURE__ */ jsx(TrashIcon, {}),
326
831
  /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
327
832
  id: "content-releases.header.actions.delete",
328
833
  defaultMessage: "Delete"
329
834
  }) })
330
- ]
835
+ ] })
331
836
  }
332
- ) })
837
+ )
333
838
  ]
334
839
  }
335
840
  ),
@@ -505,7 +1010,7 @@ const ReleaseDetailsBody = () => {
505
1010
  action: /* @__PURE__ */ jsx(
506
1011
  LinkButton,
507
1012
  {
508
- as: Link$1,
1013
+ as: Link$2,
509
1014
  to: {
510
1015
  pathname: "/content-manager"
511
1016
  },
@@ -712,11 +1217,18 @@ const ReleaseDetailsPage = () => {
712
1217
  }
713
1218
  );
714
1219
  }
715
- const title = isSuccessDetails && data?.data?.name || "";
1220
+ const releaseData = isSuccessDetails && data?.data || null;
1221
+ const title = releaseData?.name || "";
1222
+ const timezone = releaseData?.timezone ?? null;
1223
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1224
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
1225
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
716
1226
  const handleEditRelease = async (values) => {
717
1227
  const response = await updateRelease({
718
1228
  id: releaseId,
719
- name: values.name
1229
+ name: values.name,
1230
+ scheduledAt: values.scheduledAt,
1231
+ timezone: values.timezone
720
1232
  });
721
1233
  if ("data" in response) {
722
1234
  toggleNotification({
@@ -770,7 +1282,14 @@ const ReleaseDetailsPage = () => {
770
1282
  handleClose: toggleEditReleaseModal,
771
1283
  handleSubmit: handleEditRelease,
772
1284
  isLoading: isLoadingDetails || isSubmittingForm,
773
- initialValues: { name: title || "" }
1285
+ initialValues: {
1286
+ name: title || "",
1287
+ scheduledAt,
1288
+ date,
1289
+ time,
1290
+ isScheduled: Boolean(scheduledAt),
1291
+ timezone
1292
+ }
774
1293
  }
775
1294
  ),
776
1295
  /* @__PURE__ */ jsx(
@@ -790,281 +1309,6 @@ const ReleaseDetailsPage = () => {
790
1309
  }
791
1310
  );
792
1311
  };
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
1312
  const App = () => {
1069
1313
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
1070
1314
  /* @__PURE__ */ jsx(Route, { exact: true, path: `/plugins/${pluginId}`, component: ReleasesPage }),
@@ -1074,4 +1318,4 @@ const App = () => {
1074
1318
  export {
1075
1319
  App
1076
1320
  };
1077
- //# sourceMappingURL=App-U6GbyLIE.mjs.map
1321
+ //# sourceMappingURL=App-6ugQxqYE.mjs.map