import { useMemo } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { match } from 'ts-pattern'; import { z } from 'zod'; import { downloadFile } from '@documenso/lib/client-only/download-file'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@documenso/ui/primitives/dialog'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@documenso/ui/primitives/form/form'; import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { useToast } from '@documenso/ui/primitives/use-toast'; import { RecoveryCodeList } from './recovery-code-list'; export const ZViewRecoveryCodesForm = z.object({ password: z.string().min(6).max(72), }); export type TViewRecoveryCodesForm = z.infer; export type ViewRecoveryCodesDialogProps = { open: boolean; onOpenChange: (_open: boolean) => void; }; export const ViewRecoveryCodesDialog = ({ open, onOpenChange }: ViewRecoveryCodesDialogProps) => { const { toast } = useToast(); const { mutateAsync: viewRecoveryCodes, data: viewRecoveryCodesData, isLoading: isViewRecoveryCodesDataLoading, } = trpc.twoFactorAuthentication.viewRecoveryCodes.useMutation(); const viewRecoveryCodesForm = useForm({ defaultValues: { password: '', }, resolver: zodResolver(ZViewRecoveryCodesForm), }); const { isSubmitting: isViewRecoveryCodesSubmitting } = viewRecoveryCodesForm.formState; const step = useMemo(() => { if (!viewRecoveryCodesData || isViewRecoveryCodesSubmitting) { return 'authenticate'; } return 'view'; }, [viewRecoveryCodesData, isViewRecoveryCodesSubmitting]); const downloadRecoveryCodes = () => { if (viewRecoveryCodesData && viewRecoveryCodesData.recoveryCodes) { const blob = new Blob([viewRecoveryCodesData.recoveryCodes.join('\n')], { type: 'text/plain', }); downloadFile({ filename: 'documenso-2FA-recovery-codes.txt', data: blob, }); } }; const onViewRecoveryCodesFormSubmit = async ({ password }: TViewRecoveryCodesForm) => { try { await viewRecoveryCodes({ password }); } catch (_err) { toast({ title: 'Unable to view recovery codes', description: 'We were unable to view your recovery codes. Please ensure that you have entered your password correctly and try again.', variant: 'destructive', }); } }; return ( View Recovery Codes {step === 'authenticate' && ( To view your recovery codes, please enter your password below. )} {step === 'view' && ( Your recovery codes are listed below. Please store them in a safe place. )} {match(step) .with('authenticate', () => { return (
( Password )} /> ); }) .with('view', () => (
{viewRecoveryCodesData?.recoveryCodes && ( )}
)) .exhaustive()}
); };