@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 0.0.0-next.734763e5757af27ff96ad1c9662161f3f677052a

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