|
"use client"; |
|
|
|
import { useState, useEffect, useRef } from "react"; |
|
import { useParams, useRouter } from "next/navigation"; |
|
import { Button } from "@/components/ui/button"; |
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; |
|
import { ArrowLeft, Mic, MicOff } from "lucide-react"; |
|
import Link from "next/link"; |
|
import { useGameSocket } from "@/hooks/use-game-socket"; |
|
import "../../styles/background-pattern.css"; |
|
|
|
interface Player { |
|
id: number; |
|
name: string; |
|
avatar: string; |
|
isReady: boolean; |
|
isSpeaking: boolean; |
|
} |
|
|
|
interface GameState { |
|
id: number; |
|
status: "PLAYING" | "FINISHED"; |
|
players: Player[]; |
|
currentRound: number; |
|
totalRounds: number; |
|
currentSpeaker?: Player; |
|
imposteur?: Player; |
|
} |
|
|
|
export default function GamePage() { |
|
const params = useParams(); |
|
const router = useRouter(); |
|
const [gameState, setGameState] = useState<GameState | null>(null); |
|
const [isMuted, setIsMuted] = useState(true); |
|
const [currentUserId, setCurrentUserId] = useState<number | null>(null); |
|
const audioRef = useRef<HTMLAudioElement>(null); |
|
const { startCall, handleIncomingAudio, socket } = useGameSocket( |
|
params.inviteCode as string |
|
); |
|
|
|
useEffect(() => { |
|
|
|
const userId = document.cookie.match(/userId=(\d+)/)?.[1]; |
|
if (!userId) { |
|
router.push("/"); |
|
return; |
|
} |
|
setCurrentUserId(parseInt(userId)); |
|
|
|
|
|
fetchGameState(); |
|
}, []); |
|
|
|
const fetchGameState = async () => { |
|
try { |
|
const response = await fetch(`/api/group/${params.inviteCode}`); |
|
if (!response.ok) |
|
throw new Error("Erreur lors de la récupération du jeu"); |
|
const data = await response.json(); |
|
setGameState(data); |
|
} catch (error) { |
|
console.error("Erreur:", error); |
|
alert("Impossible de récupérer l'état du jeu"); |
|
} |
|
}; |
|
|
|
const toggleMicrophone = async () => { |
|
try { |
|
if (isMuted) { |
|
const stream = await navigator.mediaDevices.getUserMedia({ |
|
audio: true, |
|
}); |
|
await startCall(stream); |
|
} else { |
|
|
|
const audioTracks = audioRef.current?.srcObject as MediaStream; |
|
audioTracks?.getTracks().forEach((track) => track.stop()); |
|
} |
|
setIsMuted(!isMuted); |
|
} catch (error) { |
|
console.error("Erreur lors de l'accès au microphone:", error); |
|
alert("Impossible d'accéder au microphone"); |
|
} |
|
}; |
|
|
|
useEffect(() => { |
|
if (!socket) return; |
|
|
|
|
|
socket.on("gameStateUpdate", (newState: GameState) => { |
|
setGameState(newState); |
|
}); |
|
|
|
|
|
handleIncomingAudio((stream) => { |
|
if (audioRef.current) { |
|
audioRef.current.srcObject = stream; |
|
audioRef.current.play().catch(console.error); |
|
} |
|
}); |
|
|
|
return () => { |
|
socket.off("gameStateUpdate"); |
|
}; |
|
}, [socket]); |
|
|
|
if (!gameState) { |
|
return ( |
|
<div className="min-h-screen bg-gray-100 flex items-center justify-center"> |
|
<div className="text-xl">Chargement...</div> |
|
</div> |
|
); |
|
} |
|
|
|
const currentPlayer = currentUserId |
|
? gameState.players.find((p) => p.id === currentUserId) |
|
: null; |
|
|
|
const isImposteur = currentPlayer?.id === gameState.imposteur?.id; |
|
|
|
return ( |
|
<div className="min-h-screen relative overflow-hidden"> |
|
<div className="background-pattern" /> |
|
<div className="background-overlay" /> |
|
<div className="background-vignette" /> |
|
|
|
<audio ref={audioRef} autoPlay /> |
|
|
|
<div className="relative z-10 p-4"> |
|
<div className="max-w-6xl mx-auto bg-gray-100 bg-opacity-90 backdrop-blur-sm rounded-lg shadow-xl p-8"> |
|
<div className="flex justify-between items-center mb-8"> |
|
<Link href="/"> |
|
<Button |
|
variant="outline" |
|
className="border-orange-400 text-orange-600 hover:bg-orange-100" |
|
> |
|
<ArrowLeft className="mr-2 h-4 w-4" /> |
|
Quitter la partie |
|
</Button> |
|
</Link> |
|
<div className="text-center"> |
|
<h1 className="text-4xl font-grobold font-normal text-orange-600 mb-2"> |
|
Round {gameState.currentRound}/{gameState.totalRounds} |
|
</h1> |
|
{isImposteur && ( |
|
<div className="text-red-500 font-bold"> |
|
Vous êtes l'imposteur ! |
|
</div> |
|
)} |
|
</div> |
|
<Button |
|
onClick={toggleMicrophone} |
|
className={`${ |
|
isMuted |
|
? "bg-red-500 hover:bg-red-600" |
|
: "bg-green-500 hover:bg-green-600" |
|
} text-white`} |
|
> |
|
{isMuted ? ( |
|
<> |
|
<MicOff className="mr-2 h-5 w-5" /> |
|
Micro désactivé |
|
</> |
|
) : ( |
|
<> |
|
<Mic className="mr-2 h-5 w-5" /> |
|
Micro activé |
|
</> |
|
)} |
|
</Button> |
|
</div> |
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 mb-8"> |
|
{gameState.players.map((player) => ( |
|
<div |
|
key={player.id} |
|
className={`flex items-center space-x-4 p-4 rounded-lg ${ |
|
player.isSpeaking ? "bg-green-200" : "bg-gray-200" |
|
}`} |
|
> |
|
<Avatar className="w-16 h-16 border-4 border-orange-400"> |
|
<AvatarImage src={player.avatar} alt={player.name} /> |
|
<AvatarFallback>{player.name[0]}</AvatarFallback> |
|
</Avatar> |
|
<div> |
|
<p className="font-grobold font-normal text-orange-800"> |
|
{player.name} |
|
{player.id === currentUserId && " (Vous)"} |
|
</p> |
|
<p |
|
className={`text-${ |
|
player.isSpeaking ? "green" : "gray" |
|
}-600`} |
|
> |
|
{player.isSpeaking ? "Parle" : "Silencieux"} |
|
</p> |
|
</div> |
|
</div> |
|
))} |
|
</div> |
|
|
|
{gameState.currentSpeaker && ( |
|
<div className="text-center text-2xl font-grobold font-normal text-orange-600"> |
|
Au tour de {gameState.currentSpeaker.name} de parler ! |
|
</div> |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|