import { useEffect, useMemo, useState } from "react"; import { KeyRound, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Button, Input, Select2 } from "@termix/plugin-sdk/ui"; import { usePluginApi, useTranslation } from "@termix/plugin-sdk/frontend"; import { createWebAuthnApi, type WebAuthnCredentialSummary, type WebAuthnUserVerification, } from "./webauthn-api"; function apiErrorMessage(error: unknown, fallback: string): string { const data = (error as { response?: { data?: { error?: unknown } } }) ?.response?.data; return typeof data?.error === "string" && data.error ? data.error : fallback; } /** Settings > Security: register and remove passkeys. */ export function PasskeyEnrollment() { const { t } = useTranslation(); const pluginApi = usePluginApi(); const api = useMemo(() => createWebAuthnApi(pluginApi), [pluginApi]); const [passkeys, setPasskeys] = useState([]); const [loading, setLoading] = useState(false); const [name, setName] = useState(""); const [userVerification, setUserVerification] = useState("preferred"); useEffect(() => { api .list() .then(setPasskeys) .catch(() => {}); }, [api]); async function add() { setLoading(true); try { await api.register(name || t("defaultName"), userVerification); setPasskeys(await api.list()); setName(""); toast.success(t("added")); } catch (e) { toast.error(apiErrorMessage(e, t("addFailed"))); } finally { setLoading(false); } } async function remove(id: string) { setLoading(true); try { await api.remove(id); setPasskeys((prev) => prev.filter((item) => item.id !== id)); toast.success(t("deleted")); } catch (e) { toast.error(apiErrorMessage(e, t("deleteFailed"))); } finally { setLoading(false); } } return (
{t("description")}
setName(e.target.value)} className="h-8 text-xs" disabled={loading} /> setUserVerification(e.target.value as WebAuthnUserVerification) } className="h-8 w-28 text-xs border border-border bg-background px-2 outline-none focus:ring-1 focus:ring-ring" disabled={loading} >
{passkeys.length === 0 ? (
{t("none")}
) : ( passkeys.map((passkey) => (
{passkey.name} {passkey.deviceType || t("unknownDevice")} {passkey.backedUp ? ` / ${t("synced")}` : ""}
)) )}
); }