diff --git a/src/app/api/room/[id]/route.ts b/src/app/api/room/[id]/route.ts index c53a327..fadc181 100644 --- a/src/app/api/room/[id]/route.ts +++ b/src/app/api/room/[id]/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; import { getRoomData } from "@/lib/store"; +import type { MatchType } from "@/types"; export async function GET( _req: Request, @@ -21,13 +22,29 @@ export async function GET( const allFinished = data.users.length > 0 && data.users.every((u) => (data.swipeCounts[u] ?? 0) >= total); - const noMatch = allFinished && data.match === null; + + let match = data.match; + let matchType: MatchType = null; + let matchLikes = 0; + + if (match) { + matchType = "unanimous"; + matchLikes = data.users.length; + } else if (allFinished) { + const best = findBestMatch(data.likes, data.restaurants); + if (best) { + match = best.id; + matchType = "best"; + matchLikes = best.likes; + } + } return NextResponse.json({ roomId: id, userCount: data.users.length, - match: data.match, - noMatch, + match, + matchType, + matchLikes, restaurants: data.restaurants, }); } catch (e) { @@ -38,3 +55,29 @@ export async function GET( ); } } + +function findBestMatch( + likes: Record, + restaurants: { id: string; rating: number }[], +): { id: string; likes: number } | null { + let bestId: string | null = null; + let bestLikes = 0; + let bestRating = 0; + + for (const r of restaurants) { + const count = likes[r.id]?.length ?? 0; + if (count === 0) continue; + + if ( + count > bestLikes || + (count === bestLikes && r.rating > bestRating) + ) { + bestId = r.id; + bestLikes = count; + bestRating = r.rating; + } + } + + if (!bestId) return null; + return { id: bestId, likes: bestLikes }; +} diff --git a/src/app/room/[id]/page.tsx b/src/app/room/[id]/page.tsx index 45e2b72..5386392 100644 --- a/src/app/room/[id]/page.tsx +++ b/src/app/room/[id]/page.tsx @@ -14,7 +14,7 @@ export default function RoomPage() { const [userId, setUserId] = useState(""); const [joined, setJoined] = useState(false); - const { userCount, match, noMatch, restaurants, mutate } = + const { userCount, match, matchType, matchLikes, restaurants, mutate } = useRoomPolling(roomId); useEffect(() => { @@ -52,7 +52,9 @@ export default function RoomPage() { roomId={roomId} userId={userId} matchedRestaurantId={match} - noMatch={noMatch} + matchType={matchType} + matchLikes={matchLikes} + userCount={userCount} onReset={handleReset} /> diff --git a/src/components/MatchResult.tsx b/src/components/MatchResult.tsx index 2d042cf..f83fe8e 100644 --- a/src/components/MatchResult.tsx +++ b/src/components/MatchResult.tsx @@ -8,12 +8,18 @@ import { Navigation, Phone, Clock, + Trophy, + RotateCcw, } from "lucide-react"; -import { Restaurant } from "@/types"; +import { Restaurant, MatchType } from "@/types"; interface MatchResultProps { restaurant: Restaurant; + matchType: MatchType; + matchLikes: number; + userCount: number; onReset: () => Promise; + resetting: boolean; } function buildNavUrl(restaurant: Restaurant): string { @@ -24,10 +30,23 @@ function buildNavUrl(restaurant: Restaurant): string { return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(restaurant.name)}`; } -export default function MatchResult({ restaurant, onReset }: MatchResultProps) { +export default function MatchResult({ + restaurant, + matchType, + matchLikes, + userCount, + onReset, + resetting, +}: MatchResultProps) { + const isUnanimous = matchType === "unanimous"; + return ( - + {isUnanimous ? ( + + ) : ( + + )} - Everyone agreed on this one + {isUnanimous + ? "大家一拍即合!" + : `${matchLikes}/${userCount} 人想去这家`} @@ -159,13 +186,17 @@ export default function MatchResult({ restaurant, onReset }: MatchResultProps) { - 再来一轮 + + {resetting ? "重置中..." : "再来一轮"} ); diff --git a/src/components/SwipeDeck.tsx b/src/components/SwipeDeck.tsx index 29d2a1c..e687620 100644 --- a/src/components/SwipeDeck.tsx +++ b/src/components/SwipeDeck.tsx @@ -1,19 +1,20 @@ "use client"; import { useState, useCallback, useRef, useEffect } from "react"; -import { AnimatePresence, motion } from "framer-motion"; +import { AnimatePresence } from "framer-motion"; import SwipeableCard from "./SwipeableCard"; import ActionButtons from "./ActionButtons"; import MatchResult from "./MatchResult"; -import { Restaurant, SwipeDirection } from "@/types"; -import { Frown, RotateCcw } from "lucide-react"; +import { Restaurant, SwipeDirection, MatchType } from "@/types"; interface SwipeDeckProps { restaurants: Restaurant[]; roomId: string; userId: string; matchedRestaurantId: string | null; - noMatch: boolean; + matchType: MatchType; + matchLikes: number; + userCount: number; onReset: () => Promise; } @@ -22,7 +23,9 @@ export default function SwipeDeck({ roomId, userId, matchedRestaurantId, - noMatch, + matchType, + matchLikes, + userCount, onReset, }: SwipeDeckProps) { const [currentIndex, setCurrentIndex] = useState(0); @@ -110,13 +113,13 @@ export default function SwipeDeck({ ? restaurants.find((r) => r.id === resolvedMatchId) ?? null : null; - const showWaiting = allSwiped && !resolvedMatchId && !noMatch; + const showWaiting = allSwiped && !resolvedMatchId; return ( <>
- {!resolvedMatchId && !noMatch && ( + {!resolvedMatchId && ( {restaurants.map((restaurant, index) => { if (index < currentIndex || index > currentIndex + 1) @@ -141,39 +144,20 @@ export default function SwipeDeck({

等待其他人完成选择...

)} - - {noMatch && !showMatch && ( - -
- -
-
-

没有达成共识

-

- 大家口味不太一样,换一批试试? -

-
- -
- )}
- + {showMatch && matchRestaurant && ( - + )} ); diff --git a/src/hooks/useRoomPolling.ts b/src/hooks/useRoomPolling.ts index 031b589..ec13249 100644 --- a/src/hooks/useRoomPolling.ts +++ b/src/hooks/useRoomPolling.ts @@ -18,7 +18,7 @@ export function useRoomPolling(roomId: string) { }, ); - if (data?.match != null || data?.noMatch) { + if (data?.match != null) { settled.current = true; } else { settled.current = false; @@ -27,7 +27,8 @@ export function useRoomPolling(roomId: string) { return { userCount: data?.userCount ?? 0, match: data?.match ?? null, - noMatch: data?.noMatch ?? false, + matchType: data?.matchType ?? null, + matchLikes: data?.matchLikes ?? 0, restaurants: data?.restaurants ?? [], isLoading, error, diff --git a/src/types/index.ts b/src/types/index.ts index 7fdca21..d1026ba 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -15,10 +15,13 @@ export interface Restaurant { export type SwipeDirection = "left" | "right"; +export type MatchType = "unanimous" | "best" | null; + export interface RoomStatus { roomId: string; userCount: number; match: string | null; - noMatch: boolean; + matchType: MatchType; + matchLikes: number; restaurants: Restaurant[]; }