Created
March 28, 2024 17:41
-
-
Save artalar/b8321130a2fd5962dcfc502c1810356c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
type AsyncAction, | |
action, | |
atom, | |
spawn, | |
take, | |
toAbortError, | |
isCausedBy, | |
} from '@reatom/framework'; | |
import { reatomComponent } from '@reatom/npm-react'; | |
import { type Namespace, type ParseKeys } from 'i18next'; | |
import { useTranslation } from 'react-i18next'; | |
import { | |
Body1, | |
Button, | |
Flex, | |
Group, | |
Heading2, | |
Modal, | |
} from 'src/admin/ui/maintine/core'; | |
import { WarningCircle } from '../icons/WarningCircle'; | |
export interface ConfirmData { | |
title: ParseKeys<Namespace>; | |
body1: ParseKeys<Namespace>; | |
body2?: ParseKeys<Namespace>; | |
cancel?: ParseKeys<Namespace>; | |
confirm?: ParseKeys<Namespace>; | |
variant?: 'normal' | 'danger'; | |
} | |
// TODO should store separate `opened` flag to prevent content flickering on animated modal close | |
const confirmData = atom<null | ConfirmData>(null, 'ConfirmModal.data'); | |
export const confirm = action( | |
async (ctx, data: ConfirmData): Promise<null | boolean> => { | |
if (ctx.get(confirmData)) { | |
throw new Error(`There is already one confirm modal opened`); | |
} | |
confirmData(ctx, data); | |
return await Promise.race([ | |
take(ctx, onClose).then(() => null), | |
take(ctx, onDiscard).then(() => false), | |
take(ctx, onConfirm).then(() => true), | |
]).finally(() => { | |
confirmData(ctx, null); | |
}); | |
}, | |
'ConfirmModal.confirm', | |
); | |
const onClose = action('ConfirmModal.onClose'); | |
const onDiscard = action('ConfirmModal.onDiscard'); | |
const onConfirm = action('ConfirmModal.onConfirm'); | |
export const withConfirmation = | |
<T extends AsyncAction>(options: ConfirmData) => | |
(anAsync: T): T => { | |
const retry = action(anAsync, `${anAsync.__reatom.name}.confirm._retry`); | |
anAsync.onCall((ctx, promise, params) => { | |
if (isCausedBy(ctx, retry)) return; | |
promise.controller.abort(toAbortError('confirmation')); | |
spawn(ctx, (ctx) => { | |
confirm(ctx, options).then((isConfirmed) => { | |
if (isConfirmed) retry(ctx, ...params); | |
}); | |
}); | |
}); | |
return anAsync; | |
}; | |
export const ConfirmModal = reatomComponent(({ ctx }) => { | |
const { t } = useTranslation(); | |
const data = ctx.spy(confirmData); | |
return ( | |
<Modal | |
opened={!!data} | |
onClose={ctx.bind(onClose)} | |
centered | |
closeOnEscape | |
closeOnClickOutside | |
size="lg" | |
zIndex={500} | |
overlayProps={{ | |
backgroundOpacity: 0.55, | |
blur: 3, | |
}} | |
title={ | |
data && ( | |
<Group align={'center'} gap={'8px'}> | |
{data.variant === 'danger' && <WarningCircle />} | |
<Heading2> {t(data.title) as string}</Heading2> | |
</Group> | |
) | |
} | |
> | |
{data && ( | |
<> | |
<Body1 c="grey" mb={16}> | |
{t(data.body1) as string} | |
</Body1> | |
{data?.body2 && <Body1 c="grey">{t(data.body2) as string}</Body1>} | |
<Flex justify="end" gap="8px" mt={24}> | |
<Button | |
variant="subtle" | |
w={'fit-content'} | |
size="lg" | |
px={16} | |
onClick={ctx.bind(onDiscard)} | |
> | |
{t(data.cancel ?? 'shared:cancel') as string} | |
</Button> | |
<Button | |
style={ | |
data.variant === 'danger' | |
? { | |
background: 'var(--color-background-danger-bold-default)', | |
} | |
: undefined | |
} | |
variant="filled" | |
w={'fit-content'} | |
size="lg" | |
px={16} | |
onClick={ctx.bind(onConfirm)} | |
autoFocus | |
> | |
{t(data.confirm ?? 'shared:confirm') as string} | |
</Button> | |
</Flex> | |
</> | |
)} | |
</Modal> | |
); | |
}, 'ConfirmModal'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment