refactor: 提取 useToast hook + Toast 组件,消除 4 处重复的通知逻辑

将 state + setTimeout 自动消失逻辑封装为 useToast hook,
Toast UI 统一为组件支持 top/bottom 两种位置,净减约 12 行。
This commit is contained in:
2026-02-26 17:50:28 +08:00
parent 948274bcb9
commit b98920858c
6 changed files with 86 additions and 98 deletions
+9 -26
View File
@@ -23,6 +23,8 @@ import {
import confetti from "canvas-confetti";
import { getCachedProfile, isRegistered } from "@/lib/userId";
import ShareCardModal from "@/components/ShareCardModal";
import Toast from "@/components/Toast";
import { useToast } from "@/hooks/useToast";
import { BlindboxRoomSkeleton } from "@/components/Skeleton";
import type { UserProfile } from "@/types";
@@ -182,7 +184,7 @@ export default function BlindboxRoomPage() {
const [error, setError] = useState("");
const [showInvite, setShowInvite] = useState(false);
const [showShareCard, setShowShareCard] = useState(false);
const [toast, setToast] = useState("");
const toast = useToast();
const [confirmLeave, setConfirmLeave] = useState(false);
const [leaving, setLeaving] = useState(false);
@@ -314,8 +316,7 @@ export default function BlindboxRoomPage() {
}
setMyIdeas((prev) => prev.map((i) => (i.id === ideaId ? { ...i, content: trimmed } : i)));
} catch (e) {
setToast(e instanceof Error ? e.message : "编辑失败");
setTimeout(() => setToast(""), 2200);
toast.show(e instanceof Error ? e.message : "编辑失败");
}
}, [profile]);
@@ -334,8 +335,7 @@ export default function BlindboxRoomPage() {
setMyIdeas((prev) => prev.filter((i) => i.id !== ideaId));
setPoolCount((c) => Math.max(0, c - 1));
} catch (e) {
setToast(e instanceof Error ? e.message : "删除失败");
setTimeout(() => setToast(""), 2200);
toast.show(e instanceof Error ? e.message : "删除失败");
}
}, [profile]);
@@ -395,8 +395,7 @@ export default function BlindboxRoomPage() {
if (!room) return;
try {
await navigator.clipboard.writeText(room.code);
setToast("房间号已复制");
setTimeout(() => setToast(""), 2000);
toast.show("房间号已复制");
} catch { /* ignore */ }
};
@@ -441,8 +440,7 @@ export default function BlindboxRoomPage() {
}
router.replace("/blindbox");
} catch (e) {
setToast(e instanceof Error ? e.message : "操作失败");
setTimeout(() => setToast(""), 2200);
toast.show(e instanceof Error ? e.message : "操作失败");
setConfirmLeave(false);
} finally {
setLeaving(false);
@@ -817,10 +815,7 @@ export default function BlindboxRoomPage() {
drawer: revealedIdea.drawnBy ?? undefined,
roomName: room.name,
}}
onToast={(msg) => {
setToast(msg);
setTimeout(() => setToast(""), 2200);
}}
onToast={toast.show}
/>
)}
@@ -855,19 +850,7 @@ export default function BlindboxRoomPage() {
</motion.div>
)}
{/* Toast */}
<AnimatePresence>
{toast && (
<motion.div
className="fixed bottom-8 left-1/2 -translate-x-1/2 rounded-full bg-surface px-4 py-2 text-xs font-semibold text-secondary shadow-xl ring-1 ring-border"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
>
{toast}
</motion.div>
)}
</AnimatePresence>
<Toast message={toast.message} position="bottom" />
<div className="h-8 shrink-0" />
</div>