import { useEffect, useState } from "react" import { motion } from "framer-motion" import { Disc3, Pause, Play, Rewind } from "lucide-react" import type { BlindtestAnswer, BlindtestMode, BlindtestPerPlayerResult, BlindtestRevealTruth, BlindtestRoundPayload, RoomSnapshot, } from "@nerdware/shared" import { Button } from "@workspace/ui/components/button" import { useRoomStore } from "@/store/room" import { useYoutube } from "@/lib/youtube" import { Countdown } from "@/components/countdown" import { Avatar } from "@/components/avatar" 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" export function BlindtestView({ snapshot }: { snapshot: RoomSnapshot }) { const round = useRoomStore((s) => s.round) const reveal = useRoomStore((s) => s.reveal) const mediaSync = useRoomStore((s) => s.mediaSync) const playerId = useRoomStore((s) => s.playerId) const hasVoted = useRoomStore((s) => s.hasVoted) const voteBlindtest = useRoomStore((s) => s.voteBlindtest) const mediaControl = useRoomStore((s) => s.mediaControl) const track = round?.payload as BlindtestRoundPayload | undefined const { hostRef, ready, api } = useYoutube(track?.youtubeId ?? "") const isDj = !!playerId && round?.djId === playerId const truth = reveal ? (reveal.truth as BlindtestRevealTruth) : null const showReveal = truth !== null // Non-DJ : on suit le DJ via media:sync (avec compensation de latence). useEffect(() => { if (isDj || !ready || !mediaSync) { return } const elapsed = (Date.now() - mediaSync.atServerTs) / 1000 if (mediaSync.action === "play") { api.seek(mediaSync.positionSec + Math.max(0, elapsed)) api.play() } else if (mediaSync.action === "pause") { api.seek(mediaSync.positionSec) api.pause() } else { api.seek(mediaSync.positionSec) } }, [mediaSync, isDj, ready, api]) // Au reveal, on coupe le son. useEffect(() => { if (showReveal && ready) { api.pause() } }, [showReveal, ready, api]) function djPlay() { api.play() mediaControl("play", api.time()) } function djPause() { api.pause() mediaControl("pause", api.time()) } function djRestart() { api.seek(0) mediaControl("seek", 0) } return (
Blindtest {snapshot.currentRound + 1} / {snapshot.totalRounds} {!showReveal && round && ( )}
{/* Pochette : le lecteur YouTube est caché derrière (son seulement). */}
{!showReveal && (
)}
{isDj && !showReveal && (
Tu es le DJ 🎧
)} {!showReveal && ( )} {showReveal && truth && ( )}
) } function VoteForm({ mode, snapshot, playerId, disabled, onVote, }: { mode: BlindtestMode snapshot: RoomSnapshot playerId: string | null disabled: boolean onVote: (answer: BlindtestAnswer) => void }) { const [title, setTitle] = useState("") const [artist, setArtist] = useState("") const [guessed, setGuessed] = useState(null) const needsText = mode === "title_artist" || mode === "mixed" const needsWho = mode === "who_added" || mode === "mixed" const canSubmit = !disabled && (!needsText || title.trim().length > 0) && (!needsWho || guessed !== null) function submit() { const answer: { title?: string artist?: string guessedPlayerId?: string } = {} if (needsText) { answer.title = title.trim() answer.artist = artist.trim() } if (needsWho && guessed) { answer.guessedPlayerId = guessed } onVote(answer as BlindtestAnswer) } if (disabled) { return (

Réponse envoyée — en attente des autres

) } return (
{needsText && ( <> setTitle(e.target.value)} /> setArtist(e.target.value)} /> )} {needsWho && (
{snapshot.players.map((p) => ( ))}
)}
) } function RevealCard({ truth, result, }: { truth: BlindtestRevealTruth result?: BlindtestPerPlayerResult[string] }) { return (

{truth.title}

{truth.artist}

Ajouté par {truth.submittedByName}

{result && (

0 ? "text-green-500" : "text-red-500"}`} > {result.points > 0 ? `+${result.points} points 🎉` : "Raté 💥"}

)}
) }