@strapi/content-releases 0.0.0-next.73143c28059b343ba62d98c29672ab114562fbbc → 0.0.0-next.734763e5757af27ff96ad1c9662161f3f677052a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/_chunks/{App-lnXbSPgp.js → App-c5uGEz9O.js} +633 -608
  2. package/dist/_chunks/App-c5uGEz9O.js.map +1 -0
  3. package/dist/_chunks/{App-g3vtS2Wa.mjs → App-xQ5ljY7-.mjs} +645 -620
  4. package/dist/_chunks/App-xQ5ljY7-.mjs.map +1 -0
  5. package/dist/_chunks/{PurchaseContentReleases-Clm0iACO.mjs → PurchaseContentReleases-3tRbmbY3.mjs} +2 -2
  6. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  7. package/dist/_chunks/{PurchaseContentReleases-YhAPgpG9.js → PurchaseContentReleases-bpIYXOfu.js} +2 -2
  8. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  9. package/dist/_chunks/{en-gcJJ5htG.js → en-3SGjiVyR.js} +10 -3
  10. package/dist/_chunks/en-3SGjiVyR.js.map +1 -0
  11. package/dist/_chunks/{en-WuuhP6Bn.mjs → en-bpHsnU0n.mjs} +10 -3
  12. package/dist/_chunks/en-bpHsnU0n.mjs.map +1 -0
  13. package/dist/_chunks/{index-ItlgrLcr.js → index-4U0Q_Fgd.js} +200 -10
  14. package/dist/_chunks/index-4U0Q_Fgd.js.map +1 -0
  15. package/dist/_chunks/{index-uGex_IIQ.mjs → index-ifoPtgmH.mjs} +211 -21
  16. package/dist/_chunks/index-ifoPtgmH.mjs.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +1 -1
  19. package/dist/server/index.js +264 -154
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +264 -154
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +11 -11
  24. package/dist/_chunks/App-g3vtS2Wa.mjs.map +0 -1
  25. package/dist/_chunks/App-lnXbSPgp.js.map +0 -1
  26. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +0 -1
  27. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +0 -1
  28. package/dist/_chunks/en-WuuhP6Bn.mjs.map +0 -1
  29. package/dist/_chunks/en-gcJJ5htG.js.map +0 -1
  30. package/dist/_chunks/index-ItlgrLcr.js.map +0 -1
  31. package/dist/_chunks/index-uGex_IIQ.mjs.map +0 -1
@@ -1,17 +1,17 @@
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 { g as getTimezoneOffset, 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, h as ReleaseActionMenu, i as isAxiosError, r as releaseApi, j as useGetReleasesQuery, k as useCreateReleaseMutation } from "./index-uGex_IIQ.mjs";
2
+ import { RelativeTime, useNotification, useAPIErrorHandler, useQueryParams, useTracking, LoadingIndicatorPage, CheckPermissions, PageSizeURLQuery, PaginationURLQuery, AnErrorOccurred, ConfirmDialog, useRBAC, NoContent, Table, CheckPagePermissions } from "@strapi/helper-plugin";
3
+ import { useLocation, useHistory, useParams, Redirect, Link as Link$2, Switch, Route } from "react-router-dom";
4
+ import { g as getTimezoneOffset, p as pluginId, u as useGetReleasesQuery, a as useCreateReleaseMutation, P as PERMISSIONS, i as isAxiosError, b as useGetReleaseQuery, c as useUpdateReleaseMutation, d as useDeleteReleaseMutation, e as usePublishReleaseMutation, f as useTypedDispatch, h as useGetReleaseActionsQuery, j as useUpdateReleaseActionMutation, R as ReleaseActionOptions, k as ReleaseActionMenu, r as releaseApi } from "./index-ifoPtgmH.mjs";
5
5
  import * as React from "react";
6
- import { unstable_useDocument, useLicenseLimits } from "@strapi/admin/strapi-admin";
7
- import { ModalLayout, ModalHeader, Typography, ModalBody, Flex, TextInput, Box, Checkbox, DatePicker, TimePicker, ModalFooter, Button, Combobox, ComboboxOption, ContentLayout, Main, HeaderLayout, Link, IconButton, SingleSelect, SingleSelectOption, Badge, Tr, Td, Icon, Tooltip, Alert, TabGroup, 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
10
  import format from "date-fns/format";
11
- import { zonedTimeToUtc, utcToZonedTime } from "date-fns-tz";
11
+ import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
12
12
  import { useIntl } from "react-intl";
13
13
  import styled from "styled-components";
14
- import { formatISO, parse } from "date-fns";
14
+ import { formatISO } from "date-fns";
15
15
  import { Formik, Form, useFormikContext } from "formik";
16
16
  import * as yup from "yup";
17
17
  import "@reduxjs/toolkit/query";
@@ -47,7 +47,6 @@ const ReleaseModal = ({
47
47
  const { formatMessage } = useIntl();
48
48
  const { pathname } = useLocation();
49
49
  const isCreatingRelease = pathname === `/plugins/${pluginId}`;
50
- const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
51
50
  const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
52
51
  initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
53
52
  );
@@ -55,13 +54,12 @@ const ReleaseModal = ({
55
54
  const { date, time, timezone } = values;
56
55
  if (!date || !time || !timezone)
57
56
  return null;
58
- const formattedDate = parse(time, "HH:mm", new Date(date));
59
- const timezoneWithoutOffset = timezone.split("_")[1];
60
- return zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
57
+ const timezoneWithoutOffset = timezone.split("&")[1];
58
+ return zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
61
59
  };
62
60
  const getTimezoneWithOffset = () => {
63
61
  const currentTimezone = timezoneList.find(
64
- (timezone) => timezone.value.split("_")[1] === initialValues.timezone
62
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
65
63
  );
66
64
  return currentTimezone?.value || systemTimezone.value;
67
65
  };
@@ -79,7 +77,7 @@ const ReleaseModal = ({
79
77
  onSubmit: (values) => {
80
78
  handleSubmit({
81
79
  ...values,
82
- timezone: values.timezone ? values.timezone.split("_")[1] : null,
80
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
83
81
  scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
84
82
  });
85
83
  },
@@ -105,92 +103,88 @@ const ReleaseModal = ({
105
103
  required: true
106
104
  }
107
105
  ),
108
- IsSchedulingEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
109
- /* @__PURE__ */ jsx(Box, { width: "max-content", children: /* @__PURE__ */ jsx(
110
- Checkbox,
111
- {
112
- name: "isScheduled",
113
- value: values.isScheduled,
114
- onChange: (event) => {
115
- setFieldValue("isScheduled", event.target.checked);
116
- if (!event.target.checked) {
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: () => {
117
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: () => {
118
180
  setFieldValue("time", "");
119
- setFieldValue("timezone", null);
120
- } else {
121
- setFieldValue("date", initialValues.date);
122
- setFieldValue("time", initialValues.time);
123
- setFieldValue(
124
- "timezone",
125
- initialValues.timezone ?? systemTimezone?.value
126
- );
127
- }
128
- },
129
- children: /* @__PURE__ */ jsx(
130
- Typography,
131
- {
132
- textColor: values.isScheduled ? "primary600" : "neutral800",
133
- fontWeight: values.isScheduled ? "semiBold" : "regular",
134
- children: formatMessage({
135
- id: "modal.form.input.label.schedule-release",
136
- defaultMessage: "Schedule release"
137
- })
138
- }
139
- )
140
- }
141
- ) }),
142
- values.isScheduled && /* @__PURE__ */ jsxs(Fragment, { children: [
143
- /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "start", children: [
144
- /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
145
- DatePicker,
146
- {
147
- label: formatMessage({
148
- id: "content-releases.modal.form.input.label.date",
149
- defaultMessage: "Date"
150
- }),
151
- name: "date",
152
- error: errors.date,
153
- onChange: (date) => {
154
- const isoFormatDate = date ? formatISO(date, { representation: "date" }) : null;
155
- setFieldValue("date", isoFormatDate);
156
- },
157
- clearLabel: formatMessage({
158
- id: "content-releases.modal.form.input.clearLabel",
159
- defaultMessage: "Clear"
160
- }),
161
- onClear: () => {
162
- setFieldValue("date", null);
163
- },
164
- selectedDate: values.date || void 0,
165
- required: true
166
- }
167
- ) }),
168
- /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
169
- TimePicker,
170
- {
171
- label: formatMessage({
172
- id: "content-releases.modal.form.input.label.time",
173
- defaultMessage: "Time"
174
- }),
175
- name: "time",
176
- error: errors.time,
177
- onChange: (time) => {
178
- setFieldValue("time", time);
179
- },
180
- clearLabel: formatMessage({
181
- id: "content-releases.modal.form.input.clearLabel",
182
- defaultMessage: "Clear"
183
- }),
184
- onClear: () => {
185
- setFieldValue("time", "");
186
- },
187
- value: values.time || void 0,
188
- required: true
189
- }
190
- ) })
191
- ] }),
192
- /* @__PURE__ */ jsx(TimezoneComponent, { timezoneOptions: timezoneList })
193
- ] })
181
+ },
182
+ value: values.time || void 0,
183
+ required: true
184
+ }
185
+ ) })
186
+ ] }),
187
+ /* @__PURE__ */ jsx(TimezoneComponent, { timezoneOptions: timezoneList })
194
188
  ] })
195
189
  ] }) }),
196
190
  /* @__PURE__ */ jsx(
@@ -214,10 +208,10 @@ const ReleaseModal = ({
214
208
  const getTimezones = (selectedDate) => {
215
209
  const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
216
210
  const utcOffset = getTimezoneOffset(timezone, selectedDate);
217
- return { offset: utcOffset, value: `${utcOffset}_${timezone}` };
211
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
218
212
  });
219
213
  const systemTimezone = timezoneList.find(
220
- (timezone) => timezone.value.split("_")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
214
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
221
215
  );
222
216
  return { timezoneList, systemTimezone };
223
217
  };
@@ -229,7 +223,7 @@ const TimezoneComponent = ({ timezoneOptions }) => {
229
223
  if (values.date) {
230
224
  const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
231
225
  setTimezoneList(timezoneList2);
232
- const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("_")[1] === values.timezone.split("_")[1]);
226
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
233
227
  if (updatedTimezone) {
234
228
  setFieldValue("timezone", updatedTimezone.value);
235
229
  }
@@ -242,9 +236,10 @@ const TimezoneComponent = ({ timezoneOptions }) => {
242
236
  id: "content-releases.modal.form.input.label.timezone",
243
237
  defaultMessage: "Timezone"
244
238
  }),
239
+ autocomplete: { type: "list", filter: "contains" },
245
240
  name: "timezone",
246
241
  value: values.timezone || void 0,
247
- textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
242
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
248
243
  onChange: (timezone) => {
249
244
  setFieldValue("timezone", timezone);
250
245
  },
@@ -256,130 +251,183 @@ const TimezoneComponent = ({ timezoneOptions }) => {
256
251
  },
257
252
  error: errors.timezone,
258
253
  required: true,
259
- children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
254
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
260
255
  }
261
256
  );
262
257
  };
263
- const ReleaseInfoWrapper = styled(Flex)`
264
- align-self: stretch;
265
- border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
266
- border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
267
- border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
258
+ const LinkCard = styled(Link)`
259
+ display: block;
268
260
  `;
269
- const StyledMenuItem = styled(Menu.Item)`
270
- svg path {
271
- fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
261
+ const CapitalizeRelativeTime = styled(RelativeTime)`
262
+ text-transform: capitalize;
263
+ `;
264
+ const getBadgeProps = (status) => {
265
+ let color;
266
+ switch (status) {
267
+ case "ready":
268
+ color = "success";
269
+ break;
270
+ case "blocked":
271
+ color = "warning";
272
+ break;
273
+ case "failed":
274
+ color = "danger";
275
+ break;
276
+ case "done":
277
+ color = "primary";
278
+ break;
279
+ case "empty":
280
+ default:
281
+ color = "neutral";
272
282
  }
273
- span {
274
- color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
283
+ return {
284
+ textColor: `${color}600`,
285
+ backgroundColor: `${color}100`,
286
+ borderColor: `${color}200`
287
+ };
288
+ };
289
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
290
+ const { formatMessage } = useIntl();
291
+ if (isError) {
292
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
275
293
  }
276
-
277
- &:hover {
278
- background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
294
+ if (releases?.length === 0) {
295
+ return /* @__PURE__ */ jsx(
296
+ EmptyStateLayout,
297
+ {
298
+ content: formatMessage(
299
+ {
300
+ id: "content-releases.page.Releases.tab.emptyEntries",
301
+ defaultMessage: "No releases"
302
+ },
303
+ {
304
+ target: sectionTitle
305
+ }
306
+ ),
307
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
308
+ }
309
+ );
279
310
  }
280
- `;
281
- const PencilIcon = styled(Pencil)`
282
- width: ${({ theme }) => theme.spaces[3]};
283
- height: ${({ theme }) => theme.spaces[3]};
284
- path {
285
- fill: ${({ theme }) => theme.colors.neutral600};
311
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, scheduledAt, status }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
312
+ Flex,
313
+ {
314
+ direction: "column",
315
+ justifyContent: "space-between",
316
+ padding: 4,
317
+ hasRadius: true,
318
+ background: "neutral0",
319
+ shadow: "tableShadow",
320
+ height: "100%",
321
+ width: "100%",
322
+ alignItems: "start",
323
+ gap: 4,
324
+ children: [
325
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
326
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
327
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
328
+ id: "content-releases.pages.Releases.not-scheduled",
329
+ defaultMessage: "Not scheduled"
330
+ }) })
331
+ ] }),
332
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
333
+ ]
334
+ }
335
+ ) }) }, id)) });
336
+ };
337
+ const StyledAlert = styled(Alert)`
338
+ button {
339
+ display: none;
286
340
  }
287
- `;
288
- const TrashIcon = styled(Trash)`
289
- width: ${({ theme }) => theme.spaces[3]};
290
- height: ${({ theme }) => theme.spaces[3]};
291
- path {
292
- fill: ${({ theme }) => theme.colors.danger600};
341
+ p + div {
342
+ margin-left: auto;
293
343
  }
294
344
  `;
295
- const TypographyMaxWidth = styled(Typography)`
296
- max-width: 300px;
297
- `;
298
- const EntryValidationText = ({ action, schema, components, entry }) => {
299
- const { formatMessage } = useIntl();
300
- const { validate } = unstable_useDocument();
301
- const { errors } = validate(entry, {
302
- contentType: schema,
303
- components,
304
- isCreatingEntry: false
305
- });
306
- if (Object.keys(errors).length > 0) {
307
- const validationErrorsMessages = Object.entries(errors).map(
308
- ([key, value]) => formatMessage(
309
- { id: `${value.id}.withField`, defaultMessage: value.defaultMessage },
310
- { field: key }
311
- )
312
- ).join(" ");
313
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
314
- /* @__PURE__ */ jsx(Icon, { color: "danger600", as: CrossCircle }),
315
- /* @__PURE__ */ jsx(Tooltip, { description: validationErrorsMessages, children: /* @__PURE__ */ jsx(TypographyMaxWidth, { textColor: "danger600", variant: "omega", fontWeight: "semiBold", ellipsis: true, children: validationErrorsMessages }) })
316
- ] });
317
- }
318
- if (action == "publish") {
319
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
320
- /* @__PURE__ */ jsx(Icon, { color: "success600", as: CheckCircle }),
321
- entry.publishedAt ? /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
322
- id: "content-releases.pages.ReleaseDetails.entry-validation.already-published",
323
- defaultMessage: "Already published"
324
- }) }) : /* @__PURE__ */ jsx(Typography, { children: formatMessage({
325
- id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish",
326
- defaultMessage: "Ready to publish"
327
- }) })
328
- ] });
329
- }
330
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
331
- /* @__PURE__ */ jsx(Icon, { color: "success600", as: CheckCircle }),
332
- !entry.publishedAt ? /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
333
- id: "content-releases.pages.ReleaseDetails.entry-validation.already-unpublished",
334
- defaultMessage: "Already unpublished"
335
- }) }) : /* @__PURE__ */ jsx(Typography, { children: formatMessage({
336
- id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish",
337
- defaultMessage: "Ready to unpublish"
338
- }) })
339
- ] });
345
+ const INITIAL_FORM_VALUES = {
346
+ name: "",
347
+ date: null,
348
+ time: "",
349
+ isScheduled: true,
350
+ scheduledAt: null,
351
+ timezone: null
340
352
  };
341
- const ReleaseDetailsLayout = ({
342
- toggleEditReleaseModal,
343
- toggleWarningSubmit,
344
- children
345
- }) => {
346
- const { formatMessage, formatDate, formatTime } = useIntl();
347
- const { releaseId } = useParams();
348
- const {
349
- data,
350
- isLoading: isLoadingDetails,
351
- isError,
352
- error
353
- } = useGetReleaseQuery({ id: releaseId });
354
- const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
353
+ const ReleasesPage = () => {
354
+ const tabRef = React.useRef(null);
355
+ const location = useLocation();
356
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
355
357
  const toggleNotification = useNotification();
358
+ const { formatMessage } = useIntl();
359
+ const { push, replace } = useHistory();
356
360
  const { formatAPIError } = useAPIErrorHandler();
357
- const {
358
- allowedActions: { canUpdate, canDelete }
359
- } = useRBAC(PERMISSIONS);
360
- const dispatch = useTypedDispatch();
361
+ const [{ query }, setQuery] = useQueryParams();
362
+ const response = useGetReleasesQuery(query);
363
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
364
+ const { getFeature } = useLicenseLimits();
365
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
361
366
  const { trackUsage } = useTracking();
362
- const release = data?.data;
363
- const handlePublishRelease = async () => {
364
- const response = await publishRelease({ id: releaseId });
365
- if ("data" in response) {
367
+ const { isLoading, isSuccess, isError } = response;
368
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
369
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
370
+ React.useEffect(() => {
371
+ if (location?.state?.errors) {
366
372
  toggleNotification({
367
- type: "success",
373
+ type: "warning",
374
+ title: formatMessage({
375
+ id: "content-releases.pages.Releases.notification.error.title",
376
+ defaultMessage: "Your request could not be processed."
377
+ }),
368
378
  message: formatMessage({
369
- id: "content-releases.pages.ReleaseDetails.publish-notification-success",
370
- defaultMessage: "Release was published successfully."
379
+ id: "content-releases.pages.Releases.notification.error.message",
380
+ defaultMessage: "Please try again or open another release."
371
381
  })
372
382
  });
373
- const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
374
- trackUsage("didPublishRelease", {
375
- totalEntries: totalEntries2,
376
- totalPublishedEntries,
377
- totalUnpublishedEntries
383
+ replace({ state: null });
384
+ }
385
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
386
+ React.useEffect(() => {
387
+ if (tabRef.current) {
388
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
389
+ }
390
+ }, [activeTabIndex]);
391
+ const toggleAddReleaseModal = () => {
392
+ setReleaseModalShown((prev) => !prev);
393
+ };
394
+ if (isLoading) {
395
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
396
+ }
397
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
398
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
399
+ const handleTabChange = (index) => {
400
+ setQuery({
401
+ ...query,
402
+ page: 1,
403
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
404
+ filters: {
405
+ releasedAt: {
406
+ $notNull: index === 0 ? false : true
407
+ }
408
+ }
409
+ });
410
+ };
411
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
412
+ const response2 = await createRelease({
413
+ name,
414
+ scheduledAt,
415
+ timezone
416
+ });
417
+ if ("data" in response2) {
418
+ toggleNotification({
419
+ type: "success",
420
+ message: formatMessage({
421
+ id: "content-releases.modal.release-created-notification-success",
422
+ defaultMessage: "Release created."
423
+ })
378
424
  });
379
- } else if (isAxiosError(response.error)) {
425
+ trackUsage("didCreateRelease");
426
+ push(`/plugins/content-releases/${response2.data.data.id}`);
427
+ } else if (isAxiosError(response2.error)) {
380
428
  toggleNotification({
381
429
  type: "warning",
382
- message: formatAPIError(response.error)
430
+ message: formatAPIError(response2.error)
383
431
  });
384
432
  } else {
385
433
  toggleNotification({
@@ -388,17 +436,281 @@ const ReleaseDetailsLayout = ({
388
436
  });
389
437
  }
390
438
  };
391
- const handleRefresh = () => {
392
- dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
393
- };
394
- const getCreatedByUser = () => {
395
- if (!release?.createdBy) {
396
- return null;
397
- }
398
- if (release.createdBy.username) {
399
- return release.createdBy.username;
400
- }
401
- if (release.createdBy.firstname) {
439
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
440
+ /* @__PURE__ */ jsx(
441
+ HeaderLayout,
442
+ {
443
+ title: formatMessage({
444
+ id: "content-releases.pages.Releases.title",
445
+ defaultMessage: "Releases"
446
+ }),
447
+ subtitle: formatMessage({
448
+ id: "content-releases.pages.Releases.header-subtitle",
449
+ defaultMessage: "Create and manage content updates"
450
+ }),
451
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
452
+ Button,
453
+ {
454
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
455
+ onClick: toggleAddReleaseModal,
456
+ disabled: hasReachedMaximumPendingReleases,
457
+ children: formatMessage({
458
+ id: "content-releases.header.actions.add-release",
459
+ defaultMessage: "New release"
460
+ })
461
+ }
462
+ ) })
463
+ }
464
+ ),
465
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
466
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
467
+ StyledAlert,
468
+ {
469
+ marginBottom: 6,
470
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
471
+ id: "content-releases.pages.Releases.max-limit-reached.action",
472
+ defaultMessage: "Explore plans"
473
+ }) }),
474
+ title: formatMessage(
475
+ {
476
+ id: "content-releases.pages.Releases.max-limit-reached.title",
477
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
478
+ },
479
+ { number: maximumReleases }
480
+ ),
481
+ onClose: () => {
482
+ },
483
+ closeLabel: "",
484
+ children: formatMessage({
485
+ id: "content-releases.pages.Releases.max-limit-reached.message",
486
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
487
+ })
488
+ }
489
+ ),
490
+ /* @__PURE__ */ jsxs(
491
+ TabGroup,
492
+ {
493
+ label: formatMessage({
494
+ id: "content-releases.pages.Releases.tab-group.label",
495
+ defaultMessage: "Releases list"
496
+ }),
497
+ variant: "simple",
498
+ initialSelectedTabIndex: activeTabIndex,
499
+ onTabChange: handleTabChange,
500
+ ref: tabRef,
501
+ children: [
502
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
503
+ /* @__PURE__ */ jsxs(Tabs, { children: [
504
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
505
+ {
506
+ id: "content-releases.pages.Releases.tab.pending",
507
+ defaultMessage: "Pending ({count})"
508
+ },
509
+ {
510
+ count: totalPendingReleases
511
+ }
512
+ ) }),
513
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
514
+ id: "content-releases.pages.Releases.tab.done",
515
+ defaultMessage: "Done"
516
+ }) })
517
+ ] }),
518
+ /* @__PURE__ */ jsx(Divider, {})
519
+ ] }),
520
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
521
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
522
+ ReleasesGrid,
523
+ {
524
+ sectionTitle: "pending",
525
+ releases: response?.currentData?.data,
526
+ isError
527
+ }
528
+ ) }),
529
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
530
+ ReleasesGrid,
531
+ {
532
+ sectionTitle: "done",
533
+ releases: response?.currentData?.data,
534
+ isError
535
+ }
536
+ ) })
537
+ ] })
538
+ ]
539
+ }
540
+ ),
541
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
542
+ /* @__PURE__ */ jsx(
543
+ PageSizeURLQuery,
544
+ {
545
+ options: ["8", "16", "32", "64"],
546
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
547
+ }
548
+ ),
549
+ /* @__PURE__ */ jsx(
550
+ PaginationURLQuery,
551
+ {
552
+ pagination: {
553
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
554
+ }
555
+ }
556
+ )
557
+ ] }) : null
558
+ ] }) }),
559
+ releaseModalShown && /* @__PURE__ */ jsx(
560
+ ReleaseModal,
561
+ {
562
+ handleClose: toggleAddReleaseModal,
563
+ handleSubmit: handleAddRelease,
564
+ isLoading: isSubmittingForm,
565
+ initialValues: INITIAL_FORM_VALUES
566
+ }
567
+ )
568
+ ] });
569
+ };
570
+ const ReleaseInfoWrapper = styled(Flex)`
571
+ align-self: stretch;
572
+ border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
573
+ border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
574
+ border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
575
+ `;
576
+ const StyledMenuItem = styled(Menu.Item)`
577
+ svg path {
578
+ fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
579
+ }
580
+ span {
581
+ color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
582
+ }
583
+
584
+ &:hover {
585
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
586
+ }
587
+ `;
588
+ const PencilIcon = styled(Pencil)`
589
+ width: ${({ theme }) => theme.spaces[3]};
590
+ height: ${({ theme }) => theme.spaces[3]};
591
+ path {
592
+ fill: ${({ theme }) => theme.colors.neutral600};
593
+ }
594
+ `;
595
+ const TrashIcon = styled(Trash)`
596
+ width: ${({ theme }) => theme.spaces[3]};
597
+ height: ${({ theme }) => theme.spaces[3]};
598
+ path {
599
+ fill: ${({ theme }) => theme.colors.danger600};
600
+ }
601
+ `;
602
+ const TypographyMaxWidth = styled(Typography)`
603
+ max-width: 300px;
604
+ `;
605
+ const EntryValidationText = ({ action, schema, components, entry }) => {
606
+ const { formatMessage } = useIntl();
607
+ const { validate } = unstable_useDocument();
608
+ const { errors } = validate(entry, {
609
+ contentType: schema,
610
+ components,
611
+ isCreatingEntry: false
612
+ });
613
+ if (Object.keys(errors).length > 0) {
614
+ const validationErrorsMessages = Object.entries(errors).map(
615
+ ([key, value]) => formatMessage(
616
+ { id: `${value.id}.withField`, defaultMessage: value.defaultMessage },
617
+ { field: key }
618
+ )
619
+ ).join(" ");
620
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
621
+ /* @__PURE__ */ jsx(Icon, { color: "danger600", as: CrossCircle }),
622
+ /* @__PURE__ */ jsx(Tooltip, { description: validationErrorsMessages, children: /* @__PURE__ */ jsx(TypographyMaxWidth, { textColor: "danger600", variant: "omega", fontWeight: "semiBold", ellipsis: true, children: validationErrorsMessages }) })
623
+ ] });
624
+ }
625
+ if (action == "publish") {
626
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
627
+ /* @__PURE__ */ jsx(Icon, { color: "success600", as: CheckCircle }),
628
+ entry.publishedAt ? /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
629
+ id: "content-releases.pages.ReleaseDetails.entry-validation.already-published",
630
+ defaultMessage: "Already published"
631
+ }) }) : /* @__PURE__ */ jsx(Typography, { children: formatMessage({
632
+ id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish",
633
+ defaultMessage: "Ready to publish"
634
+ }) })
635
+ ] });
636
+ }
637
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
638
+ /* @__PURE__ */ jsx(Icon, { color: "success600", as: CheckCircle }),
639
+ !entry.publishedAt ? /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
640
+ id: "content-releases.pages.ReleaseDetails.entry-validation.already-unpublished",
641
+ defaultMessage: "Already unpublished"
642
+ }) }) : /* @__PURE__ */ jsx(Typography, { children: formatMessage({
643
+ id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish",
644
+ defaultMessage: "Ready to unpublish"
645
+ }) })
646
+ ] });
647
+ };
648
+ const ReleaseDetailsLayout = ({
649
+ toggleEditReleaseModal,
650
+ toggleWarningSubmit,
651
+ children
652
+ }) => {
653
+ const { formatMessage, formatDate, formatTime } = useIntl();
654
+ const { releaseId } = useParams();
655
+ const {
656
+ data,
657
+ isLoading: isLoadingDetails,
658
+ isError,
659
+ error
660
+ } = useGetReleaseQuery({ id: releaseId });
661
+ const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
662
+ const toggleNotification = useNotification();
663
+ const { formatAPIError } = useAPIErrorHandler();
664
+ const {
665
+ allowedActions: { canUpdate, canDelete }
666
+ } = useRBAC(PERMISSIONS);
667
+ const dispatch = useTypedDispatch();
668
+ const { trackUsage } = useTracking();
669
+ const release = data?.data;
670
+ const handlePublishRelease = async () => {
671
+ const response = await publishRelease({ id: releaseId });
672
+ if ("data" in response) {
673
+ toggleNotification({
674
+ type: "success",
675
+ message: formatMessage({
676
+ id: "content-releases.pages.ReleaseDetails.publish-notification-success",
677
+ defaultMessage: "Release was published successfully."
678
+ })
679
+ });
680
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
681
+ trackUsage("didPublishRelease", {
682
+ totalEntries: totalEntries2,
683
+ totalPublishedEntries,
684
+ totalUnpublishedEntries
685
+ });
686
+ } else if (isAxiosError(response.error)) {
687
+ toggleNotification({
688
+ type: "warning",
689
+ message: formatAPIError(response.error)
690
+ });
691
+ } else {
692
+ toggleNotification({
693
+ type: "warning",
694
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
695
+ });
696
+ }
697
+ };
698
+ const handleRefresh = () => {
699
+ dispatch(
700
+ releaseApi.util.invalidateTags([
701
+ { type: "ReleaseAction", id: "LIST" },
702
+ { type: "Release", id: releaseId }
703
+ ])
704
+ );
705
+ };
706
+ const getCreatedByUser = () => {
707
+ if (!release?.createdBy) {
708
+ return null;
709
+ }
710
+ if (release.createdBy.username) {
711
+ return release.createdBy.username;
712
+ }
713
+ if (release.createdBy.firstname) {
402
714
  return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
403
715
  }
404
716
  return release.createdBy.email;
@@ -425,7 +737,6 @@ const ReleaseDetailsLayout = ({
425
737
  }
426
738
  const totalEntries = release.actions.meta.count || 0;
427
739
  const hasCreatedByUser = Boolean(getCreatedByUser());
428
- const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
429
740
  const isScheduled = release.scheduledAt && release.timezone;
430
741
  const numberOfEntriesText = formatMessage(
431
742
  {
@@ -459,8 +770,11 @@ const ReleaseDetailsLayout = ({
459
770
  HeaderLayout,
460
771
  {
461
772
  title: release.name,
462
- subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
463
- navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
773
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
774
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
775
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
776
+ ] }),
777
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
464
778
  id: "global.back",
465
779
  defaultMessage: "Back"
466
780
  }) }),
@@ -687,7 +1001,7 @@ const ReleaseDetailsBody = () => {
687
1001
  action: /* @__PURE__ */ jsx(
688
1002
  LinkButton,
689
1003
  {
690
- as: Link$1,
1004
+ as: Link$2,
691
1005
  to: {
692
1006
  pathname: "/content-manager"
693
1007
  },
@@ -850,292 +1164,75 @@ const ReleaseDetailsBody = () => {
850
1164
  ) })
851
1165
  ] })
852
1166
  }
853
- )
854
- ] }, `releases-group-${key}`)),
855
- /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
856
- /* @__PURE__ */ jsx(PageSizeURLQuery, { defaultValue: releaseMeta?.pagination?.pageSize.toString() }),
857
- /* @__PURE__ */ jsx(
858
- PaginationURLQuery,
859
- {
860
- pagination: {
861
- pageCount: releaseMeta?.pagination?.pageCount || 0
862
- }
863
- }
864
- )
865
- ] })
866
- ] }) });
867
- };
868
- const ReleaseDetailsPage = () => {
869
- const { formatMessage } = useIntl();
870
- const { releaseId } = useParams();
871
- const toggleNotification = useNotification();
872
- const { formatAPIError } = useAPIErrorHandler();
873
- const { push } = useHistory();
874
- const [releaseModalShown, setReleaseModalShown] = React.useState(false);
875
- const [showWarningSubmit, setWarningSubmit] = React.useState(false);
876
- const {
877
- isLoading: isLoadingDetails,
878
- data,
879
- isSuccess: isSuccessDetails
880
- } = useGetReleaseQuery({ id: releaseId });
881
- const [updateRelease, { isLoading: isSubmittingForm }] = useUpdateReleaseMutation();
882
- const [deleteRelease, { isLoading: isDeletingRelease }] = useDeleteReleaseMutation();
883
- const toggleEditReleaseModal = () => {
884
- setReleaseModalShown((prev) => !prev);
885
- };
886
- const toggleWarningSubmit = () => setWarningSubmit((prevState) => !prevState);
887
- if (isLoadingDetails) {
888
- return /* @__PURE__ */ jsx(
889
- ReleaseDetailsLayout,
890
- {
891
- toggleEditReleaseModal,
892
- toggleWarningSubmit,
893
- children: /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) })
894
- }
895
- );
896
- }
897
- const releaseData = isSuccessDetails && data?.data || null;
898
- const title = releaseData?.name || "";
899
- const timezone = releaseData?.timezone ?? null;
900
- const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
901
- const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
902
- const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
903
- const handleEditRelease = async (values) => {
904
- const response = await updateRelease({
905
- id: releaseId,
906
- name: values.name,
907
- scheduledAt: values.scheduledAt,
908
- timezone: values.timezone
909
- });
910
- if ("data" in response) {
911
- toggleNotification({
912
- type: "success",
913
- message: formatMessage({
914
- id: "content-releases.modal.release-updated-notification-success",
915
- defaultMessage: "Release updated."
916
- })
917
- });
918
- } else if (isAxiosError(response.error)) {
919
- toggleNotification({
920
- type: "warning",
921
- message: formatAPIError(response.error)
922
- });
923
- } else {
924
- toggleNotification({
925
- type: "warning",
926
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
927
- });
928
- }
929
- toggleEditReleaseModal();
930
- };
931
- const handleDeleteRelease = async () => {
932
- const response = await deleteRelease({
933
- id: releaseId
934
- });
935
- if ("data" in response) {
936
- push("/plugins/content-releases");
937
- } else if (isAxiosError(response.error)) {
938
- toggleNotification({
939
- type: "warning",
940
- message: formatAPIError(response.error)
941
- });
942
- } else {
943
- toggleNotification({
944
- type: "warning",
945
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
946
- });
947
- }
948
- };
949
- return /* @__PURE__ */ jsxs(
950
- ReleaseDetailsLayout,
951
- {
952
- toggleEditReleaseModal,
953
- toggleWarningSubmit,
954
- children: [
955
- /* @__PURE__ */ jsx(ReleaseDetailsBody, {}),
956
- releaseModalShown && /* @__PURE__ */ jsx(
957
- ReleaseModal,
958
- {
959
- handleClose: toggleEditReleaseModal,
960
- handleSubmit: handleEditRelease,
961
- isLoading: isLoadingDetails || isSubmittingForm,
962
- initialValues: {
963
- name: title || "",
964
- scheduledAt,
965
- date,
966
- time,
967
- isScheduled: Boolean(scheduledAt),
968
- timezone
969
- }
970
- }
971
- ),
972
- /* @__PURE__ */ jsx(
973
- ConfirmDialog,
974
- {
975
- bodyText: {
976
- id: "content-releases.dialog.confirmation-message",
977
- defaultMessage: "Are you sure you want to delete this release?"
978
- },
979
- isOpen: showWarningSubmit,
980
- isConfirmButtonLoading: isDeletingRelease,
981
- onToggleDialog: toggleWarningSubmit,
982
- onConfirm: handleDeleteRelease
983
- }
984
- )
985
- ]
986
- }
987
- );
988
- };
989
- const LinkCard = styled(Link$2)`
990
- display: block;
991
- `;
992
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
993
- const { formatMessage } = useIntl();
994
- const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
995
- if (isError) {
996
- return /* @__PURE__ */ jsx(AnErrorOccurred, {});
997
- }
998
- if (releases?.length === 0) {
999
- return /* @__PURE__ */ jsx(
1000
- EmptyStateLayout,
1001
- {
1002
- content: formatMessage(
1003
- {
1004
- id: "content-releases.page.Releases.tab.emptyEntries",
1005
- defaultMessage: "No releases"
1006
- },
1007
- {
1008
- target: sectionTitle
1009
- }
1010
- ),
1011
- icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
1012
- }
1013
- );
1014
- }
1015
- return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
1016
- Flex,
1017
- {
1018
- direction: "column",
1019
- justifyContent: "space-between",
1020
- padding: 4,
1021
- hasRadius: true,
1022
- background: "neutral0",
1023
- shadow: "tableShadow",
1024
- height: "100%",
1025
- width: "100%",
1026
- alignItems: "start",
1027
- gap: 2,
1028
- children: [
1029
- /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
1030
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
1031
- id: "content-releases.pages.Releases.not-scheduled",
1032
- defaultMessage: "Not scheduled"
1033
- }) : formatMessage(
1034
- {
1035
- id: "content-releases.page.Releases.release-item.entries",
1036
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
1037
- },
1038
- { number: actions.meta.count }
1039
- ) })
1040
- ]
1041
- }
1042
- ) }) }, id)) });
1043
- };
1044
- const StyledAlert = styled(Alert)`
1045
- button {
1046
- display: none;
1047
- }
1048
- p + div {
1049
- margin-left: auto;
1050
- }
1051
- `;
1052
- const INITIAL_FORM_VALUES = {
1053
- name: "",
1054
- date: null,
1055
- time: "",
1056
- // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
1057
- isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
1058
- scheduledAt: null,
1059
- timezone: null
1060
- };
1061
- const ReleasesPage = () => {
1062
- const tabRef = React.useRef(null);
1063
- const location = useLocation();
1064
- const [releaseModalShown, setReleaseModalShown] = React.useState(false);
1065
- const toggleNotification = useNotification();
1066
- const { formatMessage } = useIntl();
1067
- const { push, replace } = useHistory();
1068
- const { formatAPIError } = useAPIErrorHandler();
1069
- const [{ query }, setQuery] = useQueryParams();
1070
- const response = useGetReleasesQuery(query);
1071
- const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
1072
- const { getFeature } = useLicenseLimits();
1073
- const { maximumReleases = 3 } = getFeature("cms-content-releases");
1074
- const { trackUsage } = useTracking();
1075
- const { isLoading, isSuccess, isError } = response;
1076
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
1077
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
1078
- React.useEffect(() => {
1079
- if (location?.state?.errors) {
1080
- toggleNotification({
1081
- type: "warning",
1082
- title: formatMessage({
1083
- id: "content-releases.pages.Releases.notification.error.title",
1084
- defaultMessage: "Your request could not be processed."
1085
- }),
1086
- message: formatMessage({
1087
- id: "content-releases.pages.Releases.notification.error.message",
1088
- defaultMessage: "Please try again or open another release."
1089
- })
1090
- });
1091
- replace({ state: null });
1092
- }
1093
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
1094
- React.useEffect(() => {
1095
- if (tabRef.current) {
1096
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
1097
- }
1098
- }, [activeTabIndex]);
1099
- const toggleAddReleaseModal = () => {
1167
+ )
1168
+ ] }, `releases-group-${key}`)),
1169
+ /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1170
+ /* @__PURE__ */ jsx(PageSizeURLQuery, { defaultValue: releaseMeta?.pagination?.pageSize.toString() }),
1171
+ /* @__PURE__ */ jsx(
1172
+ PaginationURLQuery,
1173
+ {
1174
+ pagination: {
1175
+ pageCount: releaseMeta?.pagination?.pageCount || 0
1176
+ }
1177
+ }
1178
+ )
1179
+ ] })
1180
+ ] }) });
1181
+ };
1182
+ const ReleaseDetailsPage = () => {
1183
+ const { formatMessage } = useIntl();
1184
+ const { releaseId } = useParams();
1185
+ const toggleNotification = useNotification();
1186
+ const { formatAPIError } = useAPIErrorHandler();
1187
+ const { replace } = useHistory();
1188
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
1189
+ const [showWarningSubmit, setWarningSubmit] = React.useState(false);
1190
+ const {
1191
+ isLoading: isLoadingDetails,
1192
+ data,
1193
+ isSuccess: isSuccessDetails
1194
+ } = useGetReleaseQuery({ id: releaseId });
1195
+ const [updateRelease, { isLoading: isSubmittingForm }] = useUpdateReleaseMutation();
1196
+ const [deleteRelease, { isLoading: isDeletingRelease }] = useDeleteReleaseMutation();
1197
+ const toggleEditReleaseModal = () => {
1100
1198
  setReleaseModalShown((prev) => !prev);
1101
1199
  };
1102
- if (isLoading) {
1103
- return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
1104
- }
1105
- const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
1106
- const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
1107
- const handleTabChange = (index) => {
1108
- setQuery({
1109
- ...query,
1110
- page: 1,
1111
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
1112
- filters: {
1113
- releasedAt: {
1114
- $notNull: index === 0 ? false : true
1115
- }
1200
+ const toggleWarningSubmit = () => setWarningSubmit((prevState) => !prevState);
1201
+ if (isLoadingDetails) {
1202
+ return /* @__PURE__ */ jsx(
1203
+ ReleaseDetailsLayout,
1204
+ {
1205
+ toggleEditReleaseModal,
1206
+ toggleWarningSubmit,
1207
+ children: /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) })
1116
1208
  }
1209
+ );
1210
+ }
1211
+ const releaseData = isSuccessDetails && data?.data || null;
1212
+ const title = releaseData?.name || "";
1213
+ const timezone = releaseData?.timezone ?? null;
1214
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1215
+ const date = scheduledAt ? format(scheduledAt, "yyyy-MM-dd") : null;
1216
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
1217
+ const handleEditRelease = async (values) => {
1218
+ const response = await updateRelease({
1219
+ id: releaseId,
1220
+ name: values.name,
1221
+ scheduledAt: values.scheduledAt,
1222
+ timezone: values.timezone
1117
1223
  });
1118
- };
1119
- const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
1120
- const response2 = await createRelease({
1121
- name,
1122
- scheduledAt,
1123
- timezone
1124
- });
1125
- if ("data" in response2) {
1224
+ if ("data" in response) {
1126
1225
  toggleNotification({
1127
1226
  type: "success",
1128
1227
  message: formatMessage({
1129
- id: "content-releases.modal.release-created-notification-success",
1130
- defaultMessage: "Release created."
1228
+ id: "content-releases.modal.release-updated-notification-success",
1229
+ defaultMessage: "Release updated."
1131
1230
  })
1132
1231
  });
1133
- trackUsage("didCreateRelease");
1134
- push(`/plugins/content-releases/${response2.data.data.id}`);
1135
- } else if (isAxiosError(response2.error)) {
1232
+ } else if (isAxiosError(response.error)) {
1136
1233
  toggleNotification({
1137
1234
  type: "warning",
1138
- message: formatAPIError(response2.error)
1235
+ message: formatAPIError(response.error)
1139
1236
  });
1140
1237
  } else {
1141
1238
  toggleNotification({
@@ -1143,137 +1240,65 @@ const ReleasesPage = () => {
1143
1240
  message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1144
1241
  });
1145
1242
  }
1243
+ toggleEditReleaseModal();
1146
1244
  };
1147
- return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
1148
- /* @__PURE__ */ jsx(
1149
- HeaderLayout,
1150
- {
1151
- title: formatMessage({
1152
- id: "content-releases.pages.Releases.title",
1153
- defaultMessage: "Releases"
1154
- }),
1155
- subtitle: formatMessage({
1156
- id: "content-releases.pages.Releases.header-subtitle",
1157
- defaultMessage: "Create and manage content updates"
1158
- }),
1159
- primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
1160
- Button,
1161
- {
1162
- startIcon: /* @__PURE__ */ jsx(Plus, {}),
1163
- onClick: toggleAddReleaseModal,
1164
- disabled: hasReachedMaximumPendingReleases,
1165
- children: formatMessage({
1166
- id: "content-releases.header.actions.add-release",
1167
- defaultMessage: "New release"
1168
- })
1169
- }
1170
- ) })
1171
- }
1172
- ),
1173
- /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
1174
- hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
1175
- StyledAlert,
1176
- {
1177
- marginBottom: 6,
1178
- action: /* @__PURE__ */ jsx(Link$2, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
1179
- id: "content-releases.pages.Releases.max-limit-reached.action",
1180
- defaultMessage: "Explore plans"
1181
- }) }),
1182
- title: formatMessage(
1183
- {
1184
- id: "content-releases.pages.Releases.max-limit-reached.title",
1185
- defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
1186
- },
1187
- { number: maximumReleases }
1188
- ),
1189
- onClose: () => {
1190
- },
1191
- closeLabel: "",
1192
- children: formatMessage({
1193
- id: "content-releases.pages.Releases.max-limit-reached.message",
1194
- defaultMessage: "Upgrade to manage an unlimited number of releases."
1195
- })
1196
- }
1197
- ),
1198
- /* @__PURE__ */ jsxs(
1199
- TabGroup,
1200
- {
1201
- label: formatMessage({
1202
- id: "content-releases.pages.Releases.tab-group.label",
1203
- defaultMessage: "Releases list"
1204
- }),
1205
- variant: "simple",
1206
- initialSelectedTabIndex: activeTabIndex,
1207
- onTabChange: handleTabChange,
1208
- ref: tabRef,
1209
- children: [
1210
- /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
1211
- /* @__PURE__ */ jsxs(Tabs, { children: [
1212
- /* @__PURE__ */ jsx(Tab, { children: formatMessage(
1213
- {
1214
- id: "content-releases.pages.Releases.tab.pending",
1215
- defaultMessage: "Pending ({count})"
1216
- },
1217
- {
1218
- count: totalPendingReleases
1219
- }
1220
- ) }),
1221
- /* @__PURE__ */ jsx(Tab, { children: formatMessage({
1222
- id: "content-releases.pages.Releases.tab.done",
1223
- defaultMessage: "Done"
1224
- }) })
1225
- ] }),
1226
- /* @__PURE__ */ jsx(Divider, {})
1227
- ] }),
1228
- /* @__PURE__ */ jsxs(TabPanels, { children: [
1229
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1230
- ReleasesGrid,
1231
- {
1232
- sectionTitle: "pending",
1233
- releases: response?.currentData?.data,
1234
- isError
1235
- }
1236
- ) }),
1237
- /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
1238
- ReleasesGrid,
1239
- {
1240
- sectionTitle: "done",
1241
- releases: response?.currentData?.data,
1242
- isError
1243
- }
1244
- ) })
1245
- ] })
1246
- ]
1247
- }
1248
- ),
1249
- response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1250
- /* @__PURE__ */ jsx(
1251
- PageSizeURLQuery,
1245
+ const handleDeleteRelease = async () => {
1246
+ const response = await deleteRelease({
1247
+ id: releaseId
1248
+ });
1249
+ if ("data" in response) {
1250
+ replace("/plugins/content-releases");
1251
+ } else if (isAxiosError(response.error)) {
1252
+ toggleNotification({
1253
+ type: "warning",
1254
+ message: formatAPIError(response.error)
1255
+ });
1256
+ } else {
1257
+ toggleNotification({
1258
+ type: "warning",
1259
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1260
+ });
1261
+ }
1262
+ };
1263
+ return /* @__PURE__ */ jsxs(
1264
+ ReleaseDetailsLayout,
1265
+ {
1266
+ toggleEditReleaseModal,
1267
+ toggleWarningSubmit,
1268
+ children: [
1269
+ /* @__PURE__ */ jsx(ReleaseDetailsBody, {}),
1270
+ releaseModalShown && /* @__PURE__ */ jsx(
1271
+ ReleaseModal,
1252
1272
  {
1253
- options: ["8", "16", "32", "64"],
1254
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1273
+ handleClose: toggleEditReleaseModal,
1274
+ handleSubmit: handleEditRelease,
1275
+ isLoading: isLoadingDetails || isSubmittingForm,
1276
+ initialValues: {
1277
+ name: title || "",
1278
+ scheduledAt,
1279
+ date,
1280
+ time,
1281
+ isScheduled: Boolean(scheduledAt),
1282
+ timezone
1283
+ }
1255
1284
  }
1256
1285
  ),
1257
1286
  /* @__PURE__ */ jsx(
1258
- PaginationURLQuery,
1287
+ ConfirmDialog,
1259
1288
  {
1260
- pagination: {
1261
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1262
- }
1289
+ bodyText: {
1290
+ id: "content-releases.dialog.confirmation-message",
1291
+ defaultMessage: "Are you sure you want to delete this release?"
1292
+ },
1293
+ isOpen: showWarningSubmit,
1294
+ isConfirmButtonLoading: isDeletingRelease,
1295
+ onToggleDialog: toggleWarningSubmit,
1296
+ onConfirm: handleDeleteRelease
1263
1297
  }
1264
1298
  )
1265
- ] }) : null
1266
- ] }) }),
1267
- releaseModalShown && /* @__PURE__ */ jsx(
1268
- ReleaseModal,
1269
- {
1270
- handleClose: toggleAddReleaseModal,
1271
- handleSubmit: handleAddRelease,
1272
- isLoading: isSubmittingForm,
1273
- initialValues: INITIAL_FORM_VALUES
1274
- }
1275
- )
1276
- ] });
1299
+ ]
1300
+ }
1301
+ );
1277
1302
  };
1278
1303
  const App = () => {
1279
1304
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
@@ -1284,4 +1309,4 @@ const App = () => {
1284
1309
  export {
1285
1310
  App
1286
1311
  };
1287
- //# sourceMappingURL=App-g3vtS2Wa.mjs.map
1312
+ //# sourceMappingURL=App-xQ5ljY7-.mjs.map