feat(web): integrate toast notifications for profile and passkey actions

This commit is contained in:
2026-03-11 17:24:56 +08:00
parent 168a5b5613
commit 8ee2378c78

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { Alert, Button, CloseButton, Input, Label, Spinner, TextField } from "@heroui/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 { Fingerprint, Lock, Pencil, Person, TrashBin, Xmark, Check } from "@gravity-ui/icons";
import { authClient, useSession } from "@/lib/auth-client"; import { authClient, useSession } from "@/lib/auth-client";
@@ -18,8 +18,6 @@ export default function SettingsPage() {
// ── Profile ────────────────────────────────────────────────────────────── // ── Profile ──────────────────────────────────────────────────────────────
const [profileName, setProfileName] = useState(""); const [profileName, setProfileName] = useState("");
const [savingProfile, setSavingProfile] = useState(false); const [savingProfile, setSavingProfile] = useState(false);
const [profileError, setProfileError] = useState("");
const [profileSuccess, setProfileSuccess] = useState("");
// sync name from session once loaded // sync name from session once loaded
useEffect(() => { useEffect(() => {
@@ -27,19 +25,17 @@ export default function SettingsPage() {
}, [session?.user.name]); }, [session?.user.name]);
const handleSaveProfile = async () => { const handleSaveProfile = async () => {
setProfileError("");
setProfileSuccess("");
setSavingProfile(true); setSavingProfile(true);
try { try {
const res = await authClient.updateUser({ name: profileName.trim() }); const res = await authClient.updateUser({ name: profileName.trim() });
if (res?.error) { if (res?.error) {
setProfileError(res.error.message ?? "保存失败"); toast.danger(res.error.message ?? "保存失败");
} else { } else {
setProfileSuccess("显示名称已更新"); toast.success("显示名称已更新");
await refetchSession(); await refetchSession();
} }
} catch { } catch {
setProfileError("保存失败,请重试"); toast.danger("保存失败,请重试");
} finally { } finally {
setSavingProfile(false); setSavingProfile(false);
} }
@@ -95,8 +91,6 @@ export default function SettingsPage() {
const [renamingId, setRenamingId] = useState<string | null>(null); const [renamingId, setRenamingId] = useState<string | null>(null);
const [renameValue, setRenameValue] = useState(""); const [renameValue, setRenameValue] = useState("");
const [renameSaving, setRenameSaving] = useState(false); const [renameSaving, setRenameSaving] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const renameInputRef = useRef<HTMLInputElement>(null); const renameInputRef = useRef<HTMLInputElement>(null);
const loadPasskeys = useCallback(async () => { const loadPasskeys = useCallback(async () => {
@@ -105,7 +99,7 @@ export default function SettingsPage() {
const res = await authClient.passkey.listUserPasskeys(); const res = await authClient.passkey.listUserPasskeys();
setPasskeys((res.data as Passkey[] | null) ?? []); setPasskeys((res.data as Passkey[] | null) ?? []);
} catch { } catch {
setError("获取 Passkey 列表失败"); toast.danger("获取 Passkey 列表失败");
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -117,48 +111,42 @@ export default function SettingsPage() {
const handleStartAdd = () => { const handleStartAdd = () => {
setAddingName(""); setAddingName("");
setError("");
setSuccess("");
}; };
const handleCancelAdd = () => setAddingName(null); const handleCancelAdd = () => setAddingName(null);
const handleRegister = async () => { const handleRegister = async () => {
setError("");
setSuccess("");
setRegistering(true); setRegistering(true);
try { try {
const res = await authClient.passkey.addPasskey({ const res = await authClient.passkey.addPasskey({
name: addingName?.trim() || undefined, name: addingName?.trim() || undefined,
}); });
if (res?.error) { if (res?.error) {
setError(res.error.message ?? "注册 Passkey 失败"); toast.danger(res.error.message ?? "注册 Passkey 失败");
} else { } else {
setSuccess("Passkey 注册成功"); toast.success("Passkey 注册成功");
setAddingName(null); setAddingName(null);
await loadPasskeys(); await loadPasskeys();
} }
} catch { } catch {
setError("注册 Passkey 失败,请重试"); toast.danger("注册 Passkey 失败,请重试");
} finally { } finally {
setRegistering(false); setRegistering(false);
} }
}; };
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
setError("");
setSuccess("");
setDeletingId(id); setDeletingId(id);
try { try {
const res = await authClient.passkey.deletePasskey({ id }); const res = await authClient.passkey.deletePasskey({ id });
if (res?.error) { if (res?.error) {
setError(res.error.message ?? "删除 Passkey 失败"); toast.danger(res.error.message ?? "删除 Passkey 失败");
} else { } else {
setPasskeys((prev) => prev.filter((p) => p.id !== id)); setPasskeys((prev) => prev.filter((p) => p.id !== id));
setSuccess("Passkey 已删除"); toast.success("Passkey 已删除");
} }
} catch { } catch {
setError("删除 Passkey 失败,请重试"); toast.danger("删除 Passkey 失败,请重试");
} finally { } finally {
setDeletingId(null); setDeletingId(null);
} }
@@ -176,7 +164,6 @@ export default function SettingsPage() {
}; };
const handleRename = async (id: string) => { const handleRename = async (id: string) => {
setError("");
setRenameSaving(true); setRenameSaving(true);
try { try {
const res = await (authClient.passkey as any).updatePasskey({ const res = await (authClient.passkey as any).updatePasskey({
@@ -184,7 +171,7 @@ export default function SettingsPage() {
name: renameValue.trim() || undefined, name: renameValue.trim() || undefined,
}); });
if (res?.error) { if (res?.error) {
setError(res.error.message ?? "重命名失败"); toast.danger(res.error.message ?? "重命名失败");
} else { } else {
setPasskeys((prev) => setPasskeys((prev) =>
prev.map((p) => (p.id === id ? { ...p, name: renameValue.trim() || null } : p)), prev.map((p) => (p.id === id ? { ...p, name: renameValue.trim() || null } : p)),
@@ -192,7 +179,7 @@ export default function SettingsPage() {
setRenamingId(null); setRenamingId(null);
} }
} catch { } catch {
setError("重命名失败,请重试"); toast.danger("重命名失败,请重试");
} finally { } finally {
setRenameSaving(false); setRenameSaving(false);
} }
@@ -228,24 +215,6 @@ export default function SettingsPage() {
}} }}
/> />
</TextField> </TextField>
{profileError && (
<Alert status="danger">
<Alert.Indicator />
<Alert.Content>
<Alert.Description>{profileError}</Alert.Description>
</Alert.Content>
<CloseButton onPress={() => setProfileError("")} />
</Alert>
)}
{profileSuccess && (
<Alert status="success">
<Alert.Indicator />
<Alert.Content>
<Alert.Description>{profileSuccess}</Alert.Description>
</Alert.Content>
<CloseButton onPress={() => setProfileSuccess("")} />
</Alert>
)}
<div className="flex justify-end"> <div className="flex justify-end">
<Button <Button
size="sm" size="sm"
@@ -379,29 +348,6 @@ export default function SettingsPage() {
</div> </div>
)} )}
{error && (
<div className="border-b border-border px-5 py-3">
<Alert status="danger">
<Alert.Indicator />
<Alert.Content>
<Alert.Description>{error}</Alert.Description>
</Alert.Content>
<CloseButton onPress={() => setError("")} />
</Alert>
</div>
)}
{success && (
<div className="border-b border-border px-5 py-3">
<Alert status="success">
<Alert.Indicator />
<Alert.Content>
<Alert.Description>{success}</Alert.Description>
</Alert.Content>
<CloseButton onPress={() => setSuccess("")} />
</Alert>
</div>
)}
{loading ? ( {loading ? (
<div className="flex justify-center py-8"> <div className="flex justify-center py-8">
<Spinner /> <Spinner />