"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { Alert, Button, CloseButton, Input, Label, Spinner, TextField, toast } from "@heroui/react"; import { Fingerprint, Lock, Pencil, Person, TrashBin, Xmark, Check } from "@gravity-ui/icons"; import { authClient, useSession } from "@/lib/auth-client"; import dayjs from "@/lib/dayjs"; type Passkey = { id: string; name?: string | null; createdAt: Date | string; deviceType?: string | null; }; export default function SettingsPage() { const { data: session, refetch: refetchSession } = useSession(); // ── Profile ────────────────────────────────────────────────────────────── const [profileName, setProfileName] = useState(""); const [savingProfile, setSavingProfile] = useState(false); // sync name from session once loaded useEffect(() => { if (session?.user.name) setProfileName(session.user.name); }, [session?.user.name]); const handleSaveProfile = async () => { setSavingProfile(true); try { const res = await authClient.updateUser({ name: profileName.trim() }); if (res?.error) { toast.danger(res.error.message ?? "保存失败"); } else { toast.success("显示名称已更新"); await refetchSession(); } } catch { toast.danger("保存失败,请重试"); } finally { setSavingProfile(false); } }; // ── Password ───────────────────────────────────────────────────────────── const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [savingPw, setSavingPw] = useState(false); const [pwError, setPwError] = useState(""); const [pwSuccess, setPwSuccess] = useState(""); const handleChangePassword = async () => { setPwError(""); setPwSuccess(""); if (newPassword !== confirmPassword) { setPwError("两次输入的新密码不一致"); return; } if (newPassword.length < 8) { setPwError("新密码至少需要 8 位"); return; } setSavingPw(true); try { const res = await authClient.changePassword({ currentPassword, newPassword, revokeOtherSessions: false, }); if (res?.error) { setPwError(res.error.message ?? "修改密码失败"); } else { setPwSuccess("密码已修改成功"); setCurrentPassword(""); setNewPassword(""); setConfirmPassword(""); } } catch { setPwError("修改密码失败,请重试"); } finally { setSavingPw(false); } }; // ── Passkey ─────────────────────────────────────────────────────────────── 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 renameInputRef = useRef(null); const loadPasskeys = useCallback(async () => { setLoading(true); try { const res = await authClient.passkey.listUserPasskeys(); setPasskeys((res.data as Passkey[] | null) ?? []); } catch { toast.danger("获取 Passkey 列表失败"); } finally { setLoading(false); } }, []); useEffect(() => { void loadPasskeys(); }, [loadPasskeys]); const handleStartAdd = () => { setAddingName(""); }; const handleCancelAdd = () => setAddingName(null); const handleRegister = async () => { setRegistering(true); try { const res = await authClient.passkey.addPasskey({ name: addingName?.trim() || undefined, }); if (res?.error) { toast.danger(res.error.message ?? "注册 Passkey 失败"); } else { toast.success("Passkey 注册成功"); setAddingName(null); await loadPasskeys(); } } catch { toast.danger("注册 Passkey 失败,请重试"); } finally { setRegistering(false); } }; const handleDelete = async (id: string) => { setDeletingId(id); try { const res = await authClient.passkey.deletePasskey({ id }); if (res?.error) { toast.danger(res.error.message ?? "删除 Passkey 失败"); } else { setPasskeys((prev) => prev.filter((p) => p.id !== id)); toast.success("Passkey 已删除"); } } catch { toast.danger("删除 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) => { setRenameSaving(true); try { const res = await (authClient.passkey as any).updatePasskey({ id, name: renameValue.trim() || undefined, }); if (res?.error) { toast.danger(res.error.message ?? "重命名失败"); } else { setPasskeys((prev) => prev.map((p) => (p.id === id ? { ...p, name: renameValue.trim() || null } : p)), ); setRenamingId(null); } } catch { toast.danger("重命名失败,请重试"); } finally { setRenameSaving(false); } }; return (

账号设置

管理您的个人信息和安全凭据

{/* ── Profile section ─────────────────────────────────────────────── */}

个人信息

修改您的显示名称

setProfileName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") void handleSaveProfile(); }} />
{/* ── Password section ─────────────────────────────────────────────── */}

修改密码

建议定期更换密码以保护账号安全

setCurrentPassword(e.target.value)} /> setNewPassword(e.target.value)} /> setConfirmPassword(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") void handleChangePassword(); }} /> {pwError && ( {pwError} setPwError("")} /> )} {pwSuccess && ( {pwSuccess} setPwSuccess("")} /> )}
{/* ── 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 */}

    添加于{" "} {dayjs(pk.createdAt).format("YYYY年M月D日")}

  • ))}
)}
); }