"use client"; import { useState, useCallback, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { useRouter } from "next/navigation"; import { MapPin, Star, PartyPopper, Navigation, Phone, Clock, Trophy, RotateCcw, ChevronDown, Swords, RefreshCw, Share2, Zap, Heart, UserPlus, } from "lucide-react"; import { Restaurant, MatchType, RunnerUp, SceneType, UserProfile, } from "@/types"; import { fireCelebration, playChime } from "@/lib/celebrate"; import { isRegistered } from "@/lib/userId"; import ShareCardModal from "@/components/ShareCardModal"; import RestaurantImage from "@/components/RestaurantImage"; import AuthModal from "@/components/AuthModal"; import Button from "@/components/Button"; import NoMatchResult from "@/components/NoMatchResult"; import RunnerUpCard from "@/components/RunnerUpCard"; import { buildNavUrl } from "@/lib/navigation"; import { useToast } from "@/hooks/useToast"; interface MatchResultProps { restaurant: Restaurant; matchType: MatchType; matchLikes: number; runnerUps: RunnerUp[]; allRestaurants: Restaurant[]; userCount: number; roomId: string; userId: string; onReset: () => Promise; onNarrow: (restaurantIds: string[]) => Promise; resetting: boolean; scene?: SceneType; } export default function MatchResult({ restaurant, matchType, matchLikes, runnerUps, allRestaurants, userCount, roomId, userId, onReset, onNarrow, resetting, scene = "eat", }: MatchResultProps) { const router = useRouter(); const [showRunnerUps, setShowRunnerUps] = useState(false); const [showShareCard, setShowShareCard] = useState(false); const toast = useToast(); const celebratedRef = useRef(false); const historySavedRef = useRef(false); const isSolo = userCount <= 1; const isUnanimous = matchType === "unanimous"; const [favorited, setFavorited] = useState(false); const [favLoading, setFavLoading] = useState(false); const [registered, setRegistered] = useState(() => isRegistered()); const [showAuth, setShowAuth] = useState(false); useEffect(() => { if (isUnanimous && !celebratedRef.current) { const timer = setTimeout(() => { celebratedRef.current = true; fireCelebration(); playChime(); }, 500); return () => clearTimeout(timer); } }, [isUnanimous]); useEffect(() => { if (historySavedRef.current) return; if (!registered) return; if (matchType === "no_match") return; historySavedRef.current = true; fetch("/api/user/history", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId, roomId, restaurant, matchType, participants: userCount, }), }).catch(() => {}); }, [registered, userId, roomId, restaurant, matchType, userCount]); const handleOpenShareCard = useCallback(() => { setShowShareCard(true); }, []); const handleAuth = useCallback( (profile: UserProfile) => { setRegistered(true); setShowAuth(false); toast.show(`欢迎,${profile.username}!记录已保存`); }, [toast], ); const handleFavorite = useCallback(async () => { if (!registered || favorited || favLoading) return; setFavLoading(true); try { const res = await fetch("/api/user/favorite", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId, restaurant }), }); if (res.ok) { setFavorited(true); toast.show("已收藏"); } } catch { /* ignore */ } setFavLoading(false); }, [registered, userId, restaurant, favorited, favLoading, toast]); if (matchType === "no_match") { return ; } const runnerUpRestaurants = runnerUps .map((ru) => { const r = allRestaurants.find((rest) => rest.id === ru.id); return r ? { restaurant: r, likes: ru.likes } : null; }) .filter((x): x is { restaurant: Restaurant; likes: number } => x !== null); const canNarrow = !isUnanimous && runnerUpRestaurants.length > 0; const narrowIds = canNarrow ? [restaurant.id, ...runnerUps.map((ru) => ru.id)] : []; return ( {/* Accent glow behind icon */}
{isUnanimous ? ( ) : ( )} {isSolo ? "帮你选好了" : "就去这了"} {isSolo ? "你的首选,别犹豫了" : isUnanimous ? "大家一拍即合!" : `${matchLikes}/${userCount} 人想去这家`} {isUnanimous && !isSolo && ( 默契度 100% · {userCount} 人全员一致 )} {/* Result card */} {registered && ( )} {restaurant.images?.[0] && ( )}

{restaurant.name}

{restaurant.category && ( {restaurant.category} )}
{restaurant.rating} {restaurant.price} {restaurant.distance && ( {restaurant.distance} )}
{restaurant.address && (

{restaurant.address}

)} {restaurant.openTime && (
{restaurant.openTime}
)} {restaurant.tag && (
{restaurant.tag .split(",") .slice(0, 4) .map((t) => ( {t.trim()} ))}
)}
{/* Action buttons */} 导航过去 {restaurant.tel && ( 打电话订位 )} {/* Registration nudge */} {!registered && (

注册后,决策记录和收藏不会丢失

10 秒注册,无需手机号

)} {/* Runner ups */} {!isUnanimous && runnerUpRestaurants.length > 0 && ( {showRunnerUps && ( {runnerUpRestaurants.map(({ restaurant: r, likes }) => ( ))} )} {canNarrow && (

还有 {runnerUpRestaurants.length} 家不相上下,再选一轮?

onNarrow(narrowIds)} disabled={resetting} className="flex w-full items-center justify-center gap-2 rounded-full bg-elevated px-8 py-3 text-sm font-bold text-secondary ring-1 ring-border transition-colors hover:bg-subtle disabled:opacity-50" whileTap={{ scale: 0.95 }} > {resetting ? "加载中..." : `Top ${narrowIds.length} 决赛`}
)}
)}
{/* Floating bottom bar */}
不满意? {resetting ? "重置中..." : "再来一轮"} router.push("/")} className="flex flex-1 items-center justify-center gap-1.5 rounded-full bg-elevated py-2.5 text-xs font-bold text-secondary ring-1 ring-border transition-colors hover:bg-subtle" whileTap={{ scale: 0.95 }} > 换一批店
setShowAuth(false)} onAuth={handleAuth} defaultTab="register" /> setShowShareCard(false)} data={{ type: "restaurant", restaurant, matchType, matchLikes, userCount, scene, }} /> ); }