376 lines
16 KiB
JavaScript
376 lines
16 KiB
JavaScript
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
|