diff --git a/index.html b/index.html index e4b78eae12304a075fa19675c4047061d6ab920d..fd87ca8ae04e74f5d3a935abbbae509ed913a6ef 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ <html lang="en"> <head> <meta charset="UTF-8" /> - <link rel="icon" type="image/svg+xml" href="/vite.svg" /> + <link rel="icon" type="image/svg+xml" href="/skitskola.png" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Vite + React + TS</title> + <title>Skitskola</title> </head> <body> <div id="root"></div> diff --git a/public/ProfilBaseImage.png b/public/ProfilBaseImage.png new file mode 100644 index 0000000000000000000000000000000000000000..123c902c001c1d4dd715c364adb55b21d129860d Binary files /dev/null and b/public/ProfilBaseImage.png differ diff --git a/public/skitskola.png b/public/skitskola.png new file mode 100644 index 0000000000000000000000000000000000000000..c86451dcd91e4b6860f3e5d0c1989c44fe3e9ed4 Binary files /dev/null and b/public/skitskola.png differ 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/components/quiz/endQuizModal/endQuizModal.css b/src/components/quiz/endQuizModal/endQuizModal.css index b4e0b680a1c90639b4ec1874ce0752c8ffe38589..d4ab6954ed8e68b5d4d28d78c71fd8d5757ef43f 100644 --- a/src/components/quiz/endQuizModal/endQuizModal.css +++ b/src/components/quiz/endQuizModal/endQuizModal.css @@ -1,4 +1,4 @@ -.end-quiz-modal-overlay { +.modal-end-quiz-modal-overlay { position: fixed; top: 0; left: 0; @@ -11,7 +11,7 @@ z-index: 1000; } -.end-quiz-modal-content { +.modal-end-quiz-modal-overlay .end-quiz-modal-content { display: flex; flex-direction: column; justify-content: space-around; @@ -80,8 +80,9 @@ opacity: 0; transform: translateY(-50%); } + to { opacity: 1; transform: translateY(0); } -} +} \ No newline at end of file diff --git a/src/components/quiz/endQuizModal/endQuizModal.tsx b/src/components/quiz/endQuizModal/endQuizModal.tsx index 0204e89882affc833b19d3018edc765ebf5c7041..e058c42301f9f90262eec72337e41aec7def0541 100644 --- a/src/components/quiz/endQuizModal/endQuizModal.tsx +++ b/src/components/quiz/endQuizModal/endQuizModal.tsx @@ -29,7 +29,7 @@ export const EndQuizModal = ({ isOpen, quizInformations, score, setIsOpen }: Pro navigate("/home"); }; return ( - <div className="end-quiz-modal-overlay"> + <div className="modal-end-quiz-modal-overlay"> <div className="end-quiz-modal-content" onClick={(e) => e.stopPropagation()}> <div> <p className="end-quiz-modal-button-title">CONGRATS !</p> diff --git a/src/components/quiz/modalButton/modalButton.css b/src/components/quiz/modalButton/modalButton.css index cb503738cbd3ca2a179634d2d0a13b342087f1ca..403efb050d8042a1f3aa7b2175758d37b0d8b68d 100644 --- a/src/components/quiz/modalButton/modalButton.css +++ b/src/components/quiz/modalButton/modalButton.css @@ -1,6 +1,6 @@ .modal-button-left { width: 100%; - height: 80px; + height: 10vh; border-radius: 12px; border: solid 5px #508bff; background-color: white; @@ -25,11 +25,12 @@ color: #508bff; font-size: 3vmin; text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5); + margin: 0; } .modal-button-right { width: 100%; - height: 80px; + height: 10vh; border-radius: 12px; background-color: #2b73fd; font-weight: bold; @@ -54,4 +55,5 @@ .modal-button-right-text { font-size: 3vmin; text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5); + margin: 0; } 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/createQuiz/imageQuiz.tsx b/src/routes/createQuiz/imageQuiz.tsx index 8c99f76c1e18799077fca64a344937e9973bb451..8a2d1825d9da7837f7471e18d5559fff0899e0c3 100644 --- a/src/routes/createQuiz/imageQuiz.tsx +++ b/src/routes/createQuiz/imageQuiz.tsx @@ -26,6 +26,10 @@ export default function ImageQuiz({ answers, indexQuestion, updateAnswer, handle const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>, indexQuestion: number, index: number) => { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; + if (file.size > 1048576) { + alert("Le fichier est trop volumineux. La taille maximale autorisée est de 1 Mo."); + return; + } try { const base64 = await convertToBase64(file); setPreviews((prev) => { diff --git a/src/routes/createQuiz/soundQuiz.tsx b/src/routes/createQuiz/soundQuiz.tsx index 89758fdd5c539cecffcf02da6d1aff7663d017f5..6a8fbb26f7b9d550391fb44fc7062ea10a5638ae 100644 --- a/src/routes/createQuiz/soundQuiz.tsx +++ b/src/routes/createQuiz/soundQuiz.tsx @@ -26,6 +26,10 @@ export default function SoundQuiz({ answers, indexQuestion, updateAnswer, handle const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>, indexQuestion: number, index: number) => { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; + if (file.size > 1048576) { + alert("Le fichier est trop volumineux. La taille maximale autorisée est de 1 Mo."); + return; + } try { const base64 = await convertToBase64(file); setPreviews((prev) => { diff --git a/src/routes/home/MyQuizzes/myQuizzes.css b/src/routes/home/MyQuizzes/myQuizzes.css index a489fc5ff0028d3dc232ff14593f7470a733b341..1cd6fa162c9349de5d01377bc4c1c954089a5103 100644 --- a/src/routes/home/MyQuizzes/myQuizzes.css +++ b/src/routes/home/MyQuizzes/myQuizzes.css @@ -13,18 +13,41 @@ padding-bottom: 2vmin; } -/* Liste */ +/* List */ .container-mq .scrollable-list { height: 45%; width: 80%; overflow-y: auto; - /* Ajoute une scrollbar verticale si nécessaire */ border: 4px solid #F3F3F3; padding: 0; border-radius: 10px; background-color: #FFFFFF; } +/* Login container*/ +.container-mq .login-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 3vmin; + margin: 2vmin; + +} + +.container-mq .login-container p { + font-size: 3vmin; + font-weight: bold; + color: #2B73FE; + text-transform: uppercase; +} + +.container-mq .login-container .blueButton { + width: 40%; + font-size: 3vmin; + text-transform: uppercase; +} + .container-mq .inputRecherche { width: 80%; } diff --git a/src/routes/home/MyQuizzes/myQuizzes.tsx b/src/routes/home/MyQuizzes/myQuizzes.tsx index 2120ee8a6247350a68d9ec72000ab5763511ab2e..0aedba7ec23e6c353e7ffd7eed6bf605bc70c01d 100644 --- a/src/routes/home/MyQuizzes/myQuizzes.tsx +++ b/src/routes/home/MyQuizzes/myQuizzes.tsx @@ -9,6 +9,7 @@ import { Quiz } from "../../../models/Quiz"; import HttpError from "../../../services/HttpError"; import { useNavigate } from "react-router-dom"; import { Run } from "../../../models/Run"; +import { useUserService } from "../../../services/UserService"; interface paqProps { loading: () => void; @@ -20,15 +21,18 @@ export function MyQuizzes({ loading }: paqProps) { const [liste, setListe] = useState<ListModel[]>([]); const [runList, setRunList] = useState<Run[] | null>(); const [run, setRun] = useState<Run | null>(); + const { getInformationsUser } = useUserService(); const { getUserRuns } = useQuizService(); const navigate = useNavigate(); - + const [isConnected, setIsConnected] = useState<boolean>(false); const fetchuserRun = async () => { const response = await getUserRuns(); if (response instanceof HttpError) { console.error("Error while fetching user runs:", response); + setIsConnected(false); return; } + setIsConnected(true); setRunList(response.runs); const tempList: ListModel[] = response.runs.map((run) => { return { @@ -69,17 +73,31 @@ export function MyQuizzes({ loading }: paqProps) { navigate("/quiz/" + selected, { state: { quizId: run.quizId } }); }; + const login = async () => { + navigate("/profile"); + } + return ( <div className="container-mq"> <p className="title">My recent quiz</p> <input type="search" className="inputRecherche" /> - <div className="scrollable-list"> - {isLoading ? ( - <div className="center-loading-spinner-container" /> - ) : ( - <List list={liste} selected={selected} setSelected={setSelected} isLoading={false} /> - )} - </div> + {isConnected ? + <div className="scrollable-list"> + {isLoading ? ( + <div className="center-loading-spinner-container" /> + ) : ( + <List list={liste} selected={selected} setSelected={setSelected} isLoading={false} /> + )} + </div> + : + <div className="scrollable-list"> + <div className="login-container"> + <p>You need to login</p> + <button className="blueButton" onClick={login}>Log in</button> + </div> + </div> + } + <input type="submit" value="PLAY" className="blueButton" onClick={handlePlay} /> </div> ); diff --git a/src/routes/home/PlayAQuiz/joinAQuiz.css b/src/routes/home/PlayAQuiz/joinAQuiz.css index a03baa5d59f0b5e8f846fb503cca2903b42b24c8..825daa066bced67f3eeb499fc02f22e130f193da 100644 --- a/src/routes/home/PlayAQuiz/joinAQuiz.css +++ b/src/routes/home/PlayAQuiz/joinAQuiz.css @@ -20,9 +20,8 @@ .quiz-info { display: flex; flex-direction: column; - justify-content: space-between; - - + justify-content: flex-start; + height: '50%'; background-color: #FEFEFE; border-radius: 10px; gap: 1rem; diff --git a/src/routes/home/PlayAQuiz/joinAQuiz.tsx b/src/routes/home/PlayAQuiz/joinAQuiz.tsx index 70162ee5bfccbaf9ac9827793d39ec494f864c8b..72f13b1e8f6486a5955512a8d8fedcc32e2a2b4d 100644 --- a/src/routes/home/PlayAQuiz/joinAQuiz.tsx +++ b/src/routes/home/PlayAQuiz/joinAQuiz.tsx @@ -85,9 +85,7 @@ export function JoinAQuiz({ loading }: paqProps) { } </div> - <div className="new-quiz"> - <button className="blueButton" onClick={handleGo}>Go !</button> - </div> + <button className="blueButton" onClick={handleGo}>Go !</button> </div> ); diff --git a/src/routes/multiplayer/Lobby/lobby.css b/src/routes/multiplayer/Lobby/lobby.css index 2286d733ae97de4c2bb58de3c069d4dcf78d0b82..8cc0aac777b1d44cd931e1dcd223df085c642589 100644 --- a/src/routes/multiplayer/Lobby/lobby.css +++ b/src/routes/multiplayer/Lobby/lobby.css @@ -49,7 +49,7 @@ .player-list { width: 100%; - height: 100%; + height: 70%; display: flex; flex-wrap: wrap; gap: 10px; @@ -79,6 +79,7 @@ color: #333; } + .title { text-align: center; } @@ -87,4 +88,5 @@ display: flex; justify-content: center; align-items: center; + gap: 15px; } diff --git a/src/routes/multiplayer/Lobby/lobby.tsx b/src/routes/multiplayer/Lobby/lobby.tsx index 814b1562801d8edd1a83116168ffcf47e3a63d7b..e3c375295a8bc24185cfe6ca959135cd3cbe6c1a 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(); @@ -26,6 +26,7 @@ export function Lobby() { let goodId = id; console.log("isHost :", isHost); + const url = import.meta.env.VITE_API_URL; const initSSE = async () => { console.log("Init SSE for quiz ID:", id); @@ -40,17 +41,24 @@ export function Lobby() { setRoomIdCreated(response); } console.log("User try to joined party with ID:", goodId); - console.log(`https://klebert-host.com:33037/party/join/${goodId}`) - interface UserJoinedEvent extends Event { + console.log(`${url}/party/join/${goodId}`) + 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}`,{ + const es = new EventSource(`${url}/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 +98,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 +118,8 @@ export function Lobby() { }; const play = async () => { - console.log("aaa"); + console.log("start party in front"); + startParty(); }; return ( @@ -146,7 +155,7 @@ export function Lobby() { <p className="quiz-info-content-value">{quiz?.category?.name}</p> </div> <div className="quiz-info-content"> - <p className="quiz-info-content-title">Nb. questions</p> + {/*<p className="quiz-info-content-title">Nb. questions</p>*/} <p className="quiz-info-content-value">{quiz?.questionCount}</p> </div> </div> diff --git a/src/routes/multiplayer/PlayAQuiz/joinAQuiz.css b/src/routes/multiplayer/PlayAQuiz/joinAQuiz.css index af2eeddc3e393f0f75ae74724faa9d40353d412e..f002557908147d5b92710d22fe1bc1a50cd95a91 100644 --- a/src/routes/multiplayer/PlayAQuiz/joinAQuiz.css +++ b/src/routes/multiplayer/PlayAQuiz/joinAQuiz.css @@ -20,9 +20,8 @@ .quiz-info { display: flex; flex-direction: column; - justify-content: space-between; - - + justify-content: flex-start; + height: 50%; background-color: #FEFEFE; border-radius: 10px; gap: 1rem; diff --git a/src/routes/multiplayer/PlayAQuiz/joinAQuiz.tsx b/src/routes/multiplayer/PlayAQuiz/joinAQuiz.tsx index 851e14b714e3d15946f8f61a79ba4fb65fb63b3e..d89cbbbe38f0ccbbc9f8358dc7e67b9a5a7e0a68 100644 --- a/src/routes/multiplayer/PlayAQuiz/joinAQuiz.tsx +++ b/src/routes/multiplayer/PlayAQuiz/joinAQuiz.tsx @@ -72,11 +72,9 @@ export function JoinAQuiz({ loading }: paqProps) { )} </div> - <div className="new-quiz"> - <button className="blueButton" onClick={handleGo}> - Go ! - </button> - </div> + <button className="blueButton" onClick={handleGo}> + Go ! + </button> </div> ); } diff --git a/src/routes/multiplayer/endQuizModal/endQuizModal.css b/src/routes/multiplayer/endQuizModal/endQuizModal.css index 994f3d4fb35a42ee51319957611339ecda2297bc..86ecf6052fdfff3290007348c1e4df60fcafba16 100644 --- a/src/routes/multiplayer/endQuizModal/endQuizModal.css +++ b/src/routes/multiplayer/endQuizModal/endQuizModal.css @@ -17,13 +17,16 @@ justify-content: space-around; align-items: center; background: white; - border-radius: 8px; - width: 30%; + width: 40%; height: 65%; padding: 20px; position: relative; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); animation: slide-down 0.3s ease-out; + background-color: #ffffff; + max-width: 600px; + border-radius: 10px; + text-align: center; } .end-quiz-modal-button-container { @@ -79,6 +82,7 @@ opacity: 0; transform: translateY(-50%); } + to { opacity: 1; transform: translateY(0); @@ -99,16 +103,6 @@ z-index: 1000; } -.end-quiz-modal-content { - background-color: #ffffff; - width: 90%; - max-width: 600px; - border-radius: 10px; - padding: 20px; - text-align: center; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); -} - .end-quiz-modal-button-title { font-size: 2rem; font-weight: bold; @@ -116,10 +110,6 @@ margin-bottom: 10px; } -.end-quiz-modal-button-text-container { - margin-bottom: 20px; -} - .end-quiz-modal-button-text { font-size: 1.2rem; color: #333; @@ -166,21 +156,28 @@ .end-quiz-modal-scores { display: flex; - flex-wrap: wrap; + /* flex-wrap: wrap; */ justify-content: center; - gap: 10px; - margin-bottom: 20px; + /* gap: 0.1vmin; */ + margin-bottom: 2vmin; + width: 100%; +} + +.end-quiz-modal-scores-container { + width: 100%; } .end-quiz-modal-scores .score-item { display: flex; + width: 5vw; + height: 20vh; flex-direction: column; align-items: center; font-size: 1rem; color: #333; } -.end-quiz-modal-scores .score-item .score-avatar { +/* .end-quiz-modal-scores .score-item .score-avatar { width: 50px; height: 50px; border-radius: 50%; @@ -192,7 +189,7 @@ font-size: 1.2rem; font-weight: bold; color: #007bff; -} +} */ .end-quiz-modal-stars { color: #ffd700; @@ -201,16 +198,39 @@ } .stars-image { - width: 80px; - height: auto; + height: 20vh; + aspect-ratio: 1 / 1; display: block; - margin: 0 auto 20px auto; + /* margin: 0 auto 20px auto; */ } .stars-image-reverse { - width: 80px; - height: auto; + height: 20vh; + aspect-ratio: 1 / 1; display: block; - margin: 20px auto 0 auto; + /* margin: 20px auto 0 auto; */ transform: rotate(90deg); } + +.player-list-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 2vmin; + margin-top: 20px; +} + +.player-list-item { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + margin: 5px 0; +} + +.avatar { + width: 90%; + aspect-ratio: 1 / 1; + border-radius: 25; +} \ No newline at end of file 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..671a4285b554653b5ae903c99f5d32850bc6e74f --- /dev/null +++ b/src/routes/multiplayer/endQuizModal/endQuizModalMulti.tsx @@ -0,0 +1,116 @@ +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"; +import { List } from "../../../components/list/list.tsx"; +import { useEffect, useState } from "react"; +import ListModel from "../../../models/ListModel.ts"; + +interface Props { + isOpen: boolean; + onClose: () => void; + users: UserEndQuiz[]; + questionCount: number; + score: number; +} + +export const EndQuizModalMulti = ({ isOpen, onClose, users, questionCount, score }: Props) => { + const [liste, setListe] = useState<ListModel[]>([]); + if (!isOpen) return null; + const navigate = useNavigate(); + + const handleBackToMenu = () => { + navigate("/multiplayer"); + }; + + // const renderUser = ({ item, index }: { item: UserEndQuiz; index: number }) => ( + // <div className={index === 0 ? "first-place score-item" : "score-item"} key={index}> + // <img + // src={require("../../../assets/ProfilBaseImage.png")} // Replace with actual profile picture if available + // className="avatar" + // /> + // <div className="infoContainer"> + // <p className={index === 0 ? "username username-firstplace" : "username"}> + // {item.username.toUpperCase()} + // </p> + // <p className="score"> + // {10 + index}/{questionCount} {/* Adjust score logic as needed */} + // </p> + // </div> + // </div> + // ); + + useEffect(() => { + formatliste(users); + }, []); + + + const formatliste = (users: UserEndQuiz[]) => { + const temp: ListModel[] = users.map((user, index) => ({ + id: user.username, + title: user.username, + content: `${user.score}/${questionCount}`, + })); + setListe(temp); + } + + const toIgnore = (index: string) => { + } + + 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}/{questionCount} ! + </p> + </div> + <div className="end-quiz-modal-scores-container"> + <div className="end-quiz-modal-scores"> + <img src="../../../../public/stars.png" alt="stars picture" className="stars-image" /> + { + users.map((user, index) => ( + index <= 2 ? ( + <div key={index} className="score-item"> + <img + src="../../../../public/ProfilBaseImage.png" // Replace with actual profile picture if available + className="avatar" + /> + <p>{user.username}</p> + <p>{user.score}/{questionCount}</p> + </div> + ) : null + )) + } + <img src="../../../../public/stars.png" alt="stars picture" className="stars-image-reverse" /> + </div> + <div className="end-quiz-modal-scores"> + <div className="player-list-container"> + {liste.map((item) => ( + <div className="player-list-item" key={item.id.toString()}> + <div className="player-list-info">{item.title}</div> + <div className="player-list-info">{item.content}</div> + </div> + ))} + </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..2c0bff8ec2640eecf6d5c307694f8409d58e3e01 --- /dev/null +++ b/src/routes/multiplayer/quiz/PlayingQuizMulti.tsx @@ -0,0 +1,94 @@ +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={isHost} setPlayingQuizState={setPlayingQuizState} playingQuizState={playingQuizState} OpenModalEndQuiz={setEndQuizModalIsOpen} setScore={setScore} /> + <StopQuizModal isOpen={stopQuizModalIsOpen} onClose={() => setStopQuizModalIsOpen(false)} /> + {!endQuizModalIsOpen ? null : <EndQuizModalMulti + isOpen={endQuizModalIsOpen} + onClose={null} + users={users} + questionCount={questionCount} + score={score} + /> + } + </> + )} + {/*{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/QuizBotMulti.tsx b/src/routes/multiplayer/quiz/quizBot/QuizBotMulti.tsx new file mode 100644 index 0000000000000000000000000000000000000000..836034ff951085bc2bb0a62f8de4c4301dc77231 --- /dev/null +++ b/src/routes/multiplayer/quiz/quizBot/QuizBotMulti.tsx @@ -0,0 +1,133 @@ +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 { 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"; +import HttpError from "../../../../services/HttpError.ts"; + +interface Props { + actualQuestion: Question; + isHost: boolean; + setPlayingQuizState: (playingQuizState: PlayingQuizState) => void; + playingQuizState: PlayingQuizState; + OpenModalEndQuiz: (bool: boolean) => void; + setScore: (score: number) => void; +} + + +const getStyleOfAnswerButton = ( + answerId: number, + selectedAnswerIndex: number | null, + correctAnswerId: number | null, + quizState: PlayingQuizState +): string => { + if (quizState === PlayingQuizState.LOADING) { + return "answer-button answer-button-loading"; + } + if (quizState === PlayingQuizState.ANSWERING) { + if (selectedAnswerIndex === null) return "answer-button"; + if (selectedAnswerIndex === answerId) return "answer-button answer-button-selected"; + } + 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 QuizBotMulti({ actualQuestion, isHost, playingQuizState, setPlayingQuizState, setScore }: Props) { + const [questionIsFinished, setQuestionIsFinished] = useState(false); + const [selectedAnswerId, setSelectedAnswerId] = useState<number | null>(null); + const [validationButtonIsDisabled, setValidationButtonIsDisabled] = useState(true); + const [correctAnswerId, setCorrectAnswerId] = useState<number | null>(null); + const { nextQuestion, answerQuestion, revealAnswer, getScore } = useMultiplayerService(); + const { eventSource } = useEventSourceContext(); + + 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(() => { + initSSE(); + }, []); + + const getCorrectAnswer = (answers: Answer[]): Answer => { + const correctAnswer = answers.find((answer) => answer.isCorrect); + if (!correctAnswer) throw new Error("No correct answer found"); + return correctAnswer; + }; + const onValidation = async () => { + console.log("onValidation"); + if(selectedAnswerId === null || actualQuestion === undefined) return; + setPlayingQuizState(PlayingQuizState.LOADING); + await answerQuestion(actualQuestion.id, selectedAnswerId); + + if(isHost) + { + await revealAnswer(); + } + const scoreFetched = await getScore(); + if(HttpError.isHttpError(scoreFetched)) + { + return; + } + setScore(scoreFetched); + + }; + + const onContinue = () => { + if(isHost) + { + nextQuestion(); + setPlayingQuizState(PlayingQuizState.ANSWERING); + setCorrectAnswerId(null); + setSelectedAnswerId(null); + } + }; + + const onAnsweredButtonClicked = (answerId: number) => { + if (playingQuizState === PlayingQuizState.SHOWING_RESULTS || playingQuizState === PlayingQuizState.LOADING) { + return; + } + setSelectedAnswerId(answerId); + }; + + return ( + <div className="quiz-bot-container"> + <div className="quiz-bot-button-container"> + {actualQuestion.answers.map((answer) => { + return ( + <> + {/*<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, playingQuizState)} + isDisabled={playingQuizState === PlayingQuizState.LOADING} + /> + </> + ); + })} + </div> + <div className="quiz-bot-validation-button-container"> + <ValidationButton + text={playingQuizState === PlayingQuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} + onClick={() => (playingQuizState === PlayingQuizState.ANSWERING ? onValidation() : onContinue())} + isDisabled={playingQuizState !== PlayingQuizState.ANSWERING && !isHost} + /> + </div> + </div> + ); +} diff --git a/src/routes/multiplayer/quiz/quizBot/quizBot.tsx b/src/routes/multiplayer/quiz/quizBot/quizBot.tsx deleted file mode 100644 index e11517eb20a8ae159f7ede0430249f48195ea54b..0000000000000000000000000000000000000000 --- a/src/routes/multiplayer/quiz/quizBot/quizBot.tsx +++ /dev/null @@ -1,120 +0,0 @@ -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"; - -interface Props { - question: any; -} - -enum QuizState { - ANSWERING = "Answering", - LOADING = "Loading", - SHOWING_RESULTS = "ShowingResults", -} - -const getStyleOfAnswerButton = ( - answerId: number, - selectedAnswerIndex: number | null, - correctAnswerId: number | null, - quizState: QuizState -): string => { - if (quizState === QuizState.LOADING) { - return "answer-button answer-button-loading"; - } - if (quizState === QuizState.ANSWERING) { - if (selectedAnswerIndex === null) return "answer-button"; - if (selectedAnswerIndex === answerId) return "answer-button answer-button-selected"; - } - if (quizState === QuizState.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) { - 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 { sendAnswer, recivedResults, recivedQuestion } = useMultiplayerService(); - - 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]); - - const getCorrectAnswer = (answers: Answer[]): Answer => { - const correctAnswer = answers.find((answer) => answer.isCorrect); - if (!correctAnswer) throw new Error("No correct answer found"); - 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; - } - setQuizState(QuizState.SHOWING_RESULTS); - const correctAnswerIdFetched = getCorrectAnswer(answers).id; - setCorrectAnswerId(correctAnswerIdFetched); - }; - - const onContinue = () => { - question = recivedQuestion(); - }; - - useEffect(() => { - if (selectedAnswerId !== null) { - setValidationButtonIsDisabled(false); - return; - } - setValidationButtonIsDisabled(true); - }, [selectedAnswerId]); - - const onAnsweredButtonClicked = (answerId: number) => { - if (quizState === QuizState.SHOWING_RESULTS || quizState === QuizState.LOADING) { - return; - } - setSelectedAnswerId(answerId); - }; - - return ( - <div className="quiz-bot-container"> - <div className="quiz-bot-button-container"> - {question.answers.map((answer) => { - return ( - <> - <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} - /> - </> - ); - })} - </div> - <div className="quiz-bot-validation-button-container"> - <ValidationButton - text={quizState === QuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} - onClick={() => (quizState === QuizState.ANSWERING ? onValidation() : onContinue())} - isDisabled={validationButtonIsDisabled} - /> - </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/profile/profile.css b/src/routes/profile/profile.css index 585c04b947e8d7570a22ff077690a0e5c8982dce..d6230a2e69e48a9a8ae1b0765853e4ce40609880 100644 --- a/src/routes/profile/profile.css +++ b/src/routes/profile/profile.css @@ -170,7 +170,7 @@ input { font-weight: semi-bold; } -img { +.profile_info img { width: 10vw; height: 10vw; border-radius: 50%; @@ -250,4 +250,4 @@ img { .stat-value { font-size: 0.8rem; } -} +} \ No newline at end of file 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/routes/root.tsx b/src/routes/root.tsx index 539c64bd4df2af398c8a02daace9083044733efa..6caf88f8c3f1ff7809770c7f8beb5c83c5a217ba 100644 --- a/src/routes/root.tsx +++ b/src/routes/root.tsx @@ -16,15 +16,23 @@ export default function Root() { > COMMUNITY </NavLink> - {/*<NavLink + <NavLink to="/multiplayer" className={location.pathname === "/multiplayer" ? "selected" : "buttonHeader"} > MULTIPLAYER - </NavLink>*/} + </NavLink> <NavLink to="/profile" className={location.pathname === "/profile" ? "selected" : "buttonHeader"}> PROFILE </NavLink> + <a + href="/app-release.apk" + download + className="buttonHeader" + style={{ marginLeft: "auto", textDecoration: "none" }} + > + DOWNLOAD APK + </a> </header> ) : null} <main className={location.pathname.includes("/quiz") ? "main-no-header" : "main-header"}> diff --git a/src/services/MultiplayerService.ts b/src/services/MultiplayerService.ts index a7a4947f0046f45e67e5ba62eb33327253dd3c5d..bb918bad8d6a902e1f8b57848f2ec31e788b816d 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> => {}; @@ -72,6 +160,25 @@ export const useMultiplayerService = () => { } }; + const getScore = async () : Promise<number | HttpError> => { + console.log("getScore"); + try { + const response = await axios.get(`${url}/party/score`, { + withCredentials: true, + }); + console.log("getScore done"); + return response.data.score; + } 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); + } + } + }; + return { createParty, getCategories, @@ -84,5 +191,10 @@ export const useMultiplayerService = () => { joinLobby, recivedPlayers, lobbyInfos, + generateRun, + revealAnswer, + answerQuestion, + nextQuestion, + getScore, }; };