// Settings → Security: sign in with Face ID, a fingerprint or a security key instead of typing a password. import { useEffect, useState } from 'react'; import { listPasskeys, passkeyRemove, passkeyRename } from '../../syncService.js'; import { addPasskeyHere, passkeysSupported } from '../../utils/passkeys.js'; import { useToast } from '../Toasts.jsx'; const btn = 'rounded-lg border border-[var(--r-line)] px-3 py-1.5 text-xs hover:bg-black/5 disabled:opacity-50'; const day = (t) => new Date(t).toLocaleDateString(); export default function PasskeysSection() { const { toast } = useToast(); const [list, setList] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [editing, setEditing] = useState(null); // { id, name } const supported = passkeysSupported(); useEffect(() => { listPasskeys().then((r) => setList(r.ok ? r.data : [])); }, []); const add = async () => { setBusy(true); setError(''); const r = await addPasskeyHere(); setBusy(false); if (r.ok) { setList(r.data); toast('Passkey added. You can now sign in with it.'); } else setError(r.error ?? 'Could not add a passkey.'); }; const remove = async (p) => { if (!window.confirm(`Remove the passkey “${p.name}”? You can still sign in with your password.`)) return; const r = await passkeyRemove(p.id); if (r.ok) setList(r.data); else setError(r.error ?? 'Could not remove it.'); }; const rename = async (e) => { e.preventDefault(); const r = await passkeyRename(editing.id, editing.name); if (r.ok) { setList(r.data); setEditing(null); } else setError(r.error ?? 'Could not rename it.'); }; return (

Passkeys

Sign in with Face ID, a fingerprint or a security key, with nothing to type. A passkey is checked by your device, so it replaces the authenticator code too.

{!supported &&

This browser or address can’t use passkeys. They need a recent browser and an https:// address.

} {list?.length > 0 && ( )} {list && list.length === 0 && supported &&

No passkeys yet.

} {error &&

{error}

} {supported && }
); }