@strapi/content-releases 0.0.0-next.3844395bef7efa05c25c6d4337306935905bc653 → 0.0.0-next.3cc05002fb92029975799c3113971bb5b5198d7c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/_chunks/{App-L1jSxCiL.mjs → App-R-kksTW7.mjs} +747 -454
  2. package/dist/_chunks/App-R-kksTW7.mjs.map +1 -0
  3. package/dist/_chunks/{App-_20W9dYa.js → App-WZHc_d3m.js} +739 -445
  4. package/dist/_chunks/App-WZHc_d3m.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-RdapH-9X.mjs} +21 -7
  10. package/dist/_chunks/en-RdapH-9X.mjs.map +1 -0
  11. package/dist/_chunks/{en-gYDqKYFd.js → en-faJDuv3q.js} +21 -7
  12. package/dist/_chunks/en-faJDuv3q.js.map +1 -0
  13. package/dist/_chunks/{index-KJa1Rb5F.js → index-k6fw6RYi.js} +158 -33
  14. package/dist/_chunks/index-k6fw6RYi.js.map +1 -0
  15. package/dist/_chunks/{index-c4zRX_sg.mjs → index-vpSczx8v.mjs} +170 -45
  16. package/dist/_chunks/index-vpSczx8v.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 +998 -399
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +997 -399
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -11
  24. package/dist/_chunks/App-L1jSxCiL.mjs.map +0 -1
  25. package/dist/_chunks/App-_20W9dYa.js.map +0 -1
  26. package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
  27. package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
  28. package/dist/_chunks/index-KJa1Rb5F.js.map +0 -1
  29. package/dist/_chunks/index-c4zRX_sg.mjs.map +0 -1
@@ -1,22 +1,42 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { useNotification, useAPIErrorHandler, LoadingIndicatorPage, ConfirmDialog, useRBAC, RelativeTime, CheckPermissions, useQueryParams, AnErrorOccurred, NoContent, Table, PageSizeURLQuery, PaginationURLQuery, CheckPagePermissions } from "@strapi/helper-plugin";
3
- import { useLocation, useParams, useHistory, Redirect, Link as Link$1, Switch, Route } from "react-router-dom";
4
- import { p as pluginId, u as useGetReleaseQuery, a as useUpdateReleaseMutation, b as useDeleteReleaseMutation, c as usePublishReleaseMutation, P as PERMISSIONS, d as useTypedDispatch, e as useGetReleaseActionsQuery, f as useUpdateReleaseActionMutation, R as ReleaseActionOptions, g as ReleaseActionMenu, i as isAxiosError, r as releaseApi, h as useGetReleasesQuery, j as useCreateReleaseMutation } from "./index-c4zRX_sg.mjs";
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-vpSczx8v.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 { 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, 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,23 @@ 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 formattedDate = parse(time, "HH:mm", new Date(date));
58
+ const timezoneWithoutOffset = timezone.split("&")[1];
59
+ return zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
60
+ };
61
+ const getTimezoneWithOffset = () => {
62
+ const currentTimezone = timezoneList.find(
63
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
64
+ );
65
+ return currentTimezone?.value || systemTimezone.value;
66
+ };
30
67
  return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
31
68
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
32
69
  {
@@ -38,45 +75,130 @@ const ReleaseModal = ({
38
75
  /* @__PURE__ */ jsx(
39
76
  Formik,
40
77
  {
41
- validateOnChange: false,
42
- onSubmit: handleSubmit,
43
- initialValues,
78
+ onSubmit: (values) => {
79
+ handleSubmit({
80
+ ...values,
81
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
82
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
83
+ });
84
+ },
85
+ initialValues: {
86
+ ...initialValues,
87
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
88
+ },
44
89
  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
- ) }),
90
+ validateOnChange: false,
91
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxs(Form, { children: [
92
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
93
+ /* @__PURE__ */ jsx(
94
+ TextInput,
95
+ {
96
+ label: formatMessage({
97
+ id: "content-releases.modal.form.input.label.release-name",
98
+ defaultMessage: "Name"
99
+ }),
100
+ name: "name",
101
+ value: values.name,
102
+ error: errors.name,
103
+ onChange: handleChange,
104
+ required: true
105
+ }
106
+ ),
107
+ /* @__PURE__ */ jsx(Box, { width: "max-content", children: /* @__PURE__ */ jsx(
108
+ Checkbox,
109
+ {
110
+ name: "isScheduled",
111
+ value: values.isScheduled,
112
+ onChange: (event) => {
113
+ setFieldValue("isScheduled", event.target.checked);
114
+ if (!event.target.checked) {
115
+ setFieldValue("date", null);
116
+ setFieldValue("time", "");
117
+ setFieldValue("timezone", null);
118
+ } else {
119
+ setFieldValue("date", initialValues.date);
120
+ setFieldValue("time", initialValues.time);
121
+ setFieldValue("timezone", initialValues.timezone ?? systemTimezone?.value);
122
+ }
123
+ },
124
+ children: /* @__PURE__ */ jsx(
125
+ Typography,
126
+ {
127
+ textColor: values.isScheduled ? "primary600" : "neutral800",
128
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
129
+ children: formatMessage({
130
+ id: "modal.form.input.label.schedule-release",
131
+ defaultMessage: "Schedule release"
132
+ })
133
+ }
134
+ )
135
+ }
136
+ ) }),
137
+ values.isScheduled && /* @__PURE__ */ jsxs(Fragment, { children: [
138
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "start", children: [
139
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
140
+ DatePicker,
141
+ {
142
+ label: formatMessage({
143
+ id: "content-releases.modal.form.input.label.date",
144
+ defaultMessage: "Date"
145
+ }),
146
+ name: "date",
147
+ error: errors.date,
148
+ onChange: (date) => {
149
+ const isoFormatDate = date ? formatISO(date, { representation: "date" }) : null;
150
+ setFieldValue("date", isoFormatDate);
151
+ },
152
+ clearLabel: formatMessage({
153
+ id: "content-releases.modal.form.input.clearLabel",
154
+ defaultMessage: "Clear"
155
+ }),
156
+ onClear: () => {
157
+ setFieldValue("date", null);
158
+ },
159
+ selectedDate: values.date || void 0,
160
+ required: true,
161
+ minDate: utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
162
+ }
163
+ ) }),
164
+ /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
165
+ TimePicker,
166
+ {
167
+ label: formatMessage({
168
+ id: "content-releases.modal.form.input.label.time",
169
+ defaultMessage: "Time"
170
+ }),
171
+ name: "time",
172
+ error: errors.time,
173
+ onChange: (time) => {
174
+ setFieldValue("time", time);
175
+ },
176
+ clearLabel: formatMessage({
177
+ id: "content-releases.modal.form.input.clearLabel",
178
+ defaultMessage: "Clear"
179
+ }),
180
+ onClear: () => {
181
+ setFieldValue("time", "");
182
+ },
183
+ value: values.time || void 0,
184
+ required: true
185
+ }
186
+ ) })
187
+ ] }),
188
+ /* @__PURE__ */ jsx(TimezoneComponent, { timezoneOptions: timezoneList })
189
+ ] })
190
+ ] }) }),
60
191
  /* @__PURE__ */ jsx(
61
192
  ModalFooter,
62
193
  {
63
194
  startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
64
- endActions: /* @__PURE__ */ jsx(
65
- Button,
195
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
66
196
  {
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
- )
197
+ id: "content-releases.modal.form.button.submit",
198
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
199
+ },
200
+ { isCreatingRelease }
201
+ ) })
80
202
  }
81
203
  )
82
204
  ] })
@@ -84,33 +206,396 @@ const ReleaseModal = ({
84
206
  )
85
207
  ] });
86
208
  };
209
+ const getTimezones = (selectedDate) => {
210
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
211
+ const utcOffset = getTimezoneOffset(timezone, selectedDate);
212
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
213
+ });
214
+ const systemTimezone = timezoneList.find(
215
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
216
+ );
217
+ return { timezoneList, systemTimezone };
218
+ };
219
+ const TimezoneComponent = ({ timezoneOptions }) => {
220
+ const { values, errors, setFieldValue } = useFormikContext();
221
+ const { formatMessage } = useIntl();
222
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
223
+ React.useEffect(() => {
224
+ if (values.date) {
225
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
226
+ setTimezoneList(timezoneList2);
227
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
228
+ if (updatedTimezone) {
229
+ setFieldValue("timezone", updatedTimezone.value);
230
+ }
231
+ }
232
+ }, [setFieldValue, values.date, values.timezone]);
233
+ return /* @__PURE__ */ jsx(
234
+ Combobox,
235
+ {
236
+ label: formatMessage({
237
+ id: "content-releases.modal.form.input.label.timezone",
238
+ defaultMessage: "Timezone"
239
+ }),
240
+ autocomplete: { type: "list", filter: "contains" },
241
+ name: "timezone",
242
+ value: values.timezone || void 0,
243
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
244
+ onChange: (timezone) => {
245
+ setFieldValue("timezone", timezone);
246
+ },
247
+ onTextValueChange: (timezone) => {
248
+ setFieldValue("timezone", timezone);
249
+ },
250
+ onClear: () => {
251
+ setFieldValue("timezone", "");
252
+ },
253
+ error: errors.timezone,
254
+ required: true,
255
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
256
+ }
257
+ );
258
+ };
259
+ const LinkCard = styled(Link)`
260
+ display: block;
261
+ `;
262
+ const CapitalizeRelativeTime = styled(RelativeTime)`
263
+ text-transform: capitalize;
264
+ `;
265
+ const getBadgeProps = (status) => {
266
+ let color;
267
+ switch (status) {
268
+ case "ready":
269
+ color = "success";
270
+ break;
271
+ case "blocked":
272
+ color = "warning";
273
+ break;
274
+ case "failed":
275
+ color = "danger";
276
+ break;
277
+ case "done":
278
+ color = "primary";
279
+ break;
280
+ case "empty":
281
+ default:
282
+ color = "neutral";
283
+ }
284
+ return {
285
+ textColor: `${color}600`,
286
+ backgroundColor: `${color}100`,
287
+ borderColor: `${color}200`
288
+ };
289
+ };
290
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
291
+ const { formatMessage } = useIntl();
292
+ if (isError) {
293
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
294
+ }
295
+ if (releases?.length === 0) {
296
+ return /* @__PURE__ */ jsx(
297
+ EmptyStateLayout,
298
+ {
299
+ content: formatMessage(
300
+ {
301
+ id: "content-releases.page.Releases.tab.emptyEntries",
302
+ defaultMessage: "No releases"
303
+ },
304
+ {
305
+ target: sectionTitle
306
+ }
307
+ ),
308
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
309
+ }
310
+ );
311
+ }
312
+ 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(
313
+ Flex,
314
+ {
315
+ direction: "column",
316
+ justifyContent: "space-between",
317
+ padding: 4,
318
+ hasRadius: true,
319
+ background: "neutral0",
320
+ shadow: "tableShadow",
321
+ height: "100%",
322
+ width: "100%",
323
+ alignItems: "start",
324
+ gap: 4,
325
+ children: [
326
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
327
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
328
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
329
+ id: "content-releases.pages.Releases.not-scheduled",
330
+ defaultMessage: "Not scheduled"
331
+ }) })
332
+ ] }),
333
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
334
+ ]
335
+ }
336
+ ) }) }, id)) });
337
+ };
338
+ const StyledAlert = styled(Alert)`
339
+ button {
340
+ display: none;
341
+ }
342
+ p + div {
343
+ margin-left: auto;
344
+ }
345
+ `;
346
+ const INITIAL_FORM_VALUES = {
347
+ name: "",
348
+ date: null,
349
+ time: "",
350
+ isScheduled: true,
351
+ scheduledAt: null,
352
+ timezone: null
353
+ };
354
+ const ReleasesPage = () => {
355
+ const tabRef = React.useRef(null);
356
+ const location = useLocation();
357
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
358
+ const toggleNotification = useNotification();
359
+ const { formatMessage } = useIntl();
360
+ const { push, replace } = useHistory();
361
+ const { formatAPIError } = useAPIErrorHandler();
362
+ const [{ query }, setQuery] = useQueryParams();
363
+ const response = useGetReleasesQuery(query);
364
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
365
+ const { getFeature } = useLicenseLimits();
366
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
367
+ const { trackUsage } = useTracking();
368
+ const { isLoading, isSuccess, isError } = response;
369
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
370
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
371
+ React.useEffect(() => {
372
+ if (location?.state?.errors) {
373
+ toggleNotification({
374
+ type: "warning",
375
+ title: formatMessage({
376
+ id: "content-releases.pages.Releases.notification.error.title",
377
+ defaultMessage: "Your request could not be processed."
378
+ }),
379
+ message: formatMessage({
380
+ id: "content-releases.pages.Releases.notification.error.message",
381
+ defaultMessage: "Please try again or open another release."
382
+ })
383
+ });
384
+ replace({ state: null });
385
+ }
386
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
387
+ React.useEffect(() => {
388
+ if (tabRef.current) {
389
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
390
+ }
391
+ }, [activeTabIndex]);
392
+ const toggleAddReleaseModal = () => {
393
+ setReleaseModalShown((prev) => !prev);
394
+ };
395
+ if (isLoading) {
396
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
397
+ }
398
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
399
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
400
+ const handleTabChange = (index) => {
401
+ setQuery({
402
+ ...query,
403
+ page: 1,
404
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
405
+ filters: {
406
+ releasedAt: {
407
+ $notNull: index === 0 ? false : true
408
+ }
409
+ }
410
+ });
411
+ };
412
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
413
+ const response2 = await createRelease({
414
+ name,
415
+ scheduledAt,
416
+ timezone
417
+ });
418
+ if ("data" in response2) {
419
+ toggleNotification({
420
+ type: "success",
421
+ message: formatMessage({
422
+ id: "content-releases.modal.release-created-notification-success",
423
+ defaultMessage: "Release created."
424
+ })
425
+ });
426
+ trackUsage("didCreateRelease");
427
+ push(`/plugins/content-releases/${response2.data.data.id}`);
428
+ } else if (isAxiosError(response2.error)) {
429
+ toggleNotification({
430
+ type: "warning",
431
+ message: formatAPIError(response2.error)
432
+ });
433
+ } else {
434
+ toggleNotification({
435
+ type: "warning",
436
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
437
+ });
438
+ }
439
+ };
440
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
441
+ /* @__PURE__ */ jsx(
442
+ HeaderLayout,
443
+ {
444
+ title: formatMessage({
445
+ id: "content-releases.pages.Releases.title",
446
+ defaultMessage: "Releases"
447
+ }),
448
+ subtitle: formatMessage({
449
+ id: "content-releases.pages.Releases.header-subtitle",
450
+ defaultMessage: "Create and manage content updates"
451
+ }),
452
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
453
+ Button,
454
+ {
455
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
456
+ onClick: toggleAddReleaseModal,
457
+ disabled: hasReachedMaximumPendingReleases,
458
+ children: formatMessage({
459
+ id: "content-releases.header.actions.add-release",
460
+ defaultMessage: "New release"
461
+ })
462
+ }
463
+ ) })
464
+ }
465
+ ),
466
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
467
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
468
+ StyledAlert,
469
+ {
470
+ marginBottom: 6,
471
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
472
+ id: "content-releases.pages.Releases.max-limit-reached.action",
473
+ defaultMessage: "Explore plans"
474
+ }) }),
475
+ title: formatMessage(
476
+ {
477
+ id: "content-releases.pages.Releases.max-limit-reached.title",
478
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
479
+ },
480
+ { number: maximumReleases }
481
+ ),
482
+ onClose: () => {
483
+ },
484
+ closeLabel: "",
485
+ children: formatMessage({
486
+ id: "content-releases.pages.Releases.max-limit-reached.message",
487
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
488
+ })
489
+ }
490
+ ),
491
+ /* @__PURE__ */ jsxs(
492
+ TabGroup,
493
+ {
494
+ label: formatMessage({
495
+ id: "content-releases.pages.Releases.tab-group.label",
496
+ defaultMessage: "Releases list"
497
+ }),
498
+ variant: "simple",
499
+ initialSelectedTabIndex: activeTabIndex,
500
+ onTabChange: handleTabChange,
501
+ ref: tabRef,
502
+ children: [
503
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
504
+ /* @__PURE__ */ jsxs(Tabs, { children: [
505
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
506
+ {
507
+ id: "content-releases.pages.Releases.tab.pending",
508
+ defaultMessage: "Pending ({count})"
509
+ },
510
+ {
511
+ count: totalPendingReleases
512
+ }
513
+ ) }),
514
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
515
+ id: "content-releases.pages.Releases.tab.done",
516
+ defaultMessage: "Done"
517
+ }) })
518
+ ] }),
519
+ /* @__PURE__ */ jsx(Divider, {})
520
+ ] }),
521
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
522
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
523
+ ReleasesGrid,
524
+ {
525
+ sectionTitle: "pending",
526
+ releases: response?.currentData?.data,
527
+ isError
528
+ }
529
+ ) }),
530
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
531
+ ReleasesGrid,
532
+ {
533
+ sectionTitle: "done",
534
+ releases: response?.currentData?.data,
535
+ isError
536
+ }
537
+ ) })
538
+ ] })
539
+ ]
540
+ }
541
+ ),
542
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
543
+ /* @__PURE__ */ jsx(
544
+ PageSizeURLQuery,
545
+ {
546
+ options: ["8", "16", "32", "64"],
547
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
548
+ }
549
+ ),
550
+ /* @__PURE__ */ jsx(
551
+ PaginationURLQuery,
552
+ {
553
+ pagination: {
554
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
555
+ }
556
+ }
557
+ )
558
+ ] }) : null
559
+ ] }) }),
560
+ releaseModalShown && /* @__PURE__ */ jsx(
561
+ ReleaseModal,
562
+ {
563
+ handleClose: toggleAddReleaseModal,
564
+ handleSubmit: handleAddRelease,
565
+ isLoading: isSubmittingForm,
566
+ initialValues: INITIAL_FORM_VALUES
567
+ }
568
+ )
569
+ ] });
570
+ };
87
571
  const ReleaseInfoWrapper = styled(Flex)`
88
572
  align-self: stretch;
89
573
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
90
574
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
91
575
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
92
576
  `;
93
- const StyledFlex = styled(Flex)`
94
- align-self: stretch;
95
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
96
-
577
+ const StyledMenuItem = styled(Menu.Item)`
97
578
  svg path {
98
579
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
99
580
  }
100
581
  span {
101
582
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
102
583
  }
584
+
585
+ &:hover {
586
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
587
+ }
103
588
  `;
104
589
  const PencilIcon = styled(Pencil)`
105
- width: ${({ theme }) => theme.spaces[4]};
106
- height: ${({ theme }) => theme.spaces[4]};
590
+ width: ${({ theme }) => theme.spaces[3]};
591
+ height: ${({ theme }) => theme.spaces[3]};
107
592
  path {
108
593
  fill: ${({ theme }) => theme.colors.neutral600};
109
594
  }
110
595
  `;
111
596
  const TrashIcon = styled(Trash)`
112
- width: ${({ theme }) => theme.spaces[4]};
113
- height: ${({ theme }) => theme.spaces[4]};
597
+ width: ${({ theme }) => theme.spaces[3]};
598
+ height: ${({ theme }) => theme.spaces[3]};
114
599
  path {
115
600
  fill: ${({ theme }) => theme.colors.danger600};
116
601
  }
@@ -118,24 +603,6 @@ const TrashIcon = styled(Trash)`
118
603
  const TypographyMaxWidth = styled(Typography)`
119
604
  max-width: 300px;
120
605
  `;
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
606
  const EntryValidationText = ({ action, schema, components, entry }) => {
140
607
  const { formatMessage } = useIntl();
141
608
  const { validate } = unstable_useDocument();
@@ -184,10 +651,8 @@ const ReleaseDetailsLayout = ({
184
651
  toggleWarningSubmit,
185
652
  children
186
653
  }) => {
187
- const { formatMessage } = useIntl();
654
+ const { formatMessage, formatDate, formatTime } = useIntl();
188
655
  const { releaseId } = useParams();
189
- const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
190
- const moreButtonRef = React.useRef(null);
191
656
  const {
192
657
  data,
193
658
  isLoading: isLoadingDetails,
@@ -201,14 +666,8 @@ const ReleaseDetailsLayout = ({
201
666
  allowedActions: { canUpdate, canDelete }
202
667
  } = useRBAC(PERMISSIONS);
203
668
  const dispatch = useTypedDispatch();
669
+ const { trackUsage } = useTracking();
204
670
  const release = data?.data;
205
- const handleTogglePopover = () => {
206
- setIsPopoverVisible((prev) => !prev);
207
- };
208
- const openReleaseModal = () => {
209
- toggleEditReleaseModal();
210
- handleTogglePopover();
211
- };
212
671
  const handlePublishRelease = async () => {
213
672
  const response = await publishRelease({ id: releaseId });
214
673
  if ("data" in response) {
@@ -219,6 +678,12 @@ const ReleaseDetailsLayout = ({
219
678
  defaultMessage: "Release was published successfully."
220
679
  })
221
680
  });
681
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
682
+ trackUsage("didPublishRelease", {
683
+ totalEntries: totalEntries2,
684
+ totalPublishedEntries,
685
+ totalUnpublishedEntries
686
+ });
222
687
  } else if (isAxiosError(response.error)) {
223
688
  toggleNotification({
224
689
  type: "warning",
@@ -231,13 +696,21 @@ const ReleaseDetailsLayout = ({
231
696
  });
232
697
  }
233
698
  };
234
- const openWarningConfirmDialog = () => {
235
- toggleWarningSubmit();
236
- handleTogglePopover();
237
- };
238
699
  const handleRefresh = () => {
239
700
  dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
240
701
  };
702
+ const getCreatedByUser = () => {
703
+ if (!release?.createdBy) {
704
+ return null;
705
+ }
706
+ if (release.createdBy.username) {
707
+ return release.createdBy.username;
708
+ }
709
+ if (release.createdBy.firstname) {
710
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
711
+ }
712
+ return release.createdBy.email;
713
+ };
241
714
  if (isLoadingDetails) {
242
715
  return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
243
716
  }
@@ -259,90 +732,127 @@ const ReleaseDetailsLayout = ({
259
732
  );
260
733
  }
261
734
  const totalEntries = release.actions.meta.count || 0;
262
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
735
+ const hasCreatedByUser = Boolean(getCreatedByUser());
736
+ const isScheduled = release.scheduledAt && release.timezone;
737
+ const numberOfEntriesText = formatMessage(
738
+ {
739
+ id: "content-releases.pages.Details.header-subtitle",
740
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
741
+ },
742
+ { number: totalEntries }
743
+ );
744
+ const scheduledText = isScheduled ? formatMessage(
745
+ {
746
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
747
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
748
+ },
749
+ {
750
+ date: formatDate(new Date(release.scheduledAt), {
751
+ weekday: "long",
752
+ day: "numeric",
753
+ month: "long",
754
+ year: "numeric",
755
+ timeZone: release.timezone
756
+ }),
757
+ time: formatTime(new Date(release.scheduledAt), {
758
+ timeZone: release.timezone,
759
+ hourCycle: "h23"
760
+ }),
761
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
762
+ }
763
+ ) : "";
263
764
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
264
765
  /* @__PURE__ */ jsx(
265
766
  HeaderLayout,
266
767
  {
267
768
  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({
769
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
770
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
771
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
772
+ ] }),
773
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
276
774
  id: "global.back",
277
775
  defaultMessage: "Back"
278
776
  }) }),
279
777
  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
- ),
778
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [
779
+ /* @__PURE__ */ jsx(
780
+ Menu.Trigger,
781
+ {
782
+ as: IconButton,
783
+ paddingLeft: 2,
784
+ paddingRight: 2,
785
+ "aria-label": formatMessage({
786
+ id: "content-releases.header.actions.open-release-actions",
787
+ defaultMessage: "Release edit and delete menu"
788
+ }),
789
+ icon: /* @__PURE__ */ jsx(More, {}),
790
+ variant: "tertiary"
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsxs(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
794
+ /* @__PURE__ */ jsxs(
795
+ Flex,
796
+ {
797
+ alignItems: "center",
798
+ justifyContent: "center",
799
+ direction: "column",
800
+ padding: 1,
801
+ width: "100%",
802
+ children: [
803
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
804
+ /* @__PURE__ */ jsx(PencilIcon, {}),
805
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
806
+ id: "content-releases.header.actions.edit",
807
+ defaultMessage: "Edit"
808
+ }) })
809
+ ] }) }),
810
+ /* @__PURE__ */ jsx(
811
+ StyledMenuItem,
812
+ {
813
+ disabled: !canDelete,
814
+ onSelect: toggleWarningSubmit,
815
+ variant: "danger",
816
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
817
+ /* @__PURE__ */ jsx(TrashIcon, {}),
818
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
819
+ id: "content-releases.header.actions.delete",
820
+ defaultMessage: "Delete"
821
+ }) })
822
+ ] })
823
+ }
824
+ )
825
+ ]
826
+ }
827
+ ),
828
+ /* @__PURE__ */ jsxs(
829
+ ReleaseInfoWrapper,
830
+ {
831
+ direction: "column",
832
+ justifyContent: "center",
833
+ alignItems: "flex-start",
834
+ gap: 1,
835
+ padding: 5,
836
+ children: [
837
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
838
+ id: "content-releases.header.actions.created",
839
+ defaultMessage: "Created"
840
+ }) }),
841
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
842
+ /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
843
+ formatMessage(
844
+ {
845
+ id: "content-releases.header.actions.created.description",
846
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
847
+ },
848
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
849
+ )
850
+ ] })
851
+ ]
852
+ }
853
+ )
854
+ ] })
855
+ ] }),
346
856
  /* @__PURE__ */ jsx(Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
347
857
  id: "content-releases.header.actions.refresh",
348
858
  defaultMessage: "Refresh"
@@ -398,6 +908,9 @@ const ReleaseDetailsBody = () => {
398
908
  isError: isReleaseError,
399
909
  error: releaseError
400
910
  } = useGetReleaseQuery({ id: releaseId });
911
+ const {
912
+ allowedActions: { canUpdate }
913
+ } = useRBAC(PERMISSIONS);
401
914
  const release = releaseData?.data;
402
915
  const selectedGroupBy = query?.groupBy || "contentType";
403
916
  const {
@@ -411,7 +924,7 @@ const ReleaseDetailsBody = () => {
411
924
  releaseId
412
925
  });
413
926
  const [updateReleaseAction] = useUpdateReleaseActionMutation();
414
- const handleChangeType = async (e, actionId) => {
927
+ const handleChangeType = async (e, actionId, actionPath) => {
415
928
  const response = await updateReleaseAction({
416
929
  params: {
417
930
  releaseId,
@@ -419,7 +932,11 @@ const ReleaseDetailsBody = () => {
419
932
  },
420
933
  body: {
421
934
  type: e.target.value
422
- }
935
+ },
936
+ query,
937
+ // We are passing the query params to make optimistic updates
938
+ actionPath
939
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
423
940
  });
424
941
  if ("error" in response) {
425
942
  if (isAxiosError(response.error)) {
@@ -480,7 +997,7 @@ const ReleaseDetailsBody = () => {
480
997
  action: /* @__PURE__ */ jsx(
481
998
  LinkButton,
482
999
  {
483
- as: Link$1,
1000
+ as: Link$2,
484
1001
  to: {
485
1002
  pathname: "/content-manager"
486
1003
  },
@@ -500,7 +1017,7 @@ const ReleaseDetailsBody = () => {
500
1017
  SingleSelect,
501
1018
  {
502
1019
  "aria-label": formatMessage({
503
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1020
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
504
1021
  defaultMessage: "Group by"
505
1022
  }),
506
1023
  customizeContent: (value) => formatMessage(
@@ -518,7 +1035,7 @@ const ReleaseDetailsBody = () => {
518
1035
  }
519
1036
  ) }),
520
1037
  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 }) }),
1038
+ /* @__PURE__ */ jsx(Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsx(Badge, { children: key }) }),
522
1039
  /* @__PURE__ */ jsx(
523
1040
  Table.Root,
524
1041
  {
@@ -588,56 +1105,59 @@ const ReleaseDetailsBody = () => {
588
1105
  )
589
1106
  ] }),
590
1107
  /* @__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,
1108
+ /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(
1109
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxs(Tr, { children: [
1110
+ /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
1111
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
1112
+ /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
1113
+ /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
615
1114
  {
616
- action: type,
617
- schema: contentTypes?.[contentType.uid],
618
- components,
619
- entry
1115
+ id: "content-releases.page.ReleaseDetails.table.action-published",
1116
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
1117
+ },
1118
+ {
1119
+ isPublish: type === "publish",
1120
+ b: (children) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children })
1121
+ }
1122
+ ) }) : /* @__PURE__ */ jsx(
1123
+ ReleaseActionOptions,
1124
+ {
1125
+ selected: type,
1126
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
1127
+ name: `release-action-${id}-type`,
1128
+ disabled: !canUpdate
620
1129
  }
621
1130
  ) }),
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,
1131
+ !release.releasedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
1132
+ /* @__PURE__ */ jsx(Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsx(
1133
+ EntryValidationText,
633
1134
  {
634
- releaseId: release.id,
635
- actionId: id
1135
+ action: type,
1136
+ schema: contentTypes?.[contentType.uid],
1137
+ components,
1138
+ entry
636
1139
  }
637
- )
638
- ] }) }) })
639
- ] })
640
- ] }, id)) })
1140
+ ) }),
1141
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { children: [
1142
+ /* @__PURE__ */ jsx(
1143
+ ReleaseActionMenu.ReleaseActionEntryLinkItem,
1144
+ {
1145
+ contentTypeUid: contentType.uid,
1146
+ entryId: entry.id,
1147
+ locale: locale?.code
1148
+ }
1149
+ ),
1150
+ /* @__PURE__ */ jsx(
1151
+ ReleaseActionMenu.DeleteReleaseActionItem,
1152
+ {
1153
+ releaseId: release.id,
1154
+ actionId: id
1155
+ }
1156
+ )
1157
+ ] }) }) })
1158
+ ] })
1159
+ ] }, id)
1160
+ ) })
641
1161
  ] })
642
1162
  }
643
1163
  )
@@ -660,7 +1180,7 @@ const ReleaseDetailsPage = () => {
660
1180
  const { releaseId } = useParams();
661
1181
  const toggleNotification = useNotification();
662
1182
  const { formatAPIError } = useAPIErrorHandler();
663
- const { push } = useHistory();
1183
+ const { replace } = useHistory();
664
1184
  const [releaseModalShown, setReleaseModalShown] = React.useState(false);
665
1185
  const [showWarningSubmit, setWarningSubmit] = React.useState(false);
666
1186
  const {
@@ -684,11 +1204,18 @@ const ReleaseDetailsPage = () => {
684
1204
  }
685
1205
  );
686
1206
  }
687
- const title = isSuccessDetails && data?.data?.name || "";
1207
+ const releaseData = isSuccessDetails && data?.data || null;
1208
+ const title = releaseData?.name || "";
1209
+ const timezone = releaseData?.timezone ?? null;
1210
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1211
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
1212
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
688
1213
  const handleEditRelease = async (values) => {
689
1214
  const response = await updateRelease({
690
1215
  id: releaseId,
691
- name: values.name
1216
+ name: values.name,
1217
+ scheduledAt: values.scheduledAt,
1218
+ timezone: values.timezone
692
1219
  });
693
1220
  if ("data" in response) {
694
1221
  toggleNotification({
@@ -716,7 +1243,7 @@ const ReleaseDetailsPage = () => {
716
1243
  id: releaseId
717
1244
  });
718
1245
  if ("data" in response) {
719
- push("/plugins/content-releases");
1246
+ replace("/plugins/content-releases");
720
1247
  } else if (isAxiosError(response.error)) {
721
1248
  toggleNotification({
722
1249
  type: "warning",
@@ -742,7 +1269,14 @@ const ReleaseDetailsPage = () => {
742
1269
  handleClose: toggleEditReleaseModal,
743
1270
  handleSubmit: handleEditRelease,
744
1271
  isLoading: isLoadingDetails || isSubmittingForm,
745
- initialValues: { name: title || "" }
1272
+ initialValues: {
1273
+ name: title || "",
1274
+ scheduledAt,
1275
+ date,
1276
+ time,
1277
+ isScheduled: Boolean(scheduledAt),
1278
+ timezone
1279
+ }
746
1280
  }
747
1281
  ),
748
1282
  /* @__PURE__ */ jsx(
@@ -762,247 +1296,6 @@ const ReleaseDetailsPage = () => {
762
1296
  }
763
1297
  );
764
1298
  };
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
1299
  const App = () => {
1007
1300
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
1008
1301
  /* @__PURE__ */ jsx(Route, { exact: true, path: `/plugins/${pluginId}`, component: ReleasesPage }),
@@ -1012,4 +1305,4 @@ const App = () => {
1012
1305
  export {
1013
1306
  App
1014
1307
  };
1015
- //# sourceMappingURL=App-L1jSxCiL.mjs.map
1308
+ //# sourceMappingURL=App-R-kksTW7.mjs.map