@strapi/content-releases 0.0.0-next.3844395bef7efa05c25c6d4337306935905bc653 → 0.0.0-next.4af8963f6880c5fb9fae32ecd580f5cd33eaddda

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/_chunks/{App-L1jSxCiL.mjs → App-ise7GunC.mjs} +493 -235
  2. package/dist/_chunks/App-ise7GunC.mjs.map +1 -0
  3. package/dist/_chunks/{App-_20W9dYa.js → App-w2Zq-wj5.js} +489 -230
  4. package/dist/_chunks/App-w2Zq-wj5.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-gYDqKYFd.js → en-7P4i1cWH.js} +14 -3
  10. package/dist/_chunks/en-7P4i1cWH.js.map +1 -0
  11. package/dist/_chunks/{en-MyLPoISH.mjs → en-pb1wUzhy.mjs} +14 -3
  12. package/dist/_chunks/en-pb1wUzhy.mjs.map +1 -0
  13. package/dist/_chunks/{index-c4zRX_sg.mjs → index-D-Yjf60c.mjs} +89 -26
  14. package/dist/_chunks/index-D-Yjf60c.mjs.map +1 -0
  15. package/dist/_chunks/{index-KJa1Rb5F.js → index-Q8Pv7enO.js} +88 -25
  16. package/dist/_chunks/index-Q8Pv7enO.js.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +1 -1
  19. package/dist/server/index.js +597 -382
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +597 -382
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +12 -9
  24. package/dist/_chunks/App-L1jSxCiL.mjs.map +0 -1
  25. package/dist/_chunks/App-_20W9dYa.js.map +0 -1
  26. package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
  27. package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
  28. package/dist/_chunks/index-KJa1Rb5F.js.map +0 -1
  29. package/dist/_chunks/index-c4zRX_sg.mjs.map +0 -1
@@ -1,22 +1,42 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { useNotification, useAPIErrorHandler, LoadingIndicatorPage, ConfirmDialog, useRBAC, RelativeTime, CheckPermissions, useQueryParams, AnErrorOccurred, NoContent, Table, PageSizeURLQuery, PaginationURLQuery, CheckPagePermissions } from "@strapi/helper-plugin";
2
+ import { useNotification, useAPIErrorHandler, LoadingIndicatorPage, ConfirmDialog, useRBAC, useTracking, RelativeTime, CheckPermissions, useQueryParams, AnErrorOccurred, NoContent, Table, PageSizeURLQuery, PaginationURLQuery, CheckPagePermissions } from "@strapi/helper-plugin";
3
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-c4zRX_sg.mjs";
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-D-Yjf60c.mjs";
5
5
  import * as React from "react";
6
- import { unstable_useDocument } from "@strapi/admin/strapi-admin";
7
- import { ModalLayout, ModalHeader, Typography, ModalBody, TextInput, ModalFooter, Button, Flex, ContentLayout, Main, HeaderLayout, Link, IconButton, Popover, SingleSelect, SingleSelectOption, Badge, Tr, Td, Icon, Tooltip, TabGroup, Box, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem } from "@strapi/design-system";
8
- import { LinkButton, Link as Link$2 } from "@strapi/design-system/v2";
6
+ import { unstable_useDocument, useLicenseLimits } from "@strapi/admin/strapi-admin";
7
+ import { ModalLayout, ModalHeader, Typography, ModalBody, Flex, TextInput, Checkbox, Box, DatePicker, TimePicker, ModalFooter, Button, Combobox, ComboboxOption, ContentLayout, Main, HeaderLayout, Link, IconButton, SingleSelect, SingleSelectOption, Badge, Tr, Td, Icon, Tooltip, Alert, TabGroup, 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
9
  import { Pencil, Trash, ArrowLeft, More, CrossCircle, CheckCircle, Plus, EmptyDocuments } 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";
16
19
  import "@reduxjs/toolkit/query/react";
17
20
  import "react-redux";
18
21
  const RELEASE_SCHEMA = yup.object().shape({
19
- name: yup.string().trim().required()
22
+ name: yup.string().trim().required(),
23
+ scheduledAt: yup.string().nullable(),
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(),
38
+ otherwise: yup.string().nullable()
39
+ })
20
40
  }).required().noUnknown();
21
41
  const ReleaseModal = ({
22
42
  handleClose,
@@ -27,6 +47,24 @@ const ReleaseModal = ({
27
47
  const { formatMessage } = useIntl();
28
48
  const { pathname } = useLocation();
29
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
+ };
30
68
  return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
31
69
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
32
70
  {
@@ -38,45 +76,134 @@ const ReleaseModal = ({
38
76
  /* @__PURE__ */ jsx(
39
77
  Formik,
40
78
  {
41
- validateOnChange: false,
42
- onSubmit: handleSubmit,
43
- 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
+ },
44
90
  validationSchema: RELEASE_SCHEMA,
45
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxs(Form, { children: [
46
- /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx(
47
- TextInput,
48
- {
49
- label: formatMessage({
50
- id: "content-releases.modal.form.input.label.release-name",
51
- defaultMessage: "Name"
52
- }),
53
- name: "name",
54
- value: values.name,
55
- error: errors.name,
56
- onChange: handleChange,
57
- required: true
58
- }
59
- ) }),
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(
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
+ ] }) }),
60
196
  /* @__PURE__ */ jsx(
61
197
  ModalFooter,
62
198
  {
63
199
  startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
64
- endActions: /* @__PURE__ */ jsx(
65
- Button,
200
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
66
201
  {
67
- name: "submit",
68
- loading: isLoading,
69
- disabled: !values.name || values.name === initialValues.name,
70
- type: "submit",
71
- children: formatMessage(
72
- {
73
- id: "content-releases.modal.form.button.submit",
74
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
75
- },
76
- { isCreatingRelease }
77
- )
78
- }
79
- )
202
+ id: "content-releases.modal.form.button.submit",
203
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
204
+ },
205
+ { isCreatingRelease }
206
+ ) })
80
207
  }
81
208
  )
82
209
  ] })
@@ -84,16 +211,67 @@ const ReleaseModal = ({
84
211
  )
85
212
  ] });
86
213
  };
214
+ const getTimezones = (selectedDate) => {
215
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
216
+ const offsetPart = new Intl.DateTimeFormat("en", {
217
+ timeZone: timezone,
218
+ timeZoneName: "longOffset"
219
+ }).formatToParts(selectedDate).find((part) => part.type === "timeZoneName");
220
+ const offset = offsetPart ? offsetPart.value : "";
221
+ let utcOffset = offset.replace("GMT", "UTC");
222
+ if (!utcOffset.includes("+") && !utcOffset.includes("-")) {
223
+ utcOffset = `${utcOffset}+00:00`;
224
+ }
225
+ return { offset: utcOffset, value: `${utcOffset}-${timezone}` };
226
+ });
227
+ const systemTimezone = timezoneList.find(
228
+ (timezone) => timezone.value.split("-")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
229
+ );
230
+ return { timezoneList, systemTimezone };
231
+ };
232
+ const TimezoneComponent = ({ timezoneOptions }) => {
233
+ const { values, errors, setFieldValue } = useFormikContext();
234
+ const { formatMessage } = useIntl();
235
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
236
+ React.useEffect(() => {
237
+ if (values.date) {
238
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
239
+ setTimezoneList(timezoneList2);
240
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("-")[1] === values.timezone.split("-")[1]);
241
+ if (updatedTimezone) {
242
+ setFieldValue("timezone", updatedTimezone.value);
243
+ }
244
+ }
245
+ }, [setFieldValue, values.date, values.timezone]);
246
+ return /* @__PURE__ */ jsx(
247
+ Combobox,
248
+ {
249
+ label: formatMessage({
250
+ id: "content-releases.modal.form.input.label.timezone",
251
+ defaultMessage: "Timezone"
252
+ }),
253
+ name: "timezone",
254
+ value: values.timezone || void 0,
255
+ textValue: values.timezone ? values.timezone.replace("-", " ") : void 0,
256
+ onChange: (timezone) => {
257
+ setFieldValue("timezone", timezone);
258
+ },
259
+ onClear: () => {
260
+ setFieldValue("timezone", "");
261
+ },
262
+ error: errors.timezone,
263
+ required: true,
264
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("-", " ") }, timezone.value))
265
+ }
266
+ );
267
+ };
87
268
  const ReleaseInfoWrapper = styled(Flex)`
88
269
  align-self: stretch;
89
270
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
271
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
272
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
273
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
274
+ const StyledMenuItem = styled(Menu.Item)`
97
275
  svg path {
98
276
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
277
  }
@@ -102,15 +280,15 @@ const StyledFlex = styled(Flex)`
102
280
  }
103
281
  `;
104
282
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
283
+ width: ${({ theme }) => theme.spaces[3]};
284
+ height: ${({ theme }) => theme.spaces[3]};
107
285
  path {
108
286
  fill: ${({ theme }) => theme.colors.neutral600};
109
287
  }
110
288
  `;
111
289
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
290
+ width: ${({ theme }) => theme.spaces[3]};
291
+ height: ${({ theme }) => theme.spaces[3]};
114
292
  path {
115
293
  fill: ${({ theme }) => theme.colors.danger600};
116
294
  }
@@ -118,24 +296,6 @@ const TrashIcon = styled(Trash)`
118
296
  const TypographyMaxWidth = styled(Typography)`
119
297
  max-width: 300px;
120
298
  `;
121
- const PopoverButton = ({ onClick, disabled, children }) => {
122
- return /* @__PURE__ */ jsx(
123
- StyledFlex,
124
- {
125
- paddingTop: 2,
126
- paddingBottom: 2,
127
- paddingLeft: 4,
128
- paddingRight: 4,
129
- alignItems: "center",
130
- gap: 2,
131
- as: "button",
132
- hasRadius: true,
133
- onClick,
134
- disabled,
135
- children
136
- }
137
- );
138
- };
139
299
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
300
  const { formatMessage } = useIntl();
141
301
  const { validate } = unstable_useDocument();
@@ -186,8 +346,6 @@ const ReleaseDetailsLayout = ({
186
346
  }) => {
187
347
  const { formatMessage } = useIntl();
188
348
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
349
  const {
192
350
  data,
193
351
  isLoading: isLoadingDetails,
@@ -201,14 +359,8 @@ const ReleaseDetailsLayout = ({
201
359
  allowedActions: { canUpdate, canDelete }
202
360
  } = useRBAC(PERMISSIONS);
203
361
  const dispatch = useTypedDispatch();
362
+ const { trackUsage } = useTracking();
204
363
  const release = data?.data;
205
- const handleTogglePopover = () => {
206
- setIsPopoverVisible((prev) => !prev);
207
- };
208
- const openReleaseModal = () => {
209
- toggleEditReleaseModal();
210
- handleTogglePopover();
211
- };
212
364
  const handlePublishRelease = async () => {
213
365
  const response = await publishRelease({ id: releaseId });
214
366
  if ("data" in response) {
@@ -219,6 +371,12 @@ const ReleaseDetailsLayout = ({
219
371
  defaultMessage: "Release was published successfully."
220
372
  })
221
373
  });
374
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
375
+ trackUsage("didPublishRelease", {
376
+ totalEntries: totalEntries2,
377
+ totalPublishedEntries,
378
+ totalUnpublishedEntries
379
+ });
222
380
  } else if (isAxiosError(response.error)) {
223
381
  toggleNotification({
224
382
  type: "warning",
@@ -231,13 +389,21 @@ const ReleaseDetailsLayout = ({
231
389
  });
232
390
  }
233
391
  };
234
- const openWarningConfirmDialog = () => {
235
- toggleWarningSubmit();
236
- handleTogglePopover();
237
- };
238
392
  const handleRefresh = () => {
239
393
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
240
394
  };
395
+ const getCreatedByUser = () => {
396
+ if (!release?.createdBy) {
397
+ return null;
398
+ }
399
+ if (release.createdBy.username) {
400
+ return release.createdBy.username;
401
+ }
402
+ if (release.createdBy.firstname) {
403
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
404
+ }
405
+ return release.createdBy.email;
406
+ };
241
407
  if (isLoadingDetails) {
242
408
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
243
409
  }
@@ -259,7 +425,7 @@ const ReleaseDetailsLayout = ({
259
425
  );
260
426
  }
261
427
  const totalEntries = release.actions.meta.count || 0;
262
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
428
+ const hasCreatedByUser = Boolean(getCreatedByUser());
263
429
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
264
430
  /* @__PURE__ */ jsx(
265
431
  HeaderLayout,
@@ -277,72 +443,98 @@ const ReleaseDetailsLayout = ({
277
443
  defaultMessage: "Back"
278
444
  }) }),
279
445
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
280
- /* @__PURE__ */ jsx(
281
- IconButton,
282
- {
283
- label: formatMessage({
284
- id: "content-releases.header.actions.open-release-actions",
285
- defaultMessage: "Release actions"
286
- }),
287
- ref: moreButtonRef,
288
- onClick: handleTogglePopover,
289
- children: /* @__PURE__ */ jsx(More, {})
290
- }
291
- ),
292
- isPopoverVisible && /* @__PURE__ */ jsxs(
293
- Popover,
294
- {
295
- source: moreButtonRef,
296
- placement: "bottom-end",
297
- onDismiss: handleTogglePopover,
298
- spacing: 4,
299
- minWidth: "242px",
300
- children: [
301
- /* @__PURE__ */ jsxs(Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
302
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
303
- /* @__PURE__ */ jsx(PencilIcon, {}),
304
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
305
- id: "content-releases.header.actions.edit",
306
- defaultMessage: "Edit"
307
- }) })
308
- ] }),
309
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
310
- /* @__PURE__ */ jsx(TrashIcon, {}),
311
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
312
- id: "content-releases.header.actions.delete",
313
- defaultMessage: "Delete"
314
- }) })
315
- ] })
316
- ] }),
317
- /* @__PURE__ */ jsxs(
318
- ReleaseInfoWrapper,
319
- {
320
- direction: "column",
321
- justifyContent: "center",
322
- alignItems: "flex-start",
323
- gap: 1,
324
- padding: 5,
325
- children: [
326
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
327
- id: "content-releases.header.actions.created",
328
- defaultMessage: "Created"
329
- }) }),
330
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
331
- /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
332
- formatMessage(
333
- {
334
- id: "content-releases.header.actions.created.description",
335
- defaultMessage: " by {createdBy}"
336
- },
337
- { createdBy }
338
- )
339
- ] })
340
- ]
341
- }
342
- )
343
- ]
344
- }
345
- ),
446
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
447
+ /* @__PURE__ */ jsx(
448
+ Menu.Trigger,
449
+ {
450
+ as: IconButton,
451
+ paddingLeft: 2,
452
+ paddingRight: 2,
453
+ "aria-label": formatMessage({
454
+ id: "content-releases.header.actions.open-release-actions",
455
+ defaultMessage: "Release edit and delete menu"
456
+ }),
457
+ icon: /* @__PURE__ */ jsx(More, {}),
458
+ variant: "tertiary"
459
+ }
460
+ ),
461
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
462
+ /* @__PURE__ */ jsxs(
463
+ Flex,
464
+ {
465
+ alignItems: "center",
466
+ justifyContent: "center",
467
+ direction: "column",
468
+ padding: 1,
469
+ width: "100%",
470
+ children: [
471
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(
472
+ Flex,
473
+ {
474
+ paddingTop: 2,
475
+ paddingBottom: 2,
476
+ alignItems: "center",
477
+ gap: 2,
478
+ hasRadius: true,
479
+ width: "100%",
480
+ children: [
481
+ /* @__PURE__ */ jsx(PencilIcon, {}),
482
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
483
+ id: "content-releases.header.actions.edit",
484
+ defaultMessage: "Edit"
485
+ }) })
486
+ ]
487
+ }
488
+ ) }),
489
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canDelete, onSelect: toggleWarningSubmit, children: /* @__PURE__ */ jsxs(
490
+ Flex,
491
+ {
492
+ paddingTop: 2,
493
+ paddingBottom: 2,
494
+ alignItems: "center",
495
+ gap: 2,
496
+ hasRadius: true,
497
+ width: "100%",
498
+ children: [
499
+ /* @__PURE__ */ jsx(TrashIcon, {}),
500
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
501
+ id: "content-releases.header.actions.delete",
502
+ defaultMessage: "Delete"
503
+ }) })
504
+ ]
505
+ }
506
+ ) })
507
+ ]
508
+ }
509
+ ),
510
+ /* @__PURE__ */ jsxs(
511
+ ReleaseInfoWrapper,
512
+ {
513
+ direction: "column",
514
+ justifyContent: "center",
515
+ alignItems: "flex-start",
516
+ gap: 1,
517
+ padding: 5,
518
+ children: [
519
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
520
+ id: "content-releases.header.actions.created",
521
+ defaultMessage: "Created"
522
+ }) }),
523
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
524
+ /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
525
+ formatMessage(
526
+ {
527
+ id: "content-releases.header.actions.created.description",
528
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
529
+ },
530
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
531
+ )
532
+ ] })
533
+ ]
534
+ }
535
+ )
536
+ ] })
537
+ ] }),
346
538
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
347
539
  id: "content-releases.header.actions.refresh",
348
540
  defaultMessage: "Refresh"
@@ -398,6 +590,9 @@ const ReleaseDetailsBody = () => {
398
590
  isError: isReleaseError,
399
591
  error: releaseError
400
592
  } = useGetReleaseQuery({ id: releaseId });
593
+ const {
594
+ allowedActions: { canUpdate }
595
+ } = useRBAC(PERMISSIONS);
401
596
  const release = releaseData?.data;
402
597
  const selectedGroupBy = query?.groupBy || "contentType";
403
598
  const {
@@ -411,7 +606,7 @@ const ReleaseDetailsBody = () => {
411
606
  releaseId
412
607
  });
413
608
  const [updateReleaseAction] = useUpdateReleaseActionMutation();
414
- const handleChangeType = async (e, actionId) => {
609
+ const handleChangeType = async (e, actionId, actionPath) => {
415
610
  const response = await updateReleaseAction({
416
611
  params: {
417
612
  releaseId,
@@ -419,7 +614,11 @@ const ReleaseDetailsBody = () => {
419
614
  },
420
615
  body: {
421
616
  type: e.target.value
422
- }
617
+ },
618
+ query,
619
+ // We are passing the query params to make optimistic updates
620
+ actionPath
621
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
423
622
  });
424
623
  if ("error" in response) {
425
624
  if (isAxiosError(response.error)) {
@@ -500,7 +699,7 @@ const ReleaseDetailsBody = () => {
500
699
  SingleSelect,
501
700
  {
502
701
  "aria-label": formatMessage({
503
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
702
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
504
703
  defaultMessage: "Group by"
505
704
  }),
506
705
  customizeContent: (value) => formatMessage(
@@ -518,7 +717,7 @@ const ReleaseDetailsBody = () => {
518
717
  }
519
718
  ) }),
520
719
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxs(Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
521
- /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
720
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
522
721
  /* @__PURE__ */ jsx(
523
722
  Table.Root,
524
723
  {
@@ -588,56 +787,59 @@ const ReleaseDetailsBody = () => {
588
787
  )
589
788
  ] }),
590
789
  /* @__PURE__ */ jsx(Table.LoadingBody, {}),
591
- /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(({ id, contentType, locale, type, entry }) => /* @__PURE__ */ jsxs(Tr, { children: [
592
- /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
593
- /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
594
- /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
595
- /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
596
- {
597
- id: "content-releases.page.ReleaseDetails.table.action-published",
598
- defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
599
- },
600
- {
601
- isPublish: type === "publish",
602
- b: (children) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children })
603
- }
604
- ) }) : /* @__PURE__ */ jsx(
605
- ReleaseActionOptions,
606
- {
607
- selected: type,
608
- handleChange: (e) => handleChangeType(e, id),
609
- name: `release-action-${id}-type`
610
- }
611
- ) }),
612
- !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
613
- /* @__PURE__ */ jsx(Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsx(
614
- EntryValidationText,
790
+ /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(
791
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxs(Tr, { children: [
792
+ /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
793
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
794
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
795
+ /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
796
+ {
797
+ id: "content-releases.page.ReleaseDetails.table.action-published",
798
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
799
+ },
615
800
  {
616
- action: type,
617
- schema: contentTypes?.[contentType.uid],
618
- components,
619
- entry
801
+ isPublish: type === "publish",
802
+ b: (children) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children })
803
+ }
804
+ ) }) : /* @__PURE__ */ jsx(
805
+ ReleaseActionOptions,
806
+ {
807
+ selected: type,
808
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
809
+ name: `release-action-${id}-type`,
810
+ disabled: !canUpdate
620
811
  }
621
812
  ) }),
622
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
623
- /* @__PURE__ */ jsx(
624
- ReleaseActionMenu.ReleaseActionEntryLinkItem,
625
- {
626
- contentTypeUid: contentType.uid,
627
- entryId: entry.id,
628
- locale: locale?.code
629
- }
630
- ),
631
- /* @__PURE__ */ jsx(
632
- ReleaseActionMenu.DeleteReleaseActionItem,
813
+ !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
814
+ /* @__PURE__ */ jsx(Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsx(
815
+ EntryValidationText,
633
816
  {
634
- releaseId: release.id,
635
- actionId: id
817
+ action: type,
818
+ schema: contentTypes?.[contentType.uid],
819
+ components,
820
+ entry
636
821
  }
637
- )
638
- ] }) }) })
639
- ] })
640
- ] }, id)) })
822
+ ) }),
823
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
824
+ /* @__PURE__ */ jsx(
825
+ ReleaseActionMenu.ReleaseActionEntryLinkItem,
826
+ {
827
+ contentTypeUid: contentType.uid,
828
+ entryId: entry.id,
829
+ locale: locale?.code
830
+ }
831
+ ),
832
+ /* @__PURE__ */ jsx(
833
+ ReleaseActionMenu.DeleteReleaseActionItem,
834
+ {
835
+ releaseId: release.id,
836
+ actionId: id
837
+ }
838
+ )
839
+ ] }) }) })
840
+ ] })
841
+ ] }, id)
842
+ ) })
641
843
  ] })
642
844
  }
643
845
  )
@@ -684,11 +886,18 @@ const ReleaseDetailsPage = () => {
684
886
  }
685
887
  );
686
888
  }
687
- const title = isSuccessDetails && data?.data?.name || "";
889
+ const releaseData = isSuccessDetails && data?.data || null;
890
+ const title = releaseData?.name || "";
891
+ const timezone = releaseData?.timezone ?? null;
892
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
893
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
894
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
688
895
  const handleEditRelease = async (values) => {
689
896
  const response = await updateRelease({
690
897
  id: releaseId,
691
- name: values.name
898
+ name: values.name,
899
+ scheduledAt: values.scheduledAt,
900
+ timezone: values.timezone
692
901
  });
693
902
  if ("data" in response) {
694
903
  toggleNotification({
@@ -742,7 +951,14 @@ const ReleaseDetailsPage = () => {
742
951
  handleClose: toggleEditReleaseModal,
743
952
  handleSubmit: handleEditRelease,
744
953
  isLoading: isLoadingDetails || isSubmittingForm,
745
- initialValues: { name: title || "" }
954
+ initialValues: {
955
+ name: title || "",
956
+ scheduledAt,
957
+ date,
958
+ time,
959
+ isScheduled: Boolean(scheduledAt),
960
+ timezone
961
+ }
746
962
  }
747
963
  ),
748
964
  /* @__PURE__ */ jsx(
@@ -762,37 +978,6 @@ const ReleaseDetailsPage = () => {
762
978
  }
763
979
  );
764
980
  };
765
- const ReleasesLayout = ({
766
- isLoading,
767
- totalReleases,
768
- onClickAddRelease,
769
- children
770
- }) => {
771
- const { formatMessage } = useIntl();
772
- return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
773
- /* @__PURE__ */ jsx(
774
- HeaderLayout,
775
- {
776
- title: formatMessage({
777
- id: "content-releases.pages.Releases.title",
778
- defaultMessage: "Releases"
779
- }),
780
- subtitle: !isLoading && formatMessage(
781
- {
782
- id: "content-releases.pages.Releases.header-subtitle",
783
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
784
- },
785
- { number: totalReleases }
786
- ),
787
- primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(Plus, {}), onClick: onClickAddRelease, children: formatMessage({
788
- id: "content-releases.header.actions.add-release",
789
- defaultMessage: "New release"
790
- }) }) })
791
- }
792
- ),
793
- children
794
- ] });
795
- };
796
981
  const LinkCard = styled(Link$2)`
797
982
  display: block;
798
983
  `;
@@ -844,8 +1029,22 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
844
1029
  }
845
1030
  ) }) }, id)) });
846
1031
  };
1032
+ const StyledAlert = styled(Alert)`
1033
+ button {
1034
+ display: none;
1035
+ }
1036
+ p + div {
1037
+ margin-left: auto;
1038
+ }
1039
+ `;
847
1040
  const INITIAL_FORM_VALUES = {
848
- name: ""
1041
+ name: "",
1042
+ date: null,
1043
+ time: "",
1044
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
1045
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
1046
+ scheduledAt: null,
1047
+ timezone: null
849
1048
  };
850
1049
  const ReleasesPage = () => {
851
1050
  const tabRef = React.useRef(null);
@@ -858,6 +1057,9 @@ const ReleasesPage = () => {
858
1057
  const [{ query }, setQuery] = useQueryParams();
859
1058
  const response = useGetReleasesQuery(query);
860
1059
  const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
1060
+ const { getFeature } = useLicenseLimits();
1061
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
1062
+ const { trackUsage } = useTracking();
861
1063
  const { isLoading, isSuccess, isError } = response;
862
1064
  const activeTab = response?.currentData?.meta?.activeTab || "pending";
863
1065
  const activeTabIndex = ["pending", "done"].indexOf(activeTab);
@@ -886,9 +1088,10 @@ const ReleasesPage = () => {
886
1088
  setReleaseModalShown((prev) => !prev);
887
1089
  };
888
1090
  if (isLoading) {
889
- return /* @__PURE__ */ jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) }) });
1091
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
890
1092
  }
891
1093
  const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
1094
+ const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
892
1095
  const handleTabChange = (index) => {
893
1096
  setQuery({
894
1097
  ...query,
@@ -901,9 +1104,11 @@ const ReleasesPage = () => {
901
1104
  }
902
1105
  });
903
1106
  };
904
- const handleAddRelease = async (values) => {
1107
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
905
1108
  const response2 = await createRelease({
906
- name: values.name
1109
+ name,
1110
+ scheduledAt,
1111
+ timezone
907
1112
  });
908
1113
  if ("data" in response2) {
909
1114
  toggleNotification({
@@ -913,6 +1118,7 @@ const ReleasesPage = () => {
913
1118
  defaultMessage: "Release created."
914
1119
  })
915
1120
  });
1121
+ trackUsage("didCreateRelease");
916
1122
  push(`/plugins/content-releases/${response2.data.data.id}`);
917
1123
  } else if (isAxiosError(response2.error)) {
918
1124
  toggleNotification({
@@ -926,8 +1132,60 @@ const ReleasesPage = () => {
926
1132
  });
927
1133
  }
928
1134
  };
929
- return /* @__PURE__ */ jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
1135
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
1136
+ /* @__PURE__ */ jsx(
1137
+ HeaderLayout,
1138
+ {
1139
+ title: formatMessage({
1140
+ id: "content-releases.pages.Releases.title",
1141
+ defaultMessage: "Releases"
1142
+ }),
1143
+ subtitle: formatMessage(
1144
+ {
1145
+ id: "content-releases.pages.Releases.header-subtitle",
1146
+ defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
1147
+ },
1148
+ { number: totalReleases }
1149
+ ),
1150
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
1151
+ Button,
1152
+ {
1153
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
1154
+ onClick: toggleAddReleaseModal,
1155
+ disabled: hasReachedMaximumPendingReleases,
1156
+ children: formatMessage({
1157
+ id: "content-releases.header.actions.add-release",
1158
+ defaultMessage: "New release"
1159
+ })
1160
+ }
1161
+ ) })
1162
+ }
1163
+ ),
930
1164
  /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
1165
+ activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
1166
+ StyledAlert,
1167
+ {
1168
+ marginBottom: 6,
1169
+ action: /* @__PURE__ */ jsx(Link$2, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
1170
+ id: "content-releases.pages.Releases.max-limit-reached.action",
1171
+ defaultMessage: "Explore plans"
1172
+ }) }),
1173
+ title: formatMessage(
1174
+ {
1175
+ id: "content-releases.pages.Releases.max-limit-reached.title",
1176
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
1177
+ },
1178
+ { number: maximumReleases }
1179
+ ),
1180
+ onClose: () => {
1181
+ },
1182
+ closeLabel: "",
1183
+ children: formatMessage({
1184
+ id: "content-releases.pages.Releases.max-limit-reached.message",
1185
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
1186
+ })
1187
+ }
1188
+ ),
931
1189
  /* @__PURE__ */ jsxs(
932
1190
  TabGroup,
933
1191
  {
@@ -1012,4 +1270,4 @@ const App = () => {
1012
1270
  export {
1013
1271
  App
1014
1272
  };
1015
- //# sourceMappingURL=App-L1jSxCiL.mjs.map
1273
+ //# sourceMappingURL=App-ise7GunC.mjs.map