mirror of
https://github.com/strapi/strapi.git
synced 2025-11-06 21:29:24 +00:00
Enhancement: Combine content-type reassignment + stage prompts
This commit is contained in:
parent
37d0f58de2
commit
9c8d157f5d
@ -1,15 +1,37 @@
|
|||||||
import * as React from 'react';
|
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 { useCollator } from '@strapi/helper-plugin';
|
||||||
import { useField } from 'formik';
|
import { useField } from 'formik';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { updateWorkflow } from '../../actions';
|
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 { formatMessage, locale } = useIntl();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [nameField, nameMeta, nameHelper] = useField('name');
|
const [nameField, nameMeta, nameHelper] = useField('name');
|
||||||
@ -39,7 +61,7 @@ export function WorkflowAttributes({ canUpdate, contentTypes: { collectionTypes,
|
|||||||
</GridItem>
|
</GridItem>
|
||||||
|
|
||||||
<GridItem col={6}>
|
<GridItem col={6}>
|
||||||
<MultiSelectNested
|
<MultiSelect
|
||||||
{...contentTypesField}
|
{...contentTypesField}
|
||||||
customizeContent={(value) =>
|
customizeContent={(value) =>
|
||||||
formatMessage(
|
formatMessage(
|
||||||
@ -62,7 +84,12 @@ export function WorkflowAttributes({ canUpdate, contentTypes: { collectionTypes,
|
|||||||
dispatch(updateWorkflow({ contentTypes: values }));
|
dispatch(updateWorkflow({ contentTypes: values }));
|
||||||
contentTypesHelper.setValue(values);
|
contentTypesHelper.setValue(values);
|
||||||
}}
|
}}
|
||||||
options={[
|
placeholder={formatMessage({
|
||||||
|
id: 'Settings.review-workflows.workflow.contentTypes.placeholder',
|
||||||
|
defaultMessage: 'Select',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{[
|
||||||
...(collectionTypes.length > 0
|
...(collectionTypes.length > 0
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
@ -94,12 +121,53 @@ export function WorkflowAttributes({ canUpdate, contentTypes: { collectionTypes,
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
]}
|
].map((opt) => {
|
||||||
placeholder={formatMessage({
|
if ('children' in opt) {
|
||||||
id: 'Settings.review-workflows.workflow.contentTypes.placeholder',
|
return (
|
||||||
defaultMessage: 'Select',
|
<MultiSelectGroup
|
||||||
|
key={opt.label}
|
||||||
|
label={opt.label}
|
||||||
|
values={opt.children.map((child) => 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 (
|
||||||
|
<NestedOption key={child.value} value={child.value}>
|
||||||
|
{formatMessage(
|
||||||
|
{
|
||||||
|
id: 'Settings.review-workflows.workflow.contentTypes.assigned.notice',
|
||||||
|
defaultMessage:
|
||||||
|
'{label} {name, select, undefined {} other {<i>(assigned to {name} workflow)</i>}}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: child.label,
|
||||||
|
name: assignedWorkflowName,
|
||||||
|
i: (...children) => (
|
||||||
|
<ContentTypeTakeNotice>{children}</ContentTypeTakeNotice>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</NestedOption>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</MultiSelectGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MultiSelectOption key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</MultiSelectOption>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
/>
|
</MultiSelect>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
@ -114,6 +182,7 @@ const ContentTypeType = PropTypes.shape({
|
|||||||
|
|
||||||
WorkflowAttributes.defaultProps = {
|
WorkflowAttributes.defaultProps = {
|
||||||
canUpdate: true,
|
canUpdate: true,
|
||||||
|
currentWorkflow: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
WorkflowAttributes.propTypes = {
|
WorkflowAttributes.propTypes = {
|
||||||
@ -122,4 +191,6 @@ WorkflowAttributes.propTypes = {
|
|||||||
collectionTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
|
collectionTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
|
||||||
singleTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
|
singleTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
currentWorkflow: PropTypes.object,
|
||||||
|
workflows: PropTypes.array.isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { Button, Flex, Loader } from '@strapi/design-system';
|
import { Button, Flex, Loader, Typography } from '@strapi/design-system';
|
||||||
import {
|
import {
|
||||||
|
ConfirmDialog,
|
||||||
useAPIErrorHandler,
|
useAPIErrorHandler,
|
||||||
useFetchClient,
|
useFetchClient,
|
||||||
useNotification,
|
useNotification,
|
||||||
@ -42,6 +43,7 @@ export function ReviewWorkflowsCreateView() {
|
|||||||
const permissions = useSelector(selectAdminPermissions);
|
const permissions = useSelector(selectAdminPermissions);
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
|
const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
|
||||||
|
const { isLoading: isWorkflowLoading, meta, workflows } = useReviewWorkflows();
|
||||||
const {
|
const {
|
||||||
clientState: {
|
clientState: {
|
||||||
currentWorkflow: { data: currentWorkflow, isDirty: currentWorkflowIsDirty },
|
currentWorkflow: { data: currentWorkflow, isDirty: currentWorkflowIsDirty },
|
||||||
@ -52,8 +54,11 @@ export function ReviewWorkflowsCreateView() {
|
|||||||
} = useRBAC(permissions.settings['review-workflows']);
|
} = useRBAC(permissions.settings['review-workflows']);
|
||||||
const [showLimitModal, setShowLimitModal] = React.useState(false);
|
const [showLimitModal, setShowLimitModal] = React.useState(false);
|
||||||
const { isLoading: isLicenseLoading, getFeature } = useLicenseLimits();
|
const { isLoading: isLicenseLoading, getFeature } = useLicenseLimits();
|
||||||
const { meta, isLoading: isWorkflowLoading } = useReviewWorkflows();
|
|
||||||
const [initialErrors, setInitialErrors] = React.useState(null);
|
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(
|
const { mutateAsync, isLoading } = useMutation(
|
||||||
async ({ workflow }) => {
|
async ({ workflow }) => {
|
||||||
@ -79,6 +84,8 @@ export function ReviewWorkflowsCreateView() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
setSavePrompts({});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const workflow = await mutateAsync({ workflow: currentWorkflow });
|
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({
|
const formik = useFormik({
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
initialErrors,
|
initialErrors,
|
||||||
initialValues: currentWorkflow,
|
initialValues: currentWorkflow,
|
||||||
async onSubmit() {
|
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
|
* 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
|
* 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)
|
parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
|
||||||
) {
|
) {
|
||||||
setShowLimitModal('stage');
|
setShowLimitModal('stage');
|
||||||
|
} else if (isContentTypeReassignment) {
|
||||||
|
setSavePrompts((prev) => ({ ...prev, hasReassignedContentTypes: true }));
|
||||||
} else {
|
} else {
|
||||||
submitForm();
|
submitForm();
|
||||||
}
|
}
|
||||||
@ -243,7 +262,10 @@ export function ReviewWorkflowsCreateView() {
|
|||||||
</Loader>
|
</Loader>
|
||||||
) : (
|
) : (
|
||||||
<Flex alignItems="stretch" direction="column" gap={7}>
|
<Flex alignItems="stretch" direction="column" gap={7}>
|
||||||
<WorkflowAttributes contentTypes={{ collectionTypes, singleTypes }} />
|
<WorkflowAttributes
|
||||||
|
contentTypes={{ collectionTypes, singleTypes }}
|
||||||
|
workflows={workflows}
|
||||||
|
/>
|
||||||
<Stages stages={formik.values?.stages} />
|
<Stages stages={formik.values?.stages} />
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
@ -252,6 +274,41 @@ export function ReviewWorkflowsCreateView() {
|
|||||||
</Form>
|
</Form>
|
||||||
</FormikProvider>
|
</FormikProvider>
|
||||||
|
|
||||||
|
<ConfirmDialog.Root
|
||||||
|
isConfirmButtonLoading={isLoading}
|
||||||
|
isOpen={Object.keys(savePrompts).length > 0}
|
||||||
|
onToggleDialog={handleConfirmClose}
|
||||||
|
onConfirm={handleConfirmDeleteDialog}
|
||||||
|
>
|
||||||
|
<ConfirmDialog.Body>
|
||||||
|
<Flex direction="column" gap={5}>
|
||||||
|
{savePrompts.hasReassignedContentTypes && (
|
||||||
|
<Typography textAlign="center" variant="omega">
|
||||||
|
{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,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Typography textAlign="center" variant="omega">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.review-workflows.page.delete.confirm.confirm',
|
||||||
|
defaultMessage: 'Are you sure you want to save?',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</Flex>
|
||||||
|
</ConfirmDialog.Body>
|
||||||
|
</ConfirmDialog.Root>
|
||||||
|
|
||||||
<LimitsModal.Root
|
<LimitsModal.Root
|
||||||
isOpen={showLimitModal === 'workflow'}
|
isOpen={showLimitModal === 'workflow'}
|
||||||
onClose={() => setShowLimitModal(false)}
|
onClose={() => setShowLimitModal(false)}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { Button, Flex, Loader } from '@strapi/design-system';
|
import { Button, Flex, Loader, Typography } from '@strapi/design-system';
|
||||||
import {
|
import {
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
useAPIErrorHandler,
|
useAPIErrorHandler,
|
||||||
@ -45,10 +45,10 @@ export function ReviewWorkflowsEditView() {
|
|||||||
const {
|
const {
|
||||||
isLoading: isWorkflowLoading,
|
isLoading: isWorkflowLoading,
|
||||||
meta,
|
meta,
|
||||||
workflows: [workflow],
|
workflows,
|
||||||
status: workflowStatus,
|
status: workflowStatus,
|
||||||
refetch,
|
refetch,
|
||||||
} = useReviewWorkflows({ id: workflowId });
|
} = useReviewWorkflows();
|
||||||
const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
|
const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
@ -56,18 +56,23 @@ export function ReviewWorkflowsEditView() {
|
|||||||
currentWorkflow: {
|
currentWorkflow: {
|
||||||
data: currentWorkflow,
|
data: currentWorkflow,
|
||||||
isDirty: currentWorkflowIsDirty,
|
isDirty: currentWorkflowIsDirty,
|
||||||
hasDeletedServerStages: currentWorkflowHasDeletedServerStages,
|
hasDeletedServerStages,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
|
} = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
|
||||||
const {
|
const {
|
||||||
allowedActions: { canDelete, canUpdate },
|
allowedActions: { canDelete, canUpdate },
|
||||||
} = useRBAC(permissions.settings['review-workflows']);
|
} = useRBAC(permissions.settings['review-workflows']);
|
||||||
const [isConfirmDeleteDialogOpen, setIsConfirmDeleteDialogOpen] = React.useState(false);
|
const [savePrompts, setSavePrompts] = React.useState({});
|
||||||
const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
|
const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
|
||||||
const [showLimitModal, setShowLimitModal] = React.useState(false);
|
const [showLimitModal, setShowLimitModal] = React.useState(false);
|
||||||
const [initialErrors, setInitialErrors] = React.useState(null);
|
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(
|
const { mutateAsync, isLoading } = useMutation(
|
||||||
async ({ workflow }) => {
|
async ({ workflow }) => {
|
||||||
const {
|
const {
|
||||||
@ -125,15 +130,15 @@ export function ReviewWorkflowsEditView() {
|
|||||||
await updateWorkflow(currentWorkflow);
|
await updateWorkflow(currentWorkflow);
|
||||||
await refetch();
|
await refetch();
|
||||||
|
|
||||||
setIsConfirmDeleteDialogOpen(false);
|
setSavePrompts({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmDeleteDialog = async () => {
|
const handleConfirmDeleteDialog = async () => {
|
||||||
await submitForm();
|
await submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleConfirmDeleteDialog = () => {
|
const handleConfirmClose = () => {
|
||||||
setIsConfirmDeleteDialogOpen((prev) => !prev);
|
setSavePrompts({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
@ -141,9 +146,11 @@ export function ReviewWorkflowsEditView() {
|
|||||||
initialErrors,
|
initialErrors,
|
||||||
initialValues: currentWorkflow,
|
initialValues: currentWorkflow,
|
||||||
async onSubmit() {
|
async onSubmit() {
|
||||||
if (currentWorkflowHasDeletedServerStages) {
|
const isContentTypeReassignment = currentWorkflow.contentTypes.some((contentType) =>
|
||||||
setIsConfirmDeleteDialogOpen(true);
|
contentTypesFromOtherWorkflows.includes(contentType)
|
||||||
} else if (
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
||||||
meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
|
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)
|
parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
|
||||||
) {
|
) {
|
||||||
setShowLimitModal('stage');
|
setShowLimitModal('stage');
|
||||||
|
} else if (hasDeletedServerStages || isContentTypeReassignment) {
|
||||||
|
if (hasDeletedServerStages) {
|
||||||
|
setSavePrompts((prev) => ({ ...prev, hasDeletedServerStages: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isContentTypeReassignment) {
|
||||||
|
setSavePrompts((prev) => ({ ...prev, hasReassignedContentTypes: true }));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
submitForm();
|
submitForm();
|
||||||
}
|
}
|
||||||
@ -243,7 +258,7 @@ export function ReviewWorkflowsEditView() {
|
|||||||
disabled={!currentWorkflowIsDirty || !canUpdate}
|
disabled={!currentWorkflowIsDirty || !canUpdate}
|
||||||
// if the confirm dialog is open the loading state is on
|
// if the confirm dialog is open the loading state is on
|
||||||
// the confirm button already
|
// the confirm button already
|
||||||
loading={!isConfirmDeleteDialogOpen && isLoading}
|
loading={!Object.keys(savePrompts).length > 0 && isLoading}
|
||||||
>
|
>
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'global.save',
|
id: 'global.save',
|
||||||
@ -279,6 +294,8 @@ export function ReviewWorkflowsEditView() {
|
|||||||
<WorkflowAttributes
|
<WorkflowAttributes
|
||||||
canUpdate={canUpdate}
|
canUpdate={canUpdate}
|
||||||
contentTypes={{ collectionTypes, singleTypes }}
|
contentTypes={{ collectionTypes, singleTypes }}
|
||||||
|
currentWorkflow={currentWorkflow}
|
||||||
|
workflows={workflows}
|
||||||
/>
|
/>
|
||||||
<Stages
|
<Stages
|
||||||
canDelete={canDelete}
|
canDelete={canDelete}
|
||||||
@ -291,17 +308,50 @@ export function ReviewWorkflowsEditView() {
|
|||||||
</Form>
|
</Form>
|
||||||
</FormikProvider>
|
</FormikProvider>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog.Root
|
||||||
bodyText={{
|
|
||||||
id: 'Settings.review-workflows.page.delete.confirm.body',
|
|
||||||
defaultMessage:
|
|
||||||
'All entries assigned to deleted stages will be moved to the previous stage. Are you sure you want to save?',
|
|
||||||
}}
|
|
||||||
isConfirmButtonLoading={isLoading}
|
isConfirmButtonLoading={isLoading}
|
||||||
isOpen={isConfirmDeleteDialogOpen}
|
isOpen={Object.keys(savePrompts).length > 0}
|
||||||
onToggleDialog={toggleConfirmDeleteDialog}
|
onToggleDialog={handleConfirmClose}
|
||||||
onConfirm={handleConfirmDeleteDialog}
|
onConfirm={handleConfirmDeleteDialog}
|
||||||
/>
|
>
|
||||||
|
<ConfirmDialog.Body>
|
||||||
|
<Flex direction="column" gap={5}>
|
||||||
|
{savePrompts.hasDeletedServerStages && (
|
||||||
|
<Typography textAlign="center" variant="omega">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.review-workflows.page.delete.confirm.stages.body',
|
||||||
|
defaultMessage:
|
||||||
|
'All entries assigned to deleted stages will be moved to the previous stage.',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{savePrompts.hasReassignedContentTypes && (
|
||||||
|
<Typography textAlign="center" variant="omega">
|
||||||
|
{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,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Typography textAlign="center" variant="omega">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.review-workflows.page.delete.confirm.confirm',
|
||||||
|
defaultMessage: 'Are you sure you want to save?',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</Flex>
|
||||||
|
</ConfirmDialog.Body>
|
||||||
|
</ConfirmDialog.Root>
|
||||||
|
|
||||||
<LimitsModal.Root
|
<LimitsModal.Root
|
||||||
isOpen={showLimitModal === 'workflow'}
|
isOpen={showLimitModal === 'workflow'}
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export function reducer(state = initialState, action) {
|
|||||||
|
|
||||||
if (!currentWorkflow.hasDeletedServerStages) {
|
if (!currentWorkflow.hasDeletedServerStages) {
|
||||||
draft.clientState.currentWorkflow.hasDeletedServerStages = !!(
|
draft.clientState.currentWorkflow.hasDeletedServerStages = !!(
|
||||||
state.serverState.currentWorkflow?.stages ?? []
|
state.serverState.workflow?.stages ?? []
|
||||||
).find((stage) => stage.id === stageId);
|
).find((stage) => stage.id === stageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,25 @@
|
|||||||
import React from 'react';
|
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 { ExclamationMarkCircle, Trash } from '@strapi/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
const ConfirmDialog = ({
|
export const Root = ({
|
||||||
bodyText,
|
children,
|
||||||
iconRightButton,
|
iconRightButton,
|
||||||
iconBody,
|
|
||||||
isConfirmButtonLoading,
|
isConfirmButtonLoading,
|
||||||
leftButtonText,
|
leftButtonText,
|
||||||
onToggleDialog,
|
|
||||||
onConfirm,
|
onConfirm,
|
||||||
|
onToggleDialog,
|
||||||
rightButtonText,
|
rightButtonText,
|
||||||
title,
|
title,
|
||||||
variantRightButton,
|
variantRightButton,
|
||||||
@ -31,46 +38,165 @@ const ConfirmDialog = ({
|
|||||||
describedBy="confirm-description"
|
describedBy="confirm-description"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<DialogBody icon={iconBody}>
|
<Box id="confirm-description">{children}</Box>
|
||||||
<Flex direction="column" alignItems="stretch" gap={2}>
|
|
||||||
<Flex justifyContent="center">
|
<Footer
|
||||||
<Typography variant="omega" id="confirm-description">
|
iconRightButton={iconRightButton}
|
||||||
{formatMessage({
|
isConfirmButtonLoading={isConfirmButtonLoading}
|
||||||
id: bodyText.id,
|
leftButtonText={leftButtonText}
|
||||||
defaultMessage: bodyText.defaultMessage,
|
onConfirm={onConfirm}
|
||||||
})}
|
onToggleDialog={onToggleDialog}
|
||||||
</Typography>
|
rightButtonText={rightButtonText}
|
||||||
</Flex>
|
variantRightButton={variantRightButton}
|
||||||
</Flex>
|
|
||||||
</DialogBody>
|
|
||||||
<DialogFooter
|
|
||||||
startAction={
|
|
||||||
<Button onClick={onToggleDialog} variant="tertiary">
|
|
||||||
{formatMessage({
|
|
||||||
id: leftButtonText.id,
|
|
||||||
defaultMessage: leftButtonText.defaultMessage,
|
|
||||||
})}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
endAction={
|
|
||||||
<Button
|
|
||||||
onClick={onConfirm}
|
|
||||||
variant={variantRightButton}
|
|
||||||
startIcon={iconRightButton}
|
|
||||||
id="confirm-delete"
|
|
||||||
loading={isConfirmButtonLoading}
|
|
||||||
>
|
|
||||||
{formatMessage({
|
|
||||||
id: rightButtonText.id,
|
|
||||||
defaultMessage: rightButtonText.defaultMessage,
|
|
||||||
})}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Root.defaultProps = {
|
||||||
|
iconBody: <ExclamationMarkCircle />,
|
||||||
|
iconRightButton: <Trash />,
|
||||||
|
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 (
|
||||||
|
<DialogBody icon={iconBody}>
|
||||||
|
<Flex direction="column" alignItems="stretch" gap={2}>
|
||||||
|
<Flex justifyContent="center">{children}</Flex>
|
||||||
|
</Flex>
|
||||||
|
</DialogBody>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Body.defaultProps = {
|
||||||
|
iconBody: <ExclamationMarkCircle />,
|
||||||
|
};
|
||||||
|
|
||||||
|
Body.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
iconBody: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Footer = ({
|
||||||
|
iconRightButton,
|
||||||
|
isConfirmButtonLoading,
|
||||||
|
leftButtonText,
|
||||||
|
onConfirm,
|
||||||
|
onToggleDialog,
|
||||||
|
rightButtonText,
|
||||||
|
variantRightButton,
|
||||||
|
}) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogFooter
|
||||||
|
startAction={
|
||||||
|
<Button onClick={onToggleDialog} variant="tertiary">
|
||||||
|
{formatMessage({
|
||||||
|
id: leftButtonText.id,
|
||||||
|
defaultMessage: leftButtonText.defaultMessage,
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
endAction={
|
||||||
|
<Button
|
||||||
|
onClick={onConfirm}
|
||||||
|
variant={variantRightButton}
|
||||||
|
startIcon={iconRightButton}
|
||||||
|
id="confirm-delete"
|
||||||
|
loading={isConfirmButtonLoading}
|
||||||
|
>
|
||||||
|
{formatMessage({
|
||||||
|
id: rightButtonText.id,
|
||||||
|
defaultMessage: rightButtonText.defaultMessage,
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Root onConfirm={onConfirm} onToggleDialog={onToggleDialog} title={title} {...props}>
|
||||||
|
<Body>
|
||||||
|
<Typography variant="omega">
|
||||||
|
{formatMessage({
|
||||||
|
id: bodyText.id,
|
||||||
|
defaultMessage: bodyText.defaultMessage,
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</Body>
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
ConfirmDialog.defaultProps = {
|
ConfirmDialog.defaultProps = {
|
||||||
bodyText: {
|
bodyText: {
|
||||||
id: 'components.popUpWarning.message',
|
id: 'components.popUpWarning.message',
|
||||||
@ -119,4 +245,7 @@ ConfirmDialog.propTypes = {
|
|||||||
variantRightButton: PropTypes.string,
|
variantRightButton: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConfirmDialog;
|
ConfirmDialog.Root = Root;
|
||||||
|
ConfirmDialog.Body = Body;
|
||||||
|
|
||||||
|
export { ConfirmDialog };
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
|||||||
import { render, screen, waitFor } from '@testing-library/react';
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import ConfirmDialog from '../index';
|
import { ConfirmDialog } from '../index';
|
||||||
|
|
||||||
const App = (
|
const App = (
|
||||||
<ThemeProvider theme={lightTheme}>
|
<ThemeProvider theme={lightTheme}>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { useIntl } from 'react-intl';
|
|||||||
|
|
||||||
import { useTracking } from '../../features/Tracking';
|
import { useTracking } from '../../features/Tracking';
|
||||||
import useQueryParams from '../../hooks/useQueryParams';
|
import useQueryParams from '../../hooks/useQueryParams';
|
||||||
import ConfirmDialog from '../ConfirmDialog';
|
import { ConfirmDialog } from '../ConfirmDialog';
|
||||||
import EmptyBodyTable from '../EmptyBodyTable';
|
import EmptyBodyTable from '../EmptyBodyTable';
|
||||||
|
|
||||||
import TableHead from './TableHead';
|
import TableHead from './TableHead';
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { useIntl } from 'react-intl';
|
|||||||
|
|
||||||
import useQueryParams from '../../hooks/useQueryParams';
|
import useQueryParams from '../../hooks/useQueryParams';
|
||||||
import SortIcon from '../../icons/SortIcon';
|
import SortIcon from '../../icons/SortIcon';
|
||||||
import ConfirmDialog from '../ConfirmDialog';
|
import { ConfirmDialog } from '../ConfirmDialog';
|
||||||
import EmptyStateLayout from '../EmptyStateLayout';
|
import EmptyStateLayout from '../EmptyStateLayout';
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { getOtherInfos, getType } from './content-manager/utils/getAttributeInfo
|
|||||||
export { default as AnErrorOccurred } from './components/AnErrorOccurred';
|
export { default as AnErrorOccurred } from './components/AnErrorOccurred';
|
||||||
export { default as CheckPagePermissions } from './components/CheckPagePermissions';
|
export { default as CheckPagePermissions } from './components/CheckPagePermissions';
|
||||||
export { default as CheckPermissions } from './components/CheckPermissions';
|
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 ContentBox } from './components/ContentBox';
|
||||||
export { default as DateTimePicker } from './components/DateTimePicker';
|
export { default as DateTimePicker } from './components/DateTimePicker';
|
||||||
export { default as DynamicTable } from './components/DynamicTable';
|
export { default as DynamicTable } from './components/DynamicTable';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user