import { useEffect, useState } from "react" import type { QuizPerPlayerResult, QuizQuestionPayload, QuizRevealTruth, RoomSnapshot, } from "@nerdware/shared" import { Button } from "@workspace/ui/components/button" import { useRoomStore } from "@/store/room" import { Countdown } from "@/components/countdown" import { useI18n, type Dict } from "@/i18n/context" const inputClass = "border-input bg-background h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50" const SERVER_URL = import.meta.env.VITE_SERVER_URL ?? "http://localhost:3001" const MAX_BLUR = 22 export function QuizView({ snapshot }: { snapshot: RoomSnapshot }) { const round = useRoomStore((s) => s.round) const reveal = useRoomStore((s) => s.reveal) const voteProgress = useRoomStore((s) => s.voteProgress) const myChoiceIndex = useRoomStore((s) => s.myChoiceIndex) const hasVoted = useRoomStore((s) => s.hasVoted) const playerId = useRoomStore((s) => s.playerId) const vote = useRoomStore((s) => s.vote) const voteText = useRoomStore((s) => s.voteText) const { t } = useI18n() if (!round) { return

{t.common.loading}

} const question = round.payload as QuizQuestionPayload const truth = reveal ? (reveal.truth as QuizRevealTruth) : null const showReveal = truth !== null const isImage = question.format === "image_reveal" const isText = question.format === "free" || isImage const myResult = reveal ? (reveal.perPlayerResult as QuizPerPlayerResult)[playerId ?? ""] : undefined function choiceClass(index: number): string { const base = "w-full rounded-lg border px-4 py-3 text-left text-sm transition-colors" if (showReveal) { if (index === truth?.correctIndex) { return `${base} border-green-500 bg-green-500/15 font-medium` } if (index === myChoiceIndex) { return `${base} border-red-500 bg-red-500/10` } return `${base} border-input opacity-60` } if (index === myChoiceIndex) { return `${base} border-primary bg-primary/10 font-medium` } return `${base} border-input hover:bg-muted/60` } return (
{t.game.question(snapshot.currentRound + 1, snapshot.totalRounds)} {question.category ? ` · ${question.category}` : ""} {!showReveal && ( )}

{question.prompt}

{isImage && question.imageUrl && ( )} {isText ? ( ) : (
{(question.choices ?? []).map((choice, index) => ( ))}
)} {!showReveal && (hasVoted ? (

{t.game.answerSent} {voteProgress ? ` (${voteProgress.count}/${voteProgress.total})` : ""}

) : ( voteProgress && (

{t.game.answeredCount(voteProgress.count, voteProgress.total)}

) ))} {showReveal && (
{isText && (

{t.game.answer} {truth?.answer}

)}

{myResult?.correct ? t.game.correct : !hasVoted ? t.game.noAnswer : t.game.wrong}

)}
) } function ImageReveal({ src, startsAt, endsAt, revealed, }: { src: string startsAt: number endsAt: number revealed: boolean }) { // Le flou décroît avec le temps (cadencé par les timestamps serveur) : tous // les clients voient le même niveau de flou au même instant. const [blur, setBlur] = useState(MAX_BLUR) useEffect(() => { if (revealed) { return } const id = setInterval(() => { const now = Date.now() const p = now < startsAt ? 0 : Math.min(1, (now - startsAt) / (endsAt - startsAt)) setBlur(MAX_BLUR * (1 - p)) }, 200) return () => clearInterval(id) }, [revealed, startsAt, endsAt]) const displayBlur = revealed ? 0 : blur return (
) } function FreeAnswer({ disabled, onSubmit, t, }: { disabled: boolean onSubmit: (text: string) => void t: Dict }) { const [text, setText] = useState("") return (
setText(e.target.value)} onKeyDown={(e) => e.key === "Enter" && text.trim() && onSubmit(text)} />
) }