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 -24
View File
@@ -34,6 +34,8 @@ import { isRegistered } from "@/lib/userId";
import ShareCardModal from "@/components/ShareCardModal";
import RestaurantImage from "@/components/RestaurantImage";
import AuthModal from "@/components/AuthModal";
import Toast from "@/components/Toast";
import { useToast } from "@/hooks/useToast";
interface MatchResultProps {
restaurant: Restaurant;
@@ -194,7 +196,7 @@ export default function MatchResult({
const router = useRouter();
const [showRunnerUps, setShowRunnerUps] = useState(false);
const [showShareCard, setShowShareCard] = useState(false);
const [toast, setToast] = useState("");
const toast = useToast();
const celebratedRef = useRef(false);
const historySavedRef = useRef(false);
const isSolo = userCount <= 1;
@@ -204,11 +206,6 @@ export default function MatchResult({
const [registered, setRegistered] = useState(() => isRegistered());
const [showAuth, setShowAuth] = useState(false);
const showToast = useCallback((msg: string) => {
setToast(msg);
setTimeout(() => setToast(""), 2200);
}, []);
useEffect(() => {
if (isUnanimous && !celebratedRef.current) {
const timer = setTimeout(() => {
@@ -247,9 +244,9 @@ export default function MatchResult({
(profile: UserProfile) => {
setRegistered(true);
setShowAuth(false);
showToast(`欢迎,${profile.username}!记录已保存`);
toast.show(`欢迎,${profile.username}!记录已保存`);
},
[showToast],
[toast],
);
const handleFavorite = useCallback(async () => {
@@ -263,13 +260,13 @@ export default function MatchResult({
});
if (res.ok) {
setFavorited(true);
showToast("已收藏");
toast.show("已收藏");
}
} catch {
/* ignore */
}
setFavLoading(false);
}, [registered, userId, restaurant, favorited, favLoading, showToast]);
}, [registered, userId, restaurant, favorited, favLoading, toast]);
if (matchType === "no_match") {
return <NoMatchResult onReset={onReset} resetting={resetting} />;
@@ -635,22 +632,10 @@ export default function MatchResult({
userCount,
scene,
}}
onToast={showToast}
onToast={toast.show}
/>
<AnimatePresence>
{toast && (
<motion.div
className="fixed left-1/2 top-10 z-60 -translate-x-1/2 rounded-xl bg-surface px-4 py-2.5 text-xs font-medium text-heading shadow-lg ring-1 ring-border"
initial={{ opacity: 0, y: -12, x: "-50%" }}
animate={{ opacity: 1, y: 0, x: "-50%" }}
exit={{ opacity: 0, y: -12, x: "-50%" }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
>
{toast}
</motion.div>
)}
</AnimatePresence>
<Toast message={toast.message} />
</motion.div>
);
}