import { useState } from 'react'; import { useLogto } from '@logto/react'; import { errorMessage } from '../../api/client'; import { Alert, Button, Card, FormField, Input, Modal, useToast, } from '@cameleer/design-system'; import { useAccountMfaStatus, useAccountPasskeyList, useAccountRenamePasskey, useAccountDeletePasskey, } from '../../api/account-hooks'; import { registerPasskey } from '../../api/logto-account-api'; import styles from '../../styles/platform.module.css'; export function PasskeyNudgeBanner() { const { data: status } = useAccountMfaStatus(); const [dismissed, setDismissed] = useState(false); const lastDismissed = localStorage.getItem('passkey_nudge_dismissed'); const recentlyDismissed = lastDismissed && (Date.now() - Number(lastDismissed)) < 30 * 24 * 60 * 60 * 1000; if (dismissed || recentlyDismissed || !status || status.passkeyEnrolled) return null; function handleDismiss() { localStorage.setItem('passkey_nudge_dismissed', String(Date.now())); setDismissed(true); } return (

Use your fingerprint, face, or security key instead of typing a code every time.

); } export function PasskeySection() { const { toast } = useToast(); const { getAccessToken } = useLogto(); const { data: passkeys, isLoading, refetch } = useAccountPasskeyList(); const renamePasskey = useAccountRenamePasskey(); const deletePasskey = useAccountDeletePasskey(); const [editingId, setEditingId] = useState(null); const [editName, setEditName] = useState(''); const [confirmDeleteId, setConfirmDeleteId] = useState(null); const [registering, setRegistering] = useState(false); const [showPasswordModal, setShowPasswordModal] = useState(false); const [regPassword, setRegPassword] = useState(''); const [regError, setRegError] = useState(null); function parseAgent(agent: string | null): string { if (!agent) return 'Unknown device'; if (agent.includes('Chrome')) return agent.includes('Windows') ? 'Chrome on Windows' : agent.includes('Mac') ? 'Chrome on macOS' : agent.includes('Android') ? 'Chrome on Android' : 'Chrome'; if (agent.includes('Safari') && !agent.includes('Chrome')) return agent.includes('iPhone') ? 'Safari on iPhone' : 'Safari on macOS'; if (agent.includes('Firefox')) return 'Firefox'; if (agent.includes('Edge')) return 'Edge'; return 'Browser'; } function startRename(id: string, currentName: string | null) { setEditingId(id); setEditName(currentName ?? ''); } async function handleRename(id: string) { try { await renamePasskey.mutateAsync({ id, name: editName }); setEditingId(null); toast({ title: 'Passkey renamed', variant: 'success' }); } catch (err) { toast({ title: 'Failed to rename passkey', description: errorMessage(err), variant: 'error' }); } } function openRegister() { setRegPassword(''); setRegError(null); setShowPasswordModal(true); } async function handleRegister(e: React.FormEvent) { e.preventDefault(); setRegError(null); setRegistering(true); try { await registerPasskey(async () => { const token = await getAccessToken(); if (!token) throw new Error('Not authenticated'); return token; }, regPassword); setShowPasswordModal(false); await refetch(); toast({ title: 'Passkey registered', variant: 'success' }); } catch (err) { // User cancelled the WebAuthn prompt — not an error if (err instanceof Error && err.name === 'NotAllowedError') { setRegistering(false); return; } setRegError(errorMessage(err)); } finally { setRegistering(false); } } async function handleDelete(id: string) { try { await deletePasskey.mutateAsync(id); setConfirmDeleteId(null); toast({ title: 'Passkey removed', variant: 'success' }); } catch (err) { toast({ title: 'Failed to remove passkey', description: errorMessage(err), variant: 'error' }); } } if (isLoading) return null; const credentials = passkeys ?? []; return ( <>

Use your fingerprint, face, or security key to sign in faster.

{credentials.length === 0 ? (

No passkeys registered yet.

) : (
{credentials.map((pk) => (
{editingId === pk.id ? (
setEditName(e.target.value)} placeholder="Passkey name" style={{ maxWidth: 200 }} />
) : ( <>
{pk.name || 'Unnamed passkey'}
{parseAgent(pk.agent)} · Added {pk.createdAt ? new Date(pk.createdAt).toLocaleDateString() : 'unknown'}
)}
{editingId !== pk.id && (
{confirmDeleteId === pk.id ? ( <> ) : ( )}
)}
))}
)}
{ if (!registering) setShowPasswordModal(false); }} title="Confirm identity" size="sm" >

Enter your password to register a new passkey.

{regError && {regError}}
setRegPassword(e.target.value)} placeholder="Enter your password" autoFocus autoComplete="current-password" />
); }