Files
Speisekarte/components/PasskeyManager.tsx
T

117 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { createClient } from "@/lib/supabase/client";
interface PasskeyItem {
id: string;
friendly_name?: string;
created_at: string;
last_used_at?: string;
}
export default function PasskeyManager() {
const supabase = useMemo(() => createClient(), []);
const [passkeys, setPasskeys] = useState<PasskeyItem[]>([]);
const [error, setError] = useState<string | null>(null);
const [info, setInfo] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
const load = useCallback(async () => {
const { data, error } = await supabase.auth.passkey.list();
if (error) setError(`Passkeys konnten nicht geladen werden: ${error.message}`);
else setPasskeys((data ?? []) as PasskeyItem[]);
}, [supabase]);
useEffect(() => {
load();
}, [load]);
async function register() {
setError(null);
setInfo(null);
setBusy(true);
try {
const { error } = await supabase.auth.registerPasskey();
if (error) {
setError(`Registrierung fehlgeschlagen: ${error.message}`);
} else {
setInfo("Passkey wurde erfolgreich registriert.");
await load();
}
} catch (err) {
setError(
/NotAllowedError|abort/i.test(String(err))
? "Die Passkey-Registrierung wurde abgebrochen."
: `Registrierung fehlgeschlagen: ${err instanceof Error ? err.message : String(err)}`
);
}
setBusy(false);
}
async function remove(pk: PasskeyItem) {
if (!window.confirm("Diesen Passkey wirklich löschen?")) return;
const { error } = await supabase.auth.passkey.delete({ passkeyId: pk.id });
if (error) setError(`Löschen fehlgeschlagen: ${error.message}`);
await load();
}
return (
<div className="max-w-2xl">
<h1 className="text-xl font-bold text-[var(--c-primary)] mb-2">
Passkeys
</h1>
<p className="text-sm text-gray-600 mb-4">
Mit einem Passkey (Fingerabdruck, Gesichtserkennung oder
Sicherheitsschlüssel) kannst du dich künftig ohne Passwort anmelden.
Passkeys gelten pro Domain – für den Live-Betrieb muss die Domain in
der Supabase-Konfiguration hinterlegt sein.
</p>
{error && (
<p className="mb-3 text-sm text-[var(--c-del)]" role="alert">
{error}
</p>
)}
{info && <p className="mb-3 text-sm text-green-700">{info}</p>}
<button
type="button"
className="btn btn-add mb-4"
onClick={register}
disabled={busy}
>
Neuen Passkey registrieren
</button>
<ul className="divide-y divide-gray-200 border border-gray-200 rounded-md bg-white">
{passkeys.map((pk) => (
<li key={pk.id} className="flex items-center gap-3 px-4 py-2 text-sm">
<span className="mr-auto">
<span className="font-semibold">
{pk.friendly_name || "Passkey"}
</span>{" "}
<span className="text-gray-500">
– angelegt am{" "}
{new Date(pk.created_at).toLocaleDateString("de-DE")}
</span>
</span>
<button
type="button"
className="btn btn-del"
onClick={() => remove(pk)}
>
Löschen
</button>
</li>
))}
{passkeys.length === 0 && (
<li className="px-4 py-3 text-gray-500 text-sm">
Noch keine Passkeys registriert.
</li>
)}
</ul>
</div>
);
}