import InputError from '@/components/input-error'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { InputOTP, InputOTPGroup, InputOTPSlot, } from '@/components/ui/input-otp'; import { useClipboard } from '@/hooks/use-clipboard'; import { OTP_MAX_LENGTH } from '@/hooks/use-two-factor-auth'; import { confirm } from '@/routes/two-factor'; import { Form } from '@inertiajs/react'; import { REGEXP_ONLY_DIGITS } from 'input-otp'; import { Check, Copy, Loader2, ScanLine } from 'lucide-react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import AlertError from './alert-error'; function GridScanIcon() { return (
{Array.from({ length: 5 }, (_, i) => (
))}
{Array.from({ length: 5 }, (_, i) => (
))}
); } function TwoFactorSetupStep({ qrCodeSvg, manualSetupKey, buttonText, onNextStep, errors, }: { qrCodeSvg: string | null; manualSetupKey: string | null; buttonText: string; onNextStep: () => void; errors: string[]; }) { const [copiedText, copy] = useClipboard(); const IconComponent = copiedText === manualSetupKey ? Check : Copy; return ( <> {errors?.length ? ( ) : ( <>
{qrCodeSvg ? (
) : ( )}
or, enter the code manually
{!manualSetupKey ? (
) : ( <> )}
)} ); } function TwoFactorVerificationStep({ onClose, onBack, }: { onClose: () => void; onBack: () => void; }) { const [code, setCode] = useState(''); const pinInputContainerRef = useRef(null); useEffect(() => { setTimeout(() => { pinInputContainerRef.current?.querySelector('input')?.focus(); }, 0); }, []); return (
onClose()} resetOnError resetOnSuccess > {({ processing, errors, }: { processing: boolean; errors?: { confirmTwoFactorAuthentication?: { code?: string } }; }) => ( <>
{Array.from( { length: OTP_MAX_LENGTH }, (_, index) => ( ), )}
)}
); } interface TwoFactorSetupModalProps { isOpen: boolean; onClose: () => void; requiresConfirmation: boolean; twoFactorEnabled: boolean; qrCodeSvg: string | null; manualSetupKey: string | null; clearSetupData: () => void; fetchSetupData: () => Promise; errors: string[]; } export default function TwoFactorSetupModal({ isOpen, onClose, requiresConfirmation, twoFactorEnabled, qrCodeSvg, manualSetupKey, clearSetupData, fetchSetupData, errors, }: TwoFactorSetupModalProps) { const [showVerificationStep, setShowVerificationStep] = useState(false); const modalConfig = useMemo<{ title: string; description: string; buttonText: string; }>(() => { if (twoFactorEnabled) { return { title: 'Two-Factor Authentication Enabled', description: 'Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.', buttonText: 'Close', }; } if (showVerificationStep) { return { title: 'Verify Authentication Code', description: 'Enter the 6-digit code from your authenticator app', buttonText: 'Continue', }; } return { title: 'Enable Two-Factor Authentication', description: 'To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app', buttonText: 'Continue', }; }, [twoFactorEnabled, showVerificationStep]); const handleModalNextStep = useCallback(() => { if (requiresConfirmation) { setShowVerificationStep(true); return; } clearSetupData(); onClose(); }, [requiresConfirmation, clearSetupData, onClose]); const resetModalState = useCallback(() => { setShowVerificationStep(false); if (twoFactorEnabled) { clearSetupData(); } }, [twoFactorEnabled, clearSetupData]); useEffect(() => { if (!isOpen) { resetModalState(); return; } if (!qrCodeSvg) { fetchSetupData(); } }, [isOpen, qrCodeSvg, fetchSetupData, resetModalState]); return ( !open && onClose()}> {modalConfig.title} {modalConfig.description}
{showVerificationStep ? ( setShowVerificationStep(false)} /> ) : ( )}
); }