@strapi/content-releases 0.0.0-experimental.cae3a5a17d131a6f59673b62d01cfac869ea9cc2 → 0.0.0-experimental.ccba4ea6043b6bab83888f85cc9a3f07f61dc262

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