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:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user