refactor: Toast 升级为全局 Context,消除 onToast prop 透传
将 useToast 从独立 state hook 改为 Context-based,在 layout 中 挂载 ToastProvider 全局渲染 Toast。QrInviteModal、RoomManageModal、 ShareCardModal 不再需要 onToast prop,直接 useToast() 调用即可。 父组件 TopNav、MatchResult、profile、blindbox 移除了本地 Toast 渲染和 onToast 传递逻辑。
This commit is contained in:
@@ -34,7 +34,6 @@ 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 {
|
||||
@@ -632,10 +631,7 @@ export default function MatchResult({
|
||||
userCount,
|
||||
scene,
|
||||
}}
|
||||
onToast={toast.show}
|
||||
/>
|
||||
|
||||
<Toast message={toast.message} />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ import { X, Copy, Share2, QrCode } from "lucide-react";
|
||||
import type { SceneType } from "@/types";
|
||||
import { getSceneConfig } from "@/lib/sceneConfig";
|
||||
import Modal from "@/components/Modal";
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
|
||||
interface QrInviteModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
roomId: string;
|
||||
onToast: (msg: string) => void;
|
||||
scene?: SceneType;
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ export default function QrInviteModal({
|
||||
open,
|
||||
onClose,
|
||||
roomId,
|
||||
onToast,
|
||||
scene = "eat",
|
||||
}: QrInviteModalProps) {
|
||||
const toast = useToast();
|
||||
const sceneConfig = getSceneConfig(scene);
|
||||
const inviteUrl =
|
||||
typeof window !== "undefined"
|
||||
@@ -31,11 +31,11 @@ export default function QrInviteModal({
|
||||
const handleCopy = useCallback(async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(inviteUrl);
|
||||
onToast("邀请链接已复制,快去发给朋友吧!");
|
||||
toast.show("邀请链接已复制,快去发给朋友吧!");
|
||||
} catch {
|
||||
onToast("复制失败,请手动复制链接");
|
||||
toast.show("复制失败,请手动复制链接");
|
||||
}
|
||||
}, [inviteUrl, onToast]);
|
||||
}, [inviteUrl, toast]);
|
||||
|
||||
const handleShare = useCallback(async () => {
|
||||
const shareData = {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { UserProfile } from "@/types";
|
||||
import { getAvatar, getAvatarBg } from "@/lib/avatars";
|
||||
import Modal from "@/components/Modal";
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
|
||||
interface RoomManageModalProps {
|
||||
open: boolean;
|
||||
@@ -24,7 +25,6 @@ interface RoomManageModalProps {
|
||||
swipeCounts: Record<string, number>;
|
||||
totalCards: number;
|
||||
userProfiles: Record<string, UserProfile>;
|
||||
onToast: (msg: string) => void;
|
||||
}
|
||||
|
||||
export default function RoomManageModal({
|
||||
@@ -37,8 +37,8 @@ export default function RoomManageModal({
|
||||
swipeCounts,
|
||||
totalCards,
|
||||
userProfiles,
|
||||
onToast,
|
||||
}: RoomManageModalProps) {
|
||||
const toast = useToast();
|
||||
const [loading, setLoading] = useState<string | null>(null);
|
||||
const [confirmKick, setConfirmKick] = useState<string | null>(null);
|
||||
const [confirmEnd, setConfirmEnd] = useState(false);
|
||||
@@ -54,33 +54,33 @@ export default function RoomManageModal({
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
onToast(data.error ?? "操作失败");
|
||||
toast.show(data.error ?? "操作失败");
|
||||
return;
|
||||
}
|
||||
switch (action) {
|
||||
case "lock":
|
||||
onToast("房间已锁定,其他人无法加入");
|
||||
toast.show("房间已锁定,其他人无法加入");
|
||||
break;
|
||||
case "unlock":
|
||||
onToast("房间已解锁");
|
||||
toast.show("房间已解锁");
|
||||
break;
|
||||
case "kick":
|
||||
onToast("已将该用户移出房间");
|
||||
toast.show("已将该用户移出房间");
|
||||
setConfirmKick(null);
|
||||
break;
|
||||
case "end_voting":
|
||||
onToast("已结束投票,正在结算结果");
|
||||
toast.show("已结束投票,正在结算结果");
|
||||
setConfirmEnd(false);
|
||||
onClose();
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
onToast("操作失败,请重试");
|
||||
toast.show("操作失败,请重试");
|
||||
} finally {
|
||||
setLoading(null);
|
||||
}
|
||||
},
|
||||
[roomId, userId, onToast, onClose],
|
||||
[roomId, userId, toast, onClose],
|
||||
);
|
||||
|
||||
const otherUsers = users.filter((u) => u !== userId);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { X, Download, Share2, Loader2, Star, MapPin, Zap } from "lucide-react";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { toPng } from "html-to-image";
|
||||
import type { Restaurant, MatchType, SceneType } from "@/types";
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
|
||||
type ShareCardData =
|
||||
| {
|
||||
@@ -28,7 +29,6 @@ interface ShareCardModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
data: ShareCardData;
|
||||
onToast?: (msg: string) => void;
|
||||
}
|
||||
|
||||
async function loadImageAsDataUrl(src: string): Promise<string | null> {
|
||||
@@ -775,8 +775,8 @@ export default function ShareCardModal({
|
||||
open,
|
||||
onClose,
|
||||
data,
|
||||
onToast,
|
||||
}: ShareCardModalProps) {
|
||||
const toast = useToast();
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const backdropRef = useRef<HTMLDivElement>(null);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
@@ -821,7 +821,7 @@ export default function ShareCardModal({
|
||||
const handleSave = useCallback(async () => {
|
||||
const png = await handleGenerate();
|
||||
if (!png) {
|
||||
onToast?.("生成图片失败,请重试");
|
||||
toast.show("生成图片失败,请重试");
|
||||
return;
|
||||
}
|
||||
const name =
|
||||
@@ -829,13 +829,13 @@ export default function ShareCardModal({
|
||||
? `NoWhatever_${data.restaurant.name}.png`
|
||||
: `NoWhatever_周末契约.png`;
|
||||
downloadDataUrl(png, name);
|
||||
onToast?.("图片已保存");
|
||||
}, [handleGenerate, onToast, data]);
|
||||
toast.show("图片已保存");
|
||||
}, [handleGenerate, toast, data]);
|
||||
|
||||
const handleShare = useCallback(async () => {
|
||||
const png = await handleGenerate();
|
||||
if (!png) {
|
||||
onToast?.("生成图片失败,请重试");
|
||||
toast.show("生成图片失败,请重试");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -852,8 +852,8 @@ export default function ShareCardModal({
|
||||
}
|
||||
|
||||
downloadDataUrl(png, "NoWhatever.png");
|
||||
onToast?.("图片已保存,快去分享吧!");
|
||||
}, [handleGenerate, onToast]);
|
||||
toast.show("图片已保存,快去分享吧!");
|
||||
}, [handleGenerate, toast]);
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === backdropRef.current) onClose();
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import { ToastContext, type ToastPosition } from "@/hooks/useToast";
|
||||
import Toast from "./Toast";
|
||||
|
||||
export default function ToastProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [message, setMessage] = useState("");
|
||||
const [position, setPosition] = useState<ToastPosition>("top");
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||
|
||||
const show = useCallback((msg: string, pos: ToastPosition = "top") => {
|
||||
clearTimeout(timerRef.current);
|
||||
setMessage(msg);
|
||||
setPosition(pos);
|
||||
timerRef.current = setTimeout(() => setMessage(""), 2200);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ show }}>
|
||||
{children}
|
||||
<Toast message={message} position={position} />
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -4,10 +4,8 @@ import { useState } from "react";
|
||||
import { QrCode, LogOut, Crown, Lock } from "lucide-react";
|
||||
import QrInviteModal from "./QrInviteModal";
|
||||
import RoomManageModal from "./RoomManageModal";
|
||||
import Toast from "@/components/Toast";
|
||||
import type { UserProfile, SceneType } from "@/types";
|
||||
import { getSceneConfig } from "@/lib/sceneConfig";
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
|
||||
interface TopNavProps {
|
||||
roomId: string;
|
||||
@@ -37,7 +35,6 @@ export default function TopNav({
|
||||
scene = "eat",
|
||||
}: TopNavProps) {
|
||||
const sceneConfig = getSceneConfig(scene);
|
||||
const toast = useToast();
|
||||
const [showQr, setShowQr] = useState(false);
|
||||
const [showManage, setShowManage] = useState(false);
|
||||
|
||||
@@ -79,13 +76,10 @@ export default function TopNav({
|
||||
</h1>
|
||||
</nav>
|
||||
|
||||
<Toast message={toast.message} />
|
||||
|
||||
<QrInviteModal
|
||||
open={showQr}
|
||||
onClose={() => setShowQr(false)}
|
||||
roomId={roomId}
|
||||
onToast={toast.show}
|
||||
scene={scene}
|
||||
/>
|
||||
|
||||
@@ -100,7 +94,6 @@ export default function TopNav({
|
||||
swipeCounts={swipeCounts}
|
||||
totalCards={totalCards}
|
||||
userProfiles={userProfiles}
|
||||
onToast={toast.show}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user