@strapi/content-releases 0.0.0-next.f8af92b375dc730ba47ed2117f25df893aae696c → 0.0.0-next.fc231041206e6f3999b094160cfa05db2892ad54

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