@strapi/content-releases 4.20.3 → 4.20.5

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-rEfNT9PC.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,137 +243,206 @@ 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
  },
253
+ onTextValueChange: (timezone) => {
254
+ setFieldValue("timezone", timezone);
255
+ },
251
256
  onClear: () => {
252
257
  setFieldValue("timezone", "");
253
258
  },
254
259
  error: errors.timezone,
255
260
  required: true,
256
- 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))
257
262
  }
258
263
  );
259
264
  };
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
- }
265
+ const LinkCard = styled(Link)`
266
+ display: block;
273
267
  `;
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
- }
268
+ const CapitalizeRelativeTime = styled(RelativeTime)`
269
+ text-transform: capitalize;
280
270
  `;
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};
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";
286
289
  }
287
- `;
288
- const TypographyMaxWidth = styled(Typography)`
289
- max-width: 300px;
290
- `;
291
- const EntryValidationText = ({ action, schema, components, entry }) => {
290
+ return {
291
+ textColor: `${color}600`,
292
+ backgroundColor: `${color}100`,
293
+ borderColor: `${color}200`
294
+ };
295
+ };
296
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
292
297
  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
- ] });
298
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
299
+ if (isError) {
300
+ return /* @__PURE__ */ jsx(AnErrorOccurred, {});
310
301
  }
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
- ] });
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
+ );
322
318
  }
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
- ] });
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)) });
333
350
  };
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();
351
+ const StyledAlert = styled(Alert)`
352
+ button {
353
+ display: none;
354
+ }
355
+ p + div {
356
+ margin-left: auto;
357
+ }
358
+ `;
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
367
+ };
368
+ const ReleasesPage = () => {
369
+ const tabRef = React.useRef(null);
370
+ const location = useLocation();
371
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
348
372
  const toggleNotification = useNotification();
373
+ const { formatMessage } = useIntl();
374
+ const { push, replace } = useHistory();
349
375
  const { formatAPIError } = useAPIErrorHandler();
350
- const {
351
- allowedActions: { canUpdate, canDelete }
352
- } = useRBAC(PERMISSIONS);
353
- 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");
354
381
  const { trackUsage } = useTracking();
355
- const release = data?.data;
356
- const handlePublishRelease = async () => {
357
- const response = await publishRelease({ id: releaseId });
358
- 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) {
359
387
  toggleNotification({
360
- 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
+ }),
361
393
  message: formatMessage({
362
- id: "content-releases.pages.ReleaseDetails.publish-notification-success",
363
- defaultMessage: "Release was published successfully."
394
+ id: "content-releases.pages.Releases.notification.error.message",
395
+ defaultMessage: "Please try again or open another release."
364
396
  })
365
397
  });
366
- const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
367
- trackUsage("didPublishRelease", {
368
- totalEntries: totalEntries2,
369
- totalPublishedEntries,
370
- 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
+ })
371
439
  });
372
- } else if (isAxiosError(response.error)) {
440
+ trackUsage("didCreateRelease");
441
+ push(`/plugins/content-releases/${response2.data.data.id}`);
442
+ } else if (isAxiosError(response2.error)) {
373
443
  toggleNotification({
374
444
  type: "warning",
375
- message: formatAPIError(response.error)
445
+ message: formatAPIError(response2.error)
376
446
  });
377
447
  } else {
378
448
  toggleNotification({
@@ -381,33 +451,292 @@ const ReleaseDetailsLayout = ({
381
451
  });
382
452
  }
383
453
  };
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
- }
397
- return release.createdBy.email;
398
- };
399
- if (isLoadingDetails) {
400
- return /* @__PURE__ */ jsx(Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsx(LoadingIndicatorPage, {}) });
401
- }
402
- if (isError || !release) {
403
- return /* @__PURE__ */ jsx(
404
- Redirect,
454
+ return /* @__PURE__ */ jsxs(Main, { "aria-busy": isLoading, children: [
455
+ /* @__PURE__ */ jsx(
456
+ HeaderLayout,
405
457
  {
406
- to: {
407
- pathname: "/plugins/content-releases",
408
- state: {
409
- errors: [
410
- {
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: {
738
+ errors: [
739
+ {
411
740
  code: error?.code
412
741
  }
413
742
  ]
@@ -452,8 +781,11 @@ const ReleaseDetailsLayout = ({
452
781
  HeaderLayout,
453
782
  {
454
783
  title: release.name,
455
- subtitle: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : ""),
456
- 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({
457
789
  id: "global.back",
458
790
  defaultMessage: "Back"
459
791
  }) }),
@@ -483,42 +815,28 @@ const ReleaseDetailsLayout = ({
483
815
  padding: 1,
484
816
  width: "100%",
485
817
  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,
818
+ /* @__PURE__ */ jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
819
+ /* @__PURE__ */ jsx(PencilIcon, {}),
820
+ /* @__PURE__ */ jsx(Typography, { ellipsis: true, children: formatMessage({
821
+ id: "content-releases.header.actions.edit",
822
+ defaultMessage: "Edit"
823
+ }) })
824
+ ] }) }),
825
+ /* @__PURE__ */ jsx(
826
+ StyledMenuItem,
506
827
  {
507
- paddingTop: 2,
508
- paddingBottom: 2,
509
- alignItems: "center",
510
- gap: 2,
511
- hasRadius: true,
512
- width: "100%",
513
- children: [
828
+ disabled: !canDelete,
829
+ onSelect: toggleWarningSubmit,
830
+ variant: "danger",
831
+ children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
514
832
  /* @__PURE__ */ jsx(TrashIcon, {}),
515
833
  /* @__PURE__ */ jsx(Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
516
834
  id: "content-releases.header.actions.delete",
517
835
  defaultMessage: "Delete"
518
836
  }) })
519
- ]
837
+ ] })
520
838
  }
521
- ) })
839
+ )
522
840
  ]
523
841
  }
524
842
  ),
@@ -694,7 +1012,7 @@ const ReleaseDetailsBody = () => {
694
1012
  action: /* @__PURE__ */ jsx(
695
1013
  LinkButton,
696
1014
  {
697
- as: Link$1,
1015
+ as: Link$2,
698
1016
  to: {
699
1017
  pathname: "/content-manager"
700
1018
  },
@@ -877,272 +1195,55 @@ const ReleaseDetailsPage = () => {
877
1195
  const { releaseId } = useParams();
878
1196
  const toggleNotification = useNotification();
879
1197
  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 = () => {
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 = () => {
1107
1209
  setReleaseModalShown((prev) => !prev);
1108
1210
  };
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
- }
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, {}) })
1123
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
1124
1234
  });
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) {
1235
+ if ("data" in response) {
1133
1236
  toggleNotification({
1134
1237
  type: "success",
1135
1238
  message: formatMessage({
1136
- id: "content-releases.modal.release-created-notification-success",
1137
- defaultMessage: "Release created."
1239
+ id: "content-releases.modal.release-updated-notification-success",
1240
+ defaultMessage: "Release updated."
1138
1241
  })
1139
1242
  });
1140
- trackUsage("didCreateRelease");
1141
- push(`/plugins/content-releases/${response2.data.data.id}`);
1142
- } else if (isAxiosError(response2.error)) {
1243
+ } else if (isAxiosError(response.error)) {
1143
1244
  toggleNotification({
1144
1245
  type: "warning",
1145
- message: formatAPIError(response2.error)
1246
+ message: formatAPIError(response.error)
1146
1247
  });
1147
1248
  } else {
1148
1249
  toggleNotification({
@@ -1150,135 +1251,65 @@ const ReleasesPage = () => {
1150
1251
  message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1151
1252
  });
1152
1253
  }
1254
+ toggleEditReleaseModal();
1153
1255
  };
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,
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,
1257
1283
  {
1258
- options: ["8", "16", "32", "64"],
1259
- 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
+ }
1260
1295
  }
1261
1296
  ),
1262
1297
  /* @__PURE__ */ jsx(
1263
- PaginationURLQuery,
1298
+ ConfirmDialog,
1264
1299
  {
1265
- pagination: {
1266
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1267
- }
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
1268
1308
  }
1269
1309
  )
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
- ] });
1310
+ ]
1311
+ }
1312
+ );
1282
1313
  };
1283
1314
  const App = () => {
1284
1315
  return /* @__PURE__ */ jsx(CheckPagePermissions, { permissions: PERMISSIONS.main, children: /* @__PURE__ */ jsxs(Switch, { children: [
@@ -1289,4 +1320,4 @@ const App = () => {
1289
1320
  export {
1290
1321
  App
1291
1322
  };
1292
- //# sourceMappingURL=App-hWBsb1nt.mjs.map
1323
+ //# sourceMappingURL=App-bpzO2Ljh.mjs.map