@strapi/content-releases 0.0.0-next.73143c28059b343ba62d98c29672ab114562fbbc → 0.0.0-next.78ea7925e0dad75936ae2e937a041a0666e3d65a

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-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-AECgcaDa.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";
@@ -56,12 +56,12 @@ const ReleaseModal = ({
56
56
  if (!date || !time || !timezone)
57
57
  return null;
58
58
  const formattedDate = parse(time, "HH:mm", new Date(date));
59
- const timezoneWithoutOffset = timezone.split("_")[1];
59
+ const timezoneWithoutOffset = timezone.split("&")[1];
60
60
  return zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
61
61
  };
62
62
  const getTimezoneWithOffset = () => {
63
63
  const currentTimezone = timezoneList.find(
64
- (timezone) => timezone.value.split("_")[1] === initialValues.timezone
64
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
65
65
  );
66
66
  return currentTimezone?.value || systemTimezone.value;
67
67
  };
@@ -79,7 +79,7 @@ const ReleaseModal = ({
79
79
  onSubmit: (values) => {
80
80
  handleSubmit({
81
81
  ...values,
82
- timezone: values.timezone ? values.timezone.split("_")[1] : null,
82
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
83
83
  scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
84
84
  });
85
85
  },
@@ -162,7 +162,8 @@ const ReleaseModal = ({
162
162
  setFieldValue("date", null);
163
163
  },
164
164
  selectedDate: values.date || void 0,
165
- required: true
165
+ required: true,
166
+ minDate: utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
166
167
  }
167
168
  ) }),
168
169
  /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
@@ -214,10 +215,10 @@ const ReleaseModal = ({
214
215
  const getTimezones = (selectedDate) => {
215
216
  const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
216
217
  const utcOffset = getTimezoneOffset(timezone, selectedDate);
217
- return { offset: utcOffset, value: `${utcOffset}_${timezone}` };
218
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
218
219
  });
219
220
  const systemTimezone = timezoneList.find(
220
- (timezone) => timezone.value.split("_")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
221
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
221
222
  );
222
223
  return { timezoneList, systemTimezone };
223
224
  };
@@ -229,7 +230,7 @@ const TimezoneComponent = ({ timezoneOptions }) => {
229
230
  if (values.date) {
230
231
  const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
231
232
  setTimezoneList(timezoneList2);
232
- const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("_")[1] === values.timezone.split("_")[1]);
233
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
233
234
  if (updatedTimezone) {
234
235
  setFieldValue("timezone", updatedTimezone.value);
235
236
  }
@@ -242,9 +243,10 @@ const TimezoneComponent = ({ timezoneOptions }) => {
242
243
  id: "content-releases.modal.form.input.label.timezone",
243
244
  defaultMessage: "Timezone"
244
245
  }),
246
+ autocomplete: { type: "list", filter: "contains" },
245
247
  name: "timezone",
246
248
  value: values.timezone || void 0,
247
- textValue: values.timezone ? values.timezone.replace("_", " ") : void 0,
249
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
248
250
  onChange: (timezone) => {
249
251
  setFieldValue("timezone", timezone);
250
252
  },
@@ -256,130 +258,191 @@ const TimezoneComponent = ({ timezoneOptions }) => {
256
258
  },
257
259
  error: errors.timezone,
258
260
  required: true,
259
- children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace("_", " ") }, timezone.value))
261
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsx(ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
260
262
  }
261
263
  );
262
264
  };
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};
265
+ const LinkCard = styled(Link)`
266
+ display: block;
268
267
  `;
269
- const StyledMenuItem = styled(Menu.Item)`
270
- svg path {
271
- fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
268
+ const CapitalizeRelativeTime = styled(RelativeTime)`
269
+ text-transform: capitalize;
270
+ `;
271
+ const getBadgeProps = (status) => {
272
+ let color;
273
+ switch (status) {
274
+ case "ready":
275
+ color = "success";
276
+ break;
277
+ case "blocked":
278
+ color = "warning";
279
+ break;
280
+ case "failed":
281
+ color = "danger";
282
+ break;
283
+ case "done":
284
+ color = "primary";
285
+ break;
286
+ case "empty":
287
+ default:
288
+ color = "neutral";
272
289
  }
273
- span {
274
- color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
290
+ return {
291
+ textColor: `${color}600`,
292
+ backgroundColor: `${color}100`,
293
+ borderColor: `${color}200`
294
+ };
295
+ };
296
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
297
+ const { formatMessage } = useIntl();
298
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
299
+ if (isError) {
300
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
275
301
  }
276
-
277
- &:hover {
278
- background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
302
+ if (releases?.length === 0) {
303
+ return /* @__PURE__ */ jsx(
304
+ EmptyStateLayout,
305
+ {
306
+ content: formatMessage(
307
+ {
308
+ id: "content-releases.page.Releases.tab.emptyEntries",
309
+ defaultMessage: "No releases"
310
+ },
311
+ {
312
+ target: sectionTitle
313
+ }
314
+ ),
315
+ icon: /* @__PURE__ */ jsx(EmptyDocuments, { width: "10rem" })
316
+ }
317
+ );
279
318
  }
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};
319
+ return /* @__PURE__ */ jsx(Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt, status }) => /* @__PURE__ */ jsx(GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxs(
320
+ Flex,
321
+ {
322
+ direction: "column",
323
+ justifyContent: "space-between",
324
+ padding: 4,
325
+ hasRadius: true,
326
+ background: "neutral0",
327
+ shadow: "tableShadow",
328
+ height: "100%",
329
+ width: "100%",
330
+ alignItems: "start",
331
+ gap: 4,
332
+ children: [
333
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
334
+ /* @__PURE__ */ jsx(Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
335
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
336
+ id: "content-releases.pages.Releases.not-scheduled",
337
+ defaultMessage: "Not scheduled"
338
+ }) : formatMessage(
339
+ {
340
+ id: "content-releases.page.Releases.release-item.entries",
341
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
342
+ },
343
+ { number: actions.meta.count }
344
+ ) })
345
+ ] }),
346
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(status), children: status })
347
+ ]
348
+ }
349
+ ) }) }, id)) });
350
+ };
351
+ const StyledAlert = styled(Alert)`
352
+ button {
353
+ display: none;
286
354
  }
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};
355
+ p + div {
356
+ margin-left: auto;
293
357
  }
294
358
  `;
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
- ] });
359
+ const INITIAL_FORM_VALUES = {
360
+ name: "",
361
+ date: null,
362
+ time: "",
363
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
364
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
365
+ scheduledAt: null,
366
+ timezone: null
340
367
  };
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();
368
+ const ReleasesPage = () => {
369
+ const tabRef = React.useRef(null);
370
+ const location = useLocation();
371
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
355
372
  const toggleNotification = useNotification();
373
+ const { formatMessage } = useIntl();
374
+ const { push, replace } = useHistory();
356
375
  const { formatAPIError } = useAPIErrorHandler();
357
- const {
358
- allowedActions: { canUpdate, canDelete }
359
- } = useRBAC(PERMISSIONS);
360
- const dispatch = useTypedDispatch();
376
+ const [{ query }, setQuery] = useQueryParams();
377
+ const response = useGetReleasesQuery(query);
378
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
379
+ const { getFeature } = useLicenseLimits();
380
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
361
381
  const { trackUsage } = useTracking();
362
- const release = data?.data;
363
- const handlePublishRelease = async () => {
364
- const response = await publishRelease({ id: releaseId });
365
- if ("data" in response) {
382
+ const { isLoading, isSuccess, isError } = response;
383
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
384
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
385
+ React.useEffect(() => {
386
+ if (location?.state?.errors) {
366
387
  toggleNotification({
367
- type: "success",
388
+ type: "warning",
389
+ title: formatMessage({
390
+ id: "content-releases.pages.Releases.notification.error.title",
391
+ defaultMessage: "Your request could not be processed."
392
+ }),
368
393
  message: formatMessage({
369
- id: "content-releases.pages.ReleaseDetails.publish-notification-success",
370
- defaultMessage: "Release was published successfully."
394
+ id: "content-releases.pages.Releases.notification.error.message",
395
+ defaultMessage: "Please try again or open another release."
371
396
  })
372
397
  });
373
- const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
374
- trackUsage("didPublishRelease", {
375
- totalEntries: totalEntries2,
376
- totalPublishedEntries,
377
- totalUnpublishedEntries
398
+ replace({ state: null });
399
+ }
400
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
401
+ React.useEffect(() => {
402
+ if (tabRef.current) {
403
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
404
+ }
405
+ }, [activeTabIndex]);
406
+ const toggleAddReleaseModal = () => {
407
+ setReleaseModalShown((prev) => !prev);
408
+ };
409
+ if (isLoading) {
410
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
411
+ }
412
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
413
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
414
+ const handleTabChange = (index) => {
415
+ setQuery({
416
+ ...query,
417
+ page: 1,
418
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
419
+ filters: {
420
+ releasedAt: {
421
+ $notNull: index === 0 ? false : true
422
+ }
423
+ }
424
+ });
425
+ };
426
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
427
+ const response2 = await createRelease({
428
+ name,
429
+ scheduledAt,
430
+ timezone
431
+ });
432
+ if ("data" in response2) {
433
+ toggleNotification({
434
+ type: "success",
435
+ message: formatMessage({
436
+ id: "content-releases.modal.release-created-notification-success",
437
+ defaultMessage: "Release created."
438
+ })
378
439
  });
379
- } else if (isAxiosError(response.error)) {
440
+ trackUsage("didCreateRelease");
441
+ push(`/plugins/content-releases/${response2.data.data.id}`);
442
+ } else if (isAxiosError(response2.error)) {
380
443
  toggleNotification({
381
444
  type: "warning",
382
- message: formatAPIError(response.error)
445
+ message: formatAPIError(response2.error)
383
446
  });
384
447
  } else {
385
448
  toggleNotification({
@@ -388,31 +451,290 @@ const ReleaseDetailsLayout = ({
388
451
  });
389
452
  }
390
453
  };
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) {
402
- return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
403
- }
404
- return release.createdBy.email;
405
- };
406
- if (isLoadingDetails) {
407
- return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
408
- }
409
- if (isError || !release) {
410
- return /* @__PURE__ */ jsx(
411
- Redirect,
454
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
455
+ /* @__PURE__ */ jsx(
456
+ HeaderLayout,
412
457
  {
413
- to: {
414
- pathname: "/plugins/content-releases",
415
- state: {
458
+ title: formatMessage({
459
+ id: "content-releases.pages.Releases.title",
460
+ defaultMessage: "Releases"
461
+ }),
462
+ subtitle: formatMessage({
463
+ id: "content-releases.pages.Releases.header-subtitle",
464
+ defaultMessage: "Create and manage content updates"
465
+ }),
466
+ primaryAction: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.create, children: /* @__PURE__ */ jsx(
467
+ Button,
468
+ {
469
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
470
+ onClick: toggleAddReleaseModal,
471
+ disabled: hasReachedMaximumPendingReleases,
472
+ children: formatMessage({
473
+ id: "content-releases.header.actions.add-release",
474
+ defaultMessage: "New release"
475
+ })
476
+ }
477
+ ) })
478
+ }
479
+ ),
480
+ /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
481
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsx(
482
+ StyledAlert,
483
+ {
484
+ marginBottom: 6,
485
+ action: /* @__PURE__ */ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
486
+ id: "content-releases.pages.Releases.max-limit-reached.action",
487
+ defaultMessage: "Explore plans"
488
+ }) }),
489
+ title: formatMessage(
490
+ {
491
+ id: "content-releases.pages.Releases.max-limit-reached.title",
492
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
493
+ },
494
+ { number: maximumReleases }
495
+ ),
496
+ onClose: () => {
497
+ },
498
+ closeLabel: "",
499
+ children: formatMessage({
500
+ id: "content-releases.pages.Releases.max-limit-reached.message",
501
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
502
+ })
503
+ }
504
+ ),
505
+ /* @__PURE__ */ jsxs(
506
+ TabGroup,
507
+ {
508
+ label: formatMessage({
509
+ id: "content-releases.pages.Releases.tab-group.label",
510
+ defaultMessage: "Releases list"
511
+ }),
512
+ variant: "simple",
513
+ initialSelectedTabIndex: activeTabIndex,
514
+ onTabChange: handleTabChange,
515
+ ref: tabRef,
516
+ children: [
517
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 8, children: [
518
+ /* @__PURE__ */ jsxs(Tabs, { children: [
519
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage(
520
+ {
521
+ id: "content-releases.pages.Releases.tab.pending",
522
+ defaultMessage: "Pending ({count})"
523
+ },
524
+ {
525
+ count: totalPendingReleases
526
+ }
527
+ ) }),
528
+ /* @__PURE__ */ jsx(Tab, { children: formatMessage({
529
+ id: "content-releases.pages.Releases.tab.done",
530
+ defaultMessage: "Done"
531
+ }) })
532
+ ] }),
533
+ /* @__PURE__ */ jsx(Divider, {})
534
+ ] }),
535
+ /* @__PURE__ */ jsxs(TabPanels, { children: [
536
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
537
+ ReleasesGrid,
538
+ {
539
+ sectionTitle: "pending",
540
+ releases: response?.currentData?.data,
541
+ isError
542
+ }
543
+ ) }),
544
+ /* @__PURE__ */ jsx(TabPanel, { children: /* @__PURE__ */ jsx(
545
+ ReleasesGrid,
546
+ {
547
+ sectionTitle: "done",
548
+ releases: response?.currentData?.data,
549
+ isError
550
+ }
551
+ ) })
552
+ ] })
553
+ ]
554
+ }
555
+ ),
556
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
557
+ /* @__PURE__ */ jsx(
558
+ PageSizeURLQuery,
559
+ {
560
+ options: ["8", "16", "32", "64"],
561
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
562
+ }
563
+ ),
564
+ /* @__PURE__ */ jsx(
565
+ PaginationURLQuery,
566
+ {
567
+ pagination: {
568
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
569
+ }
570
+ }
571
+ )
572
+ ] }) : null
573
+ ] }) }),
574
+ releaseModalShown && /* @__PURE__ */ jsx(
575
+ ReleaseModal,
576
+ {
577
+ handleClose: toggleAddReleaseModal,
578
+ handleSubmit: handleAddRelease,
579
+ isLoading: isSubmittingForm,
580
+ initialValues: INITIAL_FORM_VALUES
581
+ }
582
+ )
583
+ ] });
584
+ };
585
+ const ReleaseInfoWrapper = styled(Flex)`
586
+ align-self: stretch;
587
+ border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
588
+ border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
589
+ border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
590
+ `;
591
+ const StyledMenuItem = styled(Menu.Item)`
592
+ svg path {
593
+ fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
594
+ }
595
+ span {
596
+ color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
597
+ }
598
+
599
+ &:hover {
600
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
601
+ }
602
+ `;
603
+ const PencilIcon = styled(Pencil)`
604
+ width: ${({ theme }) => theme.spaces[3]};
605
+ height: ${({ theme }) => theme.spaces[3]};
606
+ path {
607
+ fill: ${({ theme }) => theme.colors.neutral600};
608
+ }
609
+ `;
610
+ const TrashIcon = styled(Trash)`
611
+ width: ${({ theme }) => theme.spaces[3]};
612
+ height: ${({ theme }) => theme.spaces[3]};
613
+ path {
614
+ fill: ${({ theme }) => theme.colors.danger600};
615
+ }
616
+ `;
617
+ const TypographyMaxWidth = styled(Typography)`
618
+ max-width: 300px;
619
+ `;
620
+ const EntryValidationText = ({ action, schema, components, entry }) => {
621
+ const { formatMessage } = useIntl();
622
+ const { validate } = unstable_useDocument();
623
+ const { errors } = validate(entry, {
624
+ contentType: schema,
625
+ components,
626
+ isCreatingEntry: false
627
+ });
628
+ if (Object.keys(errors).length > 0) {
629
+ const validationErrorsMessages = Object.entries(errors).map(
630
+ ([key, value]) => formatMessage(
631
+ { id: `${value.id}.withField`, defaultMessage: value.defaultMessage },
632
+ { field: key }
633
+ )
634
+ ).join(" ");
635
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
636
+ /* @__PURE__ */ jsx(Icon, { color: "danger600", as: CrossCircle }),
637
+ /* @__PURE__ */ jsx(Tooltip, { description: validationErrorsMessages, children: /* @__PURE__ */ jsx(TypographyMaxWidth, { textColor: "danger600", variant: "omega", fontWeight: "semiBold", ellipsis: true, children: validationErrorsMessages }) })
638
+ ] });
639
+ }
640
+ if (action == "publish") {
641
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
642
+ /* @__PURE__ */ jsx(Icon, { color: "success600", as: CheckCircle }),
643
+ entry.publishedAt ? /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
644
+ id: "content-releases.pages.ReleaseDetails.entry-validation.already-published",
645
+ defaultMessage: "Already published"
646
+ }) }) : /* @__PURE__ */ jsx(Typography, { children: formatMessage({
647
+ id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish",
648
+ defaultMessage: "Ready to publish"
649
+ }) })
650
+ ] });
651
+ }
652
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
653
+ /* @__PURE__ */ jsx(Icon, { color: "success600", as: CheckCircle }),
654
+ !entry.publishedAt ? /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
655
+ id: "content-releases.pages.ReleaseDetails.entry-validation.already-unpublished",
656
+ defaultMessage: "Already unpublished"
657
+ }) }) : /* @__PURE__ */ jsx(Typography, { children: formatMessage({
658
+ id: "content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish",
659
+ defaultMessage: "Ready to unpublish"
660
+ }) })
661
+ ] });
662
+ };
663
+ const ReleaseDetailsLayout = ({
664
+ toggleEditReleaseModal,
665
+ toggleWarningSubmit,
666
+ children
667
+ }) => {
668
+ const { formatMessage, formatDate, formatTime } = useIntl();
669
+ const { releaseId } = useParams();
670
+ const {
671
+ data,
672
+ isLoading: isLoadingDetails,
673
+ isError,
674
+ error
675
+ } = useGetReleaseQuery({ id: releaseId });
676
+ const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
677
+ const toggleNotification = useNotification();
678
+ const { formatAPIError } = useAPIErrorHandler();
679
+ const {
680
+ allowedActions: { canUpdate, canDelete }
681
+ } = useRBAC(PERMISSIONS);
682
+ const dispatch = useTypedDispatch();
683
+ const { trackUsage } = useTracking();
684
+ const release = data?.data;
685
+ const handlePublishRelease = async () => {
686
+ const response = await publishRelease({ id: releaseId });
687
+ if ("data" in response) {
688
+ toggleNotification({
689
+ type: "success",
690
+ message: formatMessage({
691
+ id: "content-releases.pages.ReleaseDetails.publish-notification-success",
692
+ defaultMessage: "Release was published successfully."
693
+ })
694
+ });
695
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
696
+ trackUsage("didPublishRelease", {
697
+ totalEntries: totalEntries2,
698
+ totalPublishedEntries,
699
+ totalUnpublishedEntries
700
+ });
701
+ } else if (isAxiosError(response.error)) {
702
+ toggleNotification({
703
+ type: "warning",
704
+ message: formatAPIError(response.error)
705
+ });
706
+ } else {
707
+ toggleNotification({
708
+ type: "warning",
709
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
710
+ });
711
+ }
712
+ };
713
+ const handleRefresh = () => {
714
+ dispatch(releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
715
+ };
716
+ const getCreatedByUser = () => {
717
+ if (!release?.createdBy) {
718
+ return null;
719
+ }
720
+ if (release.createdBy.username) {
721
+ return release.createdBy.username;
722
+ }
723
+ if (release.createdBy.firstname) {
724
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
725
+ }
726
+ return release.createdBy.email;
727
+ };
728
+ if (isLoadingDetails) {
729
+ return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
730
+ }
731
+ if (isError || !release) {
732
+ return /* @__PURE__ */ jsx(
733
+ Redirect,
734
+ {
735
+ to: {
736
+ pathname: "/plugins/content-releases",
737
+ state: {
416
738
  errors: [
417
739
  {
418
740
  code: error?.code
@@ -459,8 +781,11 @@ const ReleaseDetailsLayout = ({
459
781
  HeaderLayout,
460
782
  {
461
783
  title: release.name,
462
- subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
463
- navigationAction: /* @__PURE__ */ jsx(Link, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
784
+ subtitle: /* @__PURE__ */ jsxs(Flex, { gap: 2, lineHeight: 6, children: [
785
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : "") }),
786
+ /* @__PURE__ */ jsx(Badge, { ...getBadgeProps(release.status), children: release.status })
787
+ ] }),
788
+ navigationAction: /* @__PURE__ */ jsx(Link$1, { startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
464
789
  id: "global.back",
465
790
  defaultMessage: "Back"
466
791
  }) }),
@@ -687,7 +1012,7 @@ const ReleaseDetailsBody = () => {
687
1012
  action: /* @__PURE__ */ jsx(
688
1013
  LinkButton,
689
1014
  {
690
- as: Link$1,
1015
+ as: Link$2,
691
1016
  to: {
692
1017
  pathname: "/content-manager"
693
1018
  },
@@ -851,291 +1176,74 @@ const ReleaseDetailsBody = () => {
851
1176
  ] })
852
1177
  }
853
1178
  )
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 = () => {
1179
+ ] }, `releases-group-${key}`)),
1180
+ /* @__PURE__ */ jsxs(Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1181
+ /* @__PURE__ */ jsx(PageSizeURLQuery, { defaultValue: releaseMeta?.pagination?.pageSize.toString() }),
1182
+ /* @__PURE__ */ jsx(
1183
+ PaginationURLQuery,
1184
+ {
1185
+ pagination: {
1186
+ pageCount: releaseMeta?.pagination?.pageCount || 0
1187
+ }
1188
+ }
1189
+ )
1190
+ ] })
1191
+ ] }) });
1192
+ };
1193
+ const ReleaseDetailsPage = () => {
1194
+ const { formatMessage } = useIntl();
1195
+ const { releaseId } = useParams();
1196
+ const toggleNotification = useNotification();
1197
+ const { formatAPIError } = useAPIErrorHandler();
1198
+ const { replace } = useHistory();
1199
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
1200
+ const [showWarningSubmit, setWarningSubmit] = React.useState(false);
1201
+ const {
1202
+ isLoading: isLoadingDetails,
1203
+ data,
1204
+ isSuccess: isSuccessDetails
1205
+ } = useGetReleaseQuery({ id: releaseId });
1206
+ const [updateRelease, { isLoading: isSubmittingForm }] = useUpdateReleaseMutation();
1207
+ const [deleteRelease, { isLoading: isDeletingRelease }] = useDeleteReleaseMutation();
1208
+ const toggleEditReleaseModal = () => {
1100
1209
  setReleaseModalShown((prev) => !prev);
1101
1210
  };
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
- }
1211
+ const toggleWarningSubmit = () => setWarningSubmit((prevState) => !prevState);
1212
+ if (isLoadingDetails) {
1213
+ return /* @__PURE__ */ jsx(
1214
+ ReleaseDetailsLayout,
1215
+ {
1216
+ toggleEditReleaseModal,
1217
+ toggleWarningSubmit,
1218
+ children: /* @__PURE__ */ jsx(ContentLayout, { children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) })
1116
1219
  }
1220
+ );
1221
+ }
1222
+ const releaseData = isSuccessDetails && data?.data || null;
1223
+ const title = releaseData?.name || "";
1224
+ const timezone = releaseData?.timezone ?? null;
1225
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1226
+ const date = scheduledAt ? new Date(format(scheduledAt, "yyyy-MM-dd")) : null;
1227
+ const time = scheduledAt ? format(scheduledAt, "HH:mm") : "";
1228
+ const handleEditRelease = async (values) => {
1229
+ const response = await updateRelease({
1230
+ id: releaseId,
1231
+ name: values.name,
1232
+ scheduledAt: values.scheduledAt,
1233
+ timezone: values.timezone
1117
1234
  });
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) {
1235
+ if ("data" in response) {
1126
1236
  toggleNotification({
1127
1237
  type: "success",
1128
1238
  message: formatMessage({
1129
- id: "content-releases.modal.release-created-notification-success",
1130
- defaultMessage: "Release created."
1239
+ id: "content-releases.modal.release-updated-notification-success",
1240
+ defaultMessage: "Release updated."
1131
1241
  })
1132
1242
  });
1133
- trackUsage("didCreateRelease");
1134
- push(`/plugins/content-releases/${response2.data.data.id}`);
1135
- } else if (isAxiosError(response2.error)) {
1243
+ } else if (isAxiosError(response.error)) {
1136
1244
  toggleNotification({
1137
1245
  type: "warning",
1138
- message: formatAPIError(response2.error)
1246
+ message: formatAPIError(response.error)
1139
1247
  });
1140
1248
  } else {
1141
1249
  toggleNotification({
@@ -1143,137 +1251,65 @@ const ReleasesPage = () => {
1143
1251
  message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1144
1252
  });
1145
1253
  }
1254
+ toggleEditReleaseModal();
1146
1255
  };
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,
1256
+ const handleDeleteRelease = async () => {
1257
+ const response = await deleteRelease({
1258
+ id: releaseId
1259
+ });
1260
+ if ("data" in response) {
1261
+ replace("/plugins/content-releases");
1262
+ } else if (isAxiosError(response.error)) {
1263
+ toggleNotification({
1264
+ type: "warning",
1265
+ message: formatAPIError(response.error)
1266
+ });
1267
+ } else {
1268
+ toggleNotification({
1269
+ type: "warning",
1270
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1271
+ });
1272
+ }
1273
+ };
1274
+ return /* @__PURE__ */ jsxs(
1275
+ ReleaseDetailsLayout,
1276
+ {
1277
+ toggleEditReleaseModal,
1278
+ toggleWarningSubmit,
1279
+ children: [
1280
+ /* @__PURE__ */ jsx(ReleaseDetailsBody, {}),
1281
+ releaseModalShown && /* @__PURE__ */ jsx(
1282
+ ReleaseModal,
1252
1283
  {
1253
- options: ["8", "16", "32", "64"],
1254
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1284
+ handleClose: toggleEditReleaseModal,
1285
+ handleSubmit: handleEditRelease,
1286
+ isLoading: isLoadingDetails || isSubmittingForm,
1287
+ initialValues: {
1288
+ name: title || "",
1289
+ scheduledAt,
1290
+ date,
1291
+ time,
1292
+ isScheduled: Boolean(scheduledAt),
1293
+ timezone
1294
+ }
1255
1295
  }
1256
1296
  ),
1257
1297
  /* @__PURE__ */ jsx(
1258
- PaginationURLQuery,
1298
+ ConfirmDialog,
1259
1299
  {
1260
- pagination: {
1261
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1262
- }
1300
+ bodyText: {
1301
+ id: "content-releases.dialog.confirmation-message",
1302
+ defaultMessage: "Are you sure you want to delete this release?"
1303
+ },
1304
+ isOpen: showWarningSubmit,
1305
+ isConfirmButtonLoading: isDeletingRelease,
1306
+ onToggleDialog: toggleWarningSubmit,
1307
+ onConfirm: handleDeleteRelease
1263
1308
  }
1264
1309
  )
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
- ] });
1310
+ ]
1311
+ }
1312
+ );
1277
1313
  };
1278
1314
  const App = () => {
1279
1315
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
@@ -1284,4 +1320,4 @@ const App = () => {
1284
1320
  export {
1285
1321
  App
1286
1322
  };
1287
- //# sourceMappingURL=App-g3vtS2Wa.mjs.map
1323
+ //# sourceMappingURL=App-bpzO2Ljh.mjs.map