- Réalisé avec ❤ à Montréal par des finissant•e•s de l'ETS
+ Réalisé avec ❤ à Montréal par des finissant•e•s de l'ETS
diff --git a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx
index 6e13395..671ae9d 100644
--- a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx
+++ b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx
@@ -28,9 +28,9 @@ const GiftCheatSheet: React.FC = () => {
const QuestionNum ="Question {#=Nombre\n} //OU \nQuestion {#=Nombre:Tolérance\n} // OU \nQuestion {#=PetitNombre..GrandNombre\n}\n// La tolérance est un pourcentage.\n// La réponse doit être comprise entre PetitNombre et GrandNombre";
return (
-
Informations pratiques sur l'éditeur
+
Informations pratiques sur l'éditeur
- L'éditeur utilise le format GIFT (General Import Format Template) créé pour la
+ 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 :
@@ -126,7 +126,7 @@ const GiftCheatSheet: React.FC = () => {
7. Paramètres optionnels
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 \
+ réponses ou feedback, vous devez «échapper» ces derniers en ajoutant un \
devant:
@@ -140,9 +140,9 @@ const GiftCheatSheet: React.FC = () => {
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.
+ à «échapper» les caractères spéciaux mentionnés plus haut.
- Exemple d'équation:
+ Exemple d'équation:
{'$$x\\= \\frac\\{y^2\\}\\{4\\}$$'}
{'\n$x\\= \\frac\\{y^2\\}\\{4\\}$'}
@@ -167,16 +167,16 @@ const GiftCheatSheet: React.FC = () => {
{'")'}
- Exemple d'une question Vrai/Faux avec l'image d'un chat:
+ Exemple d'une question Vrai/Faux avec l'image d'un chat:
{'[markdown]Ceci est un chat: \n\n{T}'}
- 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.
+ 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.
Note : On ne peut utiliser les images dans les messages de rétroaction (GIFT), car les rétroactions ne supportent pas le texte avec formatage (Markdown).
- Attention: l'ancienne fonctionnalité avec les balises {' '} n'est plus
+ Attention: l'ancienne fonctionnalité avec les balises {' '} n'est plus
supportée.
@@ -184,7 +184,7 @@ const GiftCheatSheet: React.FC = () => {
10. Informations supplémentaires
- GIFT supporte d'autres formats de questions que nous ne gérons pas sur cette
+ 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):
diff --git a/client/src/components/GiftTemplate/templates/TextType.ts b/client/src/components/GiftTemplate/templates/TextType.ts
index c6d8ecf..02a7a14 100644
--- a/client/src/components/GiftTemplate/templates/TextType.ts
+++ b/client/src/components/GiftTemplate/templates/TextType.ts
@@ -30,7 +30,7 @@ export function formatLatex(text: string): string {
*/
export default function textType({ text }: TextTypeOptions) {
const formatText = formatLatex(text.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped
-
+ let parsedText = '';
switch (text.format) {
case 'moodle':
case 'plain':
@@ -40,7 +40,7 @@ export default function textType({ text }: TextTypeOptions) {
// Strip outer paragraph tags (not a great approach with regex)
return formatText.replace(/(^
)(.*?)(<\/p>)$/gm, '$2');
case 'markdown':
- const parsedText = marked.parse(formatText, { breaks: true }) as string; // https://github.com/markedjs/marked/discussions/3219
+ parsedText = marked.parse(formatText, { breaks: true }) as string; // https://github.com/markedjs/marked/discussions/3219
return parsedText.replace(/(^
)(.*?)(<\/p>)$/gm, '$2');
default:
throw new Error(`Unsupported text format: ${text.format}`);
diff --git a/client/src/components/ImportModal/ImportModal.tsx b/client/src/components/ImportModal/ImportModal.tsx
index a00b8bb..41ab6ca 100644
--- a/client/src/components/ImportModal/ImportModal.tsx
+++ b/client/src/components/ImportModal/ImportModal.tsx
@@ -168,7 +168,7 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel
Déposer des fichiers ici ou
- cliquez pour ouvrir l'explorateur des fichiers
+ cliquez pour ouvrir l'explorateur des fichiers
diff --git a/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx b/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx
index e174b23..3bd307b 100644
--- a/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx
+++ b/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx
@@ -1,3 +1,4 @@
+import React from 'react';
import {
Button,
Dialog,
diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx
index f08ad27..b7d1c72 100644
--- a/client/src/components/LiveResults/LiveResults.tsx
+++ b/client/src/components/LiveResults/LiveResults.tsx
@@ -300,7 +300,7 @@ const LiveResults: React.FC
= ({ questions, showSelectedQuesti
- Nom d'utilisateur
+ Nom d'utilisateur
{Array.from({ length: maxQuestions }, (_, index) => (
= ({
questionTypeComponent = (
({ ...choice, id: index.toString() }))}
handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer}
globalFeedback={question.globalFeedback?.text}
@@ -78,7 +78,7 @@ const Question: React.FC = ({
questionTypeComponent = (
({ ...choice, id: index.toString() }))}
handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer}
globalFeedback={question.globalFeedback?.text}
diff --git a/client/src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.tsx b/client/src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.tsx
index 87d077a..3f134d6 100644
--- a/client/src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.tsx
+++ b/client/src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.tsx
@@ -10,6 +10,7 @@ type Choices = {
isCorrect: boolean;
text: { format: string; text: string };
weigth?: number;
+ id: string;
};
interface Props {
@@ -33,7 +34,9 @@ const ShortAnswerQuestion: React.FC = (props) => {
<>
{choices.map((choice) => (
-
{choice.text.text}
+
+ {choice.text.text}
+
))}
{globalFeedback && {globalFeedback}
}
diff --git a/client/src/components/ReturnButton/ReturnButton.tsx b/client/src/components/ReturnButton/ReturnButton.tsx
index 0792cfb..a184356 100644
--- a/client/src/components/ReturnButton/ReturnButton.tsx
+++ b/client/src/components/ReturnButton/ReturnButton.tsx
@@ -1,4 +1,5 @@
// GoBackButton.tsx
+import React from 'react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import ConfirmDialog from '../ConfirmDialog/ConfirmDialog';
@@ -33,7 +34,7 @@ const ReturnButton: React.FC = ({
};
const handleOnReturn = () => {
- if (!!onReturn) {
+ if (onReturn) {
onReturn();
} else {
navigate(-1);
diff --git a/client/src/components/StudentWaitPage/StudentWaitPage.tsx b/client/src/components/StudentWaitPage/StudentWaitPage.tsx
index 5937f08..9989b71 100644
--- a/client/src/components/StudentWaitPage/StudentWaitPage.tsx
+++ b/client/src/components/StudentWaitPage/StudentWaitPage.tsx
@@ -1,3 +1,4 @@
+import React from 'react';
import { Box, Button, Chip } from '@mui/material';
import { StudentType } from '../../Types/StudentType';
import { PlayArrow } from '@mui/icons-material';
diff --git a/client/src/main.tsx b/client/src/main.tsx
index e4c3824..e73c979 100644
--- a/client/src/main.tsx
+++ b/client/src/main.tsx
@@ -1,3 +1,4 @@
+import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
@@ -27,10 +28,16 @@ const theme = createTheme({
}
});
-ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-);
+const rootElement = document.getElementById('root');
+if (rootElement) {
+
+ ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+ );
+} else {
+ console.error('Root element not found');
+}
diff --git a/client/src/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx
index 9fc8cc9..4b9583a 100644
--- a/client/src/pages/Teacher/Dashboard/Dashboard.tsx
+++ b/client/src/pages/Teacher/Dashboard/Dashboard.tsx
@@ -82,7 +82,7 @@ const Dashboard: React.FC = () => {
return;
}
else {
- let userFolders = await ApiService.getUserFolders();
+ const userFolders = await ApiService.getUserFolders();
setFolders(userFolders as FolderType[]);
}
@@ -102,7 +102,7 @@ const Dashboard: React.FC = () => {
if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
console.log("show all quizes")
- var quizzes: QuizType[] = [];
+ let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id);
@@ -155,8 +155,8 @@ const Dashboard: React.FC = () => {
await ApiService.duplicateQuiz(quiz._id);
if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
- console.log("show all quizes")
- var quizzes: QuizType[] = [];
+ console.log("show all quizzes")
+ let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id);
@@ -196,6 +196,7 @@ const Dashboard: React.FC = () => {
// questions[i] = QuestionService.ignoreImgTags(questions[i]);
const parsedItem = parse(questions[i]);
Template(parsedItem[0]);
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
return false;
}
@@ -216,7 +217,7 @@ const Dashboard: React.FC = () => {
//const { title, content } = selectedQuiz;
let quizContent = "";
- let title = selectedQuiz.title;
+ const title = selectedQuiz.title;
console.log(selectedQuiz.content);
selectedQuiz.content.forEach((question, qIndex) => {
const formattedQuestion = question.trim();
@@ -273,7 +274,7 @@ const Dashboard: React.FC = () => {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
console.log("show all quizzes")
- var quizzes: QuizType[] = [];
+ let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id);
diff --git a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx
index 15ceeb2..e17c91c 100644
--- a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx
+++ b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx
@@ -162,8 +162,10 @@ const QuizForm: React.FC = () => {
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`)
+
}
};
@@ -245,7 +247,7 @@ const QuizForm: React.FC = () => {
onClose={() => setDialogOpen(false)} >
Erreur
- Veuillez d'abord choisir une image à téléverser.
+ Veuillez d'abord choisir une image à téléverser.
setDialogOpen(false)} color="primary">
diff --git a/client/src/pages/Teacher/Login/Login.tsx b/client/src/pages/Teacher/Login/Login.tsx
index fe99d8b..8e914b8 100644
--- a/client/src/pages/Teacher/Login/Login.tsx
+++ b/client/src/pages/Teacher/Login/Login.tsx
@@ -28,7 +28,7 @@ const Login: React.FC = () => {
const login = async () => {
const result = await ApiService.login(email, password);
- if (result != true) {
+ if (typeof result === "string") {
setConnectionError(result);
return;
}
diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx
index 949a6de..73ac6e0 100644
--- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx
+++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx
@@ -124,8 +124,8 @@ const ManageRoom: React.FC = () => {
// This is here to make sure the correct value is sent when user join
if (socket) {
console.log(`Listening for user-joined in room ${roomName}`);
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
socket.on('user-joined', (_student: StudentType) => {
-
if (quizMode === 'teacher') {
webSocketService.nextQuestion(roomName, currentQuestion);
} else if (quizMode === 'student') {
diff --git a/client/src/pages/Teacher/Register/Register.tsx b/client/src/pages/Teacher/Register/Register.tsx
index 29d5f2b..76a683c 100644
--- a/client/src/pages/Teacher/Register/Register.tsx
+++ b/client/src/pages/Teacher/Register/Register.tsx
@@ -28,7 +28,7 @@ const Register: React.FC = () => {
const register = async () => {
const result = await ApiService.register(email, password);
- if (result != true) {
+ if (typeof result === 'string') {
setConnectionError(result);
return;
}
@@ -70,7 +70,7 @@ const Register: React.FC = () => {
sx={{ marginBottom: `${connectionError && '2rem'}` }}
disabled={!email || !password}
>
- S'inscrire
+ S'inscrire
diff --git a/client/src/pages/Teacher/ResetPassword/ResetPassword.tsx b/client/src/pages/Teacher/ResetPassword/ResetPassword.tsx
index 4901de1..27fe796 100644
--- a/client/src/pages/Teacher/ResetPassword/ResetPassword.tsx
+++ b/client/src/pages/Teacher/ResetPassword/ResetPassword.tsx
@@ -27,7 +27,7 @@ const ResetPassword: React.FC = () => {
const reset = async () => {
const result = await ApiService.resetPassword(email);
- if (result != true) {
+ if (typeof result === 'string') {
setConnectionError(result);
return;
}
diff --git a/client/src/services/ApiService.tsx b/client/src/services/ApiService.tsx
index 987c091..4893c32 100644
--- a/client/src/services/ApiService.tsx
+++ b/client/src/services/ApiService.tsx
@@ -4,6 +4,8 @@ import { FolderType } from 'src/Types/FolderType';
import { QuizType } from 'src/Types/QuizType';
import { ENV_VARIABLES } from 'src/constants';
+type ApiResponse = boolean | string;
+
class ApiService {
private BASE_URL: string;
private TTL: number;
@@ -17,7 +19,7 @@ class ApiService {
return `${this.BASE_URL}/api${endpoint}`;
}
- private constructRequestHeaders(): any {
+ private constructRequestHeaders() {
if (this.isLoggedIn()) {
return {
Authorization: `Bearer ${this.getToken()}`,
@@ -86,7 +88,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async register(email: string, password: string): Promise {
+ public async register(email: string, password: string): Promise {
try {
if (!email || !password) {
@@ -122,7 +124,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async login(email: string, password: string): Promise {
+ public async login(email: string, password: string): Promise {
try {
if (!email || !password) {
@@ -146,8 +148,13 @@ class ApiService {
} catch (error) {
console.log("Error details: ", error);
+ console.log("axios.isAxiosError(error): ", axios.isAxiosError(error));
+
if (axios.isAxiosError(error)) {
const err = error as AxiosError;
+ if (err.status === 401) {
+ return 'Email ou mot de passe incorrect.';
+ }
const data = err.response?.data as { error: string } | undefined;
return data?.error || 'Erreur serveur inconnue lors de la requête.';
}
@@ -157,10 +164,10 @@ class ApiService {
}
/**
- * @returns true if successful
+ * @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async resetPassword(email: string): Promise {
+ public async resetPassword(email: string): Promise {
try {
if (!email) {
@@ -196,7 +203,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async changePassword(email: string, oldPassword: string, newPassword: string): Promise {
+ public async changePassword(email: string, oldPassword: string, newPassword: string): Promise {
try {
if (!email || !oldPassword || !newPassword) {
@@ -232,7 +239,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async deleteUser(email: string, password: string): Promise {
+ public async deleteUser(email: string, password: string): Promise {
try {
if (!email || !password) {
@@ -270,7 +277,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async createFolder(title: string): Promise {
+ public async createFolder(title: string): Promise {
try {
if (!title) {
@@ -375,7 +382,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async deleteFolder(folderId: string): Promise {
+ public async deleteFolder(folderId: string): Promise {
try {
if (!folderId) {
@@ -410,7 +417,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async renameFolder(folderId: string, newTitle: string): Promise {
+ public async renameFolder(folderId: string, newTitle: string): Promise {
try {
if (!folderId || !newTitle) {
@@ -441,7 +448,7 @@ class ApiService {
}
}
- public async duplicateFolder(folderId: string): Promise {
+ public async duplicateFolder(folderId: string): Promise {
try {
if (!folderId) {
throw new Error(`Le folderId et le nouveau titre sont requis.`);
@@ -473,7 +480,7 @@ class ApiService {
}
}
- public async copyFolder(folderId: string, newTitle: string): Promise {
+ public async copyFolder(folderId: string, newTitle: string): Promise {
try {
if (!folderId || !newTitle) {
throw new Error(`Le folderId et le nouveau titre sont requis.`);
@@ -510,7 +517,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async createQuiz(title: string, content: string[], folderId: string): Promise {
+ public async createQuiz(title: string, content: string[], folderId: string): Promise {
try {
if (!title || !content || !folderId) {
@@ -581,7 +588,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async deleteQuiz(quizId: string): Promise {
+ public async deleteQuiz(quizId: string): Promise {
try {
if (!quizId) {
@@ -616,7 +623,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async updateQuiz(quizId: string, newTitle: string, newContent: string[]): Promise {
+ public async updateQuiz(quizId: string, newTitle: string, newContent: string[]): Promise {
try {
if (!quizId || !newTitle || !newContent) {
@@ -652,7 +659,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async moveQuiz(quizId: string, newFolderId: string): Promise {
+ public async moveQuiz(quizId: string, newFolderId: string): Promise {
try {
if (!quizId || !newFolderId) {
@@ -689,7 +696,7 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async duplicateQuiz(quizId: string): Promise {
+ public async duplicateQuiz(quizId: string): Promise {
const url: string = this.constructRequestUrl(`/quiz/duplicate`);
@@ -703,7 +710,7 @@ class ApiService {
throw new Error(`La duplication du quiz a échoué. Status: ${result.status}`);
}
- return result;
+ return result.status === 200;
} catch (error) {
console.error("Error details: ", error);
@@ -723,9 +730,9 @@ class ApiService {
* @returns true if successful
* @returns A error string if unsuccessful,
*/
- public async copyQuiz(quizId: string, newTitle: string, folderId: string): Promise {
+ public async copyQuiz(quizId: string, newTitle: string, folderId: string): Promise {
try {
- console.log(quizId, newTitle), folderId;
+ console.log(quizId, newTitle, folderId);
return "Route not implemented yet!";
} catch (error) {
@@ -741,7 +748,7 @@ class ApiService {
}
}
- async ShareQuiz(quizId: string, email: string): Promise {
+ async ShareQuiz(quizId: string, email: string): Promise {
try {
if (!quizId || !email) {
throw new Error(`quizId and email are required.`);
@@ -800,7 +807,7 @@ class ApiService {
}
}
- async receiveSharedQuiz(quizId: string, folderId: string): Promise {
+ async receiveSharedQuiz(quizId: string, folderId: string): Promise {
try {
if (!quizId || !folderId) {
throw new Error(`quizId and folderId are required.`);
@@ -869,7 +876,8 @@ class ApiService {
if (axios.isAxiosError(error)) {
const err = error as AxiosError;
const data = err.response?.data as { error: string } | undefined;
- return `ERROR : ${data?.error}` || 'ERROR : Erreur serveur inconnue lors de la requête.';
+ const msg = data?.error || 'Erreur serveur inconnue lors de la requête.';
+ return `ERROR : ${msg}`;
}
return `ERROR : Une erreur inattendue s'est produite.`
diff --git a/client/src/utils/giftUtils.ts b/client/src/utils/giftUtils.ts
index f81bda8..5eb88e9 100644
--- a/client/src/utils/giftUtils.ts
+++ b/client/src/utils/giftUtils.ts
@@ -1,4 +1,4 @@
export function escapeForGIFT(link: string): string {
- const specialChars = /[{}#~=<>\:]/g;
+ const specialChars = /[{}#~=<>\\:]/g;
return link.replace(specialChars, (match) => `\\${match}`);
}
diff --git a/server/constants/errorCodes.js b/server/constants/errorCodes.js
index d7ca180..fb691f8 100644
--- a/server/constants/errorCodes.js
+++ b/server/constants/errorCodes.js
@@ -18,7 +18,7 @@ exports.USER_ALREADY_EXISTS = {
}
exports.LOGIN_CREDENTIALS_ERROR = {
message: 'L\'email et le mot de passe ne correspondent pas.',
- code: 400
+ code: 401
}
exports.GENERATE_PASSWORD_ERROR = {
message: 'Une erreur s\'est produite lors de la création d\'un nouveau mot de passe.',
@@ -130,4 +130,4 @@ exports.NOT_IMPLEMENTED = {
// static badRequest(res, message) {400
// static unauthorized(res, message) {401
// static notFound(res, message) {404
-// static serverError(res, message) {505
\ No newline at end of file
+// static serverError(res, message) {505