157 lines
4.9 KiB
TypeScript
157 lines
4.9 KiB
TypeScript
import { useEffect, useRef, 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 { PredictionsForm } from "@/components/predictions-form"
|
|
import { JuryVoting } from "@/components/jury-voting"
|
|
import { BingoCard } from "@/components/bingo-card"
|
|
import { Leaderboard } from "@/components/leaderboard"
|
|
import { RoomHeader } from "@/components/room-header"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
|
|
export const Route = createFileRoute("/play/$roomCode")({
|
|
component: PlayerView,
|
|
})
|
|
|
|
function PlayerView() {
|
|
const { roomCode } = Route.useParams()
|
|
const { send } = useWebSocket(roomCode)
|
|
const { room, mySessionId, connectionStatus, gameState } = useRoomStore()
|
|
const joinSentRef = useRef(false)
|
|
const [manualName, setManualName] = useState("")
|
|
|
|
useEffect(() => {
|
|
if (connectionStatus !== "connected" || mySessionId || joinSentRef.current) return
|
|
|
|
const displayName = sessionStorage.getItem("esc-party-join-name")
|
|
if (displayName) {
|
|
joinSentRef.current = true
|
|
sessionStorage.removeItem("esc-party-join-name")
|
|
send({ type: "join_room", displayName })
|
|
}
|
|
}, [connectionStatus, mySessionId, send])
|
|
|
|
if (!room) {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center">
|
|
<p className="text-muted-foreground">
|
|
{connectionStatus === "connecting" ? "Connecting..." : "Room not found"}
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!mySessionId && connectionStatus === "connected" && !joinSentRef.current) {
|
|
return (
|
|
<div className="flex min-h-screen flex-col items-center justify-center gap-4 p-4">
|
|
<h2 className="text-xl font-bold">Join Room {roomCode}</h2>
|
|
<Input
|
|
placeholder="Your name"
|
|
value={manualName}
|
|
onChange={(e) => setManualName(e.target.value)}
|
|
maxLength={20}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" && manualName.trim()) {
|
|
joinSentRef.current = true
|
|
send({ type: "join_room", displayName: manualName.trim() })
|
|
}
|
|
}}
|
|
/>
|
|
<Button
|
|
onClick={() => {
|
|
if (manualName.trim()) {
|
|
joinSentRef.current = true
|
|
send({ type: "join_room", displayName: manualName.trim() })
|
|
}
|
|
}}
|
|
disabled={!manualName.trim()}
|
|
>
|
|
Join
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!mySessionId) {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center">
|
|
<p className="text-muted-foreground">Joining room...</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-screen flex-col">
|
|
<RoomHeader roomCode={roomCode} currentAct={room.currentAct} connectionStatus={connectionStatus} />
|
|
<div className="flex-1 p-4">
|
|
{gameState && (room.currentAct === "lobby" || room.currentAct === "pre-show") && (
|
|
<div className="flex flex-col gap-4">
|
|
<PredictionsForm
|
|
entries={gameState.lineup.entries}
|
|
existingPrediction={gameState.myPrediction}
|
|
locked={gameState.predictionsLocked}
|
|
onSubmit={(prediction) =>
|
|
send({ type: "submit_prediction", ...prediction })
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{gameState && room.currentAct === "live-event" && (
|
|
<Tabs defaultValue="jury" className="flex-1">
|
|
<TabsList className="w-full">
|
|
<TabsTrigger value="jury" className="flex-1">Jury</TabsTrigger>
|
|
<TabsTrigger value="bingo" className="flex-1">Bingo</TabsTrigger>
|
|
</TabsList>
|
|
<TabsContent value="jury" className="mt-4">
|
|
{gameState.currentJuryRound ? (
|
|
<JuryVoting
|
|
round={gameState.currentJuryRound}
|
|
myVote={gameState.myJuryVote}
|
|
onVote={(rating) => send({ type: "submit_jury_vote", rating })}
|
|
/>
|
|
) : (
|
|
<div className="py-8 text-center text-muted-foreground">
|
|
Waiting for host to open voting...
|
|
</div>
|
|
)}
|
|
</TabsContent>
|
|
<TabsContent value="bingo" className="mt-4">
|
|
{gameState.myBingoCard ? (
|
|
<BingoCard
|
|
card={gameState.myBingoCard}
|
|
onTap={(tropeId) => send({ type: "tap_bingo_square", tropeId })}
|
|
/>
|
|
) : (
|
|
<div className="py-8 text-center text-muted-foreground">
|
|
No bingo card yet
|
|
</div>
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
)}
|
|
|
|
{gameState && room.currentAct === "scoring" && (
|
|
<Leaderboard entries={gameState.leaderboard} />
|
|
)}
|
|
|
|
{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>
|
|
{gameState && <Leaderboard entries={gameState.leaderboard} />}
|
|
</div>
|
|
)}
|
|
|
|
<PlayerList
|
|
players={room.players}
|
|
mySessionId={mySessionId}
|
|
predictionSubmitted={gameState?.predictionSubmitted}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|