diff --git a/src/components/EventSourceContextType.tsx b/src/components/EventSourceContextType.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ae721491cf3a3909db891df590fc793b2d93af49 --- /dev/null +++ b/src/components/EventSourceContextType.tsx @@ -0,0 +1,31 @@ +// EventSourceContext.tsx +import {createContext, useContext, ReactNode, useState} from 'react'; + +interface EventSourceContextType { + eventSource: EventSource; + setEventSource: (es: EventSource) => void; +} + +const EventSourceContext = createContext<EventSourceContextType | null>(null); + +interface EventSourceProviderProps { + children: ReactNode; +} + +export function EventSourceProvider({ children }: EventSourceProviderProps) { + const [eventSource, setEventSource] = useState<EventSource | null>(null); + + return ( + <EventSourceContext.Provider value={{ eventSource, setEventSource }}> + {children} + </EventSourceContext.Provider> + ); +} + +export function useEventSourceContext() { + const context = useContext(EventSourceContext); + if (!context) { + throw new Error('useEventSourceContext must be used within an EventSourceProvider'); + } + return context; +} diff --git a/src/main.tsx b/src/main.tsx index 26bf59262b09399f800a0bd471603cfcbc0c6723..7315ece31c90c4d73bcaea056e3cfc26c929a191 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -12,6 +12,8 @@ import { PlayingQuiz } from "./routes/quiz/playingQuiz.tsx"; import { Multiplayer } from "./routes/multiplayer/multiplayer.tsx"; import { Lobby } from "./routes/multiplayer/Lobby/lobby.tsx"; import { EditQuiz } from "./routes/editQuiz/editQuiz.tsx"; +import {PlayingQuizMulti} from "./routes/multiplayer/quiz/PlayingQuizMulti.tsx"; +import {EventSourceProvider} from "./components/EventSourceContextType.tsx"; const router = createBrowserRouter([ { @@ -50,6 +52,10 @@ const router = createBrowserRouter([ path: "quiz/:id", element: <PlayingQuiz />, }, + { + path: "quizMulti/:id", + element: <PlayingQuizMulti />, + }, { path: "lobby/:id", element: <Lobby />, @@ -60,6 +66,8 @@ const router = createBrowserRouter([ createRoot(document.getElementById("root")!).render( <StrictMode> - <RouterProvider router={router} /> + <EventSourceProvider> + <RouterProvider router={router} /> + </EventSourceProvider> </StrictMode> ); diff --git a/src/models/CustomEventType.ts b/src/models/CustomEventType.ts new file mode 100644 index 0000000000000000000000000000000000000000..49be36528ee851d2e5aee004b9fb102f9a25d2a2 --- /dev/null +++ b/src/models/CustomEventType.ts @@ -0,0 +1 @@ +export type CustomEventType = "userJoined" | "nextQuestion" | "generateRun" | "gameStarted" | "answerRevealed" diff --git a/src/models/PlayingQuizState.ts b/src/models/PlayingQuizState.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7ab64871f4195b522600797eb4d042077a7aa61 --- /dev/null +++ b/src/models/PlayingQuizState.ts @@ -0,0 +1,5 @@ +export enum PlayingQuizState { + ANSWERING= "Answering", + LOADING = "Loading", + SHOWING_RESULTS= "ShowingResults", +} diff --git a/src/models/Question.ts b/src/models/Question.ts index 0802c0d86b1fcdb55b0166a6b232cc923d4a7d51..75a159bb04ca01638254449d8c6edb4de714a491 100644 --- a/src/models/Question.ts +++ b/src/models/Question.ts @@ -1,12 +1,15 @@ import { Answer, AnswerCreation } from './Answer.ts'; +import {Category} from './Quiz.ts'; export interface Question { id: number; text: string; + type: string; categoryId: number; quizId: string; order: number; answers: Answer[]; + category: Category; } export interface QuestionCreation { diff --git a/src/models/Response/AnswerSSEResponse.ts b/src/models/Response/AnswerSSEResponse.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf8d2a0b303df5fda7a7180d2bba9af84ee27c8d --- /dev/null +++ b/src/models/Response/AnswerSSEResponse.ts @@ -0,0 +1,11 @@ +import {Answer} from "../Answer"; + +export interface AnswerSSEResponse { + id: number; + text: string; + type:string; + categoryId: number; + quizId: string; + order: number; + answers: Answer[]; +} diff --git a/src/models/UserEndQuiz.ts b/src/models/UserEndQuiz.ts new file mode 100644 index 0000000000000000000000000000000000000000..eae7c8980e504b8c220c2128cbe2572d75faaa95 --- /dev/null +++ b/src/models/UserEndQuiz.ts @@ -0,0 +1,5 @@ +export interface UserEndQuiz{ + id: number, + username: string, + score: number +} diff --git a/src/routes/multiplayer/Lobby/lobby.tsx b/src/routes/multiplayer/Lobby/lobby.tsx index 814b1562801d8edd1a83116168ffcf47e3a63d7b..b34e557884135cf4bbf8094f8498c69fb1158a5f 100644 --- a/src/routes/multiplayer/Lobby/lobby.tsx +++ b/src/routes/multiplayer/Lobby/lobby.tsx @@ -7,8 +7,8 @@ import { useMultiplayerService } from "../../../services/MultiplayerService"; import {toDataURL} from "qrcode"; import { User } from "../../../models/User"; import HttpError from "../../../services/HttpError.ts"; +import {useEventSourceContext} from "../../../components/EventSourceContextType.tsx"; -type CustomEventType = "userJoined" | "nextQuestion" export function Lobby() { @@ -16,9 +16,9 @@ export function Lobby() { const [quiz, setQuiz] = useState<Quiz>(); const [players, setPlayers] = useState<User[]>([]); const [roomIdCreated, setRoomIdCreated] = useState<string | undefined>(id); - + const { setEventSource } = useEventSourceContext(); const isFirstRender = useRef(true); - const {createParty} = useMultiplayerService(); + const {createParty, startParty, generateRun} = useMultiplayerService(); const navigate = useNavigate(); const location = useLocation(); @@ -41,16 +41,23 @@ export function Lobby() { } console.log("User try to joined party with ID:", goodId); console.log(`https://klebert-host.com:33037/party/join/${goodId}`) - interface UserJoinedEvent extends Event { + interface EventData extends Event { data: string; // EventSource envoie toujours les données en tant que string } - const es = new EventSource(`https://klebert-host.com:33037/party/join/${goodId}`,{ withCredentials:true, }); - console.log(es.readyState); + setEventSource(es); + + es.addEventListener("gameStarted", (event: EventData) => { + console.log("gameStarted listener") + if(!event.data) return; + console.log("Game started event data:",event.data); + console.log("navigate to : /quizMulti/"+goodId); + navigate("/quizMulti/"+goodId, { state: { questionCount: 10, isHost: isHost } }); + }); - es.addEventListener("userJoined", (event : UserJoinedEvent) => { + es.addEventListener("userJoined", (event : EventData) => { console.log("User joined event data:", event.data); if(!event.data) return; @@ -90,7 +97,7 @@ export function Lobby() { Swal.fire({ title: "Invitation", html: ` - <p>Invite tes amis à jouer avec toi dans cette partie !</p> + <p>Invite tes amis à jouer avec toi dans cette <part></part>ie !</p> <img src="${qrCodeUrl}" alt="QR Code" style="width: 200px; height: 200px; border-radius: 0%;" /> <p>${currentUrl}</p> `, @@ -110,7 +117,8 @@ export function Lobby() { }; const play = async () => { - console.log("aaa"); + console.log("start party in front"); + startParty(); }; return ( diff --git a/src/routes/multiplayer/endQuizModal/endQuizModal.tsx b/src/routes/multiplayer/endQuizModal/endQuizModal.tsx deleted file mode 100644 index 35187643a1f3e7985c10487b2b6f44a55ea56f4a..0000000000000000000000000000000000000000 --- a/src/routes/multiplayer/endQuizModal/endQuizModal.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import "./endQuizModal.css"; -import { ModalButton } from "../../../components/quiz/modalButton/modalButton.tsx"; -import { useNavigate } from "react-router-dom"; -import { MultiplayerQuizInformations } from "../../../models/QuizInformations.ts"; - -interface Props { - isOpen: boolean; - onClose: () => void; - quizInformations: MultiplayerQuizInformations; - score: number; -} - -export const EndQuizModal = ({ isOpen, onClose, quizInformations, score }: Props) => { - if (!isOpen) return null; - const navigate = useNavigate(); - - const handleBackToMenu = () => { - navigate("/multiplayer"); - }; - return ( - <div className="end-quiz-modal-overlay"> - <div className="end-quiz-modal-content" onClick={(e) => e.stopPropagation()}> - <div> - <p className="end-quiz-modal-button-title">CONGRATS !</p> - </div> - <div className="end-quiz-modal-button-text-container"> - <p className="end-quiz-modal-button-text">YOU FINISH THE TEST WITH A</p> - <p className="end-quiz-modal-button-text"> - SCORE OF {score}/{quizInformations.questionCount} ! - </p> - </div> - <div> - <div className="end-quiz-modal-scores"> - <img src="stars.png" alt="stars picture" className="stars-image" /> - {quizInformations.scores.slice(0, 3).map((score, index) => ( - <div key={index} className="score-item"> - <div className="score-avatar"> - <img - src="https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png" - alt="profile picture" - className="player-image" - /> - </div> - <p>{quizInformations.names[index]}</p> - <p> - {score}/{quizInformations.questionCount} - </p> - </div> - ))} - <img src="stars.png" alt="stars picture" className="stars-image-reverse" /> - </div> - <div className="end-quiz-modal-scores"> - {quizInformations.scores.slice(3, 8).map((score, index) => ( - <div key={index} className="score-item"> - <p>{quizInformations.names[index + 3]}</p> - <p>{score}/20</p> - </div> - ))} - </div> - </div> - - <div className="end-quiz-modal-button-container"> - {/*<ModalButton text="RESTART" onClick={onClose} isLeftButton={true} isDisabled={false}/>*/} - <ModalButton - text="BACK TO MENU" - onClick={handleBackToMenu} - isLeftButton={false} - isDisabled={false} - /> - </div> - </div> - </div> - ); -}; diff --git a/src/routes/multiplayer/endQuizModal/endQuizModalMulti.tsx b/src/routes/multiplayer/endQuizModal/endQuizModalMulti.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b1d4b1b5e7a38ad2b28e49f12460a3c8bfc48cc1 --- /dev/null +++ b/src/routes/multiplayer/endQuizModal/endQuizModalMulti.tsx @@ -0,0 +1,75 @@ +import "./endQuizModal.css"; +import { ModalButton } from "../../../components/quiz/modalButton/modalButton.tsx"; +import { useNavigate } from "react-router-dom"; +import { MultiplayerQuizInformations } from "../../../models/QuizInformations.ts"; +import {UserEndQuiz} from "../../../models/UserEndQuiz.ts"; + +interface Props { + isOpen: boolean; + onClose: () => void; + users: UserEndQuiz[]; +} + +export const EndQuizModalMulti = ({ isOpen, onClose, users }: Props) => { + if (!isOpen) return null; + const navigate = useNavigate(); + + const handleBackToMenu = () => { + navigate("/multiplayer"); + }; + return ( + <div className="end-quiz-modal-overlay"> + <div className="end-quiz-modal-content" onClick={(e) => e.stopPropagation()}> + <div> + <p className="end-quiz-modal-button-title">CONGRATS !</p> + </div> + <div className="end-quiz-modal-button-text-container"> + <p className="end-quiz-modal-button-text">YOU FINISH THE TEST WITH A</p> + <p className="end-quiz-modal-button-text"> + {/*SCORE OF {10}/{quizInformations.questionCount} !*/} + SCORE OF {10}/{10} ! + </p> + </div> + <div> + <div className="end-quiz-modal-scores"> + <img src="../../../../public/stars.png" alt="stars picture" className="stars-image" /> + {/*{quizInformations.scores.slice(0, 3).map((score, index) => (*/} + {/* <div key={index} className="score-item">*/} + {/* <div className="score-avatar">*/} + {/* <img*/} + {/* src="https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png"*/} + {/* alt="profile picture"*/} + {/* className="player-image"*/} + {/* />*/} + {/* </div>*/} + {/* <p>{quizInformations.names[index]}</p>*/} + {/* <p>*/} + {/* {score}/{quizInformations.questionCount}*/} + {/* </p>*/} + {/* </div>*/} + {/*))}*/} + <img src="../../../../public/stars.png" alt="stars picture" className="stars-image-reverse" /> + </div> + <div className="end-quiz-modal-scores"> + {/*{quizInformations.scores.slice(3, 8).map((score, index) => (*/} + {/* <div key={index} className="score-item">*/} + {/* <p>{quizInformations.names[index + 3]}</p>*/} + {/* <p>{score}/20</p>*/} + {/* </div>*/} + {/*))}*/} + </div> + </div> + + <div className="end-quiz-modal-button-container"> + {/*<ModalButton text="RESTART" onClick={onClose} isLeftButton={true} isDisabled={false}/>*/} + <ModalButton + text="BACK TO MENU" + onClick={handleBackToMenu} + isLeftButton={false} + isDisabled={false} + /> + </div> + </div> + </div> + ); +}; diff --git a/src/routes/multiplayer/quiz/PlayingQuizMulti.tsx b/src/routes/multiplayer/quiz/PlayingQuizMulti.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1322596e5a035249084bf85bcbd65397c53db052 --- /dev/null +++ b/src/routes/multiplayer/quiz/PlayingQuizMulti.tsx @@ -0,0 +1,91 @@ +import "./quiz.css"; +import {useEffect, useState} from "react"; +import {useMultiplayerService} from "../../../services/MultiplayerService.ts"; +import {QuizTopMulti} from "./quizTop/QuizTopMulti.tsx"; +import {QuizBotMulti} from "./quizBot/QuizBotMulti.tsx"; +import {EndQuizModalMulti} from "../endQuizModal/endQuizModalMulti.tsx"; +import {MultiplayerQuizInformations} from "../../../models/QuizInformations.ts"; +import {Question} from "../../../models/Question.ts"; +import {useLocation} from "react-router-dom"; +import {useQuizService} from "../../../services/QuizService.ts"; +import {useEventSourceContext} from "../../../components/EventSourceContextType.tsx"; +import {PlayingQuizState} from "../../../models/PlayingQuizState.ts"; +import {StopQuizModal} from "../../../components/quiz/stopQuizModal/stopQuizModal.tsx"; +import {UserEndQuiz} from "../../../models/UserEndQuiz.ts"; +import {shuffleAnswers} from "../../../utils/QuizHelper.ts"; + + +enum QuizState { + LOADING = "Loading", + ERROR = "Error", + OK = "Ok", +} +export function PlayingQuizMulti() { + const { nextQuestion } = useMultiplayerService(); + const [quizState, setQuizState] = useState<QuizState>(QuizState.LOADING); + const [playingQuizState, setPlayingQuizState] = useState<PlayingQuizState>(PlayingQuizState.LOADING) + const [stopQuizModalIsOpen, setStopQuizModalIsOpen] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [endQuizModalIsOpen, setEndQuizModalIsOpen] = useState(false); + const [actualQuestion, setActualQuestion] = useState<Question | undefined>(undefined); + const [quizInformations, setQuizInformations] = useState<MultiplayerQuizInformations>(); + const [score, setScore] = useState(0); + const { eventSource } = useEventSourceContext(); + const location = useLocation(); + const [users, setUsers] = useState<UserEndQuiz[]>([]); + + const isHost = location.state?.isHost; + const questionCount = location.state?.questionCount; + + + const initES = async () => { + eventSource.addEventListener("nextQuestion", async (event) => { + console.log("nextQuestion event data:", event.data); + if (!event.data) return; + const question: Question = JSON.parse(event.data); + console.log("setActualQuestion"); + setActualQuestion(shuffleAnswers(question)); + setQuizState(QuizState.OK); + setPlayingQuizState(PlayingQuizState.ANSWERING); + }); + + eventSource.addEventListener("gameEnded", (event) => { + console.log("gameEnded event data:"); + if (!event.data) return; + const users: UserEndQuiz[] = JSON.parse(event.data); + console.log("users:", users); + setUsers(users); + setEndQuizModalIsOpen(true); + }); + } + + useEffect(() => { + initES(); + console.log("fetching first question"); + nextQuestion(); + }, []); + + + return ( + <div className="quiz-container"> + {quizState === QuizState.LOADING ? ( + <div className="center-loading-spinner-container"> + <div className="center-loading-spinner"></div> + </div> + ) : ( + <> + <QuizTopMulti quizInformations={null} actualQuestion={actualQuestion} score={score} questionCount={questionCount} setStopQuizModalIsOpen={setStopQuizModalIsOpen} /> + <QuizBotMulti actualQuestion={actualQuestion} isHost={true} setPlayingQuizState={setPlayingQuizState} playingQuizState={playingQuizState} OpenModalEndQuiz={setEndQuizModalIsOpen}/> + <StopQuizModal isOpen={stopQuizModalIsOpen} onClose={() => setStopQuizModalIsOpen(false)} /> + <EndQuizModalMulti + isOpen={endQuizModalIsOpen} + onClose={null} + users={users} + /> + </> + )} + {/*{playingQuizState === QuizState.ERROR && <h2>Error: {errorMessage}</h2>}*/} + + </div> + ); +} diff --git a/src/routes/multiplayer/quiz/playingQuiz.tsx b/src/routes/multiplayer/quiz/playingQuiz.tsx deleted file mode 100644 index 38296a66baebb32c9cb96eb139d9765b6abf8c1b..0000000000000000000000000000000000000000 --- a/src/routes/multiplayer/quiz/playingQuiz.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import "./quiz.css"; -import { useEffect, useState } from "react"; -import { useMultiplayerService } from "../../../services/MultiplayerService.ts"; -import HttpError from "../../../services/HttpError.ts"; -import { QuizTop } from "./quizTop/quizTop.tsx"; -import { QuizBot } from "./quizBot/quizBot.tsx"; -import { EndQuizModal } from "../endQuizModal/endQuizModal.tsx"; -import { MultiplayerQuizInformations } from "../../../models/QuizInformations.ts"; - -enum QuizState { - LOADING = "Loading", - ERROR = "Error", - OK = "Ok", -} -export function PlayingQuiz() { - const [question, setQuestion] = useState<any>(); - const { recivedQuestion, recivedResults, recivedEnd, sendAnswer } = useMultiplayerService(); - const [quizState, setQuizState] = useState(QuizState.LOADING); - const [errorMessage, setErrorMessage] = useState(""); - const [endQuizModalIsOpen, setEndQuizModalIsOpen] = useState(false); - const [quizInformations, setQuizInformations] = useState<MultiplayerQuizInformations>(); - const [score, setScore] = useState(0); - - const fetchQuiz = async () => { - const question = await recivedQuestion(); - if (question instanceof HttpError) { - setQuizState(QuizState.ERROR); - setErrorMessage(question.message); - return; - } - if (question.questionIndex >= question.questionCount) { - setQuizState(QuizState.ERROR); - setErrorMessage("You already finished the quiz"); - return; - } - setQuizState(QuizState.OK); - setQuestion(question); - }; - - const recivedEndQuiz = async () => { - const end = await recivedEnd(); - if (end instanceof HttpError) { - setQuizState(QuizState.ERROR); - setErrorMessage(end.message); - return; - } - setQuizInformations(end.informations); - setScore(end.score); - setEndQuizModalIsOpen(true); - }; - - useEffect(() => { - fetchQuiz(); - }, []); - - return ( - <div className="quiz-container"> - {quizState === QuizState.LOADING && ( - <div className="center-loading-spinner-container"> - <div className="center-loading-spinner"></div> - </div> - )} - {quizState === QuizState.ERROR && <h2>Error: {errorMessage}</h2>} - {quizState === QuizState.OK && question && ( - <> - <QuizTop question={question} /> - <QuizBot question={question} /> - <EndQuizModal - isOpen={endQuizModalIsOpen} - onClose={null} - quizInformations={quizInformations} - score={score} - /> - </> - )} - </div> - ); -} diff --git a/src/routes/multiplayer/quiz/quizBot/quizBot.tsx b/src/routes/multiplayer/quiz/quizBot/QuizBotMulti.tsx similarity index 50% rename from src/routes/multiplayer/quiz/quizBot/quizBot.tsx rename to src/routes/multiplayer/quiz/quizBot/QuizBotMulti.tsx index e11517eb20a8ae159f7ede0430249f48195ea54b..e26440f316b57b16ecdea595d4a1d525e6936121 100644 --- a/src/routes/multiplayer/quiz/quizBot/quizBot.tsx +++ b/src/routes/multiplayer/quiz/quizBot/QuizBotMulti.tsx @@ -2,55 +2,63 @@ import "./quizBot.css"; import { AnswerButton } from "../../../../components/quiz/answerButton/answerButton.tsx"; import { ValidationButton } from "../../../../components/quiz/validationButton/validationButton.tsx"; import { useEffect, useState } from "react"; -import HttpError from "../../../../services/HttpError.ts"; import { useMultiplayerService } from "../../../../services/MultiplayerService.ts"; import { Answer } from "../../../../models/Answer.ts"; +import {Question} from "../../../../models/Question.ts"; +import {AnswerSSEResponse} from "../../../../models/Response/AnswerSSEResponse.ts"; +import {useEventSourceContext} from "../../../../components/EventSourceContextType.tsx"; +import {PlayingQuizState} from "../../../../models/PlayingQuizState.ts"; interface Props { - question: any; + actualQuestion: Question; + isHost: boolean; + setPlayingQuizState: (playingQuizState: PlayingQuizState) => void; + playingQuizState: PlayingQuizState; + OpenModalEndQuiz: (bool: boolean) => void; } -enum QuizState { - ANSWERING = "Answering", - LOADING = "Loading", - SHOWING_RESULTS = "ShowingResults", -} const getStyleOfAnswerButton = ( answerId: number, selectedAnswerIndex: number | null, correctAnswerId: number | null, - quizState: QuizState + quizState: PlayingQuizState ): string => { - if (quizState === QuizState.LOADING) { + if (quizState === PlayingQuizState.LOADING) { return "answer-button answer-button-loading"; } - if (quizState === QuizState.ANSWERING) { + if (quizState === PlayingQuizState.ANSWERING) { if (selectedAnswerIndex === null) return "answer-button"; if (selectedAnswerIndex === answerId) return "answer-button answer-button-selected"; } - if (quizState === QuizState.SHOWING_RESULTS) { + if (quizState === PlayingQuizState.SHOWING_RESULTS) { if (answerId === correctAnswerId) return "answer-button answer-button-correct"; if (answerId !== correctAnswerId && selectedAnswerIndex === answerId) return "answer-button answer-button-wrong"; } return "answer-button"; }; -export function QuizBot({ question }: Props) { +export function QuizBotMulti({ actualQuestion, isHost, playingQuizState, setPlayingQuizState }: Props) { const [questionIsFinished, setQuestionIsFinished] = useState(false); const [selectedAnswerId, setSelectedAnswerId] = useState<number | null>(null); const [validationButtonIsDisabled, setValidationButtonIsDisabled] = useState(true); - const [quizState, setQuizState] = useState(QuizState.ANSWERING); const [correctAnswerId, setCorrectAnswerId] = useState<number | null>(null); + const { nextQuestion, answerQuestion, revealAnswer } = useMultiplayerService(); + const { eventSource } = useEventSourceContext(); - const { sendAnswer, recivedResults, recivedQuestion } = useMultiplayerService(); + const initSSE = async () => { + eventSource.addEventListener("answerRevealed", (event) => { + console.log("answerRevealed"); + if(!event.data) return; + const answers: AnswerSSEResponse = JSON.parse(event.data); + setCorrectAnswerId(getCorrectAnswer(answers.answers).id); + setPlayingQuizState(PlayingQuizState.SHOWING_RESULTS); + }); + } useEffect(() => { - setSelectedAnswerId(null); // Réinitialise la sélection - setQuestionIsFinished(false); // Réinitialise l'état de la question - setCorrectAnswerId(null); // Réinitialise l'id de la bonne réponse - setQuizState(QuizState.ANSWERING); - }, [question.questionIndex]); + initSSE(); + }, []); const getCorrectAnswer = (answers: Answer[]): Answer => { const correctAnswer = answers.find((answer) => answer.isCorrect); @@ -58,33 +66,29 @@ export function QuizBot({ question }: Props) { return correctAnswer; }; const onValidation = async () => { - setQuizState(QuizState.LOADING); - const answerId = question.answers.find((answer) => answer.id === selectedAnswerId)?.id; - if (!answerId) return; - sendAnswer(question.id_quiz, question.id, answerId); - const answers = await recivedResults(); - if (HttpError.isHttpError(answers)) { - return; + console.log("onValidation"); + if(selectedAnswerId === null || actualQuestion === undefined) return; + setPlayingQuizState(PlayingQuizState.LOADING); + await answerQuestion(actualQuestion.id, selectedAnswerId); + + if(isHost) + { + await revealAnswer(); } - setQuizState(QuizState.SHOWING_RESULTS); - const correctAnswerIdFetched = getCorrectAnswer(answers).id; - setCorrectAnswerId(correctAnswerIdFetched); }; const onContinue = () => { - question = recivedQuestion(); - }; - - useEffect(() => { - if (selectedAnswerId !== null) { - setValidationButtonIsDisabled(false); - return; + if(isHost) + { + nextQuestion(); + setPlayingQuizState(PlayingQuizState.ANSWERING); + setCorrectAnswerId(null); + setSelectedAnswerId(null); } - setValidationButtonIsDisabled(true); - }, [selectedAnswerId]); + }; const onAnsweredButtonClicked = (answerId: number) => { - if (quizState === QuizState.SHOWING_RESULTS || quizState === QuizState.LOADING) { + if (playingQuizState === PlayingQuizState.SHOWING_RESULTS || playingQuizState === PlayingQuizState.LOADING) { return; } setSelectedAnswerId(answerId); @@ -93,16 +97,16 @@ export function QuizBot({ question }: Props) { return ( <div className="quiz-bot-container"> <div className="quiz-bot-button-container"> - {question.answers.map((answer) => { + {actualQuestion.answers.map((answer) => { return ( <> - <p>{quizState === QuizState.SHOWING_RESULTS ? answer.nb_answers : null}</p> + {/*<p>{quizState === QuizState.SHOWING_RESULTS ? answer.nb_answers : null}</p>*/} <AnswerButton text={answer.text} key={answer.id} onClick={() => onAnsweredButtonClicked(answer.id)} - style={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} - isDisabled={quizState === QuizState.LOADING} + style={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, playingQuizState)} + isDisabled={playingQuizState === PlayingQuizState.LOADING} /> </> ); @@ -110,9 +114,9 @@ export function QuizBot({ question }: Props) { </div> <div className="quiz-bot-validation-button-container"> <ValidationButton - text={quizState === QuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} - onClick={() => (quizState === QuizState.ANSWERING ? onValidation() : onContinue())} - isDisabled={validationButtonIsDisabled} + text={playingQuizState === PlayingQuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} + onClick={() => (playingQuizState === PlayingQuizState.ANSWERING ? onValidation() : onContinue())} + isDisabled={false} /> </div> </div> diff --git a/src/routes/multiplayer/quiz/quizTop/QuizTopMulti.tsx b/src/routes/multiplayer/quiz/quizTop/QuizTopMulti.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d1c2ab38e073d81e20bfddec73c445baf8d5e383 --- /dev/null +++ b/src/routes/multiplayer/quiz/quizTop/QuizTopMulti.tsx @@ -0,0 +1,44 @@ +import "./quizTop.css"; +import HomeIcon from "@mui/icons-material/Home"; +import {Question} from "../../../../models/Question.ts"; +import {QuizInformations} from "../../../../models/QuizInformations.ts"; +interface Props { + actualQuestion: Question; + quizInformations?: QuizInformations; + questionCount: number; + score: number; + setStopQuizModalIsOpen: (stopQuizModalIsOpen: boolean) => void; +} +export function QuizTopMulti({ actualQuestion, quizInformations, questionCount, score, setStopQuizModalIsOpen }: Props) { + return ( + <div className="quiz-top"> + <div className="home-id-container"> + <div className="home-id-box"> + <button className="home-button" onClick={() => setStopQuizModalIsOpen(true)}> + <HomeIcon/> + </button> + <p className="quiz-id">ID QUIZ: {actualQuestion.id}</p> + </div> + </div> + + <div className="quiz-top-content"> + <div className="in-game-information"> + <p className="text-nb-question"> + QUESTION {actualQuestion ? actualQuestion.order + 1 : "?"}/{questionCount} + </p> + <p className="text-score"> + SCORE : {score}/{questionCount} + </p> + </div> + + {/*<div>*/} + {/* <p className="text-category">{question.category.name}</p>*/} + {/*</div>*/} + + <div className="quiz-top-text-question"> + <p className="text-question">{actualQuestion ? actualQuestion.text : "Loading..."}</p> + </div> + </div> + </div> + ); +} diff --git a/src/routes/multiplayer/quiz/quizTop/quizTop.tsx b/src/routes/multiplayer/quiz/quizTop/quizTop.tsx deleted file mode 100644 index 981d73f90b7aa0de9e965447cc8dccdd9b0e4446..0000000000000000000000000000000000000000 --- a/src/routes/multiplayer/quiz/quizTop/quizTop.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import "./quizTop.css"; -interface Props { - question: any; -} -export function QuizTop({ question }: Props) { - return ( - <div className="quiz-top"> - <div className="home-id-container"> - <div className="home-id-box"> - <p className="quiz-id">ID QUIZ: {question.id_quiz}</p> - </div> - </div> - - <div className="quiz-top-content"> - <div className="in-game-information"> - <p className="text-nb-question"> - QUESTION {question.questionIndex}/{question.questionCount} - </p> - <p className="text-score"> - SCORE : {question.score}/{question.questionCount} - </p> - </div> - - <div> - <p className="text-category">{question.category.name}</p> - </div> - - <div className="quiz-top-text-question"> - <p className="text-question">{question.text}</p> - </div> - </div> - </div> - ); -} diff --git a/src/routes/quiz/quizBot/quizBot.tsx b/src/routes/quiz/quizBot/quizBot.tsx index 696afcd461ea26d7eaf08513a6e94e79dccb2d56..d9cb2b31204a57797bc338dfbf6db555c9d7d28b 100644 --- a/src/routes/quiz/quizBot/quizBot.tsx +++ b/src/routes/quiz/quizBot/quizBot.tsx @@ -66,7 +66,6 @@ export function QuizBot({quizInformations, runId, actualQuestion, fetchActualQue if(!answerId || !runId) return; const answereFetched = await answerQuestion(runId, actualQuestion.id, answerId); - if (HttpError.isHttpError(answereFetched)) { return; } diff --git a/src/services/MultiplayerService.ts b/src/services/MultiplayerService.ts index a7a4947f0046f45e67e5ba62eb33327253dd3c5d..4ceef968ad176ba602994739d2ce1db87a931092 100644 --- a/src/services/MultiplayerService.ts +++ b/src/services/MultiplayerService.ts @@ -31,14 +31,102 @@ export const useMultiplayerService = () => { } } + const generateRun = async () : Promise<string | HttpError> => { + try { + const response = await axios.get(`${url}/party/generateRun`, { + withCredentials: true, + }); + return response.data.runId; + } catch (error: any) { + console.log("Error while generate run:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + + const revealAnswer = async () : Promise<boolean | HttpError> => { + try { + const response = await axios.get(`${url}/party/revealAnswer`, { + withCredentials: true, + }); + return true; + } catch (error: any) { + console.log("Error while reveal Answer:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + + const answerQuestion = async (questionId: number, answerId: number) : Promise<boolean | HttpError> => { + const requestBody = { + questionId: questionId, + answerId: answerId, + }; + + try { + const response = await axios.post(`${url}/party/answer`, requestBody,{ + withCredentials: true, + }); + return true; + } catch (error: any) { + console.log("Error while reveal Answer:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + + const nextQuestion = async () : Promise<boolean | HttpError> => { + try { + const response = await axios.get(`${url}/party/nextQuestion`, { + withCredentials: true, + }); + return true; + } catch (error: any) { + console.log("Error while nextQuestion:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + + const startParty = async () : Promise<boolean | HttpError> => { + try { + const response = await axios.get(`${url}/party/start`, { + withCredentials: true, + }); + return true; + } catch (error: any) { + console.log("Error while startParty:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + const joinLobby = async (lobbyId: string): Promise<any> => { return { isHost: true, }; }; - const startParty = async (lobbyId: string): Promise<any> => {}; - const recivedPlayers = async (): Promise<any> => {}; const recivedStart = async (): Promise<any> => {}; @@ -84,5 +172,9 @@ export const useMultiplayerService = () => { joinLobby, recivedPlayers, lobbyInfos, + generateRun, + revealAnswer, + answerQuestion, + nextQuestion, }; };