import { useCallback, useMemo, useState } from 'react' import { AlertTriangle, KeyRound, Loader2, ShieldAlert } from 'lucide-react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import dayjs from '@/lib/dayjs' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import { StatusBadge } from '@/components/status-badge' import { usePasskeyManagement } from '@/features/auth/passkey' import { SecureVerificationDialog, useSecureVerification, type VerificationMethod, type VerificationMethods, } from '@/features/auth/secure-verification' interface PasskeyCardProps { loading: boolean } export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) { const { t } = useTranslation() const [confirmOpen, setConfirmOpen] = useState(false) const [restrictedMethod, setRestrictedMethod] = useState(null) const { status, loading, registering, removing, supported, enabled, lastUsed, register, remove, } = usePasskeyManagement() const { open: verificationOpen, setOpen: setVerificationOpen, methods: verificationMethods, state: verificationState, startVerification, executeVerification, cancel: cancelVerification, setCode, switchMethod, fetchVerificationMethods, } = useSecureVerification({ onSuccess: () => { setRestrictedMethod(null) }, }) const dialogMethods = useMemo(() => { if (!restrictedMethod) return verificationMethods return { ...verificationMethods, has2FA: restrictedMethod === '2fa' && verificationMethods.has2FA, hasPasskey: restrictedMethod === 'passkey' && verificationMethods.hasPasskey, } }, [restrictedMethod, verificationMethods]) const handleRegister = useCallback(async () => { if (!supported) { toast.info(t('This device does not support Passkey')) return } const methods = await fetchVerificationMethods() if (!methods.has2FA) { // Without 2FA enabled, register directly. The browser-level Passkey prompt // is itself a strong proof of presence, so no extra verification is needed. await register() return } setRestrictedMethod('2fa') await startVerification(register, { preferredMethod: '2fa', title: t('Security verification'), description: t( 'Confirm your identity with Two-factor Authentication before registering a Passkey.' ), }) }, [fetchVerificationMethods, register, startVerification, supported, t]) const handleRemove = useCallback(async () => { const methods = await fetchVerificationMethods() const required: VerificationMethod | null = methods.has2FA ? '2fa' : methods.hasPasskey ? 'passkey' : null if (!required) { toast.error( t( 'Please enable Two-factor Authentication or Passkey before proceeding' ) ) return } if (required === 'passkey' && !methods.passkeySupported) { toast.info(t('This device does not support Passkey')) return } setConfirmOpen(false) setRestrictedMethod(required) await startVerification(remove, { preferredMethod: required, title: t('Security verification'), description: t( 'Confirm your identity before removing this Passkey from your account.' ), }) }, [fetchVerificationMethods, remove, startVerification, t]) const handleVerificationCancel = useCallback(() => { setRestrictedMethod(null) cancelVerification() }, [cancelVerification]) const handleVerificationOpenChange = useCallback( (next: boolean) => { if (!next) { setRestrictedMethod(null) } setVerificationOpen(next) }, [setVerificationOpen] ) // Adapt the hook's `Promise` return into the dialog's // `void | Promise` signature without losing error propagation // semantics (errors are surfaced via toast inside the hook). const handleDialogVerify = useCallback( async (method: VerificationMethod, code?: string) => { try { await executeVerification(method, code) } catch { // Errors are already surfaced by useSecureVerification via toast. } }, [executeVerification] ) if (pageLoading || loading) { return ( ) } const formattedLastUsed = lastUsed && !Number.isNaN(Date.parse(lastUsed)) ? dayjs(lastUsed).fromNow() : t('Not used yet') const showUnsupportedNotice = !supported && !enabled return ( <> {t('Passkey Login')} {t('Use Passkey to sign in without entering your password.')}

{t('Passkey Authentication')}

{status?.backup_eligible !== undefined && ( )}

{t('Last used:')} {formattedLastUsed}

{!enabled && ( )}
{enabled && (
{t('Remove Passkey?')} {t( 'Removing Passkey will require you to sign in with your password next time. You can re-register anytime.' )} {t('Cancel')} { event.preventDefault() handleRemove() }} > {t('Remove')}
)} {showUnsupportedNotice && (

{t('Passkey not supported on this device')}

{t( 'Use a compatible browser or device with biometric authentication or a security key to register a Passkey.' )}

)}
) }