diff --git a/client/src/components/Footer/Footer.tsx b/client/src/components/Footer/Footer.tsx index 331d8ec..db59651 100644 --- a/client/src/components/Footer/Footer.tsx +++ b/client/src/components/Footer/Footer.tsx @@ -1,21 +1,33 @@ import * as React from 'react'; -import './footer.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; // Add Bootstrap CSS import -type FooterProps = object; //empty object +type FooterProps = object; const Footer: React.FC = () => { return ( -
-
- Réalisé avec ❤ à Montréal par des finissant•e•s de l'ETS +
+ ); }; -export default Footer; +export default Footer; \ No newline at end of file diff --git a/client/src/components/Footer/footer.css b/client/src/components/Footer/footer.css deleted file mode 100644 index 907065c..0000000 --- a/client/src/components/Footer/footer.css +++ /dev/null @@ -1,23 +0,0 @@ -.footer { - flex-shrink: 0; - padding: 20px; - text-align: center; -} - -.footer-content { - margin-bottom: 10px; -} - -.footer-links a { - color: #333; - text-decoration: none; -} - -.footer-links a:hover { - text-decoration: underline; -} - -.divider { - margin: 0 10px; - color: #666; -} \ No newline at end of file diff --git a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx index 036f2d0..e6dea81 100644 --- a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx +++ b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx @@ -1,6 +1,7 @@ // GiftCheatSheet.tsx import React, { useState } from 'react'; -import './giftCheatSheet.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import FileCopyIcon from '@mui/icons-material/FileCopy'; const GiftCheatSheet: React.FC = () => { const [copySuccess, setCopySuccess] = useState(false); @@ -8,197 +9,262 @@ const GiftCheatSheet: React.FC = () => { const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text) .then(() => { - setCopySuccess(true); // Afficher le message de succès - console.log(copySuccess); - // Masquer le message de succès après quelques secondes + setCopySuccess(true); setTimeout(() => { setCopySuccess(false); - }, 3000); // 3 secondes + }, 3000); }) .catch((error) => { console.error('Erreur lors de la copie dans le presse-papiers : ', error); }); }; - const QuestionVraiFaux = "::Exemple de question vrai/faux:: \n 2+2 \\= 4 ? {T} //Utilisez les valeurs {T}, {F}, {TRUE} et {FALSE}."; const QuestionChoixMul = "::Ville capitale du Canada:: \nQuelle ville est la capitale du Canada? {\n~ Toronto\n~ Montréal\n= Ottawa #Rétroaction spécifique.\n} // Commentaire non visible (au besoin)"; const QuestionChoixMulMany = "::Villes canadiennes:: \n Quelles villes trouve-t-on au Canada? { \n~ %33.3% Montréal \n ~ %33.3% Ottawa \n ~ %33.3% Vancouver \n ~ %-100% New York \n ~ %-100% Paris \n#### Rétroaction globale de la question. \n} // Utilisez tilde (signe de vague) pour toutes les réponses. // On doit indiquer le pourcentage de chaque réponse."; - const QuestionCourte ="::Clé et porte:: \n Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n} // Permet de fournir plusieurs bonnes réponses. // Note: La casse n'est pas prise en compte."; - const QuestionNum ="::Question numérique avec marge:: \nQuel est un nombre de 1 à 5 ? {\n#3:2\n}\n \n// Plage mathématique spécifiée avec des points de fin d'intervalle. \n ::Question numérique avec plage:: \n Quel est un nombre de 1 à 5 ? {\n#1..5\n} \n\n// Réponses numériques multiples avec crédit partiel et commentaires.\n::Question numérique avec plusieurs réponses::\nQuand est né Ulysses S. Grant ? {\n# =1822:0 # Correct ! Crédit complet. \n=%50%1822:2 # Il est né en 1822. Demi-crédit pour être proche.\n}"; + const QuestionCourte = "::Clé et porte:: \n Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n} // Permet de fournir plusieurs bonnes réponses. // Note: La casse n'est pas prise en compte."; + const QuestionNum = "::Question numérique avec marge:: \nQuel est un nombre de 1 à 5 ? {\n#3:2\n}\n \n// Plage mathématique spécifiée avec des points de fin d'intervalle. \n ::Question numérique avec plage:: \n Quel est un nombre de 1 à 5 ? {\n#1..5\n} \n\n// Réponses numériques multiples avec crédit partiel et commentaires.\n::Question numérique avec plusieurs réponses::\nQuand est né Ulysses S. Grant ? {\n# =1822:0 # Correct ! Crédit complet. \n=%50%1822:2 # Il est né en 1822. Demi-crédit pour être proche.\n}"; + return ( -
-

Informations pratiques sur l'éditeur

- +
+ + {/* Add feedback alert at the top */} + {copySuccess && ( +
+ Texte copié dans le presse-papiers! + +
+ )} + +

Informations pratiques sur l'éditeur

+

L'éditeur utilise le format GIFT (General Import Format Template) créé pour la plateforme Moodle afin de générer les mini-tests. Ci-dessous vous pouvez retrouver la syntaxe pour chaque type de question : - -

-

1. Questions Vrai/Faux

-
-                    
-                        {QuestionVraiFaux}
-                    
+            

-
- -
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((section) => ( +
+ {section === 1 && ( + <> +

1. Questions Vrai/Faux

+
+                                
+                                    {QuestionVraiFaux}
+                                
+                            
+ + + )} -
-

2. Questions à choix multiple

-
-                    
-                        {
-                            QuestionChoixMul
-                        }
-                    
-                
- -
-
-

3. Questions à choix multiple avec plusieurs réponses

-
-                    
-                        {
-                            QuestionChoixMulMany
-                        }
-                    
-                
- -
+ {section === 2 && ( + <> +

2. Questions à choix multiple

+
+                                
+                                    {QuestionChoixMul}
+                                
+                            
+ + + )} -
-

4. Questions à réponse courte

-
-                    
-                        {QuestionCourte}
-                    
-                
- -
+ {section === 3 && ( + <> +

3. Questions à choix multiple avec plusieurs réponses

+
+                                
+                                    {QuestionChoixMulMany}
+                                
+                            
+ + + )} -
-

5. Questions numériques

-
-                    
-                        {
-                            QuestionNum
-                        }
-                                        
-                
- -
+ {section === 4 && ( + <> +

4. Questions à réponse courte

+
+                                
+                                    {QuestionCourte}
+                                
+                            
+ + + )} -
-

6. Paramètres optionnels

-
-                    
-                        {'::Titre:: '}
-                        
-                            {' // Ajoute un titre à une question'}
-                        
-                        
- {'# Feedback '} - - {' // Feedback pour UNE réponse'} - -
- {'// Commentaire '} - - {' // Commentaire non apparent'} - -
- {'#### Feedback général '} - - {' // Feedback général pour une question'} - -
- {'%50% '} - - {" // Poids d'une réponse (peut être négatif)"} - -
-
-
+ {section === 5 && ( + <> +

5. Questions numériques

+
+                                
+                                    {QuestionNum}
+                                
+                            
+ + + )} -
-

7. Caractères spéciaux

-

- Si vous souhaitez utiliser certains caractères spéciaux dans vos énoncés, - réponses ou feedback, vous devez «échapper» ces derniers en ajoutant un \ - devant: -

-
-                    
-                        {'\\~ \n\\= \n\\# \n\\{ \n\\} \n\\:'}
-                    
-                
-
+ {section === 6 && ( + <> +

6. Paramètres optionnels

+
+                                
+                                    {'::Titre:: '}
+                                    
+                                        {' // Ajoute un titre à une question'}
+                                    
+                                    
+ {'# Feedback '} + + {' // Feedback pour UNE réponse'} + +
+ {'// Commentaire '} + + {' // Commentaire non apparent'} + +
+ {'#### Feedback général '} + + {' // Feedback général pour une question'} + +
+ {'%50% '} + + {" // Poids d'une réponse (peut être négatif)"} + +
+
+ + )} -
-

8. LaTeX et Markdown

-

- Les formats LaTeX et Markdown sont supportés dans cette application. Vous devez cependant penser - à «échapper» les caractères spéciaux mentionnés plus haut. -

-

Exemple d'équation:

-
-                    {'$$x\\= \\frac\\{y^2\\}\\{4\\}$$'}
-                    {'\n$x\\= \\frac\\{y^2\\}\\{4\\}$'}
-                
-

Exemple de texte Markdown:

-
-                    {'[markdown]Grâce à la balise markdown, Il est possible d\'insérer du texte *italique*, **gras**, du `code` et bien plus.'}
-                
-
+ {section === 7 && ( + <> +

7. Caractères spéciaux

+

+ Si vous souhaitez utiliser certains caractères spéciaux dans vos énoncés, + réponses ou feedback, vous devez «échapper» ces derniers en ajoutant un \ + devant: +

+
+                                
+                                    {'\\~ \n\\= \n\\# \n\\{ \n\\} \n\\:'}
+                                
+                            
+ + )} -
-

9. Images

-

Il est possible d'insérer une image dans une question, une réponse (choix multiple) et dans une rétroaction. D'abord, le format de l'élément doit être [markdown]. Ensuite utilisez la syntaxe suivante :

-
-                    
-                        {'!['}
-                        {`text alternatif`}
-                        {']('}
-                        {`URL-de-l'image`}
-                        {' "'}
-                        {`texte de l'infobulle`}
-                        {'")'}
-                    
-                
-

Exemple d'une question Vrai/Faux avec l'image d'un chat:

-
-                    
-                        {'[markdown]Ceci est un chat: \n![Image de chat](https\\://www.example.com\\:8000/chat.jpg "Chat mignon")\n{T}'}
-                    
-                
-

Exemple d'une question à choix multiple avec l'image d'un chat dans une rétroaction :

-
-                    
-                        {`[markdown]Qui a initié le développement d'ÉvalueTonSavoir {=ÉTS#OUI! ![](https\\://www.etsmtl.ca/assets/img/ets.svg "\\=50px")
-                        ~EPFL#Non...}`}
-                    
-                
-

Note : les images étant spécifiées avec la syntaxe Markdown dans GIFT, on doit échapper les caractères spéciales (:) dans l'URL de l'image.

-

- Attention: l'ancienne fonctionnalité avec les balises {''} n'est plus - supportée. -

+ {section === 8 && ( + <> +

8. LaTeX et Markdown

+

+ Les formats LaTeX et Markdown sont supportés dans cette application. Vous devez cependant penser + à «échapper» les caractères spéciaux mentionnés plus haut. +

+

Exemple d'équation:

+
+                                {'$$x\\= \\frac\\{y^2\\}\\{4\\}$$'}
+                                {'\n$x\\= \\frac\\{y^2\\}\\{4\\}$'}
+                            
+

Exemple de texte Markdown:

+
+                                {'[markdown]Grâce à la balise markdown, Il est possible d\'insérer du texte *italique*, **gras**, du `code` et bien plus.'}
+                            
+ + )} + + {section === 9 && ( + <> +

9. Images

+

Il est possible d'insérer une image dans une question, une réponse (choix multiple) et dans une rétroaction. D'abord, le format de l'élément doit être [markdown]. Ensuite utilisez la syntaxe suivante :

+
+                                
+                                    {'!['}
+                                    {`text alternatif`}
+                                    {']('}
+                                    {`URL-de-l'image`}
+                                    {' "'}
+                                    {`texte de l'infobulle`}
+                                    {'")'}
+                                
+                            
+

Exemple d'une question Vrai/Faux avec l'image d'un chat:

+
+                                
+                                    {'[markdown]Ceci est un chat: \n![Image de chat](https\\://www.example.com\\:8000/chat.jpg "Chat mignon")\n{T}'}
+                                
+                            
+

Exemple d'une question à choix multiple avec l'image d'un chat dans une rétroaction :

+
+                                
+                                    {`[markdown]Qui a initié le développement d'ÉvalueTonSavoir {=ÉTS#OUI! ![](https\\://www.etsmtl.ca/assets/img/ets.svg "\\=50px")
+                                    ~EPFL#Non...}`}
+                                
+                            
+

Note : les images étant spécifiées avec la syntaxe Markdown dans GIFT, on doit échapper les caractères spéciales (:) dans l'URL de l'image.

+

+ Attention: l'ancienne fonctionnalité avec les balises {''} n'est plus + supportée. +

+ + )} + + {section === 10 && ( + <> +

10. Informations supplémentaires

+

+ GIFT supporte d'autres formats de questions que nous ne gérons pas sur cette + application. +

+

Vous pouvez retrouver la Documentation de GIFT (en anglais):

+ + Documentation de GIFT + + + )}
-
-

10. Informations supplémentaires

-

- GIFT supporte d'autres formats de questions que nous ne gérons pas sur cette - application. -

-

Vous pouvez retrouver la Documentation de GIFT (en anglais):

- - Documentation de GIFT - -
+ ))}
); -}; +}; -export default GiftCheatSheet; +export default GiftCheatSheet; \ No newline at end of file diff --git a/client/src/components/GIFTCheatSheet/giftCheatSheet.css b/client/src/components/GIFTCheatSheet/giftCheatSheet.css deleted file mode 100644 index 5fc7777..0000000 --- a/client/src/components/GIFTCheatSheet/giftCheatSheet.css +++ /dev/null @@ -1,37 +0,0 @@ -.gift-cheat-sheet { - /* width: 30vw; */ - height: 100%; -} -.subtitle { - color: #3a3a3a; - margin-bottom: 2vh; -} - -.question-type { - margin-bottom: 20; -} -.question-code-block, -.code-comment { - white-space: pre-line; -} - -code { - font-family: 'Courier New', Courier, monospace; - padding: 2px 4px; - border-radius: 4px; -} - -pre { - background-color: #ffffffbd; - padding: 10px; - border: 1px solid #000; - border-radius: 4px; - overflow-x: auto; -} -.code-comment { - color: green; -} - -.question-type h4 { - margin-top: 20px; -} diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx index 016d23e..ab2fef2 100644 --- a/client/src/components/Header/Header.tsx +++ b/client/src/components/Header/Header.tsx @@ -1,7 +1,8 @@ import { Link, useNavigate } from 'react-router-dom'; import * as React from 'react'; -import './header.css'; import { Button } from '@mui/material'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; interface HeaderProps { isLoggedIn: boolean; @@ -12,36 +13,38 @@ const Header: React.FC = ({ isLoggedIn, handleLogout }) => { const navigate = useNavigate(); return ( -
+
Logo navigate('/')} /> - {isLoggedIn && ( - - )} - - {!isLoggedIn && ( -
- - +
+ {isLoggedIn ? ( + + ) : ( + + -
- )} -
+ )} +
+ ); }; -export default Header; +export default Header; \ No newline at end of file diff --git a/client/src/components/Header/header.css b/client/src/components/Header/header.css deleted file mode 100644 index 379a60d..0000000 --- a/client/src/components/Header/header.css +++ /dev/null @@ -1,14 +0,0 @@ - -.header { - flex-shrink: 0; - padding: 15px; - overflow: hidden; - - display: flex; - justify-content: space-between; - align-items: center; -} - -.header img { - cursor: pointer; -} \ No newline at end of file diff --git a/client/src/components/ImportModal/ImportModal.tsx b/client/src/components/ImportModal/ImportModal.tsx index 41ab6ca..c8bac59 100644 --- a/client/src/components/ImportModal/ImportModal.tsx +++ b/client/src/components/ImportModal/ImportModal.tsx @@ -1,6 +1,4 @@ import React, { useState, DragEvent, useRef, useEffect } from 'react'; -import './importModal.css'; - import { Button, Dialog, @@ -12,7 +10,7 @@ import { } from '@mui/material'; import { Clear, Download } from '@mui/icons-material'; import ApiService from '../../services/ApiService'; - +import 'bootstrap/dist/css/bootstrap.min.css'; type DroppedFile = { id: number; @@ -28,7 +26,7 @@ interface Props { selectedFolder: string; } -const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, selectedFolder }) => { +const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, selectedFolder }) => { const [droppedFiles, setDroppedFiles] = useState([]); const fileInputRef = useRef(null); @@ -48,7 +46,6 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel const handleDrop = (e: DragEvent) => { e.preventDefault(); - const files = e.dataTransfer.files; handleFiles(files); }; @@ -66,8 +63,6 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel setDroppedFiles((prevFiles) => [...prevFiles, ...newDroppedFiles]); }; - - const handleOnSave = async () => { const storedQuizzes = JSON.parse(localStorage.getItem('quizzes') || '[]'); const quizzesToImportPromises = droppedFiles.map((droppedFile) => { @@ -77,23 +72,16 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel reader.onload = async (event) => { if (event.target && event.target.result) { const fileContent = event.target.result as string; - //console.log(fileContent); if (fileContent.trim() === '') { resolve(null); } - const questions = fileContent.split(/}/) + const questions = fileContent.split(/}/) .map(question => { - // Remove trailing and leading spaces - - return question.trim()+"}"; + return question.trim() + "}"; }) - .filter(question => question.trim() !== '').slice(0, -1); // Filter out lines with only whitespace characters - - try { - // const folders = await ApiService.getUserFolders(); + .filter(question => question.trim() !== '').slice(0, -1); - // Assuming you want to use the first folder - // const selectedFolder = folders.length > 0 ? folders[0]._id : null; + try { await ApiService.createQuiz(droppedFile.name.slice(0, -4) || 'Untitled quiz', questions, selectedFolder); resolve('success'); } catch (error) { @@ -105,8 +93,6 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel }); }); - - Promise.all(quizzesToImportPromises).then((quizzesToImport) => { const verifiedQuizzesToImport = quizzesToImport.filter((quiz) => { return quiz !== null; @@ -118,17 +104,10 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel setDroppedFiles([]); handleOnImport(); handleOnClose(); - window.location.reload(); }); }; - - - - - - const handleRemoveFile = (id: number) => { setDroppedFiles((prevFiles) => prevFiles.filter((file) => file.id !== id)); }; @@ -158,14 +137,15 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel {'Importation de quiz'} -
- +
+ Déposer des fichiers ici ou
cliquez pour ouvrir l'explorateur des fichiers @@ -175,7 +155,7 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel {droppedFiles.map((file) => ( -
+
{file.icon} {file.name} = ({ handleOnClose, handleOnImport, open, sel ); }; -export default DragAndDrop; +export default DragAndDrop; \ No newline at end of file diff --git a/client/src/components/ImportModal/importModal.css b/client/src/components/ImportModal/importModal.css deleted file mode 100644 index 0bcca96..0000000 --- a/client/src/components/ImportModal/importModal.css +++ /dev/null @@ -1,20 +0,0 @@ -.import-container { - border-style: dashed; - border-width: thin; - border-color: rgba(128, 128, 128, 0.5); - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - height: 20vh; - cursor: pointer; - box-sizing: border-box; - margin: 0 20px 0 20px; -} - -.file-container { - gap: 10px; - display: flex; - align-items: center; - padding: 4px; -} diff --git a/client/src/components/LoadingCircle/LoadingCircle.tsx b/client/src/components/LoadingCircle/LoadingCircle.tsx index bb0b56a..c7b9809 100644 --- a/client/src/components/LoadingCircle/LoadingCircle.tsx +++ b/client/src/components/LoadingCircle/LoadingCircle.tsx @@ -1,6 +1,6 @@ import { CircularProgress } from '@mui/material'; import React from 'react'; -import './loadingCircle.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; interface Props { text: string; @@ -8,11 +8,11 @@ interface Props { const LoadingCircle: React.FC = ({ text }) => { return ( -
-
{text}
+
+
{text}
); }; -export default LoadingCircle; +export default LoadingCircle; \ No newline at end of file diff --git a/client/src/components/LoadingCircle/loadingCircle.css b/client/src/components/LoadingCircle/loadingCircle.css deleted file mode 100644 index 8cedca3..0000000 --- a/client/src/components/LoadingCircle/loadingCircle.css +++ /dev/null @@ -1,5 +0,0 @@ -.loading-circle { - display: flex; - flex-direction: column; - align-items: center; -} diff --git a/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx b/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx index 192c0b2..6a976e8 100644 --- a/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx +++ b/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx @@ -1,7 +1,6 @@ // StudentModeQuiz.tsx import React, { useEffect, useState } from 'react'; import QuestionComponent from '../QuestionsDisplay/QuestionDisplay'; -import '../../pages/Student/JoinRoom/joinRoom.css'; import { QuestionType } from '../../Types/QuestionType'; import { Button } from '@mui/material'; //import QuestionNavigation from '../QuestionNavigation/QuestionNavigation'; diff --git a/client/src/components/StudentWaitPage/StudentWaitPage.tsx b/client/src/components/StudentWaitPage/StudentWaitPage.tsx index c5de4f2..e09d16f 100644 --- a/client/src/components/StudentWaitPage/StudentWaitPage.tsx +++ b/client/src/components/StudentWaitPage/StudentWaitPage.tsx @@ -1,10 +1,9 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Box, Button, Chip } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; import { PlayArrow } from '@mui/icons-material'; import LaunchQuizDialog from '../LaunchQuizDialog/LaunchQuizDialog'; -import { useState } from 'react'; -import './studentWaitPage.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; interface Props { students: StudentType[]; @@ -20,31 +19,31 @@ const StudentWaitPage: React.FC = ({ students, launchQuiz, setQuizMode }) }; return ( -
-
+
+
+ Lancer +
-
- +
- {students.map((student, index) => ( - + ))} - -
= ({ students, launchQuiz, setQuizMode }) launchQuiz={launchQuiz} setQuizMode={setQuizMode} /> -
); }; -export default StudentWaitPage; +export default StudentWaitPage; \ No newline at end of file diff --git a/client/src/components/StudentWaitPage/studentWaitPage.css b/client/src/components/StudentWaitPage/studentWaitPage.css deleted file mode 100644 index 6ae1f3b..0000000 --- a/client/src/components/StudentWaitPage/studentWaitPage.css +++ /dev/null @@ -1,23 +0,0 @@ -.wait { - width: 100%; - - display: flex; - flex-direction: column; -} - -.wait .button { - padding: 10px; - display: flex; - - justify-content: center; - align-items: center; -} - -.wait .students { - width: 100%; - - padding: 10px; - box-sizing: border-box; - - overflow: auto; -} \ No newline at end of file diff --git a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx index 8925c09..0103fbb 100644 --- a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx +++ b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx @@ -1,7 +1,6 @@ // TeacherModeQuiz.tsx import React, { useEffect, useState } from 'react'; import QuestionComponent from '../QuestionsDisplay/QuestionDisplay'; -import '../../pages/Student/JoinRoom/joinRoom.css'; import { QuestionType } from '../../Types/QuestionType'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx index a5ee1ff..d450f8d 100644 --- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx +++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx @@ -1,21 +1,15 @@ import React, { useEffect, useState } from 'react'; - import { Socket } from 'socket.io-client'; import { ENV_VARIABLES } from 'src/constants'; - import StudentModeQuiz from 'src/components/StudentModeQuiz/StudentModeQuiz'; import TeacherModeQuiz from 'src/components/TeacherModeQuiz/TeacherModeQuiz'; import webSocketService, { AnswerSubmissionToBackendType } from '../../../services/WebsocketService'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; - -import './joinRoom.css'; import { QuestionType } from '../../../Types/QuestionType'; -import { TextField } from '@mui/material'; -import LoadingButton from '@mui/lab/LoadingButton'; - -import LoginContainer from 'src/components/LoginContainer/LoginContainer' - -import ApiService from '../../../services/ApiService' +import { TextField, Button, CircularProgress } from '@mui/material'; +import LoginContainer from 'src/components/LoginContainer/LoginContainer'; +import ApiService from '../../../services/ApiService'; +import 'bootstrap/dist/css/bootstrap.min.css'; export type AnswerType = Array; @@ -39,69 +33,63 @@ const JoinRoom: React.FC = () => { }, []); useEffect(() => { - console.log(`JoinRoom: useEffect: questions: ${JSON.stringify(questions)}`); setAnswers(questions ? Array(questions.length).fill({} as AnswerSubmissionToBackendType) : []); }, [questions]); - const handleCreateSocket = () => { - console.log(`JoinRoom: handleCreateSocket: ${ENV_VARIABLES.VITE_BACKEND_URL}`); const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); - socket.on('join-success', (roomJoinedName) => { + socket.on('join-success', () => { setIsWaitingForTeacher(true); setIsConnecting(false); - console.log(`on(join-success): Successfully joined the room ${roomJoinedName}`); }); + socket.on('next-question', (question: QuestionType) => { - console.log('JoinRoom: on(next-question): Received next-question:', question); setQuizMode('teacher'); setIsWaitingForTeacher(false); setQuestion(question); }); + socket.on('launch-teacher-mode', (questions: QuestionType[]) => { - console.log('on(launch-teacher-mode): Received launch-teacher-mode:', questions); setQuizMode('teacher'); setIsWaitingForTeacher(true); - setQuestions([]); // clear out from last time (in case quiz is repeated) + setQuestions([]); setQuestions(questions); - // wait for next-question }); - socket.on('launch-student-mode', (questions: QuestionType[]) => { - console.log('on(launch-student-mode): Received launch-student-mode:', questions); + socket.on('launch-student-mode', (questions: QuestionType[]) => { setQuizMode('student'); setIsWaitingForTeacher(false); - setQuestions([]); // clear out from last time (in case quiz is repeated) + setQuestions([]); setQuestions(questions); setQuestion(questions[0]); }); + socket.on('end-quiz', () => { disconnect(); }); + socket.on('join-failure', (message) => { - console.log('Failed to join the room.'); setConnectionError(`Erreur de connexion : ${message}`); setIsConnecting(false); }); + socket.on('connect_error', (error) => { switch (error.message) { case 'timeout': - setConnectionError("JoinRoom: timeout: Le serveur n'est pas disponible"); + setConnectionError("Le serveur n'est pas disponible"); break; case 'websocket error': - setConnectionError("JoinRoom: websocket error: Le serveur n'est pas disponible"); + setConnectionError("Le serveur n'est pas disponible"); break; } setIsConnecting(false); - console.log('Connection Error:', error.message); }); setSocket(socket); }; const disconnect = () => { -// localStorage.clear(); webSocketService.disconnect(); setSocket(null); setQuestion(undefined); @@ -120,28 +108,22 @@ const JoinRoom: React.FC = () => { } if (username && roomName) { - console.log(`Tentative de rejoindre : ${roomName}, utilisateur : ${username}`); - webSocketService.joinRoom(roomName, username); } }; const handleOnSubmitAnswer = (answer: AnswerType, idQuestion: number) => { - console.info(`JoinRoom: handleOnSubmitAnswer: answer: ${answer}, idQuestion: ${idQuestion}`); const answerData: AnswerSubmissionToBackendType = { roomName: roomName, answer: answer, username: username, idQuestion: idQuestion }; - // localStorage.setItem(`Answer${idQuestion}`, JSON.stringify(answer)); setAnswers((prevAnswers) => { - console.log(`JoinRoom: handleOnSubmitAnswer: prevAnswers: ${JSON.stringify(prevAnswers)}`); - const newAnswers = [...prevAnswers]; // Create a copy of the previous answers array - newAnswers[idQuestion - 1] = answerData; // Update the specific answer - return newAnswers; // Return the new array + const newAnswers = [...prevAnswers]; + newAnswers[idQuestion - 1] = answerData; + return newAnswers; }); - console.log(`JoinRoom: handleOnSubmitAnswer: answers: ${JSON.stringify(answers)}`); webSocketService.submitAnswer(answerData); }; @@ -153,22 +135,20 @@ const JoinRoom: React.FC = () => { if (isWaitingForTeacher) { return ( -
-
- +
+
-
-
Salle: {roomName}
-
+
+

Salle: {roomName}

+

En attente que le professeur lance le questionnaire... -

+

-
- +
{/* Spacer for balance */}
); @@ -197,44 +177,42 @@ const JoinRoom: React.FC = () => { ); default: return ( - - + setRoomName(e.target.value.toUpperCase())} placeholder="Nom de la salle" - sx={{ marginBottom: '1rem' }} - fullWidth={true} + fullWidth onKeyDown={handleReturnKey} /> setUsername(e.target.value)} placeholder="Nom d'utilisateur" - sx={{ marginBottom: '1rem' }} - fullWidth={true} + fullWidth onKeyDown={handleReturnKey} /> - Rejoindre - + className="w-100" + onClick={handleSocket} + disabled={!username || !roomName || isConnecting} + startIcon={isConnecting ? : null} + > + Rejoindre + ); } }; -export default JoinRoom; +export default JoinRoom; \ No newline at end of file diff --git a/client/src/pages/Student/JoinRoom/joinRoom.css b/client/src/pages/Student/JoinRoom/joinRoom.css deleted file mode 100644 index 5cb6b01..0000000 --- a/client/src/pages/Student/JoinRoom/joinRoom.css +++ /dev/null @@ -1,50 +0,0 @@ - - -/* .join-room-container { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - height: 85%; -} - -.waiting-text { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 85%; - text-align: center; -} - -.login-container { - display: flex; - flex-direction: column; - align-items: center; - margin: 2rem 4rem 2rem 4rem; - width: 25vw; -} - -.login-avatar { - margin-bottom: 2rem; -} -.question-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; -} - -.question-component-container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -@media only screen and (max-device-width: 768px) { - .login-container { - width: inherit; - } -} */ diff --git a/client/src/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx index cdae619..98b69f2 100644 --- a/client/src/pages/Teacher/Dashboard/Dashboard.tsx +++ b/client/src/pages/Teacher/Dashboard/Dashboard.tsx @@ -1,19 +1,12 @@ -// Dashboard.tsx import { useNavigate } from 'react-router-dom'; import React, { useState, useEffect, useMemo } from 'react'; import { parse } from 'gift-pegjs'; - import Template from 'src/components/GiftTemplate/templates'; import { QuizType } from '../../../Types/QuizType'; import { FolderType } from '../../../Types/FolderType'; -// import { QuestionService } from '../../../services/QuestionService'; import ApiService from '../../../services/ApiService'; - -import './dashboard.css'; import ImportModal from 'src/components/ImportModal/ImportModal'; -//import axios from 'axios'; import { RoomType } from 'src/Types/RoomType'; -// import { useRooms } from '../ManageRoom/RoomContext'; import { Dialog, DialogActions, @@ -40,17 +33,31 @@ import { ContentCopy, Edit, Share - // DriveFileMove } from '@mui/icons-material'; +import 'bootstrap/dist/css/bootstrap.min.css'; -// Create a custom-styled Card component -const CustomCard = styled(Card)({ - overflow: 'visible', // Override the overflow property +const CustomCard = styled(Card)(({ theme }) => ({ + overflow: 'visible', position: 'relative', - margin: '40px 0 20px 0', // Add top margin to make space for the tab + margin: '40px 0 20px 0', borderRadius: '8px', - paddingTop: '20px' // Ensure content inside the card doesn't overlap with the tab -}); + paddingTop: '20px', + border: `2px solid ${theme.palette.divider}`, + '& .folder-tab': { + position: 'absolute', + top: '-33px', + left: '9px', + padding: '5px 10px', + borderRadius: '8px 8px 0 0', + fontWeight: 'bold', + whiteSpace: 'nowrap', + display: 'inline-block', + border: `2px solid ${theme.palette.divider}`, + borderBottom: 'none', + backgroundColor: theme.palette.background.paper, + color: theme.palette.primary.main + } +})); const Dashboard: React.FC = () => { const navigate = useNavigate(); @@ -58,19 +65,14 @@ const Dashboard: React.FC = () => { const [searchTerm, setSearchTerm] = useState(''); const [showImportModal, setShowImportModal] = useState(false); const [folders, setFolders] = useState([]); - const [selectedFolderId, setSelectedFolderId] = useState(''); // Selected folder + const [selectedFolderId, setSelectedFolderId] = useState(''); const [rooms, setRooms] = useState([]); const [openAddRoomDialog, setOpenAddRoomDialog] = useState(false); const [newRoomTitle, setNewRoomTitle] = useState(''); - // const { selectedRoom, selectRoom, createRoom } = useRooms(); - const [selectedRoom, selectRoom] = useState(); // menu + const [selectedRoom, selectRoom] = useState(); const [errorMessage, setErrorMessage] = useState(''); const [showErrorDialog, setShowErrorDialog] = useState(false); - // Filter quizzes based on search term - // const filteredQuizzes = quizzes.filter(quiz => - // quiz.title.toLowerCase().includes(searchTerm.toLowerCase()) - // ); const filteredQuizzes = useMemo(() => { return quizzes.filter( (quiz) => @@ -78,7 +80,6 @@ const Dashboard: React.FC = () => { ); }, [quizzes, searchTerm]); - // Group quizzes by folder const quizzesByFolder = filteredQuizzes.reduce((acc, quiz) => { if (!acc[quiz.folderName]) { acc[quiz.folderName] = []; @@ -90,28 +91,18 @@ const Dashboard: React.FC = () => { useEffect(() => { const fetchData = async () => { const isLoggedIn = await ApiService.isLoggedIn(); - console.log(`Dashboard: isLoggedIn: ${isLoggedIn}`); if (!isLoggedIn) { navigate('/teacher/login'); return; } else { const userRooms = await ApiService.getUserRooms(); setRooms(userRooms as RoomType[]); - const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); } }; - fetchData(); - }, []); - - useEffect(() => { - if (rooms.length > 0 && !selectedRoom) { - selectRoom(rooms[rooms.length - 1]); - localStorage.setItem('selectedRoomId', rooms[rooms.length - 1]._id); - } - }, [rooms, selectedRoom]); + }, [navigate]); const handleSelectRoom = (event: React.ChangeEvent) => { if (event.target.value === 'add-room') { @@ -424,31 +415,38 @@ const Dashboard: React.FC = () => { }; return ( -
-
Tableau de bord
- -
- - +
+

Tableau de bord

+ {/* Room Selection */} +
+
+
+ + +
+
{selectedRoom && ( -
+

Salle sélectionnée: {selectedRoom.title}

)} + {/* Dialogs */} setOpenAddRoomDialog(false)}> Créer une nouvelle salle @@ -463,6 +461,7 @@ const Dashboard: React.FC = () => { + setShowErrorDialog(false)}> Erreur @@ -473,193 +472,189 @@ const Dashboard: React.FC = () => { -
- - - - - - ) - }} - /> + {/* Search Bar */} +
+
+ + + + + + ) + }} + /> +
-
-
+ {/* Folder Selection and Actions */} +
+
- - + {folders.map((folder: FolderType) => ( ))}
- -
- - - {' '} - {' '} - - - - -
- - {' '} - {' '} - -
-
- - -
- - {' '} - {' '} - -
-
- - -
- - {' '} - {' '} - -
-
+
+
+ + + + + + + + + + + + + + + + + + + + +
-
- - - + {/* Add Quiz and Import Buttons */} +
+
+ +
+
+ +
-
+ + {/* Quiz List */} +
{Object.keys(quizzesByFolder).map((folderName) => ( - -
{folderName}
- - {quizzesByFolder[folderName].map((quiz: QuizType) => ( -
-
- -
- -
-
+ +
+
+ + downloadTxtFile(quiz)} + className="border" + > + + + + + handleEditQuiz(quiz)} + className="border" + > + + + + + handleDuplicateQuiz(quiz)} + className="border" + > + + + + + handleRemoveQuiz(quiz)} + className="border" + > + + + + + handleShareQuiz(quiz)} + className="border" + > + + + +
- -
- - downloadTxtFile(quiz)} - > - {' '} - {' '} - - - - - handleEditQuiz(quiz)} - > - {' '} - {' '} - - - - - handleDuplicateQuiz(quiz)} - > - {' '} - {' '} - - - - - handleRemoveQuiz(quiz)} - > - {' '} - {' '} - - - - - handleShareQuiz(quiz)} - > - {' '} - {' '} - - -
-
- ))} - - + ))} + + +
))}
+ setShowImportModal(false)} @@ -670,11 +665,13 @@ const Dashboard: React.FC = () => { ); }; -export default Dashboard; +// Helper function function addFolderTitleToQuizzes(folderQuizzes: string | QuizType[], folderName: string) { - if (Array.isArray(folderQuizzes)) + if (Array.isArray(folderQuizzes)) { folderQuizzes.forEach((quiz) => { quiz.folderName = folderName; - console.log(`quiz: ${quiz.title} folder: ${quiz.folderName}`); }); + } } + +export default Dashboard; \ No newline at end of file diff --git a/client/src/pages/Teacher/Dashboard/dashboard.css b/client/src/pages/Teacher/Dashboard/dashboard.css deleted file mode 100644 index a17f46a..0000000 --- a/client/src/pages/Teacher/Dashboard/dashboard.css +++ /dev/null @@ -1,119 +0,0 @@ -.dashboard { - display: flex; - flex-direction: column; - max-width: 100%; - gap: 30px; - -} - -.dashboard .folder { - display: flex; - flex-direction: row; -} - -.dashboard .folder .select { - flex-grow: 8; - - display: flex; - align-items: center; -} - -/* Select the selector to make it 100% width */ -div:has(> #select-folder) { - width: 100%; -} - -.dashboard .folder .actions { - flex-shrink: 0; - display: flex; - flex-direction: row; - align-items: center; -} - -.dashboard .ajouter { - display: flex; - flex-direction: row; - gap: 10px; -} - -.dashboard .ajouter button:first-child { - width: 100%; -} - -.dashboard .list { - display: flex; - flex-direction: column; -} - -.dashboard .list .quiz { - display: flex; - flex-direction: row; - - margin-bottom: 10px; - box-sizing: content-box; -} - -.dashboard .list .quiz .title { - flex-grow: 8; - - display: flex; - align-items: center; - - /* reset title css */ - font-size: large; - margin: 0; - font-weight: 100; - overflow: hidden; -} -.dashboard .list .quiz .title button { - overflow: hidden; - white-space: nowrap; - display: block; - text-overflow: ellipsis; -} - -.dashboard .list .quiz .actions { - flex-shrink: 0; - display: flex; - flex-direction: row; - align-items: center; -} - -.dashboard .list .quiz .actions { - flex-shrink: 0; - display: flex; - flex-direction: row; - align-items: center; -} - -.folder-card { - position: relative; - /* margin: 40px 0 20px 0; /* Add top margin to make space for the tab */ - border-radius: 8px; - color: #f9f9f9; - --outline-color: #e1e1e1; - border: 2px solid var(--outline-color); -} - -.folder-tab { - position: absolute; - top: -33px; - left: 9px; - padding: 5px 10px; - border-radius: 8px 8px 0 0; - font-weight: bold; - white-space: nowrap; /* Prevent text from wrapping */ - display: inline-block; /* Ensure the tab width is based on content */ - border: 2px solid var(--outline-color); - border-bottom-style: none; - background-color: white; /* Optional: background color to match the card */ - color: #3f51b5; /* Text color to match the outline */ -} - -/* .folder-card:nth-child(odd) { - background-color: #f9f9f9; -} - -.folder-card:nth-child(even) { - background-color: #e0e0e0; -} */ diff --git a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx index 89f822a..f0dd150 100644 --- a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx +++ b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx @@ -1,22 +1,16 @@ -// EditorQuiz.tsx -import React, { useState, useEffect, useRef, CSSProperties } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; - import { FolderType } from '../../../Types/FolderType'; - import Editor from 'src/components/Editor/Editor'; import GiftCheatSheet from 'src/components/GIFTCheatSheet/GiftCheatSheet'; import GIFTTemplatePreview from 'src/components/GiftTemplate/GIFTTemplatePreview'; - import { QuizType } from '../../../Types/QuizType'; - -import './editorQuiz.css'; import { Button, TextField, NativeSelect, Divider, Dialog, DialogTitle, DialogActions, DialogContent } from '@mui/material'; import ReturnButton from 'src/components/ReturnButton/ReturnButton'; - import ApiService from '../../../services/ApiService'; import { escapeForGIFT } from '../../../utils/giftUtils'; import { Upload } from '@mui/icons-material'; +import 'bootstrap/dist/css/bootstrap.min.css'; interface EditQuizParams { id: string; @@ -27,7 +21,6 @@ const QuizForm: React.FC = () => { const [quizTitle, setQuizTitle] = useState(''); const [selectedFolder, setSelectedFolder] = useState(''); const [filteredValue, setFilteredValue] = useState([]); - const { id } = useParams(); const [value, setValue] = useState(''); const [isNewQuiz, setNewQuiz] = useState(false); @@ -35,9 +28,6 @@ const QuizForm: React.FC = () => { const navigate = useNavigate(); const [folders, setFolders] = useState([]); const [imageLinks, setImageLinks] = useState([]); - const handleSelectFolder = (event: React.ChangeEvent) => { - setSelectedFolder(event.target.value); - }; const fileInputRef = useRef(null); const [dialogOpen, setDialogOpen] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false); @@ -48,25 +38,15 @@ const QuizForm: React.FC = () => { useEffect(() => { const handleScroll = () => { - if (window.scrollY > 300) { - setShowScrollButton(true); - } else { - setShowScrollButton(false); - } + setShowScrollButton(window.scrollY > 300); }; - window.addEventListener('scroll', handleScroll); - return () => { - window.removeEventListener('scroll', handleScroll); - }; + return () => window.removeEventListener('scroll', handleScroll); }, []); const scrollToImagesSection = (event: { preventDefault: () => void; }) => { event.preventDefault(); - const section = document.getElementById('images-section'); - if (section) { - section.scrollIntoView({ behavior: 'smooth' }); - } + document.getElementById('images-section')?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { @@ -74,7 +54,6 @@ const QuizForm: React.FC = () => { const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); }; - fetchData(); }, []); @@ -87,116 +66,83 @@ const QuizForm: React.FC = () => { } const quiz = await ApiService.getQuiz(id) as QuizType; - if (!quiz) { - window.alert(`Une erreur est survenue.\n Le quiz ${id} n'a pas été trouvé\nVeuillez réessayer plus tard`) - console.error('Quiz not found for id:', id); + window.alert(`Une erreur est survenue.\n Le quiz ${id} n'a pas été trouvé\nVeuillez réessayer plus tard`); navigate('/teacher/dashboard'); return; } - setQuiz(quiz as QuizType); - const { title, content, folderId } = quiz; - - setQuizTitle(title); - setSelectedFolder(folderId); - setFilteredValue(content); + setQuiz(quiz); + setQuizTitle(quiz.title); + setSelectedFolder(quiz.folderId); + setFilteredValue(quiz.content); setValue(quiz.content.join('\n\n')); - } catch (error) { - window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) - console.error('Error fetching quiz:', error); + window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`); navigate('/teacher/dashboard'); } }; - fetchData(); - }, [id]); + }, [id, navigate]); function handleUpdatePreview(value: string) { if (value !== '') { setValue(value); + const linesArray = value.split(/\n{2,}/); + if (linesArray[0] === '') linesArray.shift(); + if (linesArray[linesArray.length - 1] === '') linesArray.pop(); + setFilteredValue(linesArray); } - - // split value when there is at least one blank line - const linesArray = value.split(/\n{2,}/); - - // if the first item in linesArray is blank, remove it - if (linesArray[0] === '') linesArray.shift(); - - if (linesArray[linesArray.length - 1] === '') linesArray.pop(); - - setFilteredValue(linesArray); } const handleQuizTitleChange = (event: React.ChangeEvent) => { setQuizTitle(event.target.value); }; + const handleSelectFolder = (event: React.ChangeEvent) => { + setSelectedFolder(event.target.value); + }; + const handleQuizSave = async () => { try { - // check if everything is there - if (quizTitle == '') { + if (quizTitle === '') { alert("Veuillez choisir un titre"); return; } - - if (selectedFolder == '') { + if (selectedFolder === '') { alert("Veuillez choisir un dossier"); return; } if (isNewQuiz) { await ApiService.createQuiz(quizTitle, filteredValue, selectedFolder); - } else { - if (quiz) { - await ApiService.updateQuiz(quiz._id, quizTitle, filteredValue); - } + } else if (quiz) { + await ApiService.updateQuiz(quiz._id, quizTitle, filteredValue); } - navigate('/teacher/dashboard'); } catch (error) { - window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) - console.log(error) + window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`); } }; - // I do not know what this does but do not remove - if (!isNewQuiz && !quiz) { - return
Chargement...
; - } - const handleSaveImage = async () => { try { const inputElement = document.getElementById('file-input') as HTMLInputElement; - if (!inputElement?.files || inputElement.files.length === 0) { setDialogOpen(true); return; } - if (!inputElement.files || inputElement.files.length === 0) { - window.alert("Veuillez d'abord choisir une image à téléverser.") - return; - } - const imageUrl = await ApiService.uploadImage(inputElement.files[0]); - - // Check for errors - if(imageUrl.indexOf("ERROR") >= 0) { - window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) + if (imageUrl.indexOf("ERROR") >= 0) { + window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`); return; } setImageLinks(prevLinks => [...prevLinks, imageUrl]); - - // Reset the file input element - if (fileInputRef.current) { - fileInputRef.current.value = ''; - } + if (fileInputRef.current) fileInputRef.current.value = ''; } catch (error) { - window.alert(`Une erreur est survenue.\n${error}\nVeuillez réessayer plus tard.`) - + window.alert(`Une erreur est survenue.\n${error}\nVeuillez réessayer plus tard.`); } }; @@ -204,109 +150,125 @@ const QuizForm: React.FC = () => { navigator.clipboard.writeText(link); } + if (!isNewQuiz && !quiz) { + return
Chargement...
; + } + return ( -
- -
- - -
Éditeur de quiz
- -
+
+ {/* Header */} +
+
+ +
+

Éditeur de quiz

+
{/* Spacer for balance */}
- {/*

Éditeur

*/} + {/* Quiz Info */} +
+
+ +
+
+
+ + + + {folders.map((folder: FolderType) => ( + + ))} + +
+
+
- - - - - + -
- -
+ {/* Editor Section */} +
+ {/* Editor Column */} +
+ onEditorChange={handleUpdatePreview} + /> -
-
-