@strapi/content-releases 0.0.0-next.44f19b3d2f81d983c343a219aa2781ee0deecb5f → 0.0.0-next.488120b06b467623f033f6175193eed2ec912bfd

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-5PsAyVt2.js → App-OP70yd5M.js} +689 -435
  2. package/dist/_chunks/App-OP70yd5M.js.map +1 -0
  3. package/dist/_chunks/{App-3ycH2d3s.mjs → App-x6Tjj3HN.mjs} +697 -444
  4. package/dist/_chunks/App-x6Tjj3HN.mjs.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  9. package/dist/_chunks/{en-2DuPv5k0.js → en-3SGjiVyR.js} +26 -7
  10. package/dist/_chunks/en-3SGjiVyR.js.map +1 -0
  11. package/dist/_chunks/{en-SOqjCdyh.mjs → en-bpHsnU0n.mjs} +26 -7
  12. package/dist/_chunks/en-bpHsnU0n.mjs.map +1 -0
  13. package/dist/_chunks/{index-4gUWuCQV.mjs → index-1ejXLtzt.mjs} +331 -37
  14. package/dist/_chunks/index-1ejXLtzt.mjs.map +1 -0
  15. package/dist/_chunks/{index-D57Rztnc.js → index-ydocdaZ0.js} +319 -25
  16. package/dist/_chunks/index-ydocdaZ0.js.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +2 -2
  19. package/dist/server/index.js +962 -408
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +961 -408
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +15 -12
  24. package/dist/_chunks/App-3ycH2d3s.mjs.map +0 -1
  25. package/dist/_chunks/App-5PsAyVt2.js.map +0 -1
  26. package/dist/_chunks/en-2DuPv5k0.js.map +0 -1
  27. package/dist/_chunks/en-SOqjCdyh.mjs.map +0 -1
  28. package/dist/_chunks/index-4gUWuCQV.mjs.map +0 -1
  29. package/dist/_chunks/index-D57Rztnc.js.map +0 -1
@@ -1,22 +1,42 @@
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-4gUWuCQV.mjs";
2
+ import { RelativeTime as RelativeTime$1, 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-1ejXLtzt.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, Popover, SingleSelect, SingleSelectOption, Badge, Tr, Td, Icon, Tooltip, Alert, 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";
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 { utcToZonedTime, zonedTimeToUtc } 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 } 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,22 @@ const ReleaseModal = ({
27
47
  const { formatMessage } = useIntl();
28
48
  const { pathname } = useLocation();
29
49
  const isCreatingRelease = pathname === `/plugins/${pluginId}`;
50
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
51
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
52
+ );
53
+ const getScheduledTimestamp = (values) => {
54
+ const { date, time, timezone } = values;
55
+ if (!date || !time || !timezone)
56
+ return null;
57
+ const timezoneWithoutOffset = timezone.split("&")[1];
58
+ return zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
59
+ };
60
+ const getTimezoneWithOffset = () => {
61
+ const currentTimezone = timezoneList.find(
62
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
63
+ );
64
+ return currentTimezone?.value || systemTimezone.value;
65
+ };
30
66
  return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
31
67
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
32
68
  {
@@ -38,45 +74,130 @@ const ReleaseModal = ({
38
74
  /* @__PURE__ */ jsx(
39
75
  Formik,
40
76
  {
41
- validateOnChange: false,
42
- onSubmit: handleSubmit,
43
- initialValues,
77
+ onSubmit: (values) => {
78
+ handleSubmit({
79
+ ...values,
80
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
81
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
82
+ });
83
+ },
84
+ initialValues: {
85
+ ...initialValues,
86
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
87
+ },
44
88
  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
- ) }),
89
+ validateOnChange: false,
90
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxs(Form, { children: [
91
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
92
+ /* @__PURE__ */ jsx(
93
+ TextInput,
94
+ {
95
+ label: formatMessage({
96
+ id: "content-releases.modal.form.input.label.release-name",
97
+ defaultMessage: "Name"
98
+ }),
99
+ name: "name",
100
+ value: values.name,
101
+ error: errors.name,
102
+ onChange: handleChange,
103
+ required: true
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsx(Box, { width: "max-content", children: /* @__PURE__ */ jsx(
107
+ Checkbox,
108
+ {
109
+ name: "isScheduled",
110
+ value: values.isScheduled,
111
+ onChange: (event) => {
112
+ setFieldValue("isScheduled", event.target.checked);
113
+ if (!event.target.checked) {
114
+ setFieldValue("date", null);
115
+ setFieldValue("time", "");
116
+ setFieldValue("timezone", null);
117
+ } else {
118
+ setFieldValue("date", initialValues.date);
119
+ setFieldValue("time", initialValues.time);
120
+ setFieldValue("timezone", initialValues.timezone ?? systemTimezone?.value);
121
+ }
122
+ },
123
+ children: /* @__PURE__ */ jsx(
124
+ Typography,
125
+ {
126
+ textColor: values.isScheduled ? "primary600" : "neutral800",
127
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
128
+ children: formatMessage({
129
+ id: "modal.form.input.label.schedule-release",
130
+ defaultMessage: "Schedule release"
131
+ })
132
+ }
133
+ )
134
+ }
135
+ ) }),
136
+ values.isScheduled && /* @__PURE__ */ jsxs(Fragment, { children: [
137
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "start", children: [
138
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
139
+ DatePicker,
140
+ {
141
+ label: formatMessage({
142
+ id: "content-releases.modal.form.input.label.date",
143
+ defaultMessage: "Date"
144
+ }),
145
+ name: "date",
146
+ error: errors.date,
147
+ onChange: (date) => {
148
+ const isoFormatDate = date ? formatISO(date, { representation: "date" }) : null;
149
+ setFieldValue("date", isoFormatDate);
150
+ },
151
+ clearLabel: formatMessage({
152
+ id: "content-releases.modal.form.input.clearLabel",
153
+ defaultMessage: "Clear"
154
+ }),
155
+ onClear: () => {
156
+ setFieldValue("date", null);
157
+ },
158
+ selectedDate: values.date || void 0,
159
+ required: true,
160
+ minDate: utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
161
+ }
162
+ ) }),
163
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
164
+ TimePicker,
165
+ {
166
+ label: formatMessage({
167
+ id: "content-releases.modal.form.input.label.time",
168
+ defaultMessage: "Time"
169
+ }),
170
+ name: "time",
171
+ error: errors.time,
172
+ onChange: (time) => {
173
+ setFieldValue("time", time);
174
+ },
175
+ clearLabel: formatMessage({
176
+ id: "content-releases.modal.form.input.clearLabel",
177
+ defaultMessage: "Clear"
178
+ }),
179
+ onClear: () => {
180
+ setFieldValue("time", "");
181
+ },
182
+ value: values.time || void 0,
183
+ required: true
184
+ }
185
+ ) })
186
+ ] }),
187
+ /* @__PURE__ */ jsx(TimezoneComponent, { timezoneOptions: timezoneList })
188
+ ] })
189
+ ] }) }),
60
190
  /* @__PURE__ */ jsx(
61
191
  ModalFooter,
62
192
  {
63
193
  startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
64
- endActions: /* @__PURE__ */ jsx(
65
- Button,
194
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
66
195
  {
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
- )
196
+ id: "content-releases.modal.form.button.submit",
197
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
198
+ },
199
+ { isCreatingRelease }
200
+ ) })
80
201
  }
81
202
  )
82
203
  ] })
@@ -84,33 +205,399 @@ const ReleaseModal = ({
84
205
  )
85
206
  ] });
86
207
  };
208
+ const getTimezones = (selectedDate) => {
209
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
210
+ const utcOffset = getTimezoneOffset(timezone, selectedDate);
211
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
212
+ });
213
+ const systemTimezone = timezoneList.find(
214
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
215
+ );
216
+ return { timezoneList, systemTimezone };
217
+ };
218
+ const TimezoneComponent = ({ timezoneOptions }) => {
219
+ const { values, errors, setFieldValue } = useFormikContext();
220
+ const { formatMessage } = useIntl();
221
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
222
+ React.useEffect(() => {
223
+ if (values.date) {
224
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
225
+ setTimezoneList(timezoneList2);
226
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
227
+ if (updatedTimezone) {
228
+ setFieldValue("timezone", updatedTimezone.value);
229
+ }
230
+ }
231
+ }, [setFieldValue, values.date, values.timezone]);
232
+ return /* @__PURE__ */ jsx(
233
+ Combobox,
234
+ {
235
+ label: formatMessage({
236
+ id: "content-releases.modal.form.input.label.timezone",
237
+ defaultMessage: "Timezone"
238
+ }),
239
+ autocomplete: { type: "list", filter: "contains" },
240
+ name: "timezone",
241
+ value: values.timezone || void 0,
242
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
243
+ onChange: (timezone) => {
244
+ setFieldValue("timezone", timezone);
245
+ },
246
+ onTextValueChange: (timezone) => {
247
+ setFieldValue("timezone", timezone);
248
+ },
249
+ onClear: () => {
250
+ setFieldValue("timezone", "");
251
+ },
252
+ error: errors.timezone,
253
+ required: true,
254
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
255
+ }
256
+ );
257
+ };
258
+ const LinkCard = styled(Link)`
259
+ display: block;
260
+ `;
261
+ const RelativeTime = styled(RelativeTime$1)`
262
+ display: inline-block;
263
+ &::first-letter {
264
+ text-transform: uppercase;
265
+ }
266
+ `;
267
+ const getBadgeProps = (status) => {
268
+ let color;
269
+ switch (status) {
270
+ case "ready":
271
+ color = "success";
272
+ break;
273
+ case "blocked":
274
+ color = "warning";
275
+ break;
276
+ case "failed":
277
+ color = "danger";
278
+ break;
279
+ case "done":
280
+ color = "primary";
281
+ break;
282
+ case "empty":
283
+ default:
284
+ color = "neutral";
285
+ }
286
+ return {
287
+ textColor: `${color}600`,
288
+ backgroundColor: `${color}100`,
289
+ borderColor: `${color}200`
290
+ };
291
+ };
292
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
293
+ const { formatMessage } = useIntl();
294
+ if (isError) {
295
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
296
+ }
297
+ if (releases?.length === 0) {
298
+ return /* @__PURE__ */ jsx(
299
+ EmptyStateLayout,
300
+ {
301
+ content: formatMessage(
302
+ {
303
+ id: "content-releases.page.Releases.tab.emptyEntries",
304
+ defaultMessage: "No releases"
305
+ },
306
+ {
307
+ target: sectionTitle
308
+ }
309
+ ),
310
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
311
+ }
312
+ );
313
+ }
314
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, scheduledAt, status }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
315
+ Flex,
316
+ {
317
+ direction: "column",
318
+ justifyContent: "space-between",
319
+ padding: 4,
320
+ hasRadius: true,
321
+ background: "neutral0",
322
+ shadow: "tableShadow",
323
+ height: "100%",
324
+ width: "100%",
325
+ alignItems: "start",
326
+ gap: 4,
327
+ children: [
328
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
329
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
330
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
331
+ id: "content-releases.pages.Releases.not-scheduled",
332
+ defaultMessage: "Not scheduled"
333
+ }) })
334
+ ] }),
335
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
336
+ ]
337
+ }
338
+ ) }) }, id)) });
339
+ };
340
+ const StyledAlert = styled(Alert)`
341
+ button {
342
+ display: none;
343
+ }
344
+ p + div {
345
+ margin-left: auto;
346
+ }
347
+ `;
348
+ const INITIAL_FORM_VALUES = {
349
+ name: "",
350
+ date: null,
351
+ time: "",
352
+ isScheduled: true,
353
+ scheduledAt: null,
354
+ timezone: null
355
+ };
356
+ const ReleasesPage = () => {
357
+ const tabRef = React.useRef(null);
358
+ const location = useLocation();
359
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
360
+ const toggleNotification = useNotification();
361
+ const { formatMessage } = useIntl();
362
+ const { push, replace } = useHistory();
363
+ const { formatAPIError } = useAPIErrorHandler();
364
+ const [{ query }, setQuery] = useQueryParams();
365
+ const response = useGetReleasesQuery(query);
366
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
367
+ const { getFeature } = useLicenseLimits();
368
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
369
+ const { trackUsage } = useTracking();
370
+ const { isLoading, isSuccess, isError } = response;
371
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
372
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
373
+ React.useEffect(() => {
374
+ if (location?.state?.errors) {
375
+ toggleNotification({
376
+ type: "warning",
377
+ title: formatMessage({
378
+ id: "content-releases.pages.Releases.notification.error.title",
379
+ defaultMessage: "Your request could not be processed."
380
+ }),
381
+ message: formatMessage({
382
+ id: "content-releases.pages.Releases.notification.error.message",
383
+ defaultMessage: "Please try again or open another release."
384
+ })
385
+ });
386
+ replace({ state: null });
387
+ }
388
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
389
+ React.useEffect(() => {
390
+ if (tabRef.current) {
391
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
392
+ }
393
+ }, [activeTabIndex]);
394
+ const toggleAddReleaseModal = () => {
395
+ setReleaseModalShown((prev) => !prev);
396
+ };
397
+ if (isLoading) {
398
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
399
+ }
400
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
401
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
402
+ const handleTabChange = (index) => {
403
+ setQuery({
404
+ ...query,
405
+ page: 1,
406
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
407
+ filters: {
408
+ releasedAt: {
409
+ $notNull: index === 0 ? false : true
410
+ }
411
+ }
412
+ });
413
+ };
414
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
415
+ const response2 = await createRelease({
416
+ name,
417
+ scheduledAt,
418
+ timezone
419
+ });
420
+ if ("data" in response2) {
421
+ toggleNotification({
422
+ type: "success",
423
+ message: formatMessage({
424
+ id: "content-releases.modal.release-created-notification-success",
425
+ defaultMessage: "Release created."
426
+ })
427
+ });
428
+ trackUsage("didCreateRelease");
429
+ push(`/plugins/content-releases/${response2.data.data.id}`);
430
+ } else if (isAxiosError(response2.error)) {
431
+ toggleNotification({
432
+ type: "warning",
433
+ message: formatAPIError(response2.error)
434
+ });
435
+ } else {
436
+ toggleNotification({
437
+ type: "warning",
438
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
439
+ });
440
+ }
441
+ };
442
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
443
+ /* @__PURE__ */ jsx(
444
+ HeaderLayout,
445
+ {
446
+ title: formatMessage({
447
+ id: "content-releases.pages.Releases.title",
448
+ defaultMessage: "Releases"
449
+ }),
450
+ subtitle: formatMessage({
451
+ id: "content-releases.pages.Releases.header-subtitle",
452
+ defaultMessage: "Create and manage content updates"
453
+ }),
454
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
455
+ Button,
456
+ {
457
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
458
+ onClick: toggleAddReleaseModal,
459
+ disabled: hasReachedMaximumPendingReleases,
460
+ children: formatMessage({
461
+ id: "content-releases.header.actions.add-release",
462
+ defaultMessage: "New release"
463
+ })
464
+ }
465
+ ) })
466
+ }
467
+ ),
468
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
469
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
470
+ StyledAlert,
471
+ {
472
+ marginBottom: 6,
473
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
474
+ id: "content-releases.pages.Releases.max-limit-reached.action",
475
+ defaultMessage: "Explore plans"
476
+ }) }),
477
+ title: formatMessage(
478
+ {
479
+ id: "content-releases.pages.Releases.max-limit-reached.title",
480
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
481
+ },
482
+ { number: maximumReleases }
483
+ ),
484
+ onClose: () => {
485
+ },
486
+ closeLabel: "",
487
+ children: formatMessage({
488
+ id: "content-releases.pages.Releases.max-limit-reached.message",
489
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
490
+ })
491
+ }
492
+ ),
493
+ /* @__PURE__ */ jsxs(
494
+ TabGroup,
495
+ {
496
+ label: formatMessage({
497
+ id: "content-releases.pages.Releases.tab-group.label",
498
+ defaultMessage: "Releases list"
499
+ }),
500
+ variant: "simple",
501
+ initialSelectedTabIndex: activeTabIndex,
502
+ onTabChange: handleTabChange,
503
+ ref: tabRef,
504
+ children: [
505
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
506
+ /* @__PURE__ */ jsxs(Tabs, { children: [
507
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
508
+ {
509
+ id: "content-releases.pages.Releases.tab.pending",
510
+ defaultMessage: "Pending ({count})"
511
+ },
512
+ {
513
+ count: totalPendingReleases
514
+ }
515
+ ) }),
516
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
517
+ id: "content-releases.pages.Releases.tab.done",
518
+ defaultMessage: "Done"
519
+ }) })
520
+ ] }),
521
+ /* @__PURE__ */ jsx(Divider, {})
522
+ ] }),
523
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
524
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
525
+ ReleasesGrid,
526
+ {
527
+ sectionTitle: "pending",
528
+ releases: response?.currentData?.data,
529
+ isError
530
+ }
531
+ ) }),
532
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
533
+ ReleasesGrid,
534
+ {
535
+ sectionTitle: "done",
536
+ releases: response?.currentData?.data,
537
+ isError
538
+ }
539
+ ) })
540
+ ] })
541
+ ]
542
+ }
543
+ ),
544
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
545
+ /* @__PURE__ */ jsx(
546
+ PageSizeURLQuery,
547
+ {
548
+ options: ["8", "16", "32", "64"],
549
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
550
+ }
551
+ ),
552
+ /* @__PURE__ */ jsx(
553
+ PaginationURLQuery,
554
+ {
555
+ pagination: {
556
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
557
+ }
558
+ }
559
+ )
560
+ ] }) : null
561
+ ] }) }),
562
+ releaseModalShown && /* @__PURE__ */ jsx(
563
+ ReleaseModal,
564
+ {
565
+ handleClose: toggleAddReleaseModal,
566
+ handleSubmit: handleAddRelease,
567
+ isLoading: isSubmittingForm,
568
+ initialValues: INITIAL_FORM_VALUES
569
+ }
570
+ )
571
+ ] });
572
+ };
87
573
  const ReleaseInfoWrapper = styled(Flex)`
88
574
  align-self: stretch;
89
575
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
576
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
577
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
578
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
579
+ const StyledMenuItem = styled(Menu.Item)`
97
580
  svg path {
98
581
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
582
  }
100
583
  span {
101
584
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
102
585
  }
586
+
587
+ &:hover {
588
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
589
+ }
103
590
  `;
104
591
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
592
+ width: ${({ theme }) => theme.spaces[3]};
593
+ height: ${({ theme }) => theme.spaces[3]};
107
594
  path {
108
595
  fill: ${({ theme }) => theme.colors.neutral600};
109
596
  }
110
597
  `;
111
598
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
599
+ width: ${({ theme }) => theme.spaces[3]};
600
+ height: ${({ theme }) => theme.spaces[3]};
114
601
  path {
115
602
  fill: ${({ theme }) => theme.colors.danger600};
116
603
  }
@@ -118,24 +605,6 @@ const TrashIcon = styled(Trash)`
118
605
  const TypographyMaxWidth = styled(Typography)`
119
606
  max-width: 300px;
120
607
  `;
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
608
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
609
  const { formatMessage } = useIntl();
141
610
  const { validate } = unstable_useDocument();
@@ -184,10 +653,8 @@ const ReleaseDetailsLayout = ({
184
653
  toggleWarningSubmit,
185
654
  children
186
655
  }) => {
187
- const { formatMessage } = useIntl();
656
+ const { formatMessage, formatDate, formatTime } = useIntl();
188
657
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
658
  const {
192
659
  data,
193
660
  isLoading: isLoadingDetails,
@@ -203,13 +670,6 @@ const ReleaseDetailsLayout = ({
203
670
  const dispatch = useTypedDispatch();
204
671
  const { trackUsage } = useTracking();
205
672
  const release = data?.data;
206
- const handleTogglePopover = () => {
207
- setIsPopoverVisible((prev) => !prev);
208
- };
209
- const openReleaseModal = () => {
210
- toggleEditReleaseModal();
211
- handleTogglePopover();
212
- };
213
673
  const handlePublishRelease = async () => {
214
674
  const response = await publishRelease({ id: releaseId });
215
675
  if ("data" in response) {
@@ -238,12 +698,25 @@ const ReleaseDetailsLayout = ({
238
698
  });
239
699
  }
240
700
  };
241
- const openWarningConfirmDialog = () => {
242
- toggleWarningSubmit();
243
- handleTogglePopover();
244
- };
245
701
  const handleRefresh = () => {
246
- dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
702
+ dispatch(
703
+ releaseApi.util.invalidateTags([
704
+ { type: "ReleaseAction", id: "LIST" },
705
+ { type: "Release", id: releaseId }
706
+ ])
707
+ );
708
+ };
709
+ const getCreatedByUser = () => {
710
+ if (!release?.createdBy) {
711
+ return null;
712
+ }
713
+ if (release.createdBy.username) {
714
+ return release.createdBy.username;
715
+ }
716
+ if (release.createdBy.firstname) {
717
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
718
+ }
719
+ return release.createdBy.email;
247
720
  };
248
721
  if (isLoadingDetails) {
249
722
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
@@ -266,90 +739,127 @@ const ReleaseDetailsLayout = ({
266
739
  );
267
740
  }
268
741
  const totalEntries = release.actions.meta.count || 0;
269
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
742
+ const hasCreatedByUser = Boolean(getCreatedByUser());
743
+ const isScheduled = release.scheduledAt && release.timezone;
744
+ const numberOfEntriesText = formatMessage(
745
+ {
746
+ id: "content-releases.pages.Details.header-subtitle",
747
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
748
+ },
749
+ { number: totalEntries }
750
+ );
751
+ const scheduledText = isScheduled ? formatMessage(
752
+ {
753
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
754
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
755
+ },
756
+ {
757
+ date: formatDate(new Date(release.scheduledAt), {
758
+ weekday: "long",
759
+ day: "numeric",
760
+ month: "long",
761
+ year: "numeric",
762
+ timeZone: release.timezone
763
+ }),
764
+ time: formatTime(new Date(release.scheduledAt), {
765
+ timeZone: release.timezone,
766
+ hourCycle: "h23"
767
+ }),
768
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
769
+ }
770
+ ) : "";
270
771
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
271
772
  /* @__PURE__ */ jsx(
272
773
  HeaderLayout,
273
774
  {
274
775
  title: release.name,
275
- subtitle: formatMessage(
276
- {
277
- id: "content-releases.pages.Details.header-subtitle",
278
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
279
- },
280
- { number: totalEntries }
281
- ),
282
- navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
776
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
777
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
778
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
779
+ ] }),
780
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
283
781
  id: "global.back",
284
782
  defaultMessage: "Back"
285
783
  }) }),
286
784
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
287
- /* @__PURE__ */ jsx(
288
- IconButton,
289
- {
290
- label: formatMessage({
291
- id: "content-releases.header.actions.open-release-actions",
292
- defaultMessage: "Release actions"
293
- }),
294
- ref: moreButtonRef,
295
- onClick: handleTogglePopover,
296
- children: /* @__PURE__ */ jsx(More, {})
297
- }
298
- ),
299
- isPopoverVisible && /* @__PURE__ */ jsxs(
300
- Popover,
301
- {
302
- source: moreButtonRef,
303
- placement: "bottom-end",
304
- onDismiss: handleTogglePopover,
305
- spacing: 4,
306
- minWidth: "242px",
307
- children: [
308
- /* @__PURE__ */ jsxs(Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
309
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
310
- /* @__PURE__ */ jsx(PencilIcon, {}),
311
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
312
- id: "content-releases.header.actions.edit",
313
- defaultMessage: "Edit"
314
- }) })
315
- ] }),
316
- /* @__PURE__ */ jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
317
- /* @__PURE__ */ jsx(TrashIcon, {}),
318
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
319
- id: "content-releases.header.actions.delete",
320
- defaultMessage: "Delete"
321
- }) })
322
- ] })
323
- ] }),
324
- /* @__PURE__ */ jsxs(
325
- ReleaseInfoWrapper,
326
- {
327
- direction: "column",
328
- justifyContent: "center",
329
- alignItems: "flex-start",
330
- gap: 1,
331
- padding: 5,
332
- children: [
333
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
334
- id: "content-releases.header.actions.created",
335
- defaultMessage: "Created"
336
- }) }),
337
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
338
- /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
339
- formatMessage(
340
- {
341
- id: "content-releases.header.actions.created.description",
342
- defaultMessage: " by {createdBy}"
343
- },
344
- { createdBy }
345
- )
346
- ] })
347
- ]
348
- }
349
- )
350
- ]
351
- }
352
- ),
785
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
786
+ /* @__PURE__ */ jsx(
787
+ Menu.Trigger,
788
+ {
789
+ as: IconButton,
790
+ paddingLeft: 2,
791
+ paddingRight: 2,
792
+ "aria-label": formatMessage({
793
+ id: "content-releases.header.actions.open-release-actions",
794
+ defaultMessage: "Release edit and delete menu"
795
+ }),
796
+ icon: /* @__PURE__ */ jsx(More, {}),
797
+ variant: "tertiary"
798
+ }
799
+ ),
800
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
801
+ /* @__PURE__ */ jsxs(
802
+ Flex,
803
+ {
804
+ alignItems: "center",
805
+ justifyContent: "center",
806
+ direction: "column",
807
+ padding: 1,
808
+ width: "100%",
809
+ children: [
810
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
811
+ /* @__PURE__ */ jsx(PencilIcon, {}),
812
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
813
+ id: "content-releases.header.actions.edit",
814
+ defaultMessage: "Edit"
815
+ }) })
816
+ ] }) }),
817
+ /* @__PURE__ */ jsx(
818
+ StyledMenuItem,
819
+ {
820
+ disabled: !canDelete,
821
+ onSelect: toggleWarningSubmit,
822
+ variant: "danger",
823
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
824
+ /* @__PURE__ */ jsx(TrashIcon, {}),
825
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
826
+ id: "content-releases.header.actions.delete",
827
+ defaultMessage: "Delete"
828
+ }) })
829
+ ] })
830
+ }
831
+ )
832
+ ]
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsxs(
836
+ ReleaseInfoWrapper,
837
+ {
838
+ direction: "column",
839
+ justifyContent: "center",
840
+ alignItems: "flex-start",
841
+ gap: 1,
842
+ padding: 5,
843
+ children: [
844
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
845
+ id: "content-releases.header.actions.created",
846
+ defaultMessage: "Created"
847
+ }) }),
848
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
849
+ /* @__PURE__ */ jsx(RelativeTime$1, { timestamp: new Date(release.createdAt) }),
850
+ formatMessage(
851
+ {
852
+ id: "content-releases.header.actions.created.description",
853
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
854
+ },
855
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
856
+ )
857
+ ] })
858
+ ]
859
+ }
860
+ )
861
+ ] })
862
+ ] }),
353
863
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
354
864
  id: "content-releases.header.actions.refresh",
355
865
  defaultMessage: "Refresh"
@@ -405,6 +915,9 @@ const ReleaseDetailsBody = () => {
405
915
  isError: isReleaseError,
406
916
  error: releaseError
407
917
  } = useGetReleaseQuery({ id: releaseId });
918
+ const {
919
+ allowedActions: { canUpdate }
920
+ } = useRBAC(PERMISSIONS);
408
921
  const release = releaseData?.data;
409
922
  const selectedGroupBy = query?.groupBy || "contentType";
410
923
  const {
@@ -491,7 +1004,7 @@ const ReleaseDetailsBody = () => {
491
1004
  action: /* @__PURE__ */ jsx(
492
1005
  LinkButton,
493
1006
  {
494
- as: Link$1,
1007
+ as: Link$2,
495
1008
  to: {
496
1009
  pathname: "/content-manager"
497
1010
  },
@@ -511,7 +1024,7 @@ const ReleaseDetailsBody = () => {
511
1024
  SingleSelect,
512
1025
  {
513
1026
  "aria-label": formatMessage({
514
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1027
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
515
1028
  defaultMessage: "Group by"
516
1029
  }),
517
1030
  customizeContent: (value) => formatMessage(
@@ -529,7 +1042,7 @@ const ReleaseDetailsBody = () => {
529
1042
  }
530
1043
  ) }),
531
1044
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxs(Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
532
- /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
1045
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
533
1046
  /* @__PURE__ */ jsx(
534
1047
  Table.Root,
535
1048
  {
@@ -618,7 +1131,8 @@ const ReleaseDetailsBody = () => {
618
1131
  {
619
1132
  selected: type,
620
1133
  handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
621
- name: `release-action-${id}-type`
1134
+ name: `release-action-${id}-type`,
1135
+ disabled: !canUpdate
622
1136
  }
623
1137
  ) }),
624
1138
  !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -673,7 +1187,7 @@ const ReleaseDetailsPage = () => {
673
1187
  const { releaseId } = useParams();
674
1188
  const toggleNotification = useNotification();
675
1189
  const { formatAPIError } = useAPIErrorHandler();
676
- const { push } = useHistory();
1190
+ const { replace } = useHistory();
677
1191
  const [releaseModalShown, setReleaseModalShown] = React.useState(false);
678
1192
  const [showWarningSubmit, setWarningSubmit] = React.useState(false);
679
1193
  const {
@@ -697,11 +1211,18 @@ const ReleaseDetailsPage = () => {
697
1211
  }
698
1212
  );
699
1213
  }
700
- const title = isSuccessDetails && data?.data?.name || "";
1214
+ const releaseData = isSuccessDetails && data?.data || null;
1215
+ const title = releaseData?.name || "";
1216
+ const timezone = releaseData?.timezone ?? null;
1217
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1218
+ const date = scheduledAt ? format(scheduledAt, "yyyy-MM-dd") : null;
1219
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
701
1220
  const handleEditRelease = async (values) => {
702
1221
  const response = await updateRelease({
703
1222
  id: releaseId,
704
- name: values.name
1223
+ name: values.name,
1224
+ scheduledAt: values.scheduledAt,
1225
+ timezone: values.timezone
705
1226
  });
706
1227
  if ("data" in response) {
707
1228
  toggleNotification({
@@ -711,6 +1232,7 @@ const ReleaseDetailsPage = () => {
711
1232
  defaultMessage: "Release updated."
712
1233
  })
713
1234
  });
1235
+ toggleEditReleaseModal();
714
1236
  } else if (isAxiosError(response.error)) {
715
1237
  toggleNotification({
716
1238
  type: "warning",
@@ -722,14 +1244,13 @@ const ReleaseDetailsPage = () => {
722
1244
  message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
723
1245
  });
724
1246
  }
725
- toggleEditReleaseModal();
726
1247
  };
727
1248
  const handleDeleteRelease = async () => {
728
1249
  const response = await deleteRelease({
729
1250
  id: releaseId
730
1251
  });
731
1252
  if ("data" in response) {
732
- push("/plugins/content-releases");
1253
+ replace("/plugins/content-releases");
733
1254
  } else if (isAxiosError(response.error)) {
734
1255
  toggleNotification({
735
1256
  type: "warning",
@@ -755,7 +1276,14 @@ const ReleaseDetailsPage = () => {
755
1276
  handleClose: toggleEditReleaseModal,
756
1277
  handleSubmit: handleEditRelease,
757
1278
  isLoading: isLoadingDetails || isSubmittingForm,
758
- initialValues: { name: title || "" }
1279
+ initialValues: {
1280
+ name: title || "",
1281
+ scheduledAt,
1282
+ date,
1283
+ time,
1284
+ isScheduled: Boolean(scheduledAt),
1285
+ timezone
1286
+ }
759
1287
  }
760
1288
  ),
761
1289
  /* @__PURE__ */ jsx(
@@ -775,281 +1303,6 @@ const ReleaseDetailsPage = () => {
775
1303
  }
776
1304
  );
777
1305
  };
778
- const LinkCard = styled(Link$2)`
779
- display: block;
780
- `;
781
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
782
- const { formatMessage } = useIntl();
783
- if (isError) {
784
- return /* @__PURE__ */ jsx(AnErrorOccurred, {});
785
- }
786
- if (releases?.length === 0) {
787
- return /* @__PURE__ */ jsx(
788
- EmptyStateLayout,
789
- {
790
- content: formatMessage(
791
- {
792
- id: "content-releases.page.Releases.tab.emptyEntries",
793
- defaultMessage: "No releases"
794
- },
795
- {
796
- target: sectionTitle
797
- }
798
- ),
799
- icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
800
- }
801
- );
802
- }
803
- 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(
804
- Flex,
805
- {
806
- direction: "column",
807
- justifyContent: "space-between",
808
- padding: 4,
809
- hasRadius: true,
810
- background: "neutral0",
811
- shadow: "tableShadow",
812
- height: "100%",
813
- width: "100%",
814
- alignItems: "start",
815
- gap: 2,
816
- children: [
817
- /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
818
- /* @__PURE__ */ jsx(Typography, { variant: "pi", children: formatMessage(
819
- {
820
- id: "content-releases.page.Releases.release-item.entries",
821
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
822
- },
823
- { number: actions.meta.count }
824
- ) })
825
- ]
826
- }
827
- ) }) }, id)) });
828
- };
829
- const StyledAlert = styled(Alert)`
830
- button {
831
- display: none;
832
- }
833
- p + div {
834
- margin-left: auto;
835
- }
836
- `;
837
- const INITIAL_FORM_VALUES = {
838
- name: ""
839
- };
840
- const ReleasesPage = () => {
841
- const tabRef = React.useRef(null);
842
- const location = useLocation();
843
- const [releaseModalShown, setReleaseModalShown] = React.useState(false);
844
- const toggleNotification = useNotification();
845
- const { formatMessage } = useIntl();
846
- const { push, replace } = useHistory();
847
- const { formatAPIError } = useAPIErrorHandler();
848
- const [{ query }, setQuery] = useQueryParams();
849
- const response = useGetReleasesQuery(query);
850
- const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
851
- const { getFeature } = useLicenseLimits();
852
- const { maximumReleases = 3 } = getFeature("cms-content-releases");
853
- const { trackUsage } = useTracking();
854
- const { isLoading, isSuccess, isError } = response;
855
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
856
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
857
- React.useEffect(() => {
858
- if (location?.state?.errors) {
859
- toggleNotification({
860
- type: "warning",
861
- title: formatMessage({
862
- id: "content-releases.pages.Releases.notification.error.title",
863
- defaultMessage: "Your request could not be processed."
864
- }),
865
- message: formatMessage({
866
- id: "content-releases.pages.Releases.notification.error.message",
867
- defaultMessage: "Please try again or open another release."
868
- })
869
- });
870
- replace({ state: null });
871
- }
872
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
873
- React.useEffect(() => {
874
- if (tabRef.current) {
875
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
876
- }
877
- }, [activeTabIndex]);
878
- const toggleAddReleaseModal = () => {
879
- setReleaseModalShown((prev) => !prev);
880
- };
881
- if (isLoading) {
882
- return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
883
- }
884
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
885
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
886
- const handleTabChange = (index) => {
887
- setQuery({
888
- ...query,
889
- page: 1,
890
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
891
- filters: {
892
- releasedAt: {
893
- $notNull: index === 0 ? false : true
894
- }
895
- }
896
- });
897
- };
898
- const handleAddRelease = async (values) => {
899
- const response2 = await createRelease({
900
- name: values.name
901
- });
902
- if ("data" in response2) {
903
- toggleNotification({
904
- type: "success",
905
- message: formatMessage({
906
- id: "content-releases.modal.release-created-notification-success",
907
- defaultMessage: "Release created."
908
- })
909
- });
910
- trackUsage("didCreateRelease");
911
- push(`/plugins/content-releases/${response2.data.data.id}`);
912
- } else if (isAxiosError(response2.error)) {
913
- toggleNotification({
914
- type: "warning",
915
- message: formatAPIError(response2.error)
916
- });
917
- } else {
918
- toggleNotification({
919
- type: "warning",
920
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
921
- });
922
- }
923
- };
924
- return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
925
- /* @__PURE__ */ jsx(
926
- HeaderLayout,
927
- {
928
- title: formatMessage({
929
- id: "content-releases.pages.Releases.title",
930
- defaultMessage: "Releases"
931
- }),
932
- subtitle: formatMessage(
933
- {
934
- id: "content-releases.pages.Releases.header-subtitle",
935
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
936
- },
937
- { number: totalReleases }
938
- ),
939
- primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
940
- Button,
941
- {
942
- startIcon: /* @__PURE__ */ jsx(Plus, {}),
943
- onClick: toggleAddReleaseModal,
944
- disabled: hasReachedMaximumPendingReleases,
945
- children: formatMessage({
946
- id: "content-releases.header.actions.add-release",
947
- defaultMessage: "New release"
948
- })
949
- }
950
- ) })
951
- }
952
- ),
953
- /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
954
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
955
- StyledAlert,
956
- {
957
- marginBottom: 6,
958
- action: /* @__PURE__ */ jsx(Link$2, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
959
- id: "content-releases.pages.Releases.max-limit-reached.action",
960
- defaultMessage: "Explore plans"
961
- }) }),
962
- title: formatMessage(
963
- {
964
- id: "content-releases.pages.Releases.max-limit-reached.title",
965
- defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
966
- },
967
- { number: maximumReleases }
968
- ),
969
- onClose: () => {
970
- },
971
- closeLabel: "",
972
- children: formatMessage({
973
- id: "content-releases.pages.Releases.max-limit-reached.message",
974
- defaultMessage: "Upgrade to manage an unlimited number of releases."
975
- })
976
- }
977
- ),
978
- /* @__PURE__ */ jsxs(
979
- TabGroup,
980
- {
981
- label: formatMessage({
982
- id: "content-releases.pages.Releases.tab-group.label",
983
- defaultMessage: "Releases list"
984
- }),
985
- variant: "simple",
986
- initialSelectedTabIndex: activeTabIndex,
987
- onTabChange: handleTabChange,
988
- ref: tabRef,
989
- children: [
990
- /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
991
- /* @__PURE__ */ jsxs(Tabs, { children: [
992
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
993
- id: "content-releases.pages.Releases.tab.pending",
994
- defaultMessage: "Pending"
995
- }) }),
996
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
997
- id: "content-releases.pages.Releases.tab.done",
998
- defaultMessage: "Done"
999
- }) })
1000
- ] }),
1001
- /* @__PURE__ */ jsx(Divider, {})
1002
- ] }),
1003
- /* @__PURE__ */ jsxs(TabPanels, { children: [
1004
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1005
- ReleasesGrid,
1006
- {
1007
- sectionTitle: "pending",
1008
- releases: response?.currentData?.data,
1009
- isError
1010
- }
1011
- ) }),
1012
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1013
- ReleasesGrid,
1014
- {
1015
- sectionTitle: "done",
1016
- releases: response?.currentData?.data,
1017
- isError
1018
- }
1019
- ) })
1020
- ] })
1021
- ]
1022
- }
1023
- ),
1024
- totalReleases > 0 && /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1025
- /* @__PURE__ */ jsx(
1026
- PageSizeURLQuery,
1027
- {
1028
- options: ["8", "16", "32", "64"],
1029
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1030
- }
1031
- ),
1032
- /* @__PURE__ */ jsx(
1033
- PaginationURLQuery,
1034
- {
1035
- pagination: {
1036
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1037
- }
1038
- }
1039
- )
1040
- ] })
1041
- ] }) }),
1042
- releaseModalShown && /* @__PURE__ */ jsx(
1043
- ReleaseModal,
1044
- {
1045
- handleClose: toggleAddReleaseModal,
1046
- handleSubmit: handleAddRelease,
1047
- isLoading: isSubmittingForm,
1048
- initialValues: INITIAL_FORM_VALUES
1049
- }
1050
- )
1051
- ] });
1052
- };
1053
1306
  const App = () => {
1054
1307
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
1055
1308
  /* @__PURE__ */ jsx(Route, { exact: true, path: `/plugins/${pluginId}`, component: ReleasesPage }),
@@ -1059,4 +1312,4 @@ const App = () => {
1059
1312
  export {
1060
1313
  App
1061
1314
  };
1062
- //# sourceMappingURL=App-3ycH2d3s.mjs.map
1315
+ //# sourceMappingURL=App-x6Tjj3HN.mjs.map