mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(console): extract confirm modal component (#1163)
This commit is contained in:
parent
5c43da2201
commit
3e1c040ebe
11 changed files with 82 additions and 155 deletions
|
@ -8,9 +8,11 @@ import { Ring as Spinner } from '@/components/Spinner';
|
|||
import DangerousRaw from '../DangerousRaw';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export type ButtonType = 'primary' | 'danger' | 'outline' | 'plain' | 'default';
|
||||
|
||||
type BaseProps = Omit<HTMLProps<HTMLButtonElement>, 'type' | 'size' | 'title'> & {
|
||||
htmlType?: 'button' | 'submit' | 'reset';
|
||||
type?: 'primary' | 'danger' | 'outline' | 'plain' | 'default';
|
||||
type?: ButtonType;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
isLoading?: boolean;
|
||||
loadingDelay?: number;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.content {
|
||||
font: var(--font-body-medium);
|
||||
|
||||
> :not(:first-child) {
|
||||
margin: _.unit(6) 0;
|
||||
}
|
||||
}
|
|
@ -1,55 +1,58 @@
|
|||
import { AdminConsoleKey, I18nKey } from '@logto/phrases';
|
||||
import React from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import classNames from 'classnames';
|
||||
import React, { ReactNode } from 'react';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import Button, { ButtonType } from '@/components/Button';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
import Button from '../Button';
|
||||
import ModalLayout from '../ModalLayout';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
title: AdminConsoleKey;
|
||||
children: React.ReactNode;
|
||||
export type ConfirmModalProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
title?: AdminConsoleKey;
|
||||
confirmButtonType?: ButtonType;
|
||||
confirmButtonText?: I18nKey;
|
||||
cancelButtonText?: I18nKey;
|
||||
isOpen: boolean;
|
||||
isPending?: boolean;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
const ConfirmModal = ({
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
title = 'form.confirm',
|
||||
confirmButtonType = 'danger',
|
||||
confirmButtonText = 'general.confirm',
|
||||
cancelButtonText = 'general.cancel',
|
||||
isOpen,
|
||||
isPending,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: Props) => (
|
||||
<Modal isOpen={isOpen} className={modalStyles.content} overlayClassName={modalStyles.overlay}>
|
||||
<ModalLayout
|
||||
title={title}
|
||||
footer={
|
||||
<>
|
||||
<Button type="outline" title={cancelButtonText} onClick={onCancel} />
|
||||
<Button
|
||||
isLoading={isPending}
|
||||
type="primary"
|
||||
title={confirmButtonText}
|
||||
onClick={onConfirm}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
className={className}
|
||||
onClose={onCancel}
|
||||
onConfirm,
|
||||
}: ConfirmModalProps) => {
|
||||
return (
|
||||
<ReactModal
|
||||
isOpen={isOpen}
|
||||
className={modalStyles.content}
|
||||
overlayClassName={modalStyles.overlay}
|
||||
>
|
||||
{children}
|
||||
</ModalLayout>
|
||||
</Modal>
|
||||
);
|
||||
<ModalLayout
|
||||
title={title}
|
||||
footer={
|
||||
<>
|
||||
<Button type="outline" title={cancelButtonText} onClick={onCancel} />
|
||||
<Button type={confirmButtonType} title={confirmButtonText} onClick={onConfirm} />
|
||||
</>
|
||||
}
|
||||
className={classNames(styles.content, className)}
|
||||
onClose={onCancel}
|
||||
>
|
||||
{children}
|
||||
</ModalLayout>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmModal;
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import { I18nKey } from '@logto/phrases';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Button from '../Button';
|
||||
import ModalLayout from '../ModalLayout';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
title: I18nKey;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const DeleteConfirm = ({ title, onConfirm, onClose }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
title="form.confirm"
|
||||
footer={
|
||||
<>
|
||||
<Button type="outline" title="admin_console.form.cancel" onClick={onClose} />
|
||||
<Button
|
||||
type="danger"
|
||||
title="admin_console.form.delete"
|
||||
onClick={() => {
|
||||
onConfirm();
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
className={styles.content}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.confirmation}>
|
||||
{t('admin_console.form.deletion_confirmation', { title: t(title) })}
|
||||
</div>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteConfirm;
|
|
@ -30,15 +30,3 @@
|
|||
margin-top: _.unit(1);
|
||||
}
|
||||
}
|
||||
|
||||
.reminder {
|
||||
.content {
|
||||
> :not(:first-child) {
|
||||
margin: _.unit(6) 0;
|
||||
}
|
||||
|
||||
.confirmation {
|
||||
font: var(--font-body-medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,13 @@ import { I18nKey } from '@logto/phrases';
|
|||
import classNames from 'classnames';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import * as textButtonStyles from '@/components/TextButton/index.module.scss';
|
||||
import Minus from '@/icons/Minus';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
import ConfirmModal from '../ConfirmModal';
|
||||
import IconButton from '../IconButton';
|
||||
import TextInput from '../TextInput';
|
||||
import DeleteConfirm from './DeleteConfirm';
|
||||
import * as styles from './index.module.scss';
|
||||
import { MultiTextInputError } from './types';
|
||||
|
||||
|
@ -22,9 +20,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const MultiTextInput = ({ title, value, onChange, error }: Props) => {
|
||||
const { t } = useTranslation(undefined, {
|
||||
keyPrefix: 'admin_console',
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [deleteFieldIndex, setDeleteFieldIndex] = useState<number>();
|
||||
|
||||
|
@ -86,25 +82,23 @@ const MultiTextInput = ({ title, value, onChange, error }: Props) => {
|
|||
</div>
|
||||
))}
|
||||
<div className={classNames(textButtonStyles.button, styles.addAnother)} onClick={handleAdd}>
|
||||
{t('form.add_another')}
|
||||
{t('admin_console.form.add_another')}
|
||||
</div>
|
||||
<Modal
|
||||
<ConfirmModal
|
||||
isOpen={deleteFieldIndex !== undefined}
|
||||
className={modalStyles.content}
|
||||
overlayClassName={modalStyles.overlay}
|
||||
>
|
||||
<DeleteConfirm
|
||||
title={title}
|
||||
onClose={() => {
|
||||
confirmButtonText="admin_console.form.delete"
|
||||
onCancel={() => {
|
||||
setDeleteFieldIndex(undefined);
|
||||
}}
|
||||
onConfirm={() => {
|
||||
if (deleteFieldIndex !== undefined) {
|
||||
handleRemove(deleteFieldIndex);
|
||||
setDeleteFieldIndex(undefined);
|
||||
}}
|
||||
onConfirm={() => {
|
||||
if (deleteFieldIndex !== undefined) {
|
||||
handleRemove(deleteFieldIndex);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('admin_console.form.deletion_confirmation', { title: t(title) })}
|
||||
</ConfirmModal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -71,6 +71,7 @@ const GetStarted = () => {
|
|||
<ConfirmModal
|
||||
title="get_started.confirm"
|
||||
isOpen={showConfirmModal}
|
||||
confirmButtonType="primary"
|
||||
confirmButtonText="admin_console.get_started.hide_this"
|
||||
onConfirm={hideGetStarted}
|
||||
onCancel={() => {
|
||||
|
|
|
@ -2,40 +2,19 @@ import { SignInExperience } from '@logto/schemas';
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import ModalLayout from '@/components/ModalLayout';
|
||||
|
||||
import * as styles from './SaveAlert.module.scss';
|
||||
import * as styles from './SignInMethodsChangePreview.module.scss';
|
||||
import SignInMethodsPreview from './SignInMethodsPreview';
|
||||
|
||||
type Props = {
|
||||
before: SignInExperience;
|
||||
after: SignInExperience;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
const SaveAlert = ({ before, after, onClose, onConfirm }: Props) => {
|
||||
const SignInMethodsChangePreview = ({ before, after }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
title="sign_in_exp.save_alert.title"
|
||||
footer={
|
||||
<>
|
||||
<Button type="outline" title="general.cancel" onClick={onClose} />
|
||||
<Button
|
||||
type="danger"
|
||||
title="general.confirm"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
onConfirm();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div>
|
||||
<div className={styles.description}>{t('sign_in_exp.save_alert.description')}</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.section}>
|
||||
|
@ -47,8 +26,8 @@ const SaveAlert = ({ before, after, onClose, onConfirm }: Props) => {
|
|||
<SignInMethodsPreview data={after} />
|
||||
</div>
|
||||
</div>
|
||||
</ModalLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveAlert;
|
||||
export default SignInMethodsChangePreview;
|
|
@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||
import useConnectorGroups from '@/hooks/use-connector-groups';
|
||||
|
||||
import * as styles from './SaveAlert.module.scss';
|
||||
import * as styles from './SignInMethodsChangePreview.module.scss';
|
||||
|
||||
type Props = {
|
||||
data: SignInExperience;
|
||||
|
|
|
@ -4,23 +4,22 @@ import React, { useEffect, useState } from 'react';
|
|||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import CardTitle from '@/components/CardTitle';
|
||||
import ConfirmModal from '@/components/ConfirmModal';
|
||||
import TabNav, { TabNavItem } from '@/components/TabNav';
|
||||
import useApi, { RequestError } from '@/hooks/use-api';
|
||||
import useSettings from '@/hooks/use-settings';
|
||||
import * as detailsStyles from '@/scss/details.module.scss';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
import BrandingForm from './components/BrandingForm';
|
||||
import LanguagesForm from './components/LanguagesForm';
|
||||
import Preview from './components/Preview';
|
||||
import SaveAlert from './components/SaveAlert';
|
||||
import SignInMethodsChangePreview from './components/SignInMethodsChangePreview';
|
||||
import SignInMethodsForm from './components/SignInMethodsForm';
|
||||
import TermsForm from './components/TermsForm';
|
||||
import Welcome from './components/Welcome';
|
||||
|
@ -144,22 +143,18 @@ const SignInExperience = () => {
|
|||
</div>
|
||||
{formData.id && <Preview signInExperience={previewConfigs} />}
|
||||
{data && (
|
||||
<ReactModal
|
||||
<ConfirmModal
|
||||
isOpen={Boolean(dataToCompare)}
|
||||
className={modalStyles.content}
|
||||
overlayClassName={modalStyles.overlay}
|
||||
onCancel={() => {
|
||||
setDataToCompare(undefined);
|
||||
}}
|
||||
onConfirm={async () => {
|
||||
await saveData();
|
||||
setDataToCompare(undefined);
|
||||
}}
|
||||
>
|
||||
{dataToCompare && (
|
||||
<SaveAlert
|
||||
before={data}
|
||||
after={dataToCompare}
|
||||
onClose={() => {
|
||||
setDataToCompare(undefined);
|
||||
}}
|
||||
onConfirm={saveData}
|
||||
/>
|
||||
)}
|
||||
</ReactModal>
|
||||
{dataToCompare && <SignInMethodsChangePreview before={data} after={dataToCompare} />}
|
||||
</ConfirmModal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue