"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { Alert, Button, CloseButton, Input, Label, Spinner, TextField } from "@heroui/react"; import { Fingerprint, Pencil, TrashBin, Xmark, Check } from "@gravity-ui/icons"; import { authClient } from "@/lib/auth-client"; type Passkey = { id: string; name?: string | null; createdAt: Date | string; deviceType?: string | null; }; export default function SettingsPage() { const [passkeys, setPasskeys] = useState([]); const [loading, setLoading] = useState(true); const [addingName, setAddingName] = useState(null); // null = collapsed const [registering, setRegistering] = useState(false); const [deletingId, setDeletingId] = useState(null); const [renamingId, setRenamingId] = useState(null); const [renameValue, setRenameValue] = useState(""); const [renameSaving, setRenameSaving] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const renameInputRef = useRef(null); const loadPasskeys = useCallback(async () => { setLoading(true); try { const res = await authClient.passkey.listUserPasskeys(); setPasskeys((res.data as Passkey[] | null) ?? []); } catch { setError("获取 Passkey 列表失败"); } finally { setLoading(false); } }, []); useEffect(() => { void loadPasskeys(); }, [loadPasskeys]); const handleStartAdd = () => { setAddingName(""); setError(""); setSuccess(""); }; const handleCancelAdd = () => setAddingName(null); const handleRegister = async () => { setError(""); setSuccess(""); setRegistering(true); try { const res = await authClient.passkey.addPasskey({ name: addingName?.trim() || undefined, }); if (res?.error) { setError(res.error.message ?? "注册 Passkey 失败"); } else { setSuccess("Passkey 注册成功"); setAddingName(null); await loadPasskeys(); } } catch { setError("注册 Passkey 失败,请重试"); } finally { setRegistering(false); } }; const handleDelete = async (id: string) => { setError(""); setSuccess(""); setDeletingId(id); try { const res = await authClient.passkey.deletePasskey({ id }); if (res?.error) { setError(res.error.message ?? "删除 Passkey 失败"); } else { setPasskeys((prev) => prev.filter((p) => p.id !== id)); setSuccess("Passkey 已删除"); } } catch { setError("删除 Passkey 失败,请重试"); } finally { setDeletingId(null); } }; const startRename = (pk: Passkey) => { setRenamingId(pk.id); setRenameValue(pk.name ?? ""); setTimeout(() => renameInputRef.current?.focus(), 50); }; const cancelRename = () => { setRenamingId(null); setRenameValue(""); }; const handleRename = async (id: string) => { setError(""); setRenameSaving(true); try { const res = await (authClient.passkey as any).updatePasskey({ id, name: renameValue.trim() || undefined, }); if (res?.error) { setError(res.error.message ?? "重命名失败"); } else { setPasskeys((prev) => prev.map((p) => (p.id === id ? { ...p, name: renameValue.trim() || null } : p)), ); setRenamingId(null); } } catch { setError("重命名失败,请重试"); } finally { setRenameSaving(false); } }; return (

账号设置

管理您的安全凭据

{error && ( {error} setError("")} /> )} {success && ( {success} setSuccess("")} /> )} {/* Passkey section */}

Passkey

使用设备生物识别或 PIN 免密登录

{addingName === null && ( )}
{/* Inline add form */} {addingName !== null && (
setAddingName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") void handleRegister(); if (e.key === "Escape") handleCancelAdd(); }} />
)} {loading ? (
) : passkeys.length === 0 ? (
尚未添加任何 Passkey
) : (
    {passkeys.map((pk) => (
  • {/* Name row: text+pencil or inline input */}
    {renamingId === pk.id ? ( <> setRenameValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") void handleRename(pk.id); if (e.key === "Escape") cancelRename(); }} placeholder="输入名称" /> ) : ( <>

    {pk.name ?? pk.deviceType ?? "Passkey"}

    )}
    {/* Date row: always visible */}

    添加于{" "} {new Date(pk.createdAt).toLocaleString("zh-CN", { year: "numeric", month: "short", day: "numeric", })}

  • ))}
)}
); }