restructure host routes: layout with nested game, bingo, board, host children
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
15
packages/client/src/routes/host.$roomCode.bingo.tsx
Normal file
15
packages/client/src/routes/host.$roomCode.bingo.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import { useRoomStore } from "@/stores/room-store"
|
||||||
|
import { BingoTab } from "@/components/bingo-tab"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/host/$roomCode/bingo")({
|
||||||
|
component: HostBingo,
|
||||||
|
})
|
||||||
|
|
||||||
|
function HostBingo() {
|
||||||
|
const { room, gameState, send } = useRoomStore()
|
||||||
|
|
||||||
|
if (!room || !gameState) return null
|
||||||
|
|
||||||
|
return <BingoTab currentAct={room.currentAct} gameState={gameState} send={send} />
|
||||||
|
}
|
||||||
15
packages/client/src/routes/host.$roomCode.board.tsx
Normal file
15
packages/client/src/routes/host.$roomCode.board.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import { useRoomStore } from "@/stores/room-store"
|
||||||
|
import { BoardTab } from "@/components/board-tab"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/host/$roomCode/board")({
|
||||||
|
component: HostBoard,
|
||||||
|
})
|
||||||
|
|
||||||
|
function HostBoard() {
|
||||||
|
const { room, gameState } = useRoomStore()
|
||||||
|
|
||||||
|
if (!room || !gameState) return null
|
||||||
|
|
||||||
|
return <BoardTab currentAct={room.currentAct} gameState={gameState} />
|
||||||
|
}
|
||||||
15
packages/client/src/routes/host.$roomCode.game.tsx
Normal file
15
packages/client/src/routes/host.$roomCode.game.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import { useRoomStore } from "@/stores/room-store"
|
||||||
|
import { GameTab } from "@/components/game-tab"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/host/$roomCode/game")({
|
||||||
|
component: HostGame,
|
||||||
|
})
|
||||||
|
|
||||||
|
function HostGame() {
|
||||||
|
const { room, gameState, send } = useRoomStore()
|
||||||
|
|
||||||
|
if (!room || !gameState) return null
|
||||||
|
|
||||||
|
return <GameTab currentAct={room.currentAct} gameState={gameState} send={send} />
|
||||||
|
}
|
||||||
16
packages/client/src/routes/host.$roomCode.host.tsx
Normal file
16
packages/client/src/routes/host.$roomCode.host.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import { useRoomStore } from "@/stores/room-store"
|
||||||
|
import { HostTab } from "@/components/host-tab"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/host/$roomCode/host")({
|
||||||
|
component: HostControls,
|
||||||
|
})
|
||||||
|
|
||||||
|
function HostControls() {
|
||||||
|
const { roomCode } = Route.useParams()
|
||||||
|
const { room, gameState, send } = useRoomStore()
|
||||||
|
|
||||||
|
if (!room || !gameState) return null
|
||||||
|
|
||||||
|
return <HostTab roomCode={roomCode} currentAct={room.currentAct} gameState={gameState} send={send} />
|
||||||
|
}
|
||||||
7
packages/client/src/routes/host.$roomCode.index.tsx
Normal file
7
packages/client/src/routes/host.$roomCode.index.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/host/$roomCode/")({
|
||||||
|
beforeLoad: ({ params }) => {
|
||||||
|
throw redirect({ to: "/host/$roomCode/game", params })
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,43 +1,17 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { useWebSocket } from "@/hooks/use-websocket"
|
import { useWebSocket } from "@/hooks/use-websocket"
|
||||||
import { useRoomStore } from "@/stores/room-store"
|
import { useRoomStore } from "@/stores/room-store"
|
||||||
import { PlayerList } from "@/components/player-list"
|
import { RoomLayout } from "@/components/room-layout"
|
||||||
import { PredictionsForm } from "@/components/predictions-form"
|
import { BottomNav } from "@/components/bottom-nav"
|
||||||
import { JuryHost } from "@/components/jury-host"
|
|
||||||
import { JuryVoting } from "@/components/jury-voting"
|
|
||||||
import { BingoCard } from "@/components/bingo-card"
|
|
||||||
import { ActualResultsForm } from "@/components/actual-results-form"
|
|
||||||
import { QuizHost } from "@/components/quiz-host"
|
|
||||||
import { QuizBuzzer } from "@/components/quiz-buzzer"
|
|
||||||
import { Leaderboard } from "@/components/leaderboard"
|
|
||||||
import { RoomHeader } from "@/components/room-header"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import type { Act } from "@celebrate-esc/shared"
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/host/$roomCode")({
|
export const Route = createFileRoute("/host/$roomCode")({
|
||||||
component: HostView,
|
component: HostLayout,
|
||||||
})
|
})
|
||||||
|
|
||||||
const nextActLabels: Partial<Record<Act, string>> = {
|
function HostLayout() {
|
||||||
lobby: "Start Pre-Show",
|
|
||||||
"pre-show": "Start Live Event",
|
|
||||||
"live-event": "Start Scoring",
|
|
||||||
scoring: "End Party",
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevActLabels: Partial<Record<Act, string>> = {
|
|
||||||
"pre-show": "Back to Lobby",
|
|
||||||
"live-event": "Back to Pre-Show",
|
|
||||||
scoring: "Back to Live Event",
|
|
||||||
ended: "Back to Scoring",
|
|
||||||
}
|
|
||||||
|
|
||||||
function HostView() {
|
|
||||||
const { roomCode } = Route.useParams()
|
const { roomCode } = Route.useParams()
|
||||||
const { send } = useWebSocket(roomCode)
|
useWebSocket(roomCode)
|
||||||
const { room, mySessionId, connectionStatus, gameState } = useRoomStore()
|
const { room, connectionStatus } = useRoomStore()
|
||||||
|
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return (
|
return (
|
||||||
@@ -50,175 +24,9 @@ function HostView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col">
|
<>
|
||||||
<RoomHeader roomCode={roomCode} currentAct={room.currentAct} connectionStatus={connectionStatus} />
|
<RoomLayout roomCode={roomCode} connectionStatus={connectionStatus} />
|
||||||
<Tabs defaultValue="host" className="flex-1">
|
<BottomNav basePath="/host/$roomCode" roomCode={roomCode} isHost={true} />
|
||||||
<TabsList className="w-full rounded-none">
|
</>
|
||||||
<TabsTrigger value="play" className="flex-1">
|
|
||||||
Play
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="host" className="flex-1">
|
|
||||||
Host
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="play" className="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">
|
|
||||||
<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" || room.currentAct === "ended") && gameState.myPrediction && (
|
|
||||||
<PredictionsForm
|
|
||||||
entries={gameState.lineup.entries}
|
|
||||||
existingPrediction={gameState.myPrediction}
|
|
||||||
locked={true}
|
|
||||||
actualResults={gameState.actualResults}
|
|
||||||
onSubmit={() => {}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{gameState && room.currentAct === "scoring" && gameState.currentQuizQuestion && (
|
|
||||||
<QuizBuzzer
|
|
||||||
question={gameState.currentQuizQuestion}
|
|
||||||
buzzStatus={gameState.myQuizBuzzStatus}
|
|
||||||
onBuzz={() => send({ type: "buzz" })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{gameState && (room.currentAct === "scoring" || room.currentAct === "ended") && (
|
|
||||||
<Leaderboard entries={gameState.leaderboard} resultsEntered={!!gameState.actualResults} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<PlayerList
|
|
||||||
players={room.players}
|
|
||||||
mySessionId={mySessionId}
|
|
||||||
predictionSubmitted={gameState?.predictionSubmitted}
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="host" className="p-4">
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Room Controls</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex flex-col gap-3">
|
|
||||||
{room.currentAct !== "ended" && (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{room.currentAct !== "lobby" && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => send({ type: "revert_act" })}
|
|
||||||
className="flex-1"
|
|
||||||
>
|
|
||||||
{prevActLabels[room.currentAct] ?? "Back"}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button onClick={() => send({ type: "advance_act" })} className="flex-1">
|
|
||||||
{nextActLabels[room.currentAct] ?? "Next"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{room.currentAct !== "ended" && (
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => send({ type: "end_room" })}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
End Party
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{room.currentAct === "ended" && (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<p className="text-center text-muted-foreground">
|
|
||||||
The party has ended. Thanks for playing!
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => send({ type: "revert_act" })}
|
|
||||||
>
|
|
||||||
{prevActLabels[room.currentAct] ?? "Back"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{room.currentAct === "live-event" && gameState && (
|
|
||||||
<JuryHost
|
|
||||||
entries={gameState.lineup.entries}
|
|
||||||
currentRound={gameState.currentJuryRound}
|
|
||||||
results={gameState.juryResults}
|
|
||||||
onOpenVote={(countryCode) => send({ type: "open_jury_vote", countryCode })}
|
|
||||||
onCloseVote={() => send({ type: "close_jury_vote" })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{gameState && room.currentAct === "scoring" && (
|
|
||||||
<QuizHost
|
|
||||||
question={gameState.currentQuizQuestion}
|
|
||||||
onStartQuestion={() => send({ type: "start_quiz_question" })}
|
|
||||||
onJudge={(correct) => send({ type: "judge_quiz_answer", correct })}
|
|
||||||
onSkip={() => send({ type: "skip_quiz_question" })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{gameState && (room.currentAct === "scoring" || room.currentAct === "ended") && (
|
|
||||||
<ActualResultsForm
|
|
||||||
entries={gameState.lineup.entries}
|
|
||||||
existingResults={gameState.actualResults}
|
|
||||||
onSubmit={(results) => send({ type: "submit_actual_results", ...results })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{gameState && (room.currentAct === "scoring" || room.currentAct === "ended") && (
|
|
||||||
<Leaderboard entries={gameState.leaderboard} resultsEntered={!!gameState.actualResults} />
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<PlayerList
|
|
||||||
players={room.players}
|
|
||||||
mySessionId={mySessionId}
|
|
||||||
predictionSubmitted={gameState?.predictionSubmitted}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user