diff --git a/packages/core/admin/admin/src/index.ts b/packages/core/admin/admin/src/index.ts index a662976485..c7e1e454cc 100644 --- a/packages/core/admin/admin/src/index.ts +++ b/packages/core/admin/admin/src/index.ts @@ -5,3 +5,5 @@ export type { Store } from './core/store/configure'; export type { SanitizedAdminUser } from '../../shared/contracts/shared'; export { useDocument as unstable_useDocument } from './hooks/useDocument'; +// TODO: Replace this export with the same hook exported from the @strapi/admin/strapi-admin/ee in another iteration of this solution +export { useLicenseLimits } from '../../ee/admin/src/hooks/useLicenseLimits'; diff --git a/packages/core/admin/shared/contracts/admin.ts b/packages/core/admin/shared/contracts/admin.ts index 190221a447..2e1ee1872c 100644 --- a/packages/core/admin/shared/contracts/admin.ts +++ b/packages/core/admin/shared/contracts/admin.ts @@ -172,6 +172,13 @@ export interface ReviewWorkflowsFeature { options?: { numberOfWorkflows: number | null; stagesPerWorkflow: number | null }; } +export interface ContentReleasesFeature { + name: 'cms-content-releases'; + options?: { + maximumReleases: number; + }; +} + /** * TODO: this response needs refactoring because we're mixing the admin seat limit info with * regular EE feature info. @@ -185,7 +192,7 @@ export declare namespace GetLicenseLimitInformation { data: { currentActiveUserCount: number; enforcementUserCount: number; - features: (SSOFeature | AuditLogsFeature | ReviewWorkflowsFeature)[]; + features: (SSOFeature | AuditLogsFeature | ReviewWorkflowsFeature | ContentReleasesFeature)[]; isHostedOnStrapiCloud: boolean; licenseLimitStatus: unknown; permittedSeats: number; diff --git a/packages/core/content-releases/admin/src/pages/ReleasesPage.tsx b/packages/core/content-releases/admin/src/pages/ReleasesPage.tsx index be2437fdfd..f07d0a2cca 100644 --- a/packages/core/content-releases/admin/src/pages/ReleasesPage.tsx +++ b/packages/core/content-releases/admin/src/pages/ReleasesPage.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; +// TODO: Replace this import with the same hook exported from the @strapi/admin/strapi-admin/ee in another iteration of this solution +import { useLicenseLimits } from '@strapi/admin/strapi-admin'; import { + Alert, Box, Button, ContentLayout, @@ -44,57 +47,6 @@ import { useCreateReleaseMutation, } from '../services/release'; -/* ------------------------------------------------------------------------------------------------- - * ReleasesLayout - * -----------------------------------------------------------------------------------------------*/ -interface ReleasesLayoutProps { - isLoading?: boolean; - totalReleases?: number; - onClickAddRelease: () => void; - children: React.ReactNode; -} - -export const ReleasesLayout = ({ - isLoading, - totalReleases, - onClickAddRelease, - children, -}: ReleasesLayoutProps) => { - const { formatMessage } = useIntl(); - return ( -
- - - - } - /> - {children} -
- ); -}; - /* ------------------------------------------------------------------------------------------------- * ReleasesGrid * -----------------------------------------------------------------------------------------------*/ @@ -177,6 +129,15 @@ interface CustomLocationState { errors?: Record<'code', string>[]; } +const StyledAlert = styled(Alert)` + button { + display: none; + } + p + div { + margin-left: auto; + } +`; + const INITIAL_FORM_VALUES = { name: '', } satisfies FormValues; @@ -192,7 +153,10 @@ const ReleasesPage = () => { const [{ query }, setQuery] = useQueryParams(); const response = useGetReleasesQuery(query); const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation(); - + const { getFeature } = useLicenseLimits(); + const { maximumNumberOfPendingReleases = 3 } = getFeature('cms-content-releases') as { + maximumNumberOfPendingReleases: number; + }; const { isLoading, isSuccess, isError } = response; const activeTab = response?.currentData?.meta?.activeTab || 'pending'; const activeTabIndex = ['pending', 'done'].indexOf(activeTab); @@ -229,15 +193,14 @@ const ReleasesPage = () => { if (isLoading) { return ( - - - - - +
+ +
); } const totalReleases = (isSuccess && response.currentData?.meta?.pagination?.total) || 0; + const hasReachedMaximumPendingReleases = totalReleases >= maximumNumberOfPendingReleases; const handleTabChange = (index: number) => { setQuery({ @@ -283,9 +246,64 @@ const ReleasesPage = () => { }; return ( - +
+ + + + } + /> <> + {activeTab === 'pending' && hasReachedMaximumPendingReleases && ( + + {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: maximumNumberOfPendingReleases } + )} + onClose={() => {}} + closeLabel="" + > + {formatMessage({ + id: 'content-releases.pages.Releases.max-limit-reached.message', + defaultMessage: 'Upgrade to manage an unlimited number of releases.', + })} + + )} { initialValues={INITIAL_FORM_VALUES} /> )} - +
); }; diff --git a/packages/core/content-releases/admin/src/pages/tests/ReleasesPage.test.tsx b/packages/core/content-releases/admin/src/pages/tests/ReleasesPage.test.tsx index 787fbc17b7..c9bfa0658a 100644 --- a/packages/core/content-releases/admin/src/pages/tests/ReleasesPage.test.tsx +++ b/packages/core/content-releases/admin/src/pages/tests/ReleasesPage.test.tsx @@ -12,6 +12,21 @@ jest.mock('@strapi/helper-plugin', () => ({ CheckPermissions: ({ children }: { children: JSX.Element }) =>
{children}
, })); +jest.mock('@strapi/admin/strapi-admin', () => ({ + ...jest.requireActual('@strapi/admin/strapi-admin'), + useLicenseLimits: jest.fn().mockReturnValue({ + isLoading: false, + isError: false, + license: { + enforcementUserCount: 10, + licenseLimitStatus: '', + permittedSeats: 3, + isHostedOnStrapiCloud: false, + }, + getFeature: jest.fn().mockReturnValue({ maximumReleases: 3 }), + }), +})); + describe('Releases home page', () => { it('renders the tab content correctly when there are no releases', async () => { server.use( @@ -54,5 +69,11 @@ describe('Releases home page', () => { const lastEntry = screen.getByRole('heading', { level: 3, name: 'entry 17' }); expect(lastEntry).toBeInTheDocument(); + + // Check if you reached the maximum number of releases for license + const newReleaseButton = screen.queryByRole('button', { name: /new release/i }); + expect(newReleaseButton).toBeDisabled(); + const limitReachedMessage = screen.getByText(/you have reached the 3 pending releases limit/i); + expect(limitReachedMessage).toBeInTheDocument(); }); }); diff --git a/packages/core/content-releases/admin/src/translations/en.json b/packages/core/content-releases/admin/src/translations/en.json index b309e13271..cc80db47a4 100644 --- a/packages/core/content-releases/admin/src/translations/en.json +++ b/packages/core/content-releases/admin/src/translations/en.json @@ -17,6 +17,9 @@ "plugin.name": "Releases", "pages.Releases.title": "Releases", "pages.Releases.header-subtitle": "{number, plural, =0 {No releases} one {# release} other {# releases}}", + "pages.Releases.max-limit-reached.title": "You have reached the {number} pending {number, plural, one {release} other {releases}} limit.", + "pages.Releases.max-limit-reached.message": "Upgrade to manage an unlimited number of releases.", + "pages.Releases.max-limit-reached.action": "Explore plans", "header.actions.add-release": "New Release", "header.actions.refresh": "Refresh", "header.actions.publish": "Publish",