feat(web): integrate toast notifications for profile and passkey actions
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
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 { authClient, useSession } from "@/lib/auth-client";
|
||||
|
||||
@@ -18,8 +18,6 @@ export default function SettingsPage() {
|
||||
// ── Profile ──────────────────────────────────────────────────────────────
|
||||
const [profileName, setProfileName] = useState("");
|
||||
const [savingProfile, setSavingProfile] = useState(false);
|
||||
const [profileError, setProfileError] = useState("");
|
||||
const [profileSuccess, setProfileSuccess] = useState("");
|
||||
|
||||
// sync name from session once loaded
|
||||
useEffect(() => {
|
||||
@@ -27,19 +25,17 @@ export default function SettingsPage() {
|
||||
}, [session?.user.name]);
|
||||
|
||||
const handleSaveProfile = async () => {
|
||||
setProfileError("");
|
||||
setProfileSuccess("");
|
||||
setSavingProfile(true);
|
||||
try {
|
||||
const res = await authClient.updateUser({ name: profileName.trim() });
|
||||
if (res?.error) {
|
||||
setProfileError(res.error.message ?? "保存失败");
|
||||
toast.danger(res.error.message ?? "保存失败");
|
||||
} else {
|
||||
setProfileSuccess("显示名称已更新");
|
||||
toast.success("显示名称已更新");
|
||||
await refetchSession();
|
||||
}
|
||||
} catch {
|
||||
setProfileError("保存失败,请重试");
|
||||
toast.danger("保存失败,请重试");
|
||||
} finally {
|
||||
setSavingProfile(false);
|
||||
}
|
||||
@@ -95,8 +91,6 @@ export default function SettingsPage() {
|
||||
const [renamingId, setRenamingId] = useState<string | null>(null);
|
||||
const [renameValue, setRenameValue] = useState("");
|
||||
const [renameSaving, setRenameSaving] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
const renameInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const loadPasskeys = useCallback(async () => {
|
||||
@@ -105,7 +99,7 @@ export default function SettingsPage() {
|
||||
const res = await authClient.passkey.listUserPasskeys();
|
||||
setPasskeys((res.data as Passkey[] | null) ?? []);
|
||||
} catch {
|
||||
setError("获取 Passkey 列表失败");
|
||||
toast.danger("获取 Passkey 列表失败");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -117,48 +111,42 @@ export default function SettingsPage() {
|
||||
|
||||
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 失败");
|
||||
toast.danger(res.error.message ?? "注册 Passkey 失败");
|
||||
} else {
|
||||
setSuccess("Passkey 注册成功");
|
||||
toast.success("Passkey 注册成功");
|
||||
setAddingName(null);
|
||||
await loadPasskeys();
|
||||
}
|
||||
} catch {
|
||||
setError("注册 Passkey 失败,请重试");
|
||||
toast.danger("注册 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 失败");
|
||||
toast.danger(res.error.message ?? "删除 Passkey 失败");
|
||||
} else {
|
||||
setPasskeys((prev) => prev.filter((p) => p.id !== id));
|
||||
setSuccess("Passkey 已删除");
|
||||
toast.success("Passkey 已删除");
|
||||
}
|
||||
} catch {
|
||||
setError("删除 Passkey 失败,请重试");
|
||||
toast.danger("删除 Passkey 失败,请重试");
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
@@ -176,7 +164,6 @@ export default function SettingsPage() {
|
||||
};
|
||||
|
||||
const handleRename = async (id: string) => {
|
||||
setError("");
|
||||
setRenameSaving(true);
|
||||
try {
|
||||
const res = await (authClient.passkey as any).updatePasskey({
|
||||
@@ -184,7 +171,7 @@ export default function SettingsPage() {
|
||||
name: renameValue.trim() || undefined,
|
||||
});
|
||||
if (res?.error) {
|
||||
setError(res.error.message ?? "重命名失败");
|
||||
toast.danger(res.error.message ?? "重命名失败");
|
||||
} else {
|
||||
setPasskeys((prev) =>
|
||||
prev.map((p) => (p.id === id ? { ...p, name: renameValue.trim() || null } : p)),
|
||||
@@ -192,7 +179,7 @@ export default function SettingsPage() {
|
||||
setRenamingId(null);
|
||||
}
|
||||
} catch {
|
||||
setError("重命名失败,请重试");
|
||||
toast.danger("重命名失败,请重试");
|
||||
} finally {
|
||||
setRenameSaving(false);
|
||||
}
|
||||
@@ -228,24 +215,6 @@ export default function SettingsPage() {
|
||||
}}
|
||||
/>
|
||||
</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">
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -379,29 +348,6 @@ export default function SettingsPage() {
|
||||
</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 ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<Spinner />
|
||||
|
||||
Reference in New Issue
Block a user