Skip to content

Instantly share code, notes, and snippets.

@artalar
Created March 28, 2024 17:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save artalar/b8321130a2fd5962dcfc502c1810356c to your computer and use it in GitHub Desktop.
Save artalar/b8321130a2fd5962dcfc502c1810356c to your computer and use it in GitHub Desktop.
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