@strapi/content-releases 0.0.0-next.aa7c7ec6724534e157d8a23fe85ee8318dabbf37 → 0.0.0-next.bb2e16aa064f446907390a674fa0eb556b5b75a1

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-pspKUC-W.js → App-HjWtUYmc.js} +639 -380
  2. package/dist/_chunks/App-HjWtUYmc.js.map +1 -0
  3. package/dist/_chunks/{App-8FCxPK8-.mjs → App-gu1aiP6i.mjs} +650 -392
  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-r9YocBH0.js → en-HrREghh3.js} +24 -5
  10. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  11. package/dist/_chunks/{en-m9eTk4UF.mjs → en-ltT1TlKQ.mjs} +24 -5
  12. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  13. package/dist/_chunks/{index-nGaPcY9m.js → index-ZNwxYN8H.js} +397 -19
  14. package/dist/_chunks/index-ZNwxYN8H.js.map +1 -0
  15. package/dist/_chunks/{index-8aK7GzI5.mjs → index-mvj9PSKd.mjs} +413 -35
  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 +953 -443
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +952 -443
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -12
  24. package/dist/_chunks/App-8FCxPK8-.mjs.map +0 -1
  25. package/dist/_chunks/App-pspKUC-W.js.map +0 -1
  26. package/dist/_chunks/en-m9eTk4UF.mjs.map +0 -1
  27. package/dist/_chunks/en-r9YocBH0.js.map +0 -1
  28. package/dist/_chunks/index-8aK7GzI5.mjs.map +0 -1
  29. package/dist/_chunks/index-nGaPcY9m.js.map +0 -1
@@ -1,15 +1,18 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { useNotification, useAPIErrorHandler, LoadingIndicatorPage, ConfirmDialog, useRBAC, useTracking, 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-8aK7GzI5.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, SingleSelect, SingleSelectOption, Badge, Tr, Td, Icon, Tooltip, Alert, TabGroup, Box, Tabs, Tab, Divider, TabPanels, TabPanel, EmptyStateLayout, Grid, GridItem } from "@strapi/design-system";
8
- import { Menu, 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";
@@ -17,8 +20,23 @@ import "@reduxjs/toolkit/query/react";
17
20
  import "react-redux";
18
21
  const RELEASE_SCHEMA = yup.object().shape({
19
22
  name: yup.string().trim().required(),
20
- // scheduledAt is a date, but we always receive strings from the client
21
- scheduledAt: yup.string().nullable()
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
+ })
22
40
  }).required().noUnknown();
23
41
  const ReleaseModal = ({
24
42
  handleClose,
@@ -29,6 +47,22 @@ const ReleaseModal = ({
29
47
  const { formatMessage } = useIntl();
30
48
  const { pathname } = useLocation();
31
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
+ };
32
66
  return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
33
67
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
34
68
  {
@@ -40,45 +74,130 @@ const ReleaseModal = ({
40
74
  /* @__PURE__ */ jsx(
41
75
  Formik,
42
76
  {
43
- validateOnChange: false,
44
- onSubmit: handleSubmit,
45
- 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
+ },
46
88
  validationSchema: RELEASE_SCHEMA,
47
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxs(Form, { children: [
48
- /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx(
49
- TextInput,
50
- {
51
- label: formatMessage({
52
- id: "content-releases.modal.form.input.label.release-name",
53
- defaultMessage: "Name"
54
- }),
55
- name: "name",
56
- value: values.name,
57
- error: errors.name,
58
- onChange: handleChange,
59
- required: true
60
- }
61
- ) }),
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
+ ] }) }),
62
190
  /* @__PURE__ */ jsx(
63
191
  ModalFooter,
64
192
  {
65
193
  startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
66
- endActions: /* @__PURE__ */ jsx(
67
- Button,
194
+ endActions: /* @__PURE__ */ jsx(Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
68
195
  {
69
- name: "submit",
70
- loading: isLoading,
71
- disabled: !values.name || values.name === initialValues.name,
72
- type: "submit",
73
- children: formatMessage(
74
- {
75
- id: "content-releases.modal.form.button.submit",
76
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
77
- },
78
- { isCreatingRelease }
79
- )
80
- }
81
- )
196
+ id: "content-releases.modal.form.button.submit",
197
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
198
+ },
199
+ { isCreatingRelease }
200
+ ) })
82
201
  }
83
202
  )
84
203
  ] })
@@ -86,6 +205,371 @@ const ReleaseModal = ({
86
205
  )
87
206
  ] });
88
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
+ };
89
573
  const ReleaseInfoWrapper = styled(Flex)`
90
574
  align-self: stretch;
91
575
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
@@ -99,6 +583,10 @@ const StyledMenuItem = styled(Menu.Item)`
99
583
  span {
100
584
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
101
585
  }
586
+
587
+ &:hover {
588
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
589
+ }
102
590
  `;
103
591
  const PencilIcon = styled(Pencil)`
104
592
  width: ${({ theme }) => theme.spaces[3]};
@@ -165,7 +653,7 @@ const ReleaseDetailsLayout = ({
165
653
  toggleWarningSubmit,
166
654
  children
167
655
  }) => {
168
- const { formatMessage } = useIntl();
656
+ const { formatMessage, formatDate, formatTime } = useIntl();
169
657
  const { releaseId } = useParams();
170
658
  const {
171
659
  data,
@@ -211,7 +699,12 @@ const ReleaseDetailsLayout = ({
211
699
  }
212
700
  };
213
701
  const handleRefresh = () => {
214
- 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
+ );
215
708
  };
216
709
  const getCreatedByUser = () => {
217
710
  if (!release?.createdBy) {
@@ -247,19 +740,44 @@ const ReleaseDetailsLayout = ({
247
740
  }
248
741
  const totalEntries = release.actions.meta.count || 0;
249
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
+ ) : "";
250
771
  return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoadingDetails, children: [
251
772
  /* @__PURE__ */ jsx(
252
773
  HeaderLayout,
253
774
  {
254
775
  title: release.name,
255
- subtitle: formatMessage(
256
- {
257
- id: "content-releases.pages.Details.header-subtitle",
258
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
259
- },
260
- { number: totalEntries }
261
- ),
262
- 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({
263
781
  id: "global.back",
264
782
  defaultMessage: "Back"
265
783
  }) }),
@@ -289,42 +807,28 @@ const ReleaseDetailsLayout = ({
289
807
  padding: 1,
290
808
  width: "100%",
291
809
  children: [
292
- /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(
293
- Flex,
294
- {
295
- paddingTop: 2,
296
- paddingBottom: 2,
297
- alignItems: "center",
298
- gap: 2,
299
- hasRadius: true,
300
- width: "100%",
301
- children: [
302
- /* @__PURE__ */ jsx(PencilIcon, {}),
303
- /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
304
- id: "content-releases.header.actions.edit",
305
- defaultMessage: "Edit"
306
- }) })
307
- ]
308
- }
309
- ) }),
310
- /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canDelete, onSelect: toggleWarningSubmit, children: /* @__PURE__ */ jsxs(
311
- Flex,
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,
312
819
  {
313
- paddingTop: 2,
314
- paddingBottom: 2,
315
- alignItems: "center",
316
- gap: 2,
317
- hasRadius: true,
318
- width: "100%",
319
- children: [
820
+ disabled: !canDelete,
821
+ onSelect: toggleWarningSubmit,
822
+ variant: "danger",
823
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
320
824
  /* @__PURE__ */ jsx(TrashIcon, {}),
321
825
  /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
322
826
  id: "content-releases.header.actions.delete",
323
827
  defaultMessage: "Delete"
324
828
  }) })
325
- ]
829
+ ] })
326
830
  }
327
- ) })
831
+ )
328
832
  ]
329
833
  }
330
834
  ),
@@ -342,7 +846,7 @@ const ReleaseDetailsLayout = ({
342
846
  defaultMessage: "Created"
343
847
  }) }),
344
848
  /* @__PURE__ */ jsxs(Typography, { variant: "pi", color: "neutral300", children: [
345
- /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(release.createdAt) }),
849
+ /* @__PURE__ */ jsx(RelativeTime$1, { timestamp: new Date(release.createdAt) }),
346
850
  formatMessage(
347
851
  {
348
852
  id: "content-releases.header.actions.created.description",
@@ -381,6 +885,7 @@ const ReleaseDetailsLayout = ({
381
885
  ] });
382
886
  };
383
887
  const GROUP_BY_OPTIONS = ["contentType", "locale", "action"];
888
+ const GROUP_BY_OPTIONS_NO_LOCALE = ["contentType", "action"];
384
889
  const getGroupByOptionLabel = (value) => {
385
890
  if (value === "locale") {
386
891
  return {
@@ -399,6 +904,21 @@ const getGroupByOptionLabel = (value) => {
399
904
  defaultMessage: "Content-Types"
400
905
  };
401
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
+ ];
402
922
  const ReleaseDetailsBody = () => {
403
923
  const { formatMessage } = useIntl();
404
924
  const { releaseId } = useParams();
@@ -414,6 +934,17 @@ const ReleaseDetailsBody = () => {
414
934
  const {
415
935
  allowedActions: { canUpdate }
416
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
+ );
417
948
  const release = releaseData?.data;
418
949
  const selectedGroupBy = query?.groupBy || "contentType";
419
950
  const {
@@ -500,7 +1031,7 @@ const ReleaseDetailsBody = () => {
500
1031
  action: /* @__PURE__ */ jsx(
501
1032
  LinkButton,
502
1033
  {
503
- as: Link$1,
1034
+ as: Link$2,
504
1035
  to: {
505
1036
  pathname: "/content-manager"
506
1037
  },
@@ -515,6 +1046,7 @@ const ReleaseDetailsBody = () => {
515
1046
  }
516
1047
  ) });
517
1048
  }
1049
+ const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
518
1050
  return /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Flex, { gap: 8, direction: "column", alignItems: "stretch", children: [
519
1051
  /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
520
1052
  SingleSelect,
@@ -534,7 +1066,7 @@ const ReleaseDetailsBody = () => {
534
1066
  ),
535
1067
  value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
536
1068
  onChange: (value) => setQuery({ groupBy: value }),
537
- 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))
538
1070
  }
539
1071
  ) }),
540
1072
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxs(Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
@@ -549,30 +1081,17 @@ const ReleaseDetailsBody = () => {
549
1081
  colCount: releaseActions[key].length,
550
1082
  isLoading,
551
1083
  isFetching,
552
- children: /* @__PURE__ */ jsxs(Table.Content, { children: [
553
- /* @__PURE__ */ jsxs(Table.Head, { children: [
554
- /* @__PURE__ */ jsx(
555
- Table.HeaderCell,
556
- {
557
- fieldSchemaType: "string",
558
- label: formatMessage({
559
- id: "content-releases.page.ReleaseDetails.table.header.label.name",
560
- defaultMessage: "name"
561
- }),
562
- name: "name"
563
- }
564
- ),
565
- /* @__PURE__ */ jsx(
566
- Table.HeaderCell,
567
- {
568
- fieldSchemaType: "string",
569
- label: formatMessage({
570
- id: "content-releases.page.ReleaseDetails.table.header.label.locale",
571
- defaultMessage: "locale"
572
- }),
573
- name: "locale"
574
- }
575
- ),
1084
+ children: /* @__PURE__ */ jsxs(Table.Content, { children: [
1085
+ /* @__PURE__ */ jsxs(Table.Head, { children: [
1086
+ displayedHeaders.map(({ key: key2, fieldSchema, metadatas, name }) => /* @__PURE__ */ jsx(
1087
+ Table.HeaderCell,
1088
+ {
1089
+ fieldSchemaType: fieldSchema.type,
1090
+ label: formatMessage(metadatas.label),
1091
+ name
1092
+ },
1093
+ key2
1094
+ )),
576
1095
  /* @__PURE__ */ jsx(
577
1096
  Table.HeaderCell,
578
1097
  {
@@ -611,7 +1130,7 @@ const ReleaseDetailsBody = () => {
611
1130
  /* @__PURE__ */ jsx(Table.Body, { children: releaseActions[key].map(
612
1131
  ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxs(Tr, { children: [
613
1132
  /* @__PURE__ */ jsx(Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
614
- /* @__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 : "-"}` }) }),
615
1134
  /* @__PURE__ */ jsx(Td, { width: "10%", children: /* @__PURE__ */ jsx(Typography, { children: contentType.displayName || "" }) }),
616
1135
  /* @__PURE__ */ jsx(Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsx(Typography, { children: formatMessage(
617
1136
  {
@@ -683,7 +1202,7 @@ const ReleaseDetailsPage = () => {
683
1202
  const { releaseId } = useParams();
684
1203
  const toggleNotification = useNotification();
685
1204
  const { formatAPIError } = useAPIErrorHandler();
686
- const { push } = useHistory();
1205
+ const { replace } = useHistory();
687
1206
  const [releaseModalShown, setReleaseModalShown] = React.useState(false);
688
1207
  const [showWarningSubmit, setWarningSubmit] = React.useState(false);
689
1208
  const {
@@ -707,11 +1226,18 @@ const ReleaseDetailsPage = () => {
707
1226
  }
708
1227
  );
709
1228
  }
710
- 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") : "";
711
1235
  const handleEditRelease = async (values) => {
712
1236
  const response = await updateRelease({
713
1237
  id: releaseId,
714
- name: values.name
1238
+ name: values.name,
1239
+ scheduledAt: values.scheduledAt,
1240
+ timezone: values.timezone
715
1241
  });
716
1242
  if ("data" in response) {
717
1243
  toggleNotification({
@@ -721,6 +1247,7 @@ const ReleaseDetailsPage = () => {
721
1247
  defaultMessage: "Release updated."
722
1248
  })
723
1249
  });
1250
+ toggleEditReleaseModal();
724
1251
  } else if (isAxiosError(response.error)) {
725
1252
  toggleNotification({
726
1253
  type: "warning",
@@ -732,14 +1259,13 @@ const ReleaseDetailsPage = () => {
732
1259
  message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
733
1260
  });
734
1261
  }
735
- toggleEditReleaseModal();
736
1262
  };
737
1263
  const handleDeleteRelease = async () => {
738
1264
  const response = await deleteRelease({
739
1265
  id: releaseId
740
1266
  });
741
1267
  if ("data" in response) {
742
- push("/plugins/content-releases");
1268
+ replace("/plugins/content-releases");
743
1269
  } else if (isAxiosError(response.error)) {
744
1270
  toggleNotification({
745
1271
  type: "warning",
@@ -765,7 +1291,14 @@ const ReleaseDetailsPage = () => {
765
1291
  handleClose: toggleEditReleaseModal,
766
1292
  handleSubmit: handleEditRelease,
767
1293
  isLoading: isLoadingDetails || isSubmittingForm,
768
- initialValues: { name: title || "" }
1294
+ initialValues: {
1295
+ name: title || "",
1296
+ scheduledAt,
1297
+ date,
1298
+ time,
1299
+ isScheduled: Boolean(scheduledAt),
1300
+ timezone
1301
+ }
769
1302
  }
770
1303
  ),
771
1304
  /* @__PURE__ */ jsx(
@@ -785,281 +1318,6 @@ const ReleaseDetailsPage = () => {
785
1318
  }
786
1319
  );
787
1320
  };
788
- const LinkCard = styled(Link$2)`
789
- display: block;
790
- `;
791
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
792
- const { formatMessage } = useIntl();
793
- if (isError) {
794
- return /* @__PURE__ */ jsx(AnErrorOccurred, {});
795
- }
796
- if (releases?.length === 0) {
797
- return /* @__PURE__ */ jsx(
798
- EmptyStateLayout,
799
- {
800
- content: formatMessage(
801
- {
802
- id: "content-releases.page.Releases.tab.emptyEntries",
803
- defaultMessage: "No releases"
804
- },
805
- {
806
- target: sectionTitle
807
- }
808
- ),
809
- icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
810
- }
811
- );
812
- }
813
- 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(
814
- Flex,
815
- {
816
- direction: "column",
817
- justifyContent: "space-between",
818
- padding: 4,
819
- hasRadius: true,
820
- background: "neutral0",
821
- shadow: "tableShadow",
822
- height: "100%",
823
- width: "100%",
824
- alignItems: "start",
825
- gap: 2,
826
- children: [
827
- /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
828
- /* @__PURE__ */ jsx(Typography, { variant: "pi", children: formatMessage(
829
- {
830
- id: "content-releases.page.Releases.release-item.entries",
831
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
832
- },
833
- { number: actions.meta.count }
834
- ) })
835
- ]
836
- }
837
- ) }) }, id)) });
838
- };
839
- const StyledAlert = styled(Alert)`
840
- button {
841
- display: none;
842
- }
843
- p + div {
844
- margin-left: auto;
845
- }
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 { getFeature } = useLicenseLimits();
862
- const { maximumReleases = 3 } = getFeature("cms-content-releases");
863
- const { trackUsage } = useTracking();
864
- const { isLoading, isSuccess, isError } = response;
865
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
866
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
867
- React.useEffect(() => {
868
- if (location?.state?.errors) {
869
- toggleNotification({
870
- type: "warning",
871
- title: formatMessage({
872
- id: "content-releases.pages.Releases.notification.error.title",
873
- defaultMessage: "Your request could not be processed."
874
- }),
875
- message: formatMessage({
876
- id: "content-releases.pages.Releases.notification.error.message",
877
- defaultMessage: "Please try again or open another release."
878
- })
879
- });
880
- replace({ state: null });
881
- }
882
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
883
- React.useEffect(() => {
884
- if (tabRef.current) {
885
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
886
- }
887
- }, [activeTabIndex]);
888
- const toggleAddReleaseModal = () => {
889
- setReleaseModalShown((prev) => !prev);
890
- };
891
- if (isLoading) {
892
- return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
893
- }
894
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
895
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
896
- const handleTabChange = (index) => {
897
- setQuery({
898
- ...query,
899
- page: 1,
900
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
901
- filters: {
902
- releasedAt: {
903
- $notNull: index === 0 ? false : true
904
- }
905
- }
906
- });
907
- };
908
- const handleAddRelease = async (values) => {
909
- const response2 = await createRelease({
910
- name: values.name
911
- });
912
- if ("data" in response2) {
913
- toggleNotification({
914
- type: "success",
915
- message: formatMessage({
916
- id: "content-releases.modal.release-created-notification-success",
917
- defaultMessage: "Release created."
918
- })
919
- });
920
- trackUsage("didCreateRelease");
921
- push(`/plugins/content-releases/${response2.data.data.id}`);
922
- } else if (isAxiosError(response2.error)) {
923
- toggleNotification({
924
- type: "warning",
925
- message: formatAPIError(response2.error)
926
- });
927
- } else {
928
- toggleNotification({
929
- type: "warning",
930
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
931
- });
932
- }
933
- };
934
- return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
935
- /* @__PURE__ */ jsx(
936
- HeaderLayout,
937
- {
938
- title: formatMessage({
939
- id: "content-releases.pages.Releases.title",
940
- defaultMessage: "Releases"
941
- }),
942
- subtitle: formatMessage(
943
- {
944
- id: "content-releases.pages.Releases.header-subtitle",
945
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
946
- },
947
- { number: totalReleases }
948
- ),
949
- primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
950
- Button,
951
- {
952
- startIcon: /* @__PURE__ */ jsx(Plus, {}),
953
- onClick: toggleAddReleaseModal,
954
- disabled: hasReachedMaximumPendingReleases,
955
- children: formatMessage({
956
- id: "content-releases.header.actions.add-release",
957
- defaultMessage: "New release"
958
- })
959
- }
960
- ) })
961
- }
962
- ),
963
- /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
964
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
965
- StyledAlert,
966
- {
967
- marginBottom: 6,
968
- action: /* @__PURE__ */ jsx(Link$2, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
969
- id: "content-releases.pages.Releases.max-limit-reached.action",
970
- defaultMessage: "Explore plans"
971
- }) }),
972
- title: formatMessage(
973
- {
974
- id: "content-releases.pages.Releases.max-limit-reached.title",
975
- defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
976
- },
977
- { number: maximumReleases }
978
- ),
979
- onClose: () => {
980
- },
981
- closeLabel: "",
982
- children: formatMessage({
983
- id: "content-releases.pages.Releases.max-limit-reached.message",
984
- defaultMessage: "Upgrade to manage an unlimited number of releases."
985
- })
986
- }
987
- ),
988
- /* @__PURE__ */ jsxs(
989
- TabGroup,
990
- {
991
- label: formatMessage({
992
- id: "content-releases.pages.Releases.tab-group.label",
993
- defaultMessage: "Releases list"
994
- }),
995
- variant: "simple",
996
- initialSelectedTabIndex: activeTabIndex,
997
- onTabChange: handleTabChange,
998
- ref: tabRef,
999
- children: [
1000
- /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
1001
- /* @__PURE__ */ jsxs(Tabs, { children: [
1002
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
1003
- id: "content-releases.pages.Releases.tab.pending",
1004
- defaultMessage: "Pending"
1005
- }) }),
1006
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
1007
- id: "content-releases.pages.Releases.tab.done",
1008
- defaultMessage: "Done"
1009
- }) })
1010
- ] }),
1011
- /* @__PURE__ */ jsx(Divider, {})
1012
- ] }),
1013
- /* @__PURE__ */ jsxs(TabPanels, { children: [
1014
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1015
- ReleasesGrid,
1016
- {
1017
- sectionTitle: "pending",
1018
- releases: response?.currentData?.data,
1019
- isError
1020
- }
1021
- ) }),
1022
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1023
- ReleasesGrid,
1024
- {
1025
- sectionTitle: "done",
1026
- releases: response?.currentData?.data,
1027
- isError
1028
- }
1029
- ) })
1030
- ] })
1031
- ]
1032
- }
1033
- ),
1034
- totalReleases > 0 && /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1035
- /* @__PURE__ */ jsx(
1036
- PageSizeURLQuery,
1037
- {
1038
- options: ["8", "16", "32", "64"],
1039
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1040
- }
1041
- ),
1042
- /* @__PURE__ */ jsx(
1043
- PaginationURLQuery,
1044
- {
1045
- pagination: {
1046
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1047
- }
1048
- }
1049
- )
1050
- ] })
1051
- ] }) }),
1052
- releaseModalShown && /* @__PURE__ */ jsx(
1053
- ReleaseModal,
1054
- {
1055
- handleClose: toggleAddReleaseModal,
1056
- handleSubmit: handleAddRelease,
1057
- isLoading: isSubmittingForm,
1058
- initialValues: INITIAL_FORM_VALUES
1059
- }
1060
- )
1061
- ] });
1062
- };
1063
1321
  const App = () => {
1064
1322
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
1065
1323
  /* @__PURE__ */ jsx(Route, { exact: true, path: `/plugins/${pluginId}`, component: ReleasesPage }),
@@ -1069,4 +1327,4 @@ const App = () => {
1069
1327
  export {
1070
1328
  App
1071
1329
  };
1072
- //# sourceMappingURL=App-8FCxPK8-.mjs.map
1330
+ //# sourceMappingURL=App-gu1aiP6i.mjs.map