@strapi/content-releases 0.0.0-next.56199ab7a5f3320e0debcbe4a24fe0b8cd599e21 → 0.0.0-next.583e758623dc82206a4b2758d01dd5948b6e3f6a

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-8J9a-MD5.mjs → App-6ugQxqYE.mjs} +758 -452
  2. package/dist/_chunks/App-6ugQxqYE.mjs.map +1 -0
  3. package/dist/_chunks/{App-YFvVMqB8.js → App-P1kyM3gT.js} +750 -443
  4. package/dist/_chunks/App-P1kyM3gT.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-MyLPoISH.mjs → en-WuuhP6Bn.mjs} +21 -6
  10. package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
  11. package/dist/_chunks/{en-gYDqKYFd.js → en-gcJJ5htG.js} +21 -6
  12. package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
  13. package/dist/_chunks/{index-vxli-E-l.js → index-2xzbhaQP.js} +159 -33
  14. package/dist/_chunks/index-2xzbhaQP.js.map +1 -0
  15. package/dist/_chunks/{index-ej8MzbQl.mjs → index-_eBuegHN.mjs} +171 -45
  16. package/dist/_chunks/index-_eBuegHN.mjs.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 +928 -402
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +928 -403
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -11
  24. package/dist/_chunks/App-8J9a-MD5.mjs.map +0 -1
  25. package/dist/_chunks/App-YFvVMqB8.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-ej8MzbQl.mjs.map +0 -1
  29. package/dist/_chunks/index-vxli-E-l.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, 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-ej8MzbQl.mjs";
2
+ import { RelativeTime, useNotification, useAPIErrorHandler, useQueryParams, useTracking, LoadingIndicatorPage, CheckPermissions, PageSizeURLQuery, PaginationURLQuery, AnErrorOccurred, ConfirmDialog, useRBAC, NoContent, Table, CheckPagePermissions } from "@strapi/helper-plugin";
3
+ import { useLocation, useHistory, useParams, Redirect, Link as Link$2, Switch, Route } from "react-router-dom";
4
+ import { g as getTimezoneOffset, p as pluginId, u as useGetReleasesQuery, a as useCreateReleaseMutation, P as PERMISSIONS, i as isAxiosError, b as useGetReleaseQuery, c as useUpdateReleaseMutation, d as useDeleteReleaseMutation, e as usePublishReleaseMutation, f as useTypedDispatch, h as useGetReleaseActionsQuery, j as useUpdateReleaseActionMutation, R as ReleaseActionOptions, k as ReleaseActionMenu, r as releaseApi } from "./index-_eBuegHN.mjs";
5
5
  import * as React from "react";
6
- import { unstable_useDocument } 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";
9
- import { Pencil, Trash, ArrowLeft, More, CrossCircle, CheckCircle, Plus, EmptyDocuments } from "@strapi/icons";
6
+ import { useLicenseLimits, unstable_useDocument } from "@strapi/admin/strapi-admin";
7
+ import { ModalLayout, ModalHeader, Typography, ModalBody, Flex, TextInput, Box, Checkbox, DatePicker, TimePicker, ModalFooter, Button, Combobox, ComboboxOption, Alert, Main, HeaderLayout, ContentLayout, TabGroup, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem, Badge, Link as Link$1, IconButton, SingleSelect, SingleSelectOption, Tr, Td, Icon, Tooltip } from "@strapi/design-system";
8
+ import { Link, Menu, LinkButton } from "@strapi/design-system/v2";
9
+ import { Plus, EmptyDocuments, Pencil, Trash, ArrowLeft, More, CrossCircle, CheckCircle } from "@strapi/icons";
10
+ import format from "date-fns/format";
11
+ import { zonedTimeToUtc, utcToZonedTime } from "date-fns-tz";
10
12
  import { useIntl } from "react-intl";
11
13
  import styled from "styled-components";
12
- import { Formik, Form } from "formik";
14
+ import { formatISO, parse } from "date-fns";
15
+ import { Formik, Form, useFormikContext } from "formik";
13
16
  import * as yup from "yup";
14
17
  import "@reduxjs/toolkit/query";
15
18
  import "axios";
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(Box, { width: "max-content", children: /* @__PURE__ */ jsx(
110
+ Checkbox,
111
+ {
112
+ name: "isScheduled",
113
+ value: values.isScheduled,
114
+ onChange: (event) => {
115
+ setFieldValue("isScheduled", event.target.checked);
116
+ if (!event.target.checked) {
117
+ setFieldValue("date", null);
118
+ setFieldValue("time", "");
119
+ setFieldValue("timezone", null);
120
+ } else {
121
+ setFieldValue("date", initialValues.date);
122
+ setFieldValue("time", initialValues.time);
123
+ setFieldValue(
124
+ "timezone",
125
+ initialValues.timezone ?? systemTimezone?.value
126
+ );
127
+ }
128
+ },
129
+ children: /* @__PURE__ */ jsx(
130
+ Typography,
131
+ {
132
+ textColor: values.isScheduled ? "primary600" : "neutral800",
133
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
134
+ children: formatMessage({
135
+ id: "modal.form.input.label.schedule-release",
136
+ defaultMessage: "Schedule release"
137
+ })
138
+ }
139
+ )
140
+ }
141
+ ) }),
142
+ values.isScheduled && /* @__PURE__ */ jsxs(Fragment, { children: [
143
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "start", children: [
144
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
145
+ DatePicker,
146
+ {
147
+ label: formatMessage({
148
+ id: "content-releases.modal.form.input.label.date",
149
+ defaultMessage: "Date"
150
+ }),
151
+ name: "date",
152
+ error: errors.date,
153
+ onChange: (date) => {
154
+ const isoFormatDate = date ? formatISO(date, { representation: "date" }) : null;
155
+ setFieldValue("date", isoFormatDate);
156
+ },
157
+ clearLabel: formatMessage({
158
+ id: "content-releases.modal.form.input.clearLabel",
159
+ defaultMessage: "Clear"
160
+ }),
161
+ onClear: () => {
162
+ setFieldValue("date", null);
163
+ },
164
+ selectedDate: values.date || void 0,
165
+ required: true
166
+ }
167
+ ) }),
168
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
169
+ TimePicker,
170
+ {
171
+ label: formatMessage({
172
+ id: "content-releases.modal.form.input.label.time",
173
+ defaultMessage: "Time"
174
+ }),
175
+ name: "time",
176
+ error: errors.time,
177
+ onChange: (time) => {
178
+ setFieldValue("time", time);
179
+ },
180
+ clearLabel: formatMessage({
181
+ id: "content-releases.modal.form.input.clearLabel",
182
+ defaultMessage: "Clear"
183
+ }),
184
+ onClear: () => {
185
+ setFieldValue("time", "");
186
+ },
187
+ value: values.time || void 0,
188
+ required: true
189
+ }
190
+ ) })
191
+ ] }),
192
+ /* @__PURE__ */ jsx(TimezoneComponent, { timezoneOptions: timezoneList })
193
+ ] })
194
+ ] })
195
+ ] }) }),
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,33 +211,403 @@ const ReleaseModal = ({
84
211
  )
85
212
  ] });
86
213
  };
214
+ const getTimezones = (selectedDate) => {
215
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
216
+ const utcOffset = getTimezoneOffset(timezone, selectedDate);
217
+ return { offset: utcOffset, value: `${utcOffset}_${timezone}` };
218
+ });
219
+ const systemTimezone = timezoneList.find(
220
+ (timezone) => timezone.value.split("_")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
221
+ );
222
+ return { timezoneList, systemTimezone };
223
+ };
224
+ const TimezoneComponent = ({ timezoneOptions }) => {
225
+ const { values, errors, setFieldValue } = useFormikContext();
226
+ const { formatMessage } = useIntl();
227
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
228
+ React.useEffect(() => {
229
+ if (values.date) {
230
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
231
+ setTimezoneList(timezoneList2);
232
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("_")[1] === values.timezone.split("_")[1]);
233
+ if (updatedTimezone) {
234
+ setFieldValue("timezone", updatedTimezone.value);
235
+ }
236
+ }
237
+ }, [setFieldValue, values.date, values.timezone]);
238
+ return /* @__PURE__ */ jsx(
239
+ Combobox,
240
+ {
241
+ label: formatMessage({
242
+ id: "content-releases.modal.form.input.label.timezone",
243
+ defaultMessage: "Timezone"
244
+ }),
245
+ name: "timezone",
246
+ value: values.timezone || void 0,
247
+ textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
248
+ onChange: (timezone) => {
249
+ setFieldValue("timezone", timezone);
250
+ },
251
+ onTextValueChange: (timezone) => {
252
+ setFieldValue("timezone", timezone);
253
+ },
254
+ onClear: () => {
255
+ setFieldValue("timezone", "");
256
+ },
257
+ error: errors.timezone,
258
+ required: true,
259
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
260
+ }
261
+ );
262
+ };
263
+ const LinkCard = styled(Link)`
264
+ display: block;
265
+ `;
266
+ const CapitalizeRelativeTime = styled(RelativeTime)`
267
+ text-transform: capitalize;
268
+ `;
269
+ const getBadgeProps = (status) => {
270
+ let color;
271
+ switch (status) {
272
+ case "ready":
273
+ color = "success";
274
+ break;
275
+ case "blocked":
276
+ color = "warning";
277
+ break;
278
+ case "failed":
279
+ color = "danger";
280
+ break;
281
+ case "done":
282
+ color = "primary";
283
+ break;
284
+ case "empty":
285
+ default:
286
+ color = "neutral";
287
+ }
288
+ return {
289
+ textColor: `${color}600`,
290
+ backgroundColor: `${color}100`,
291
+ borderColor: `${color}200`
292
+ };
293
+ };
294
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
295
+ const { formatMessage } = useIntl();
296
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
297
+ if (isError) {
298
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
299
+ }
300
+ if (releases?.length === 0) {
301
+ return /* @__PURE__ */ jsx(
302
+ EmptyStateLayout,
303
+ {
304
+ content: formatMessage(
305
+ {
306
+ id: "content-releases.page.Releases.tab.emptyEntries",
307
+ defaultMessage: "No releases"
308
+ },
309
+ {
310
+ target: sectionTitle
311
+ }
312
+ ),
313
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
314
+ }
315
+ );
316
+ }
317
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt, status }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
318
+ Flex,
319
+ {
320
+ direction: "column",
321
+ justifyContent: "space-between",
322
+ padding: 4,
323
+ hasRadius: true,
324
+ background: "neutral0",
325
+ shadow: "tableShadow",
326
+ height: "100%",
327
+ width: "100%",
328
+ alignItems: "start",
329
+ gap: 4,
330
+ children: [
331
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
332
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
333
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
334
+ id: "content-releases.pages.Releases.not-scheduled",
335
+ defaultMessage: "Not scheduled"
336
+ }) : formatMessage(
337
+ {
338
+ id: "content-releases.page.Releases.release-item.entries",
339
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
340
+ },
341
+ { number: actions.meta.count }
342
+ ) })
343
+ ] }),
344
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
345
+ ]
346
+ }
347
+ ) }) }, id)) });
348
+ };
349
+ const StyledAlert = styled(Alert)`
350
+ button {
351
+ display: none;
352
+ }
353
+ p + div {
354
+ margin-left: auto;
355
+ }
356
+ `;
357
+ const INITIAL_FORM_VALUES = {
358
+ name: "",
359
+ date: null,
360
+ time: "",
361
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
362
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
363
+ scheduledAt: null,
364
+ timezone: null
365
+ };
366
+ const ReleasesPage = () => {
367
+ const tabRef = React.useRef(null);
368
+ const location = useLocation();
369
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
370
+ const toggleNotification = useNotification();
371
+ const { formatMessage } = useIntl();
372
+ const { push, replace } = useHistory();
373
+ const { formatAPIError } = useAPIErrorHandler();
374
+ const [{ query }, setQuery] = useQueryParams();
375
+ const response = useGetReleasesQuery(query);
376
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
377
+ const { getFeature } = useLicenseLimits();
378
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
379
+ const { trackUsage } = useTracking();
380
+ const { isLoading, isSuccess, isError } = response;
381
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
382
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
383
+ React.useEffect(() => {
384
+ if (location?.state?.errors) {
385
+ toggleNotification({
386
+ type: "warning",
387
+ title: formatMessage({
388
+ id: "content-releases.pages.Releases.notification.error.title",
389
+ defaultMessage: "Your request could not be processed."
390
+ }),
391
+ message: formatMessage({
392
+ id: "content-releases.pages.Releases.notification.error.message",
393
+ defaultMessage: "Please try again or open another release."
394
+ })
395
+ });
396
+ replace({ state: null });
397
+ }
398
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
399
+ React.useEffect(() => {
400
+ if (tabRef.current) {
401
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
402
+ }
403
+ }, [activeTabIndex]);
404
+ const toggleAddReleaseModal = () => {
405
+ setReleaseModalShown((prev) => !prev);
406
+ };
407
+ if (isLoading) {
408
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
409
+ }
410
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
411
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
412
+ const handleTabChange = (index) => {
413
+ setQuery({
414
+ ...query,
415
+ page: 1,
416
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
417
+ filters: {
418
+ releasedAt: {
419
+ $notNull: index === 0 ? false : true
420
+ }
421
+ }
422
+ });
423
+ };
424
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
425
+ const response2 = await createRelease({
426
+ name,
427
+ scheduledAt,
428
+ timezone
429
+ });
430
+ if ("data" in response2) {
431
+ toggleNotification({
432
+ type: "success",
433
+ message: formatMessage({
434
+ id: "content-releases.modal.release-created-notification-success",
435
+ defaultMessage: "Release created."
436
+ })
437
+ });
438
+ trackUsage("didCreateRelease");
439
+ push(`/plugins/content-releases/${response2.data.data.id}`);
440
+ } else if (isAxiosError(response2.error)) {
441
+ toggleNotification({
442
+ type: "warning",
443
+ message: formatAPIError(response2.error)
444
+ });
445
+ } else {
446
+ toggleNotification({
447
+ type: "warning",
448
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
449
+ });
450
+ }
451
+ };
452
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
453
+ /* @__PURE__ */ jsx(
454
+ HeaderLayout,
455
+ {
456
+ title: formatMessage({
457
+ id: "content-releases.pages.Releases.title",
458
+ defaultMessage: "Releases"
459
+ }),
460
+ subtitle: formatMessage({
461
+ id: "content-releases.pages.Releases.header-subtitle",
462
+ defaultMessage: "Create and manage content updates"
463
+ }),
464
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
465
+ Button,
466
+ {
467
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
468
+ onClick: toggleAddReleaseModal,
469
+ disabled: hasReachedMaximumPendingReleases,
470
+ children: formatMessage({
471
+ id: "content-releases.header.actions.add-release",
472
+ defaultMessage: "New release"
473
+ })
474
+ }
475
+ ) })
476
+ }
477
+ ),
478
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
479
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
480
+ StyledAlert,
481
+ {
482
+ marginBottom: 6,
483
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
484
+ id: "content-releases.pages.Releases.max-limit-reached.action",
485
+ defaultMessage: "Explore plans"
486
+ }) }),
487
+ title: formatMessage(
488
+ {
489
+ id: "content-releases.pages.Releases.max-limit-reached.title",
490
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
491
+ },
492
+ { number: maximumReleases }
493
+ ),
494
+ onClose: () => {
495
+ },
496
+ closeLabel: "",
497
+ children: formatMessage({
498
+ id: "content-releases.pages.Releases.max-limit-reached.message",
499
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
500
+ })
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsxs(
504
+ TabGroup,
505
+ {
506
+ label: formatMessage({
507
+ id: "content-releases.pages.Releases.tab-group.label",
508
+ defaultMessage: "Releases list"
509
+ }),
510
+ variant: "simple",
511
+ initialSelectedTabIndex: activeTabIndex,
512
+ onTabChange: handleTabChange,
513
+ ref: tabRef,
514
+ children: [
515
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
516
+ /* @__PURE__ */ jsxs(Tabs, { children: [
517
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
518
+ {
519
+ id: "content-releases.pages.Releases.tab.pending",
520
+ defaultMessage: "Pending ({count})"
521
+ },
522
+ {
523
+ count: totalPendingReleases
524
+ }
525
+ ) }),
526
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
527
+ id: "content-releases.pages.Releases.tab.done",
528
+ defaultMessage: "Done"
529
+ }) })
530
+ ] }),
531
+ /* @__PURE__ */ jsx(Divider, {})
532
+ ] }),
533
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
534
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
535
+ ReleasesGrid,
536
+ {
537
+ sectionTitle: "pending",
538
+ releases: response?.currentData?.data,
539
+ isError
540
+ }
541
+ ) }),
542
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
543
+ ReleasesGrid,
544
+ {
545
+ sectionTitle: "done",
546
+ releases: response?.currentData?.data,
547
+ isError
548
+ }
549
+ ) })
550
+ ] })
551
+ ]
552
+ }
553
+ ),
554
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
555
+ /* @__PURE__ */ jsx(
556
+ PageSizeURLQuery,
557
+ {
558
+ options: ["8", "16", "32", "64"],
559
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
560
+ }
561
+ ),
562
+ /* @__PURE__ */ jsx(
563
+ PaginationURLQuery,
564
+ {
565
+ pagination: {
566
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
567
+ }
568
+ }
569
+ )
570
+ ] }) : null
571
+ ] }) }),
572
+ releaseModalShown && /* @__PURE__ */ jsx(
573
+ ReleaseModal,
574
+ {
575
+ handleClose: toggleAddReleaseModal,
576
+ handleSubmit: handleAddRelease,
577
+ isLoading: isSubmittingForm,
578
+ initialValues: INITIAL_FORM_VALUES
579
+ }
580
+ )
581
+ ] });
582
+ };
87
583
  const ReleaseInfoWrapper = styled(Flex)`
88
584
  align-self: stretch;
89
585
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
586
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
587
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
588
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
589
+ const StyledMenuItem = styled(Menu.Item)`
97
590
  svg path {
98
591
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
592
  }
100
593
  span {
101
594
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
102
595
  }
596
+
597
+ &:hover {
598
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
599
+ }
103
600
  `;
104
601
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
602
+ width: ${({ theme }) => theme.spaces[3]};
603
+ height: ${({ theme }) => theme.spaces[3]};
107
604
  path {
108
605
  fill: ${({ theme }) => theme.colors.neutral600};
109
606
  }
110
607
  `;
111
608
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
609
+ width: ${({ theme }) => theme.spaces[3]};
610
+ height: ${({ theme }) => theme.spaces[3]};
114
611
  path {
115
612
  fill: ${({ theme }) => theme.colors.danger600};
116
613
  }
@@ -118,24 +615,6 @@ const TrashIcon = styled(Trash)`
118
615
  const TypographyMaxWidth = styled(Typography)`
119
616
  max-width: 300px;
120
617
  `;
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
618
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
619
  const { formatMessage } = useIntl();
141
620
  const { validate } = unstable_useDocument();
@@ -184,10 +663,8 @@ const ReleaseDetailsLayout = ({
184
663
  toggleWarningSubmit,
185
664
  children
186
665
  }) => {
187
- const { formatMessage } = useIntl();
666
+ const { formatMessage, formatDate, formatTime } = useIntl();
188
667
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
668
  const {
192
669
  data,
193
670
  isLoading: isLoadingDetails,
@@ -201,14 +678,8 @@ const ReleaseDetailsLayout = ({
201
678
  allowedActions: { canUpdate, canDelete }
202
679
  } = useRBAC(PERMISSIONS);
203
680
  const dispatch = useTypedDispatch();
681
+ const { trackUsage } = useTracking();
204
682
  const release = data?.data;
205
- const handleTogglePopover = () => {
206
- setIsPopoverVisible((prev) => !prev);
207
- };
208
- const openReleaseModal = () => {
209
- toggleEditReleaseModal();
210
- handleTogglePopover();
211
- };
212
683
  const handlePublishRelease = async () => {
213
684
  const response = await publishRelease({ id: releaseId });
214
685
  if ("data" in response) {
@@ -219,6 +690,12 @@ const ReleaseDetailsLayout = ({
219
690
  defaultMessage: "Release was published successfully."
220
691
  })
221
692
  });
693
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
694
+ trackUsage("didPublishRelease", {
695
+ totalEntries: totalEntries2,
696
+ totalPublishedEntries,
697
+ totalUnpublishedEntries
698
+ });
222
699
  } else if (isAxiosError(response.error)) {
223
700
  toggleNotification({
224
701
  type: "warning",
@@ -231,13 +708,21 @@ const ReleaseDetailsLayout = ({
231
708
  });
232
709
  }
233
710
  };
234
- const openWarningConfirmDialog = () => {
235
- toggleWarningSubmit();
236
- handleTogglePopover();
237
- };
238
711
  const handleRefresh = () => {
239
712
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
240
713
  };
714
+ const getCreatedByUser = () => {
715
+ if (!release?.createdBy) {
716
+ return null;
717
+ }
718
+ if (release.createdBy.username) {
719
+ return release.createdBy.username;
720
+ }
721
+ if (release.createdBy.firstname) {
722
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
723
+ }
724
+ return release.createdBy.email;
725
+ };
241
726
  if (isLoadingDetails) {
242
727
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
243
728
  }
@@ -259,90 +744,128 @@ const ReleaseDetailsLayout = ({
259
744
  );
260
745
  }
261
746
  const totalEntries = release.actions.meta.count || 0;
262
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
747
+ const hasCreatedByUser = Boolean(getCreatedByUser());
748
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
749
+ const isScheduled = release.scheduledAt && release.timezone;
750
+ const numberOfEntriesText = formatMessage(
751
+ {
752
+ id: "content-releases.pages.Details.header-subtitle",
753
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
754
+ },
755
+ { number: totalEntries }
756
+ );
757
+ const scheduledText = isScheduled ? formatMessage(
758
+ {
759
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
760
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
761
+ },
762
+ {
763
+ date: formatDate(new Date(release.scheduledAt), {
764
+ weekday: "long",
765
+ day: "numeric",
766
+ month: "long",
767
+ year: "numeric",
768
+ timeZone: release.timezone
769
+ }),
770
+ time: formatTime(new Date(release.scheduledAt), {
771
+ timeZone: release.timezone,
772
+ hourCycle: "h23"
773
+ }),
774
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
775
+ }
776
+ ) : "";
263
777
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
264
778
  /* @__PURE__ */ jsx(
265
779
  HeaderLayout,
266
780
  {
267
781
  title: release.name,
268
- subtitle: formatMessage(
269
- {
270
- id: "content-releases.pages.Details.header-subtitle",
271
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
272
- },
273
- { number: totalEntries }
274
- ),
275
- navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
782
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
783
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : "") }),
784
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
785
+ ] }),
786
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
276
787
  id: "global.back",
277
788
  defaultMessage: "Back"
278
789
  }) }),
279
790
  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
- ),
791
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
792
+ /* @__PURE__ */ jsx(
793
+ Menu.Trigger,
794
+ {
795
+ as: IconButton,
796
+ paddingLeft: 2,
797
+ paddingRight: 2,
798
+ "aria-label": formatMessage({
799
+ id: "content-releases.header.actions.open-release-actions",
800
+ defaultMessage: "Release edit and delete menu"
801
+ }),
802
+ icon: /* @__PURE__ */ jsx(More, {}),
803
+ variant: "tertiary"
804
+ }
805
+ ),
806
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
807
+ /* @__PURE__ */ jsxs(
808
+ Flex,
809
+ {
810
+ alignItems: "center",
811
+ justifyContent: "center",
812
+ direction: "column",
813
+ padding: 1,
814
+ width: "100%",
815
+ children: [
816
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
817
+ /* @__PURE__ */ jsx(PencilIcon, {}),
818
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
819
+ id: "content-releases.header.actions.edit",
820
+ defaultMessage: "Edit"
821
+ }) })
822
+ ] }) }),
823
+ /* @__PURE__ */ jsx(
824
+ StyledMenuItem,
825
+ {
826
+ disabled: !canDelete,
827
+ onSelect: toggleWarningSubmit,
828
+ variant: "danger",
829
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
830
+ /* @__PURE__ */ jsx(TrashIcon, {}),
831
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
832
+ id: "content-releases.header.actions.delete",
833
+ defaultMessage: "Delete"
834
+ }) })
835
+ ] })
836
+ }
837
+ )
838
+ ]
839
+ }
840
+ ),
841
+ /* @__PURE__ */ jsxs(
842
+ ReleaseInfoWrapper,
843
+ {
844
+ direction: "column",
845
+ justifyContent: "center",
846
+ alignItems: "flex-start",
847
+ gap: 1,
848
+ padding: 5,
849
+ children: [
850
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
851
+ id: "content-releases.header.actions.created",
852
+ defaultMessage: "Created"
853
+ }) }),
854
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
855
+ /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
856
+ formatMessage(
857
+ {
858
+ id: "content-releases.header.actions.created.description",
859
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
860
+ },
861
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
862
+ )
863
+ ] })
864
+ ]
865
+ }
866
+ )
867
+ ] })
868
+ ] }),
346
869
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
347
870
  id: "content-releases.header.actions.refresh",
348
871
  defaultMessage: "Refresh"
@@ -398,6 +921,9 @@ const ReleaseDetailsBody = () => {
398
921
  isError: isReleaseError,
399
922
  error: releaseError
400
923
  } = useGetReleaseQuery({ id: releaseId });
924
+ const {
925
+ allowedActions: { canUpdate }
926
+ } = useRBAC(PERMISSIONS);
401
927
  const release = releaseData?.data;
402
928
  const selectedGroupBy = query?.groupBy || "contentType";
403
929
  const {
@@ -411,7 +937,7 @@ const ReleaseDetailsBody = () => {
411
937
  releaseId
412
938
  });
413
939
  const [updateReleaseAction] = useUpdateReleaseActionMutation();
414
- const handleChangeType = async (e, actionId) => {
940
+ const handleChangeType = async (e, actionId, actionPath) => {
415
941
  const response = await updateReleaseAction({
416
942
  params: {
417
943
  releaseId,
@@ -419,7 +945,11 @@ const ReleaseDetailsBody = () => {
419
945
  },
420
946
  body: {
421
947
  type: e.target.value
422
- }
948
+ },
949
+ query,
950
+ // We are passing the query params to make optimistic updates
951
+ actionPath
952
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
423
953
  });
424
954
  if ("error" in response) {
425
955
  if (isAxiosError(response.error)) {
@@ -480,7 +1010,7 @@ const ReleaseDetailsBody = () => {
480
1010
  action: /* @__PURE__ */ jsx(
481
1011
  LinkButton,
482
1012
  {
483
- as: Link$1,
1013
+ as: Link$2,
484
1014
  to: {
485
1015
  pathname: "/content-manager"
486
1016
  },
@@ -500,7 +1030,7 @@ const ReleaseDetailsBody = () => {
500
1030
  SingleSelect,
501
1031
  {
502
1032
  "aria-label": formatMessage({
503
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1033
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
504
1034
  defaultMessage: "Group by"
505
1035
  }),
506
1036
  customizeContent: (value) => formatMessage(
@@ -518,7 +1048,7 @@ const ReleaseDetailsBody = () => {
518
1048
  }
519
1049
  ) }),
520
1050
  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 }) }),
1051
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
522
1052
  /* @__PURE__ */ jsx(
523
1053
  Table.Root,
524
1054
  {
@@ -588,56 +1118,59 @@ const ReleaseDetailsBody = () => {
588
1118
  )
589
1119
  ] }),
590
1120
  /* @__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,
1121
+ /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(
1122
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxs(Tr, { children: [
1123
+ /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
1124
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
1125
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
1126
+ /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
1127
+ {
1128
+ id: "content-releases.page.ReleaseDetails.table.action-published",
1129
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
1130
+ },
1131
+ {
1132
+ isPublish: type === "publish",
1133
+ b: (children) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children })
1134
+ }
1135
+ ) }) : /* @__PURE__ */ jsx(
1136
+ ReleaseActionOptions,
615
1137
  {
616
- action: type,
617
- schema: contentTypes?.[contentType.uid],
618
- components,
619
- entry
1138
+ selected: type,
1139
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
1140
+ name: `release-action-${id}-type`,
1141
+ disabled: !canUpdate
620
1142
  }
621
1143
  ) }),
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,
1144
+ !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
1145
+ /* @__PURE__ */ jsx(Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsx(
1146
+ EntryValidationText,
633
1147
  {
634
- releaseId: release.id,
635
- actionId: id
1148
+ action: type,
1149
+ schema: contentTypes?.[contentType.uid],
1150
+ components,
1151
+ entry
636
1152
  }
637
- )
638
- ] }) }) })
639
- ] })
640
- ] }, id)) })
1153
+ ) }),
1154
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
1155
+ /* @__PURE__ */ jsx(
1156
+ ReleaseActionMenu.ReleaseActionEntryLinkItem,
1157
+ {
1158
+ contentTypeUid: contentType.uid,
1159
+ entryId: entry.id,
1160
+ locale: locale?.code
1161
+ }
1162
+ ),
1163
+ /* @__PURE__ */ jsx(
1164
+ ReleaseActionMenu.DeleteReleaseActionItem,
1165
+ {
1166
+ releaseId: release.id,
1167
+ actionId: id
1168
+ }
1169
+ )
1170
+ ] }) }) })
1171
+ ] })
1172
+ ] }, id)
1173
+ ) })
641
1174
  ] })
642
1175
  }
643
1176
  )
@@ -684,11 +1217,18 @@ const ReleaseDetailsPage = () => {
684
1217
  }
685
1218
  );
686
1219
  }
687
- const title = isSuccessDetails && data?.data?.name || "";
1220
+ const releaseData = isSuccessDetails && data?.data || null;
1221
+ const title = releaseData?.name || "";
1222
+ const timezone = releaseData?.timezone ?? null;
1223
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1224
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
1225
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
688
1226
  const handleEditRelease = async (values) => {
689
1227
  const response = await updateRelease({
690
1228
  id: releaseId,
691
- name: values.name
1229
+ name: values.name,
1230
+ scheduledAt: values.scheduledAt,
1231
+ timezone: values.timezone
692
1232
  });
693
1233
  if ("data" in response) {
694
1234
  toggleNotification({
@@ -742,7 +1282,14 @@ const ReleaseDetailsPage = () => {
742
1282
  handleClose: toggleEditReleaseModal,
743
1283
  handleSubmit: handleEditRelease,
744
1284
  isLoading: isLoadingDetails || isSubmittingForm,
745
- initialValues: { name: title || "" }
1285
+ initialValues: {
1286
+ name: title || "",
1287
+ scheduledAt,
1288
+ date,
1289
+ time,
1290
+ isScheduled: Boolean(scheduledAt),
1291
+ timezone
1292
+ }
746
1293
  }
747
1294
  ),
748
1295
  /* @__PURE__ */ jsx(
@@ -762,247 +1309,6 @@ const ReleaseDetailsPage = () => {
762
1309
  }
763
1310
  );
764
1311
  };
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
- const LinkCard = styled(Link$2)`
797
- display: block;
798
- `;
799
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
800
- const { formatMessage } = useIntl();
801
- if (isError) {
802
- return /* @__PURE__ */ jsx(AnErrorOccurred, {});
803
- }
804
- if (releases?.length === 0) {
805
- return /* @__PURE__ */ jsx(
806
- EmptyStateLayout,
807
- {
808
- content: formatMessage(
809
- {
810
- id: "content-releases.page.Releases.tab.emptyEntries",
811
- defaultMessage: "No releases"
812
- },
813
- {
814
- target: sectionTitle
815
- }
816
- ),
817
- icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
818
- }
819
- );
820
- }
821
- 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(
822
- Flex,
823
- {
824
- direction: "column",
825
- justifyContent: "space-between",
826
- padding: 4,
827
- hasRadius: true,
828
- background: "neutral0",
829
- shadow: "tableShadow",
830
- height: "100%",
831
- width: "100%",
832
- alignItems: "start",
833
- gap: 2,
834
- children: [
835
- /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
836
- /* @__PURE__ */ jsx(Typography, { variant: "pi", children: formatMessage(
837
- {
838
- id: "content-releases.page.Releases.release-item.entries",
839
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
840
- },
841
- { number: actions.meta.count }
842
- ) })
843
- ]
844
- }
845
- ) }) }, id)) });
846
- };
847
- const INITIAL_FORM_VALUES = {
848
- name: ""
849
- };
850
- const ReleasesPage = () => {
851
- const tabRef = React.useRef(null);
852
- const location = useLocation();
853
- const [releaseModalShown, setReleaseModalShown] = React.useState(false);
854
- const toggleNotification = useNotification();
855
- const { formatMessage } = useIntl();
856
- const { push, replace } = useHistory();
857
- const { formatAPIError } = useAPIErrorHandler();
858
- const [{ query }, setQuery] = useQueryParams();
859
- const response = useGetReleasesQuery(query);
860
- const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
861
- const { isLoading, isSuccess, isError } = response;
862
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
863
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
864
- React.useEffect(() => {
865
- if (location?.state?.errors) {
866
- toggleNotification({
867
- type: "warning",
868
- title: formatMessage({
869
- id: "content-releases.pages.Releases.notification.error.title",
870
- defaultMessage: "Your request could not be processed."
871
- }),
872
- message: formatMessage({
873
- id: "content-releases.pages.Releases.notification.error.message",
874
- defaultMessage: "Please try again or open another release."
875
- })
876
- });
877
- replace({ state: null });
878
- }
879
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
880
- React.useEffect(() => {
881
- if (tabRef.current) {
882
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
883
- }
884
- }, [activeTabIndex]);
885
- const toggleAddReleaseModal = () => {
886
- setReleaseModalShown((prev) => !prev);
887
- };
888
- if (isLoading) {
889
- return /* @__PURE__ */ jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) }) });
890
- }
891
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
892
- const handleTabChange = (index) => {
893
- setQuery({
894
- ...query,
895
- page: 1,
896
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
897
- filters: {
898
- releasedAt: {
899
- $notNull: index === 0 ? false : true
900
- }
901
- }
902
- });
903
- };
904
- const handleAddRelease = async (values) => {
905
- const response2 = await createRelease({
906
- name: values.name
907
- });
908
- if ("data" in response2) {
909
- toggleNotification({
910
- type: "success",
911
- message: formatMessage({
912
- id: "content-releases.modal.release-created-notification-success",
913
- defaultMessage: "Release created."
914
- })
915
- });
916
- push(`/plugins/content-releases/${response2.data.data.id}`);
917
- } else if (isAxiosError(response2.error)) {
918
- toggleNotification({
919
- type: "warning",
920
- message: formatAPIError(response2.error)
921
- });
922
- } else {
923
- toggleNotification({
924
- type: "warning",
925
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
926
- });
927
- }
928
- };
929
- return /* @__PURE__ */ jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
930
- /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
931
- /* @__PURE__ */ jsxs(
932
- TabGroup,
933
- {
934
- label: formatMessage({
935
- id: "content-releases.pages.Releases.tab-group.label",
936
- defaultMessage: "Releases list"
937
- }),
938
- variant: "simple",
939
- initialSelectedTabIndex: activeTabIndex,
940
- onTabChange: handleTabChange,
941
- ref: tabRef,
942
- children: [
943
- /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
944
- /* @__PURE__ */ jsxs(Tabs, { children: [
945
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
946
- id: "content-releases.pages.Releases.tab.pending",
947
- defaultMessage: "Pending"
948
- }) }),
949
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
950
- id: "content-releases.pages.Releases.tab.done",
951
- defaultMessage: "Done"
952
- }) })
953
- ] }),
954
- /* @__PURE__ */ jsx(Divider, {})
955
- ] }),
956
- /* @__PURE__ */ jsxs(TabPanels, { children: [
957
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
958
- ReleasesGrid,
959
- {
960
- sectionTitle: "pending",
961
- releases: response?.currentData?.data,
962
- isError
963
- }
964
- ) }),
965
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
966
- ReleasesGrid,
967
- {
968
- sectionTitle: "done",
969
- releases: response?.currentData?.data,
970
- isError
971
- }
972
- ) })
973
- ] })
974
- ]
975
- }
976
- ),
977
- totalReleases > 0 && /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
978
- /* @__PURE__ */ jsx(
979
- PageSizeURLQuery,
980
- {
981
- options: ["8", "16", "32", "64"],
982
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
983
- }
984
- ),
985
- /* @__PURE__ */ jsx(
986
- PaginationURLQuery,
987
- {
988
- pagination: {
989
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
990
- }
991
- }
992
- )
993
- ] })
994
- ] }) }),
995
- releaseModalShown && /* @__PURE__ */ jsx(
996
- ReleaseModal,
997
- {
998
- handleClose: toggleAddReleaseModal,
999
- handleSubmit: handleAddRelease,
1000
- isLoading: isSubmittingForm,
1001
- initialValues: INITIAL_FORM_VALUES
1002
- }
1003
- )
1004
- ] });
1005
- };
1006
1312
  const App = () => {
1007
1313
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
1008
1314
  /* @__PURE__ */ jsx(Route, { exact: true, path: `/plugins/${pluginId}`, component: ReleasesPage }),
@@ -1012,4 +1318,4 @@ const App = () => {
1012
1318
  export {
1013
1319
  App
1014
1320
  };
1015
- //# sourceMappingURL=App-8J9a-MD5.mjs.map
1321
+ //# sourceMappingURL=App-6ugQxqYE.mjs.map