@strapi/content-releases 0.0.0-next.56199ab7a5f3320e0debcbe4a24fe0b8cd599e21 → 0.0.0-next.5ab818b8ee36a4f090027477a602736c6adbd1a4

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