diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js
index 7182743aec..53e4de2100 100644
--- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js
+++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js
@@ -1,15 +1,37 @@
import * as React from 'react';
-import { Grid, GridItem, MultiSelectNested, TextInput } from '@strapi/design-system';
+import {
+ Grid,
+ GridItem,
+ MultiSelect,
+ MultiSelectGroup,
+ MultiSelectOption,
+ TextInput,
+ Typography,
+} from '@strapi/design-system';
import { useCollator } from '@strapi/helper-plugin';
import { useField } from 'formik';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
+import styled from 'styled-components';
import { updateWorkflow } from '../../actions';
-export function WorkflowAttributes({ canUpdate, contentTypes: { collectionTypes, singleTypes } }) {
+const NestedOption = styled(MultiSelectOption)`
+ padding-left: ${({ theme }) => theme.spaces[7]};
+`;
+
+const ContentTypeTakeNotice = styled(Typography)`
+ font-style: italic;
+`;
+
+export function WorkflowAttributes({
+ canUpdate,
+ contentTypes: { collectionTypes, singleTypes },
+ currentWorkflow,
+ workflows,
+}) {
const { formatMessage, locale } = useIntl();
const dispatch = useDispatch();
const [nameField, nameMeta, nameHelper] = useField('name');
@@ -39,7 +61,7 @@ export function WorkflowAttributes({ canUpdate, contentTypes: { collectionTypes,
-
formatMessage(
@@ -62,7 +84,12 @@ export function WorkflowAttributes({ canUpdate, contentTypes: { collectionTypes,
dispatch(updateWorkflow({ contentTypes: values }));
contentTypesHelper.setValue(values);
}}
- options={[
+ placeholder={formatMessage({
+ id: 'Settings.review-workflows.workflow.contentTypes.placeholder',
+ defaultMessage: 'Select',
+ })}
+ >
+ {[
...(collectionTypes.length > 0
? [
{
@@ -94,12 +121,53 @@ export function WorkflowAttributes({ canUpdate, contentTypes: { collectionTypes,
},
]
: []),
- ]}
- placeholder={formatMessage({
- id: 'Settings.review-workflows.workflow.contentTypes.placeholder',
- defaultMessage: 'Select',
+ ].map((opt) => {
+ if ('children' in opt) {
+ return (
+ child.value.toString())}
+ >
+ {opt.children.map((child) => {
+ const { name: assignedWorkflowName } =
+ workflows.find(
+ (workflow) =>
+ ((currentWorkflow && workflow.id !== currentWorkflow.id) ||
+ !currentWorkflow) &&
+ workflow.contentTypes.includes(child.value)
+ ) ?? {};
+
+ return (
+
+ {formatMessage(
+ {
+ id: 'Settings.review-workflows.workflow.contentTypes.assigned.notice',
+ defaultMessage:
+ '{label} {name, select, undefined {} other {(assigned to {name} workflow)}}',
+ },
+ {
+ label: child.label,
+ name: assignedWorkflowName,
+ i: (...children) => (
+ {children}
+ ),
+ }
+ )}
+
+ );
+ })}
+
+ );
+ }
+
+ return (
+
+ {opt.label}
+
+ );
})}
- />
+
);
@@ -114,6 +182,7 @@ const ContentTypeType = PropTypes.shape({
WorkflowAttributes.defaultProps = {
canUpdate: true,
+ currentWorkflow: undefined,
};
WorkflowAttributes.propTypes = {
@@ -122,4 +191,6 @@ WorkflowAttributes.propTypes = {
collectionTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
singleTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
}).isRequired,
+ currentWorkflow: PropTypes.object,
+ workflows: PropTypes.array.isRequired,
};
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 0cc578fde0..c5cc3ce05c 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
@@ -1,7 +1,8 @@
import * as React from 'react';
-import { Button, Flex, Loader } from '@strapi/design-system';
+import { Button, Flex, Loader, Typography } from '@strapi/design-system';
import {
+ ConfirmDialog,
useAPIErrorHandler,
useFetchClient,
useNotification,
@@ -42,6 +43,7 @@ export function ReviewWorkflowsCreateView() {
const permissions = useSelector(selectAdminPermissions);
const toggleNotification = useNotification();
const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
+ const { isLoading: isWorkflowLoading, meta, workflows } = useReviewWorkflows();
const {
clientState: {
currentWorkflow: { data: currentWorkflow, isDirty: currentWorkflowIsDirty },
@@ -52,8 +54,11 @@ export function ReviewWorkflowsCreateView() {
} = useRBAC(permissions.settings['review-workflows']);
const [showLimitModal, setShowLimitModal] = React.useState(false);
const { isLoading: isLicenseLoading, getFeature } = useLicenseLimits();
- const { meta, isLoading: isWorkflowLoading } = useReviewWorkflows();
const [initialErrors, setInitialErrors] = React.useState(null);
+ const [savePrompts, setSavePrompts] = React.useState({});
+
+ const limits = getFeature('review-workflows');
+ const contentTypesFromOtherWorkflows = workflows.flatMap((workflow) => workflow.contentTypes);
const { mutateAsync, isLoading } = useMutation(
async ({ workflow }) => {
@@ -79,6 +84,8 @@ export function ReviewWorkflowsCreateView() {
);
const submitForm = async () => {
+ setSavePrompts({});
+
try {
const workflow = await mutateAsync({ workflow: currentWorkflow });
@@ -110,13 +117,23 @@ export function ReviewWorkflowsCreateView() {
}
};
- const limits = getFeature('review-workflows');
+ const handleConfirmDeleteDialog = async () => {
+ await submitForm();
+ };
+
+ const handleConfirmClose = () => {
+ setSavePrompts({});
+ };
const formik = useFormik({
enableReinitialize: true,
initialErrors,
initialValues: currentWorkflow,
async onSubmit() {
+ const isContentTypeReassignment = currentWorkflow.contentTypes.some((contentType) =>
+ contentTypesFromOtherWorkflows.includes(contentType)
+ );
+
/**
* If the current license has a limit, check if the total count of workflows
* exceeds that limit and display the limits modal instead of sending the
@@ -140,6 +157,8 @@ export function ReviewWorkflowsCreateView() {
parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
) {
setShowLimitModal('stage');
+ } else if (isContentTypeReassignment) {
+ setSavePrompts((prev) => ({ ...prev, hasReassignedContentTypes: true }));
} else {
submitForm();
}
@@ -243,7 +262,10 @@ export function ReviewWorkflowsCreateView() {
) : (
-
+
)}
@@ -252,6 +274,41 @@ export function ReviewWorkflowsCreateView() {
+ 0}
+ onToggleDialog={handleConfirmClose}
+ onConfirm={handleConfirmDeleteDialog}
+ >
+
+
+ {savePrompts.hasReassignedContentTypes && (
+
+ {formatMessage(
+ {
+ id: 'Settings.review-workflows.page.delete.confirm.contentType.body',
+ defaultMessage:
+ '{count} {count, plural, one {content-type} other {content-types}} {count, plural, one {is} other {are}} already mapped to {count, plural, one {another workflow} other {other workflows}}. If you save changes, {count, plural, one {this} other {these}} {count, plural, one {content-type} other {{count} content-types}} will no more be mapped to the {count, plural, one {another workflow} other {other workflows}} and all corresponding information will be removed.',
+ },
+ {
+ count: contentTypesFromOtherWorkflows.filter((contentType) =>
+ currentWorkflow.contentTypes.includes(contentType)
+ ).length,
+ }
+ )}
+
+ )}
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.page.delete.confirm.confirm',
+ defaultMessage: 'Are you sure you want to save?',
+ })}
+
+
+
+
+
setShowLimitModal(false)}
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 97e95635a4..35563a0846 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
@@ -1,6 +1,6 @@
import * as React from 'react';
-import { Button, Flex, Loader } from '@strapi/design-system';
+import { Button, Flex, Loader, Typography } from '@strapi/design-system';
import {
ConfirmDialog,
useAPIErrorHandler,
@@ -45,10 +45,10 @@ export function ReviewWorkflowsEditView() {
const {
isLoading: isWorkflowLoading,
meta,
- workflows: [workflow],
+ workflows,
status: workflowStatus,
refetch,
- } = useReviewWorkflows({ id: workflowId });
+ } = useReviewWorkflows();
const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
const {
status,
@@ -56,18 +56,23 @@ export function ReviewWorkflowsEditView() {
currentWorkflow: {
data: currentWorkflow,
isDirty: currentWorkflowIsDirty,
- hasDeletedServerStages: currentWorkflowHasDeletedServerStages,
+ hasDeletedServerStages,
},
},
} = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
const {
allowedActions: { canDelete, canUpdate },
} = useRBAC(permissions.settings['review-workflows']);
- const [isConfirmDeleteDialogOpen, setIsConfirmDeleteDialogOpen] = React.useState(false);
+ const [savePrompts, setSavePrompts] = React.useState({});
const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
const [showLimitModal, setShowLimitModal] = React.useState(false);
const [initialErrors, setInitialErrors] = React.useState(null);
+ const workflow = workflows.find((workflow) => workflow.id === parseInt(workflowId, 10));
+ const contentTypesFromOtherWorkflows = workflows
+ .filter((workflow) => workflow.id !== parseInt(workflowId, 10))
+ .flatMap((workflow) => workflow.contentTypes);
+
const { mutateAsync, isLoading } = useMutation(
async ({ workflow }) => {
const {
@@ -125,15 +130,15 @@ export function ReviewWorkflowsEditView() {
await updateWorkflow(currentWorkflow);
await refetch();
- setIsConfirmDeleteDialogOpen(false);
+ setSavePrompts({});
};
const handleConfirmDeleteDialog = async () => {
await submitForm();
};
- const toggleConfirmDeleteDialog = () => {
- setIsConfirmDeleteDialogOpen((prev) => !prev);
+ const handleConfirmClose = () => {
+ setSavePrompts({});
};
const formik = useFormik({
@@ -141,9 +146,11 @@ export function ReviewWorkflowsEditView() {
initialErrors,
initialValues: currentWorkflow,
async onSubmit() {
- if (currentWorkflowHasDeletedServerStages) {
- setIsConfirmDeleteDialogOpen(true);
- } else if (
+ const isContentTypeReassignment = currentWorkflow.contentTypes.some((contentType) =>
+ contentTypesFromOtherWorkflows.includes(contentType)
+ );
+
+ if (
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
) {
@@ -165,6 +172,14 @@ export function ReviewWorkflowsEditView() {
parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
) {
setShowLimitModal('stage');
+ } else if (hasDeletedServerStages || isContentTypeReassignment) {
+ if (hasDeletedServerStages) {
+ setSavePrompts((prev) => ({ ...prev, hasDeletedServerStages: true }));
+ }
+
+ if (isContentTypeReassignment) {
+ setSavePrompts((prev) => ({ ...prev, hasReassignedContentTypes: true }));
+ }
} else {
submitForm();
}
@@ -243,7 +258,7 @@ export function ReviewWorkflowsEditView() {
disabled={!currentWorkflowIsDirty || !canUpdate}
// if the confirm dialog is open the loading state is on
// the confirm button already
- loading={!isConfirmDeleteDialogOpen && isLoading}
+ loading={!Object.keys(savePrompts).length > 0 && isLoading}
>
{formatMessage({
id: 'global.save',
@@ -279,6 +294,8 @@ export function ReviewWorkflowsEditView() {
- 0}
+ onToggleDialog={handleConfirmClose}
onConfirm={handleConfirmDeleteDialog}
- />
+ >
+
+
+ {savePrompts.hasDeletedServerStages && (
+
+ {formatMessage({
+ id: 'Settings.review-workflows.page.delete.confirm.stages.body',
+ defaultMessage:
+ 'All entries assigned to deleted stages will be moved to the previous stage.',
+ })}
+
+ )}
+
+ {savePrompts.hasReassignedContentTypes && (
+
+ {formatMessage(
+ {
+ id: 'Settings.review-workflows.page.delete.confirm.contentType.body',
+ defaultMessage:
+ '{count} {count, plural, one {content-type} other {content-types}} {count, plural, one {is} other {are}} already mapped to {count, plural, one {another workflow} other {other workflows}}. If you save changes, {count, plural, one {this} other {these}} {count, plural, one {content-type} other {{count} content-types}} will no more be mapped to the {count, plural, one {another workflow} other {other workflows}} and all corresponding information will be removed.',
+ },
+ {
+ count: contentTypesFromOtherWorkflows.filter((contentType) =>
+ currentWorkflow.contentTypes.includes(contentType)
+ ).length,
+ }
+ )}
+
+ )}
+
+
+ {formatMessage({
+ id: 'Settings.review-workflows.page.delete.confirm.confirm',
+ defaultMessage: 'Are you sure you want to save?',
+ })}
+
+
+
+
stage.id === stageId);
}
diff --git a/packages/core/helper-plugin/src/components/ConfirmDialog/index.js b/packages/core/helper-plugin/src/components/ConfirmDialog/index.js
index 44ecd4022b..544454c055 100644
--- a/packages/core/helper-plugin/src/components/ConfirmDialog/index.js
+++ b/packages/core/helper-plugin/src/components/ConfirmDialog/index.js
@@ -1,18 +1,25 @@
import React from 'react';
-import { Button, Dialog, DialogBody, DialogFooter, Flex, Typography } from '@strapi/design-system';
+import {
+ Button,
+ Box,
+ Dialog,
+ DialogBody,
+ DialogFooter,
+ Flex,
+ Typography,
+} from '@strapi/design-system';
import { ExclamationMarkCircle, Trash } from '@strapi/icons';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
-const ConfirmDialog = ({
- bodyText,
+export const Root = ({
+ children,
iconRightButton,
- iconBody,
isConfirmButtonLoading,
leftButtonText,
- onToggleDialog,
onConfirm,
+ onToggleDialog,
rightButtonText,
title,
variantRightButton,
@@ -31,46 +38,165 @@ const ConfirmDialog = ({
describedBy="confirm-description"
{...props}
>
-
-
-
-
- {formatMessage({
- id: bodyText.id,
- defaultMessage: bodyText.defaultMessage,
- })}
-
-
-
-
-
- {formatMessage({
- id: leftButtonText.id,
- defaultMessage: leftButtonText.defaultMessage,
- })}
-
- }
- endAction={
-
- }
+ {children}
+
+
);
};
+Root.defaultProps = {
+ iconBody: ,
+ iconRightButton: ,
+ isConfirmButtonLoading: false,
+ leftButtonText: {
+ id: 'app.components.Button.cancel',
+ defaultMessage: 'Cancel',
+ },
+ rightButtonText: {
+ id: 'app.components.Button.confirm',
+ defaultMessage: 'Confirm',
+ },
+ title: {
+ id: 'app.components.ConfirmDialog.title',
+ defaultMessage: 'Confirmation',
+ },
+ variantRightButton: 'danger-light',
+};
+
+Root.propTypes = {
+ children: PropTypes.node.isRequired,
+ iconBody: PropTypes.node,
+ iconRightButton: PropTypes.node,
+ isConfirmButtonLoading: PropTypes.bool,
+ onConfirm: PropTypes.func.isRequired,
+ onToggleDialog: PropTypes.func.isRequired,
+ leftButtonText: PropTypes.shape({
+ id: PropTypes.string,
+ defaultMessage: PropTypes.string,
+ }),
+ rightButtonText: PropTypes.shape({
+ id: PropTypes.string,
+ defaultMessage: PropTypes.string,
+ }),
+ title: PropTypes.shape({
+ id: PropTypes.string,
+ defaultMessage: PropTypes.string,
+ }),
+ variantRightButton: PropTypes.string,
+};
+
+export const Body = ({ iconBody, children }) => {
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+Body.defaultProps = {
+ iconBody: ,
+};
+
+Body.propTypes = {
+ children: PropTypes.node.isRequired,
+ iconBody: PropTypes.node,
+};
+
+const Footer = ({
+ iconRightButton,
+ isConfirmButtonLoading,
+ leftButtonText,
+ onConfirm,
+ onToggleDialog,
+ rightButtonText,
+ variantRightButton,
+}) => {
+ const { formatMessage } = useIntl();
+
+ return (
+
+ {formatMessage({
+ id: leftButtonText.id,
+ defaultMessage: leftButtonText.defaultMessage,
+ })}
+
+ }
+ endAction={
+
+ }
+ />
+ );
+};
+
+Footer.propTypes = {
+ iconRightButton: PropTypes.node.isRequired,
+ isConfirmButtonLoading: PropTypes.bool.isRequired,
+ onConfirm: PropTypes.func.isRequired,
+ onToggleDialog: PropTypes.func.isRequired,
+ leftButtonText: PropTypes.shape({
+ id: PropTypes.string,
+ defaultMessage: PropTypes.string,
+ }).isRequired,
+ rightButtonText: PropTypes.shape({
+ id: PropTypes.string,
+ defaultMessage: PropTypes.string,
+ }).isRequired,
+ variantRightButton: PropTypes.string.isRequired,
+};
+
+const ConfirmDialog = ({
+ bodyText,
+ iconRightButton,
+ iconBody,
+ isConfirmButtonLoading,
+ leftButtonText,
+ onToggleDialog,
+ onConfirm,
+ rightButtonText,
+ title,
+ variantRightButton,
+ ...props
+}) => {
+ const { formatMessage } = useIntl();
+
+ return (
+
+
+
+ {formatMessage({
+ id: bodyText.id,
+ defaultMessage: bodyText.defaultMessage,
+ })}
+
+
+
+ );
+};
+
ConfirmDialog.defaultProps = {
bodyText: {
id: 'components.popUpWarning.message',
@@ -119,4 +245,7 @@ ConfirmDialog.propTypes = {
variantRightButton: PropTypes.string,
};
-export default ConfirmDialog;
+ConfirmDialog.Root = Root;
+ConfirmDialog.Body = Body;
+
+export { ConfirmDialog };
diff --git a/packages/core/helper-plugin/src/components/ConfirmDialog/tests/index.test.js b/packages/core/helper-plugin/src/components/ConfirmDialog/tests/index.test.js
index 0661feea86..703d599fa9 100644
--- a/packages/core/helper-plugin/src/components/ConfirmDialog/tests/index.test.js
+++ b/packages/core/helper-plugin/src/components/ConfirmDialog/tests/index.test.js
@@ -4,7 +4,7 @@ import { lightTheme, ThemeProvider } from '@strapi/design-system';
import { render, screen, waitFor } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
-import ConfirmDialog from '../index';
+import { ConfirmDialog } from '../index';
const App = (
diff --git a/packages/core/helper-plugin/src/components/DynamicTable/index.js b/packages/core/helper-plugin/src/components/DynamicTable/index.js
index 4b046d641e..3d70eb6eb5 100644
--- a/packages/core/helper-plugin/src/components/DynamicTable/index.js
+++ b/packages/core/helper-plugin/src/components/DynamicTable/index.js
@@ -7,7 +7,7 @@ import { useIntl } from 'react-intl';
import { useTracking } from '../../features/Tracking';
import useQueryParams from '../../hooks/useQueryParams';
-import ConfirmDialog from '../ConfirmDialog';
+import { ConfirmDialog } from '../ConfirmDialog';
import EmptyBodyTable from '../EmptyBodyTable';
import TableHead from './TableHead';
diff --git a/packages/core/helper-plugin/src/components/Table/index.js b/packages/core/helper-plugin/src/components/Table/index.js
index 9727724d3a..5b6bf4a226 100644
--- a/packages/core/helper-plugin/src/components/Table/index.js
+++ b/packages/core/helper-plugin/src/components/Table/index.js
@@ -23,7 +23,7 @@ import { useIntl } from 'react-intl';
import useQueryParams from '../../hooks/useQueryParams';
import SortIcon from '../../icons/SortIcon';
-import ConfirmDialog from '../ConfirmDialog';
+import { ConfirmDialog } from '../ConfirmDialog';
import EmptyStateLayout from '../EmptyStateLayout';
/* -------------------------------------------------------------------------------------------------
diff --git a/packages/core/helper-plugin/src/index.js b/packages/core/helper-plugin/src/index.js
index aeb72a5f8c..5405cb8b51 100644
--- a/packages/core/helper-plugin/src/index.js
+++ b/packages/core/helper-plugin/src/index.js
@@ -7,7 +7,7 @@ import { getOtherInfos, getType } from './content-manager/utils/getAttributeInfo
export { default as AnErrorOccurred } from './components/AnErrorOccurred';
export { default as CheckPagePermissions } from './components/CheckPagePermissions';
export { default as CheckPermissions } from './components/CheckPermissions';
-export { default as ConfirmDialog } from './components/ConfirmDialog';
+export * from './components/ConfirmDialog';
export { default as ContentBox } from './components/ContentBox';
export { default as DateTimePicker } from './components/DateTimePicker';
export { default as DynamicTable } from './components/DynamicTable';