diff --git a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js
index 4326a729c2..3a215aaa10 100644
--- a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js
+++ b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js
@@ -11,6 +11,8 @@ import { useIntl } from 'react-intl';
import { useMutation } from 'react-query';
import Information from '../../../../../../admin/src/content-manager/pages/EditView/Information';
+import * as LimitsModal from '../../../../pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal';
+import { useReviewWorkflowLicenseLimits } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflowLicenseLimits';
import { useReviewWorkflows } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows';
import { getStageColorByHex } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
@@ -33,8 +35,11 @@ export function InformationBoxEE() {
const { formatMessage } = useIntl();
const { formatAPIError } = useAPIErrorHandler();
const toggleNotification = useNotification();
+ const { limits } = useReviewWorkflowLicenseLimits();
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
const {
+ pagination,
workflows: [workflow],
isLoading: isWorkflowLoading,
} = useReviewWorkflows({ filters: { contentTypes: uid } });
@@ -72,6 +77,17 @@ export function InformationBoxEE() {
const handleStageChange = async ({ value: stageId }) => {
try {
+ if (limits?.workflows > pagination.total) {
+ setShowLimitModal('workflow');
+
+ return;
+ }
+ if (limits?.stagesPerWorkflow > workflow.stages.length) {
+ setShowLimitModal('stage');
+
+ return;
+ }
+
await mutateAsync({
entityId: initialData.id,
stageId,
@@ -152,6 +168,44 @@ export function InformationBoxEE() {
)}
+
+ setShowLimitModal(false)}
+ >
+
+ {formatMessage({
+ id: 'content-manager.reviewWorkflows.workflows.limit.title',
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'content-manager.reviewWorkflows.workflows.limit.body',
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
+ })}
+
+
+
+ setShowLimitModal(false)}
+ >
+
+ {formatMessage({
+ id: 'content-manager.reviewWorkflows.stages.limit.title',
+ defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'content-manager.reviewWorkflows.stages.limit.body',
+ defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
+ })}
+
+
);
}
diff --git a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/tests/InformationBoxEE.test.js b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/tests/InformationBoxEE.test.js
index b73afa1082..8aa3941b0a 100644
--- a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/tests/InformationBoxEE.test.js
+++ b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/tests/InformationBoxEE.test.js
@@ -1,7 +1,8 @@
-import React from 'react';
+import * as React from 'react';
+import { fixtures } from '@strapi/admin-test-utils';
import { lightTheme, ThemeProvider } from '@strapi/design-system';
-import { useCMEditViewDataManager } from '@strapi/helper-plugin';
+import { useCMEditViewDataManager, RBACContext } from '@strapi/helper-plugin';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from 'react-intl';
@@ -66,13 +67,27 @@ const ComponentFixture = (props) => ;
const setup = (props) => ({
...render(, {
wrapper({ children }) {
- const store = createStore((state = {}) => state, {});
+ const store = createStore((state = {}) => state, {
+ admin_app: {
+ permissions: fixtures.permissions.app,
+ },
+ });
+
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const rbacContextValue = React.useMemo(
+ () => ({
+ allPermissions: fixtures.permissions.allPermissions,
+ }),
+ []
+ );
return (
- {children}
+
+ {children}
+
diff --git a/packages/core/admin/ee/admin/hooks/useLicenseLimits/index.js b/packages/core/admin/ee/admin/hooks/useLicenseLimits/index.js
index 9d2a0ff734..aa4c8f79e1 100644
--- a/packages/core/admin/ee/admin/hooks/useLicenseLimits/index.js
+++ b/packages/core/admin/ee/admin/hooks/useLicenseLimits/index.js
@@ -6,27 +6,27 @@ import { selectAdminPermissions } from '../../../../admin/src/pages/App/selector
const useLicenseLimits = () => {
const permissions = useSelector(selectAdminPermissions);
- const rbac = useRBAC(permissions.settings.users);
-
+ const { get } = useFetchClient();
const {
isLoading: isRBACLoading,
allowedActions: { canRead, canCreate, canUpdate, canDelete },
- } = rbac;
+ } = useRBAC(permissions.settings.users);
const isRBACAllowed = canRead && canCreate && canUpdate && canDelete;
- const { get } = useFetchClient();
- const fetchLicenseLimitInfo = async () => {
- const {
- data: { data },
- } = await get('/admin/license-limit-information');
+ const license = useQuery(
+ ['ee', 'license-limit-info'],
+ async () => {
+ const {
+ data: { data },
+ } = await get('/admin/license-limit-information');
- return data;
- };
-
- const license = useQuery(['ee', 'license-limit-info'], fetchLicenseLimitInfo, {
- enabled: !isRBACLoading && isRBACAllowed,
- });
+ return data;
+ },
+ {
+ enabled: !isRBACLoading && isRBACAllowed,
+ }
+ );
return { license };
};
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/LimitsModal.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/LimitsModal.js
new file mode 100644
index 0000000000..7b14f93944
--- /dev/null
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/LimitsModal.js
@@ -0,0 +1,111 @@
+import * as React from 'react';
+
+import { Box, Flex, IconButton, ModalLayout, ModalBody, Typography } from '@strapi/design-system';
+import { LinkButton } from '@strapi/design-system/v2';
+import { Cross } from '@strapi/icons';
+import PropTypes from 'prop-types';
+import { useIntl } from 'react-intl';
+import styled from 'styled-components';
+
+import balloonImageSrc from './assets/balloon.png';
+
+const TITLE_ID = 'limits-title';
+
+const CTA_LEARN_MORE_HREF = 'https://strapi.io/pricing-cloud';
+const CTA_SALES_HREF = 'https://strapi.io/contact-sales';
+
+export function Title({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+Title.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+export function Body({ children }) {
+ return {children};
+}
+
+Body.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+function CallToActions() {
+ const { formatMessage } = useIntl();
+
+ return (
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.limit.cta.learn',
+ defaultMessage: 'Learn more',
+ })}
+
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.limit.cta.sales',
+ defaultMessage: 'Contact Sales',
+ })}
+
+
+ );
+}
+
+const BalloonImage = styled.img`
+ // Margin top|right reverse the padding of ModalBody
+ margin-right: ${({ theme }) => `-${theme.spaces[7]}`};
+ margin-top: ${({ theme }) => `-${theme.spaces[7]}`};
+ width: 360px;
+`;
+
+export function LimitsModal({ children, isOpen, onClose }) {
+ const { formatMessage } = useIntl();
+
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+ }
+ aria-label={formatMessage({
+ id: 'global.close',
+ defaultMessage: 'Close',
+ })}
+ onClick={onClose}
+ />
+
+
+
+
+
+ );
+}
+
+LimitsModal.defaultProps = {
+ isOpen: false,
+};
+
+LimitsModal.propTypes = {
+ children: PropTypes.node.isRequired,
+ isOpen: PropTypes.bool,
+ onClose: PropTypes.func.isRequired,
+};
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/assets/balloon.png b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/assets/balloon.png
new file mode 100644
index 0000000000..8a779c8993
Binary files /dev/null and b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/assets/balloon.png differ
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/index.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/index.js
new file mode 100644
index 0000000000..ef4508bceb
--- /dev/null
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/index.js
@@ -0,0 +1,3 @@
+import { Title, Body, LimitsModal as Root } from './LimitsModal';
+
+export { Title, Body, Root };
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/tests/LimitsModal.test.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/tests/LimitsModal.test.js
new file mode 100644
index 0000000000..315a5c6332
--- /dev/null
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/tests/LimitsModal.test.js
@@ -0,0 +1,62 @@
+import React from 'react';
+
+import { ThemeProvider, lightTheme } from '@strapi/design-system';
+import { render } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { IntlProvider } from 'react-intl';
+
+import * as LimitsModal from '..';
+
+const setup = (props) => ({
+ ...render(
+ {}} {...props}>
+ Title
+ Body
+ ,
+ {
+ wrapper({ children }) {
+ return (
+
+
+ {children}
+
+
+ );
+ },
+ }
+ ),
+
+ user: userEvent.setup(),
+});
+
+describe('Admin | Settings | Review Workflow | LimitsModal', () => {
+ it('should not render the modal if isOpen=false', () => {
+ const { queryByText } = setup({ isOpen: false });
+
+ expect(queryByText('Title')).not.toBeInTheDocument();
+ expect(queryByText('Body')).not.toBeInTheDocument();
+ });
+
+ it('should render the modal if isOpen=true', () => {
+ const { getByText } = setup();
+
+ expect(getByText('Title')).toBeInTheDocument();
+ expect(getByText('Body')).toBeInTheDocument();
+ });
+
+ it('should render call to action links', () => {
+ const { getByText } = setup();
+
+ expect(getByText('Learn more')).toBeInTheDocument();
+ expect(getByText('Contact Sales')).toBeInTheDocument();
+ });
+
+ it('should call onClose callback when closing the modal', async () => {
+ const onCloseSpy = jest.fn();
+ const { getByRole, user } = setup({ onClose: onCloseSpy });
+
+ await user.click(getByRole('button', { name: /close/i }));
+
+ expect(onCloseSpy).toBeCalledTimes(1);
+ });
+});
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/tests/useReviewWorkflowLicenseLimits.test.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/tests/useReviewWorkflowLicenseLimits.test.js
new file mode 100644
index 0000000000..6fec4f9881
--- /dev/null
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/tests/useReviewWorkflowLicenseLimits.test.js
@@ -0,0 +1,44 @@
+import { renderHook } from '@testing-library/react';
+
+import { useReviewWorkflowLicenseLimits } from '../useReviewWorkflowLicenseLimits';
+
+// TODO: use msw instead. I wish I could have done it already, but in its current
+// state, useLicenseLimits requires to be wrapped in a redux provider and RBAC
+// context provider and at the time of writing there wasn't any time to create
+// that setup.
+
+jest.mock('../../../../../../hooks', () => ({
+ ...jest.requireActual('../../../../../../hooks'),
+ useLicenseLimits: jest.fn(() => ({
+ isLoading: false,
+ license: {
+ data: {
+ something: true,
+ features: [
+ {
+ name: 'review-workflows',
+ options: {
+ workflows: 10,
+ stagesPerWorkflow: 10,
+ },
+ },
+ ],
+ },
+ },
+ })),
+}));
+
+function setup(...args) {
+ return renderHook(() => useReviewWorkflowLicenseLimits(...args));
+}
+
+describe('useReviewWorkflowLicenseLimits', () => {
+ it('returns options for the feature only', async () => {
+ const { result } = setup();
+
+ expect(result.current.limits).toStrictEqual({
+ workflows: 10,
+ stagesPerWorkflow: 10,
+ });
+ });
+});
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/tests/useReviewWorkflows.test.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/tests/useReviewWorkflows.test.js
index 530f2b381d..c6979f9d14 100644
--- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/tests/useReviewWorkflows.test.js
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/tests/useReviewWorkflows.test.js
@@ -25,6 +25,13 @@ const server = setupServer(
stages: populate === 'stages' ? [STAGE_FIXTURE] : [],
},
],
+
+ pagination: {
+ page: 1,
+ pageSize: 100,
+ pageCount: 1,
+ total: 1,
+ },
})
);
}),
@@ -38,6 +45,13 @@ const server = setupServer(
id: 1,
stages: populate === 'stages' ? [STAGE_FIXTURE] : [],
},
+
+ pagination: {
+ page: 1,
+ pageSize: 100,
+ pageCount: 1,
+ total: 1,
+ },
})
);
})
@@ -80,6 +94,7 @@ describe('useReviewWorkflows', () => {
expect(result.current).toStrictEqual(
expect.objectContaining({
status: 'success',
+ pagination: expect.objectContaining({ total: 1 }),
workflows: [{ id: expect.any(Number), stages: expect.any(Array) }],
})
);
@@ -92,6 +107,7 @@ describe('useReviewWorkflows', () => {
expect(result.current).toStrictEqual(
expect.objectContaining({
+ pagination: expect.objectContaining({ total: 1 }),
workflows: [expect.objectContaining({ id: 1, stages: expect.any(Array) })],
})
);
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflowLicenseLimits.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflowLicenseLimits.js
new file mode 100644
index 0000000000..51b38e8ee1
--- /dev/null
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflowLicenseLimits.js
@@ -0,0 +1,12 @@
+import { useLicenseLimits } from '../../../../../hooks';
+
+export function useReviewWorkflowLicenseLimits() {
+ const { license, isLoading } = useLicenseLimits();
+ const limits =
+ license?.data?.features?.filter(({ name }) => name === 'review-workflows')?.[0]?.options ?? {};
+
+ return {
+ limits,
+ isLoading,
+ };
+}
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js
index 9d186bdc55..eb2b64a010 100644
--- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js
@@ -15,9 +15,7 @@ export function useReviewWorkflows(params = {}) {
['review-workflows', 'workflows', id],
async () => {
try {
- const {
- data: { data },
- } = await get(
+ const { data } = await get(
`/admin/review-workflows/workflows/${id}${queryString ? `?${queryString}` : ''}`
);
@@ -31,13 +29,14 @@ export function useReviewWorkflows(params = {}) {
let workflows = [];
- if (id && data) {
- workflows = [data];
- } else if (Array.isArray(data)) {
- workflows = data;
+ if (id && data?.data) {
+ workflows = [data.data];
+ } else if (Array.isArray(data?.data)) {
+ workflows = data.data;
}
return {
+ pagination: data?.pagination ?? {},
workflows,
isLoading,
status,
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js
index a8a895a1cb..0a41179ba5 100644
--- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js
@@ -13,9 +13,12 @@ import { useContentTypes } from '../../../../../../../../admin/src/hooks/useCont
import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
import { resetWorkflow } from '../../actions';
import * as Layout from '../../components/Layout';
+import * as LimitsModal from '../../components/LimitsModal';
import { Stages } from '../../components/Stages';
import { WorkflowAttributes } from '../../components/WorkflowAttributes';
import { REDUX_NAMESPACE } from '../../constants';
+import { useReviewWorkflowLicenseLimits } from '../../hooks/useReviewWorkflowLicenseLimits';
+import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
import { reducer, initialState } from '../../reducer';
import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
@@ -32,6 +35,9 @@ export function ReviewWorkflowsCreateView() {
currentWorkflow: { data: currentWorkflow, isDirty: currentWorkflowIsDirty },
},
} = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
+ const { limits, isLoading: isLicenseLoading } = useReviewWorkflowLicenseLimits();
+ const { pagination, isLoading: isWorkflowLoading } = useReviewWorkflows();
const { mutateAsync, isLoading } = useMutation(
async ({ workflow }) => {
@@ -88,6 +94,23 @@ export function ReviewWorkflowsCreateView() {
dispatch(resetWorkflow());
}, [dispatch]);
+ React.useEffect(() => {
+ if (!isWorkflowLoading && !isLicenseLoading) {
+ if (pagination?.total >= limits?.workflows) {
+ setShowLimitModal('workflow');
+ } else if (currentWorkflow.stages.length >= limits.stagesPerWorkflow) {
+ setShowLimitModal('stage');
+ }
+ }
+ }, [
+ currentWorkflow.stages.length,
+ isLicenseLoading,
+ isWorkflowLoading,
+ limits.stagesPerWorkflow,
+ limits?.workflows,
+ pagination?.total,
+ ]);
+
return (
<>
@@ -141,6 +164,44 @@ export function ReviewWorkflowsCreateView() {
+
+ setShowLimitModal(false)}
+ >
+
+ {formatMessage({
+ id: 'Settings.review-workflows.create.page.workflows.limit.title',
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.create.page.workflows.limit.body',
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
+ })}
+
+
+
+ setShowLimitModal(false)}
+ >
+
+ {formatMessage({
+ id: 'Settings.review-workflows.create.page.stages.limit.title',
+ defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.create.page.stages.limit.body',
+ defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
+ })}
+
+
>
);
}
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js
index 664ceac188..7e015b8ca2 100644
--- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js
@@ -19,9 +19,11 @@ import { useContentTypes } from '../../../../../../../../admin/src/hooks/useCont
import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
import { setWorkflow } from '../../actions';
import * as Layout from '../../components/Layout';
+import * as LimitsModal from '../../components/LimitsModal';
import { Stages } from '../../components/Stages';
import { WorkflowAttributes } from '../../components/WorkflowAttributes';
import { REDUX_NAMESPACE } from '../../constants';
+import { useReviewWorkflowLicenseLimits } from '../../hooks/useReviewWorkflowLicenseLimits';
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
import { reducer, initialState } from '../../reducer';
import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
@@ -35,6 +37,8 @@ export function ReviewWorkflowsEditView() {
const { formatAPIError } = useAPIErrorHandler();
const toggleNotification = useNotification();
const {
+ isLoading: isWorkflowLoading,
+ pagination,
workflows: [workflow],
status: workflowStatus,
refetch,
@@ -51,6 +55,8 @@ export function ReviewWorkflowsEditView() {
},
} = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
const [isConfirmDeleteDialogOpen, setIsConfirmDeleteDialogOpen] = React.useState(false);
+ const { limits, isLoading: isLicenseLoading } = useReviewWorkflowLicenseLimits();
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
const { mutateAsync, isLoading } = useMutation(
async ({ workflow }) => {
@@ -126,6 +132,23 @@ export function ReviewWorkflowsEditView() {
dispatch(setWorkflow({ status: workflowStatus, data: workflow }));
}, [workflowStatus, workflow, dispatch]);
+ React.useEffect(() => {
+ if (!isWorkflowLoading && !isLicenseLoading) {
+ if (pagination?.total >= limits?.workflows) {
+ setShowLimitModal('workflow');
+ } else if (currentWorkflow.stages.length >= limits?.stagesPerWorkflow) {
+ setShowLimitModal('stage');
+ }
+ }
+ }, [
+ currentWorkflow.stages.length,
+ isLicenseLoading,
+ isWorkflowLoading,
+ limits?.stagesPerWorkflow,
+ limits?.workflows,
+ pagination?.total,
+ ]);
+
// TODO redirect back to list-view if workflow is not found?
return (
@@ -191,6 +214,44 @@ export function ReviewWorkflowsEditView() {
onToggleDialog={toggleConfirmDeleteDialog}
onConfirm={handleConfirmDeleteDialog}
/>
+
+ setShowLimitModal(false)}
+ >
+
+ {formatMessage({
+ id: 'Settings.review-workflows.edit.page.workflows.limit.title',
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.edit.page.workflows.limit.body',
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
+ })}
+
+
+
+ setShowLimitModal(false)}
+ >
+
+ {formatMessage({
+ id: 'Settings.review-workflows.edit.page.stages.limit.title',
+ defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.edit.page.stages.limit.body',
+ defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
+ })}
+
+
>
);
}
diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js
index 10e200a33c..4291e54876 100644
--- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js
@@ -32,6 +32,8 @@ import styled from 'styled-components';
import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
import * as Layout from '../../components/Layout';
+import * as LimitsModal from '../../components/LimitsModal';
+import { useReviewWorkflowLicenseLimits } from '../../hooks/useReviewWorkflowLicenseLimits';
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
const ActionLink = styled(Link)`
@@ -65,11 +67,13 @@ export function ReviewWorkflowsListView() {
const { formatMessage } = useIntl();
const { push } = useHistory();
const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
- const { workflows, isLoading, refetch } = useReviewWorkflows();
+ const { pagination, workflows, isLoading, refetch } = useReviewWorkflows();
const [workflowToDelete, setWorkflowToDelete] = React.useState(null);
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
const { del } = useFetchClient();
const { formatAPIError } = useAPIErrorHandler();
const toggleNotification = useNotification();
+ const { limits } = useReviewWorkflowLicenseLimits();
const { mutateAsync, isLoading: isLoadingMutation } = useMutation(
async ({ workflowId, stages }) => {
@@ -129,7 +133,17 @@ export function ReviewWorkflowsListView() {
<>
} size="S" to="/settings/review-workflows/create">
+ }
+ size="S"
+ to="/settings/review-workflows/create"
+ onClick={(event) => {
+ if (pagination?.total >= limits.workflows) {
+ event.preventDefault();
+ setShowLimitModal(true);
+ }
+ }}
+ >
{formatMessage({
id: 'Settings.review-workflows.list.page.create',
defaultMessage: 'Create new workflow',
@@ -158,9 +172,18 @@ export function ReviewWorkflowsListView() {
) : (
} onClick={() => push('/settings/review-workflows/create')}>
+ // TODO: we should be able to use a link here instead of an (inaccessible onClick) handler
+ }
+ onClick={() => {
+ if (pagination?.total >= limits?.workflows) {
+ setShowLimitModal(true);
+ } else {
+ push('/settings/review-workflows/create');
+ }
+ }}
+ >
{formatMessage({
id: 'Settings.review-workflows.list.page.create',
defaultMessage: 'Create new workflow',
@@ -283,6 +306,22 @@ export function ReviewWorkflowsListView() {
onToggleDialog={toggleConfirmDeleteDialog}
onConfirm={handleConfirmDeleteDialog}
/>
+
+ setShowLimitModal(false)}>
+
+ {formatMessage({
+ id: 'Settings.review-workflows.list.page.workflows.limit.title',
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.list.page.workflows.limit.body',
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
+ })}
+
+
>
);