import { useCallback, useEffect, useState } from "react"; import { startRegistration } from "@simplewebauthn/browser"; import { api } from "./api.js"; interface Passkey { id: string; name: string; createdAt: number; operator: string | null; } export function PasskeysPanel() { const [passkeys, setPasskeys] = useState([]); const [name, setName] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const refresh = useCallback(() => { api .passkeys() .then((r) => setPasskeys(r.passkeys)) .catch((e) => setError(e.message)); }, []); useEffect(refresh, [refresh]); const add = async () => { setBusy(true); setError(null); try { const options = await api.passkeyRegisterOptions(); const response = await startRegistration({ optionsJSON: options }); await api.passkeyRegister(name.trim(), response); setName(""); refresh(); } catch (e) { const err = e as Error; if (err.name !== "NotAllowedError") setError(err.message); } finally { setBusy(false); } }; const remove = async (id: string) => { setBusy(true); setError(null); try { await api.passkeyDelete(id); refresh(); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } }; return (

Passkeys

{error &&
{error}
} {passkeys.length === 0 && (

no passkeys yet. password sign-in only.

)} {passkeys.map((p) => (
{p.name} {p.operator && @{p.operator}} {new Date(p.createdAt).toLocaleDateString()}
))}
setName(e.target.value)} />
); }