- results page: when there are no tracks (quiz-only game), the recap was sitting in the left column of a 2-col grid; now a single section is centered (2 columns only when both tracks and recap exist) - add a shadcn Tooltip component (@workspace/ui, via radix-ui) and replace native browser title= tooltips: connection dot, back-office active toggle, and the locked blindtest tiles (now aria-disabled so the tooltip still triggers) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
156 lines
5.3 KiB
TypeScript
156 lines
5.3 KiB
TypeScript
import { Link, useLocation } from "wouter"
|
|
import { AnimatePresence, motion } from "framer-motion"
|
|
import { LogOut } from "lucide-react"
|
|
import { Button } from "@workspace/ui/components/button"
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@workspace/ui/components/tooltip"
|
|
import { useRoomStore } from "@/store/room"
|
|
import { LobbyView } from "@/components/lobby-view"
|
|
import { QuizView } from "@/components/quiz-view"
|
|
import { BlindtestView } from "@/components/blindtest-view"
|
|
import { GameEndView } from "@/components/game-end-view"
|
|
import { PlayerCards } from "@/components/player-cards"
|
|
import { RoomCode } from "@/components/room-code"
|
|
import { Boom } from "@/components/boom"
|
|
import { RoundTransition } from "@/components/round-transition"
|
|
import { PseudoScreen } from "@/components/pseudo-screen"
|
|
|
|
export function RoomPage({ code }: { code: string }) {
|
|
const [, navigate] = useLocation()
|
|
const snapshot = useRoomStore((s) => s.snapshot)
|
|
const roomCode = useRoomStore((s) => s.roomCode)
|
|
const leaveRoom = useRoomStore((s) => s.leaveRoom)
|
|
const playerId = useRoomStore((s) => s.playerId)
|
|
const connected = useRoomStore((s) => s.connected)
|
|
const pseudoSet = useRoomStore((s) => s.pseudoSet)
|
|
const boomKey = useRoomStore((s) => s.boomKey)
|
|
const roundKey = useRoomStore((s) => s.roundKey)
|
|
const voteProgress = useRoomStore((s) => s.voteProgress)
|
|
const round = useRoomStore((s) => s.round)
|
|
const roundKind = useRoomStore((s) => s.roundKind)
|
|
const roundModeChanged = useRoomStore((s) => s.roundModeChanged)
|
|
|
|
// Pas dans cette room (accès direct sans rejoindre / refresh) : retour accueil.
|
|
if (roomCode !== code) {
|
|
return (
|
|
<div className="flex min-h-svh flex-col items-center justify-center gap-4 p-6 text-center">
|
|
<p className="text-muted-foreground text-sm">
|
|
Room introuvable ou session perdue.
|
|
</p>
|
|
<Link href="/">
|
|
<Button variant="secondary">Retour à l'accueil</Button>
|
|
</Link>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Pseudo pas encore choisi : on l'affiche d'abord.
|
|
if (!pseudoSet) {
|
|
return <PseudoScreen code={code} />
|
|
}
|
|
|
|
// En attente du premier snapshot.
|
|
if (!snapshot || snapshot.code !== code) {
|
|
return (
|
|
<div className="flex min-h-svh items-center justify-center p-6">
|
|
<p className="text-muted-foreground text-sm">Chargement…</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Phase grossière : on n'anime qu'aux frontières lobby ↔ jeu ↔ fin pour
|
|
// préserver le lecteur YouTube entre in_round / reveal / scores.
|
|
const phase =
|
|
snapshot.status === "lobby"
|
|
? "lobby"
|
|
: snapshot.status === "ended"
|
|
? "ended"
|
|
: "game"
|
|
|
|
return (
|
|
<div className="relative flex min-h-svh items-start justify-center overflow-hidden p-4 pt-10 sm:p-6 sm:pt-12">
|
|
{/* Halo d'ambiance en fond */}
|
|
<div
|
|
aria-hidden
|
|
className="pointer-events-none absolute top-[-20vmin] -z-10 size-[90vmin] rounded-full bg-gradient-to-br from-fuchsia-500/10 via-purple-500/5 to-cyan-400/10 blur-3xl"
|
|
/>
|
|
<div
|
|
className={`flex w-full flex-col gap-6 transition-[max-width] ${
|
|
phase === "lobby"
|
|
? "max-w-3xl"
|
|
: phase === "ended"
|
|
? "max-w-4xl"
|
|
: "max-w-lg"
|
|
}`}
|
|
>
|
|
<header className="flex items-center justify-between">
|
|
<RoomCode code={snapshot.code} />
|
|
<div className="flex items-center gap-3">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<span
|
|
className={`size-2.5 rounded-full ${connected ? "bg-green-500" : "bg-red-500"}`}
|
|
/>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
{connected ? "Connecté" : "Déconnecté"}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<Button
|
|
size="sm"
|
|
variant="secondary"
|
|
onClick={() => {
|
|
leaveRoom()
|
|
navigate("/")
|
|
}}
|
|
>
|
|
<LogOut /> Quitter
|
|
</Button>
|
|
</div>
|
|
</header>
|
|
|
|
<AnimatePresence mode="wait">
|
|
<motion.div
|
|
key={phase}
|
|
className="flex flex-col gap-6"
|
|
initial={{ opacity: 0, y: 12 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -12 }}
|
|
transition={{ duration: 0.22, ease: "easeOut" }}
|
|
>
|
|
{phase === "lobby" && <LobbyView snapshot={snapshot} />}
|
|
{phase === "game" && (
|
|
<>
|
|
<PlayerCards
|
|
snapshot={snapshot}
|
|
playerId={playerId}
|
|
showVoteStatus={snapshot.status === "in_round"}
|
|
votedIds={voteProgress?.voted}
|
|
/>
|
|
{round?.type === "blindtest" ? (
|
|
<BlindtestView snapshot={snapshot} />
|
|
) : (
|
|
<QuizView snapshot={snapshot} />
|
|
)}
|
|
</>
|
|
)}
|
|
{phase === "ended" && <GameEndView snapshot={snapshot} />}
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</div>
|
|
|
|
{boomKey > 0 && <Boom key={boomKey} />}
|
|
{roundKey > 0 && round && (
|
|
<RoundTransition
|
|
key={roundKey}
|
|
kind={roundKind}
|
|
index={snapshot.currentRound}
|
|
modeChanged={roundModeChanged}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|