update routes: remove dish UI, update act refs, add copy-to-clipboard on lobby display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 17:43:58 +01:00
parent 42f032f67c
commit 0561f9350b
3 changed files with 69 additions and 70 deletions

View File

@@ -1,9 +1,10 @@
import { useState } from "react"
import { createFileRoute } from "@tanstack/react-router"
import { useWebSocket } from "@/hooks/use-websocket"
import { useRoomStore } from "@/stores/room-store"
import { PlayerList } from "@/components/player-list"
import { DishResults } from "@/components/dish-results"
import { RoomHeader } from "@/components/room-header"
import { ACT_LABELS } from "@celebrate-esc/shared"
export const Route = createFileRoute("/display/$roomCode")({
component: DisplayView,
@@ -29,44 +30,71 @@ function DisplayView() {
<RoomHeader roomCode={roomCode} currentAct={room.currentAct} connectionStatus={connectionStatus} />
<div className="flex flex-1 flex-col items-center justify-center gap-8 p-8">
{room.currentAct === "lobby" && <LobbyDisplay roomCode={roomCode} />}
{gameState?.dishResults && (
<div className="mx-auto max-w-2xl p-8">
<DishResults results={gameState.dishResults} countries={gameState.lineup.countries} />
</div>
)}
{room.currentAct === "act1" && gameState && !gameState.dishResults && (
{room.currentAct === "pre-show" && gameState && (
<div className="flex flex-col items-center gap-4 py-12">
<p className="text-2xl text-muted-foreground">Act 1 Predictions & Dishes</p>
<p className="text-2xl text-muted-foreground">Pre-Show Predictions</p>
<p className="text-lg text-muted-foreground">
{gameState.dishes.length} dish(es) added
{Object.values(gameState.predictionSubmitted).filter(Boolean).length} / {Object.keys(gameState.predictionSubmitted).length} predictions submitted
</p>
</div>
)}
<PlayerList players={room.players} mySessionId={null} />
{room.currentAct !== "lobby" && room.currentAct !== "ended" && room.currentAct !== "pre-show" && (
<div className="flex flex-col items-center gap-4 py-12">
<p className="text-2xl text-muted-foreground">{ACT_LABELS[room.currentAct]}</p>
</div>
)}
{room.currentAct === "ended" && (
<div className="flex flex-col items-center gap-4 py-12">
<p className="text-2xl text-muted-foreground">The party has ended. Thanks for playing!</p>
</div>
)}
<PlayerList
players={room.players}
mySessionId={null}
predictionSubmitted={gameState?.predictionSubmitted}
/>
</div>
</div>
)
}
function LobbyDisplay({ roomCode }: { roomCode: string }) {
const joinUrl = `${window.location.origin}/play/${roomCode}`
const [copied, setCopied] = useState(false)
const base = import.meta.env.BASE_URL.replace(/\/$/, "")
const joinUrl = `${window.location.origin}${base}/play/${roomCode}`
function copyCode() {
navigator.clipboard.writeText(roomCode).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
})
}
return (
<div className="flex flex-col items-center gap-6">
<h2 className="text-2xl text-muted-foreground">Join the party!</h2>
<div className="rounded-lg border-4 border-dashed border-muted p-8">
<button
type="button"
onClick={copyCode}
className="cursor-pointer rounded-lg border-4 border-dashed border-muted p-8 transition-colors hover:border-primary/50"
title="Click to copy room code"
>
<span className="font-mono text-8xl font-bold tracking-[0.3em]">{roomCode}</span>
</div>
</button>
<p className="text-muted-foreground">
{copied ? (
<span className="font-medium text-green-600">Copied!</span>
) : (
<>Tap the code to copy</>
)}
</p>
<p className="text-muted-foreground">
Go to <span className="font-mono font-medium">{joinUrl}</span>
</p>
<p className="text-sm text-muted-foreground">or scan the QR code</p>
{/* QR code will be added in Plan 5 (polish) */}
<div className="flex h-48 w-48 items-center justify-center rounded-lg border-2 border-dashed border-muted">
<span className="text-sm text-muted-foreground">QR code</span>
</div>
</div>
)
}

View File

@@ -3,9 +3,6 @@ import { useWebSocket } from "@/hooks/use-websocket"
import { useRoomStore } from "@/stores/room-store"
import { PlayerList } from "@/components/player-list"
import { PredictionsForm } from "@/components/predictions-form"
import { DishList } from "@/components/dish-list"
import { DishHost } from "@/components/dish-host"
import { DishResults } from "@/components/dish-results"
import { RoomHeader } from "@/components/room-header"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
@@ -17,10 +14,10 @@ export const Route = createFileRoute("/host/$roomCode")({
})
const nextActLabels: Partial<Record<Act, string>> = {
lobby: "Start Act 1",
act1: "Start Act 2",
act2: "Start Act 3",
act3: "End Party",
lobby: "Start Pre-Show",
"pre-show": "Start Live Event",
"live-event": "Start Scoring",
scoring: "End Party",
}
function HostView() {
@@ -51,30 +48,23 @@ function HostView() {
</TabsTrigger>
</TabsList>
<TabsContent value="play" className="p-4">
{gameState && (room.currentAct === "lobby" || room.currentAct === "act1") && (
{gameState && room.currentAct !== "ended" && (
<div className="flex flex-col gap-4">
<PredictionsForm
countries={gameState.lineup.countries}
entries={gameState.lineup.entries}
existingPrediction={gameState.myPrediction}
locked={gameState.predictionsLocked}
onSubmit={(prediction) =>
send({ type: "submit_prediction", ...prediction })
}
/>
<DishList
dishes={gameState.dishes}
myGuesses={gameState.myDishGuesses}
countries={gameState.lineup.countries}
onGuess={(dishId, guessedCountry) =>
send({ type: "submit_dish_guess", dishId, guessedCountry })
}
/>
</div>
)}
{gameState?.dishResults && (
<DishResults results={gameState.dishResults} countries={gameState.lineup.countries} />
)}
<PlayerList players={room.players} mySessionId={mySessionId} />
<PlayerList
players={room.players}
mySessionId={mySessionId}
predictionSubmitted={gameState?.predictionSubmitted}
/>
</TabsContent>
<TabsContent value="host" className="p-4">
<div className="flex flex-col gap-4">
@@ -104,17 +94,11 @@ function HostView() {
)}
</CardContent>
</Card>
{gameState && (room.currentAct === "lobby" || room.currentAct === "act1") && (
<DishHost
dishes={gameState.dishes}
countries={gameState.lineup.countries}
onAddDish={(name, correctCountry) =>
send({ type: "add_dish", name, correctCountry })
}
onReveal={() => send({ type: "reveal_dishes" })}
/>
)}
<PlayerList players={room.players} mySessionId={mySessionId} />
<PlayerList
players={room.players}
mySessionId={mySessionId}
predictionSubmitted={gameState?.predictionSubmitted}
/>
</div>
</TabsContent>
</Tabs>

View File

@@ -4,8 +4,6 @@ import { useWebSocket } from "@/hooks/use-websocket"
import { useRoomStore } from "@/stores/room-store"
import { PlayerList } from "@/components/player-list"
import { PredictionsForm } from "@/components/predictions-form"
import { DishList } from "@/components/dish-list"
import { DishResults } from "@/components/dish-results"
import { RoomHeader } from "@/components/room-header"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
@@ -21,7 +19,6 @@ function PlayerView() {
const joinSentRef = useRef(false)
const [manualName, setManualName] = useState("")
// Auto-send join_room when connected for the first time (no existing session)
useEffect(() => {
if (connectionStatus !== "connected" || mySessionId || joinSentRef.current) return
@@ -43,8 +40,6 @@ function PlayerView() {
)
}
// Fallback: if no stored display name and no session (e.g., direct URL access),
// show a name input form
if (!mySessionId && connectionStatus === "connected" && !joinSentRef.current) {
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-4 p-4">
@@ -94,38 +89,30 @@ function PlayerView() {
</div>
)}
{gameState && (room.currentAct === "lobby" || room.currentAct === "act1") && (
{gameState && room.currentAct !== "ended" && (
<div className="flex flex-col gap-4">
<PredictionsForm
countries={gameState.lineup.countries}
entries={gameState.lineup.entries}
existingPrediction={gameState.myPrediction}
locked={gameState.predictionsLocked}
onSubmit={(prediction) =>
send({ type: "submit_prediction", ...prediction })
}
/>
<DishList
dishes={gameState.dishes}
myGuesses={gameState.myDishGuesses}
countries={gameState.lineup.countries}
onGuess={(dishId, guessedCountry) =>
send({ type: "submit_dish_guess", dishId, guessedCountry })
}
/>
</div>
)}
{gameState?.dishResults && (
<DishResults results={gameState.dishResults} countries={gameState.lineup.countries} />
)}
{room.currentAct === "ended" && (
<div className="flex flex-col items-center gap-4 py-8">
<p className="text-lg text-muted-foreground">The party has ended. Thanks for playing!</p>
</div>
)}
<PlayerList players={room.players} mySessionId={mySessionId} />
<PlayerList
players={room.players}
mySessionId={mySessionId}
predictionSubmitted={gameState?.predictionSubmitted}
/>
</div>
</div>
)