import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import * as React from 'react'; import { useNotification, useAPIErrorHandler, useQueryParams, useTracking, useRBAC, Page, Layouts, Pagination, isFetchError } from '@strapi/admin/strapi-admin'; import { useLicenseLimits } from '@strapi/admin/strapi-admin/ee'; import { Link, Alert, Main, Button, Tabs, Box, Divider, EmptyStateLayout, Grid, Flex, Typography, Badge } from '@strapi/design-system'; import { Plus } from '@strapi/icons'; import { EmptyDocuments } from '@strapi/icons/symbols'; import { format } from 'date-fns'; import { useIntl } from 'react-intl'; import { useLocation, useNavigate, NavLink } from 'react-router-dom'; import { styled } from 'styled-components'; import { RelativeTime as RelativeTime$1 } from '../components/RelativeTime.mjs'; import { ReleaseModal } from '../components/ReleaseModal.mjs'; import { PERMISSIONS } from '../constants.mjs'; import { useGetReleasesQuery, useGetReleaseSettingsQuery, useCreateReleaseMutation } from '../services/release.mjs'; const LinkCard = styled(Link)` display: block; `; const RelativeTime = styled(RelativeTime$1)` display: inline-block; &::first-letter { text-transform: uppercase; } `; const getBadgeProps = (status)=>{ let color; switch(status){ case 'ready': color = 'success'; break; case 'blocked': color = 'warning'; break; case 'failed': color = 'danger'; break; case 'done': color = 'primary'; break; case 'empty': default: color = 'neutral'; } return { textColor: `${color}600`, backgroundColor: `${color}100`, borderColor: `${color}200` }; }; const ReleasesGrid = ({ sectionTitle, releases = [], isError = false })=>{ const { formatMessage } = useIntl(); if (isError) { return /*#__PURE__*/ jsx(Page.Error, {}); } if (releases?.length === 0) { return /*#__PURE__*/ jsx(EmptyStateLayout, { content: formatMessage({ id: 'content-releases.page.Releases.tab.emptyEntries', defaultMessage: 'No releases' }, { target: sectionTitle }), icon: /*#__PURE__*/ jsx(EmptyDocuments, { width: "16rem" }) }); } return /*#__PURE__*/ jsx(Grid.Root, { gap: 4, children: releases.map(({ id, name, scheduledAt, status })=>/*#__PURE__*/ jsx(Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /*#__PURE__*/ jsx(LinkCard, { tag: NavLink, to: `${id}`, isExternal: false, children: /*#__PURE__*/ jsxs(Flex, { direction: "column", justifyContent: "space-between", padding: 4, hasRadius: true, background: "neutral0", shadow: "tableShadow", height: "100%", width: "100%", alignItems: "start", gap: 4, children: [ /*#__PURE__*/ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [ /*#__PURE__*/ jsx(Typography, { textColor: "neutral800", tag: "h3", variant: "delta", fontWeight: "bold", children: name }), /*#__PURE__*/ jsx(Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /*#__PURE__*/ jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({ id: 'content-releases.pages.Releases.not-scheduled', defaultMessage: 'Not scheduled' }) }) ] }), /*#__PURE__*/ jsx(Badge, { ...getBadgeProps(status), children: status }) ] }) }) }, id)) }); }; /* ------------------------------------------------------------------------------------------------- * ReleasesPage * -----------------------------------------------------------------------------------------------*/ const StyledAlert = styled(Alert)` button { display: none; } p + div { margin-left: auto; } `; const INITIAL_FORM_VALUES = { name: '', date: format(new Date(), 'yyyy-MM-dd'), time: '', isScheduled: true, scheduledAt: null, timezone: null }; const ReleasesPage = ()=>{ const location = useLocation(); const [releaseModalShown, setReleaseModalShown] = React.useState(false); const { toggleNotification } = useNotification(); const { formatMessage } = useIntl(); const navigate = useNavigate(); const { formatAPIError } = useAPIErrorHandler(); const [{ query }, setQuery] = useQueryParams(); const response = useGetReleasesQuery(query); const { data, isLoading: isLoadingSettings } = useGetReleaseSettingsQuery(); const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation(); const { getFeature } = useLicenseLimits(); const { maximumReleases = 3 } = getFeature('cms-content-releases'); const { trackUsage } = useTracking(); const { allowedActions: { canCreate } } = useRBAC(PERMISSIONS); const { isLoading: isLoadingReleases, isSuccess, isError } = response; const activeTab = response?.currentData?.meta?.activeTab || 'pending'; // Check if we have some errors and show a notification to the user to explain the error React.useEffect(()=>{ if (location?.state?.errors) { toggleNotification({ type: 'danger', title: formatMessage({ id: 'content-releases.pages.Releases.notification.error.title', defaultMessage: 'Your request could not be processed.' }), message: formatMessage({ id: 'content-releases.pages.Releases.notification.error.message', defaultMessage: 'Please try again or open another release.' }) }); navigate('', { replace: true, state: null }); } }, [ formatMessage, location?.state?.errors, navigate, toggleNotification ]); const toggleAddReleaseModal = ()=>{ setReleaseModalShown((prev)=>!prev); }; if (isLoadingReleases || isLoadingSettings) { return /*#__PURE__*/ jsx(Page.Loading, {}); } const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0; const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases; const handleTabChange = (tabValue)=>{ setQuery({ ...query, page: 1, pageSize: response?.currentData?.meta?.pagination?.pageSize || 16, filters: { releasedAt: { $notNull: tabValue !== 'pending' } } }); }; const handleAddRelease = async ({ name, scheduledAt, timezone })=>{ const response = await createRelease({ name, scheduledAt, timezone }); if ('data' in response) { // When the response returns an object with 'data', handle success toggleNotification({ type: 'success', message: formatMessage({ id: 'content-releases.modal.release-created-notification-success', defaultMessage: 'Release created.' }) }); trackUsage('didCreateRelease'); navigate(response.data.data.id.toString()); } else if (isFetchError(response.error)) { // When the response returns an object with 'error', handle fetch error toggleNotification({ type: 'danger', message: formatAPIError(response.error) }); } else { // Otherwise, the response returns an object with 'error', handle a generic error toggleNotification({ type: 'danger', message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }) }); } }; return /*#__PURE__*/ jsxs(Main, { "aria-busy": isLoadingReleases || isLoadingSettings, children: [ /*#__PURE__*/ jsx(Layouts.Header, { title: formatMessage({ id: 'content-releases.pages.Releases.title', defaultMessage: 'Releases' }), subtitle: formatMessage({ id: 'content-releases.pages.Releases.header-subtitle', defaultMessage: 'Create and manage content updates' }), primaryAction: canCreate ? /*#__PURE__*/ jsx(Button, { startIcon: /*#__PURE__*/ jsx(Plus, {}), onClick: toggleAddReleaseModal, disabled: hasReachedMaximumPendingReleases, children: formatMessage({ id: 'content-releases.header.actions.add-release', defaultMessage: 'New release' }) }) : null }), /*#__PURE__*/ jsx(Layouts.Content, { children: /*#__PURE__*/ jsxs(Fragment, { children: [ hasReachedMaximumPendingReleases && /*#__PURE__*/ jsx(StyledAlert, { marginBottom: 6, action: /*#__PURE__*/ jsx(Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({ id: 'content-releases.pages.Releases.max-limit-reached.action', defaultMessage: 'Explore plans' }) }), title: formatMessage({ id: 'content-releases.pages.Releases.max-limit-reached.title', defaultMessage: 'You have reached the {number} pending {number, plural, one {release} other {releases}} limit.' }, { number: maximumReleases }), onClose: ()=>{}, closeLabel: "", children: formatMessage({ id: 'content-releases.pages.Releases.max-limit-reached.message', defaultMessage: 'Upgrade to manage an unlimited number of releases.' }) }), /*#__PURE__*/ jsxs(Tabs.Root, { variant: "simple", onValueChange: handleTabChange, value: activeTab, children: [ /*#__PURE__*/ jsxs(Box, { paddingBottom: 8, children: [ /*#__PURE__*/ jsxs(Tabs.List, { "aria-label": formatMessage({ id: 'content-releases.pages.Releases.tab-group.label', defaultMessage: 'Releases list' }), children: [ /*#__PURE__*/ jsx(Tabs.Trigger, { value: "pending", children: formatMessage({ id: 'content-releases.pages.Releases.tab.pending', defaultMessage: 'Pending ({count})' }, { count: totalPendingReleases }) }), /*#__PURE__*/ jsx(Tabs.Trigger, { value: "done", children: formatMessage({ id: 'content-releases.pages.Releases.tab.done', defaultMessage: 'Done' }) }) ] }), /*#__PURE__*/ jsx(Divider, {}) ] }), /*#__PURE__*/ jsx(Tabs.Content, { value: "pending", children: /*#__PURE__*/ jsx(ReleasesGrid, { sectionTitle: "pending", releases: response?.currentData?.data, isError: isError }) }), /*#__PURE__*/ jsx(Tabs.Content, { value: "done", children: /*#__PURE__*/ jsx(ReleasesGrid, { sectionTitle: "done", releases: response?.currentData?.data, isError: isError }) }) ] }), /*#__PURE__*/ jsxs(Pagination.Root, { ...response?.currentData?.meta?.pagination, defaultPageSize: response?.currentData?.meta?.pagination?.pageSize, children: [ /*#__PURE__*/ jsx(Pagination.PageSize, { options: [ '8', '16', '32', '64' ] }), /*#__PURE__*/ jsx(Pagination.Links, {}) ] }) ] }) }), /*#__PURE__*/ jsx(ReleaseModal, { open: releaseModalShown, handleClose: toggleAddReleaseModal, handleSubmit: handleAddRelease, isLoading: isSubmittingForm, initialValues: { ...INITIAL_FORM_VALUES, timezone: data?.data.defaultTimezone ? data.data.defaultTimezone.split('&')[1] : null } }) ] }); }; export { ReleasesPage, getBadgeProps }; //# sourceMappingURL=ReleasesPage.mjs.map