| "use client"; | |
| import { useState, useRef } from "react"; | |
| import { useParams, useRouter } from "next/navigation"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; | |
| import { ArrowLeft, Mic, MicOff, RefreshCcw } from "lucide-react"; | |
| import Link from "next/link"; | |
| import "../../styles/background-pattern.css"; | |
| const avatars = [ | |
| "/avatar1.png", | |
| "/avatar2.png", | |
| "/avatar3.png", | |
| "/avatar4.png", | |
| "/avatar5.png", | |
| ]; | |
| export default function JoinGroup() { | |
| const params = useParams(); | |
| const router = useRouter(); | |
| const [userName, setUserName] = useState(""); | |
| const [isRecording, setIsRecording] = useState(false); | |
| const [audioBlob, setAudioBlob] = useState<Blob | null>(null); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [avatarIndex, setAvatarIndex] = useState(0); | |
| const mediaRecorder = useRef<MediaRecorder | null>(null); | |
| const audioChunks = useRef<Blob[]>([]); | |
| const startRecording = async () => { | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| mediaRecorder.current = new MediaRecorder(stream); | |
| audioChunks.current = []; | |
| mediaRecorder.current.ondataavailable = (event) => { | |
| audioChunks.current.push(event.data); | |
| }; | |
| mediaRecorder.current.onstop = () => { | |
| const audioBlob = new Blob(audioChunks.current, { type: "audio/wav" }); | |
| setAudioBlob(audioBlob); | |
| }; | |
| mediaRecorder.current.start(); | |
| setIsRecording(true); | |
| } catch (error) { | |
| console.error("Erreur lors de l'accès au microphone:", error); | |
| alert("Impossible d'accéder au microphone"); | |
| } | |
| }; | |
| const stopRecording = () => { | |
| if (mediaRecorder.current && isRecording) { | |
| mediaRecorder.current.stop(); | |
| setIsRecording(false); | |
| mediaRecorder.current.stream.getTracks().forEach((track) => track.stop()); | |
| } | |
| }; | |
| const changeAvatar = () => { | |
| setAvatarIndex((prevIndex) => (prevIndex + 1) % avatars.length); | |
| }; | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!userName.trim() || !audioBlob) { | |
| alert("Veuillez entrer votre nom et enregistrer votre voix"); | |
| return; | |
| } | |
| setIsLoading(true); | |
| try { | |
| const formData = new FormData(); | |
| formData.append("audio", audioBlob); | |
| formData.append("name", userName); | |
| formData.append("avatar", avatars[avatarIndex]); | |
| const voiceResponse = await fetch("/api/voice", { | |
| method: "POST", | |
| body: formData, | |
| }); | |
| if (!voiceResponse.ok) | |
| throw new Error("Erreur lors de l'upload de la voix"); | |
| const userData = await voiceResponse.json(); | |
| const expirationDate = new Date(); | |
| expirationDate.setDate(expirationDate.getDate() + 30); | |
| document.cookie = `userId=${ | |
| userData.id | |
| };expires=${expirationDate.toUTCString()};path=/`; | |
| document.cookie = `userName=${encodeURIComponent( | |
| userName | |
| )};expires=${expirationDate.toUTCString()};path=/`; | |
| document.cookie = `userAvatar=${encodeURIComponent( | |
| avatars[avatarIndex] | |
| )};expires=${expirationDate.toUTCString()};path=/`; | |
| const joinResponse = await fetch(`/api/group/${params.inviteCode}/join`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| userId: userData.id, | |
| userName, | |
| avatar: avatars[avatarIndex], | |
| }), | |
| }); | |
| if (!joinResponse.ok) | |
| throw new Error("Erreur lors de la jointure au groupe"); | |
| router.push(`/group/${params.inviteCode}`); | |
| } catch (error) { | |
| console.error("Erreur:", error); | |
| alert("Une erreur est survenue lors de la jointure au groupe"); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="min-h-screen relative overflow-hidden"> | |
| <div className="background-pattern" /> | |
| <div className="background-overlay" /> | |
| <div className="background-vignette" /> | |
| <div className="relative z-10 p-4"> | |
| <div className="max-w-md 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" /> | |
| Retour | |
| </Button> | |
| </Link> | |
| <h1 className="text-3xl font-grobold font-normal text-center text-orange-600"> | |
| Rejoindre la partie | |
| </h1> | |
| </div> | |
| <form onSubmit={handleSubmit} className="space-y-8"> | |
| <div className="flex flex-col items-center space-y-4"> | |
| <Avatar className="w-32 h-32 border-4 border-orange-400"> | |
| <AvatarImage src={avatars[avatarIndex]} alt="Avatar" /> | |
| <AvatarFallback>AV</AvatarFallback> | |
| </Avatar> | |
| <Button | |
| type="button" | |
| onClick={changeAvatar} | |
| variant="outline" | |
| className="border-orange-400 text-orange-600 hover:bg-orange-100" | |
| > | |
| <RefreshCcw className="mr-2 h-5 w-5" /> | |
| Changer d'avatar | |
| </Button> | |
| </div> | |
| <div className="space-y-2"> | |
| <label className="text-lg font-grobold font-normal text-orange-600"> | |
| Votre pseudo | |
| </label> | |
| <Input | |
| type="text" | |
| value={userName} | |
| onChange={(e) => setUserName(e.target.value)} | |
| className="bg-orange-50 text-orange-800 placeholder-orange-300 border-orange-200 focus:border-orange-400 focus:ring-orange-400" | |
| placeholder="Entrez votre pseudo" | |
| required | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <label className="text-lg font-grobold font-normal text-orange-600"> | |
| Votre voix | |
| </label> | |
| <div className="flex items-center space-x-4"> | |
| <Button | |
| type="button" | |
| onClick={isRecording ? stopRecording : startRecording} | |
| className={`flex-1 py-6 ${ | |
| isRecording | |
| ? "bg-red-500 hover:bg-red-600" | |
| : "bg-orange-500 hover:bg-orange-600" | |
| } text-white`} | |
| > | |
| {isRecording ? ( | |
| <> | |
| <MicOff className="mr-2 h-5 w-5" /> | |
| Arrêter | |
| </> | |
| ) : ( | |
| <> | |
| <Mic className="mr-2 h-5 w-5" /> | |
| Enregistrer | |
| </> | |
| )} | |
| </Button> | |
| </div> | |
| {audioBlob && !isRecording && ( | |
| <div className="text-green-600 font-medium text-center mt-2"> | |
| ✓ Voix enregistrée | |
| </div> | |
| )} | |
| </div> | |
| <Button | |
| type="submit" | |
| disabled={isLoading || !audioBlob} | |
| className={`w-full py-6 text-lg font-grobold font-normal ${ | |
| isLoading || !audioBlob | |
| ? "bg-gray-400" | |
| : "bg-green-500 hover:bg-green-600" | |
| } text-white`} | |
| > | |
| {isLoading ? "Jointure en cours..." : "Rejoindre la partie"} | |
| </Button> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |