install eslint (client), fix errors (many)

This commit is contained in:
C. Fuhrman 2025-01-11 02:22:14 -05:00
parent fd81e3b0d1
commit 50e92fb458
51 changed files with 2055 additions and 174 deletions

View file

@ -1,18 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

View file

@ -1,3 +1,4 @@
/* eslint-disable no-undef */
module.exports = { module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'] presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']
}; };

29
client/eslint.config.js Normal file
View file

@ -0,0 +1,29 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
/** @type {import('eslint').Linter.Config[]} */
export default [
{
files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
languageOptions: {
globals: globals.browser,
},
rules: {
"no-unused-vars": ["error", {
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_" // Ignore catch clause parameters that start with _
}],
},
settings: {
react: {
version: "detect", // Automatically detect the React version
},
},
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
pluginReact.configs.flat.recommended,
];

View file

@ -1,3 +1,4 @@
/* eslint-disable no-undef */
/** @type {import('ts-jest').JestConfigWithTsJest} */ /** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = { module.exports = {

View file

@ -1,2 +1,3 @@
/* eslint-disable no-undef */
process.env.VITE_BACKEND_URL = 'http://localhost:4000/'; process.env.VITE_BACKEND_URL = 'http://localhost:4000/';
process.env.VITE_BACKEND_SOCKET_URL = 'https://ets-glitch-backend.glitch.me/'; process.env.VITE_BACKEND_SOCKET_URL = 'https://ets-glitch-backend.glitch.me/';

1967
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -43,6 +43,7 @@
"@babel/preset-env": "^7.23.3", "@babel/preset-env": "^7.23.3",
"@babel/preset-react": "^7.23.3", "@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3", "@babel/preset-typescript": "^7.23.3",
"@eslint/js": "^9.18.0",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.5.0", "@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1", "@testing-library/react": "^16.0.1",
@ -54,13 +55,16 @@
"@typescript-eslint/eslint-plugin": "^8.5.0", "@typescript-eslint/eslint-plugin": "^8.5.0",
"@typescript-eslint/parser": "^8.5.0", "@typescript-eslint/parser": "^8.5.0",
"@vitejs/plugin-react-swc": "^3.3.2", "@vitejs/plugin-react-swc": "^3.3.2",
"eslint": "^9.10.0", "eslint": "^9.18.0",
"eslint-plugin-react": "^7.37.3",
"eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912", "eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912",
"eslint-plugin-react-refresh": "^0.4.12", "eslint-plugin-react-refresh": "^0.4.12",
"globals": "^15.14.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"ts-jest": "^29.1.1", "ts-jest": "^29.1.1",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"typescript-eslint": "^8.19.1",
"vite": "^5.4.5", "vite": "^5.4.5",
"vite-plugin-environment": "^1.1.3" "vite-plugin-environment": "^1.1.3"
} }

View file

@ -1,3 +1,4 @@
import React from 'react';
// App.tsx // App.tsx
import { Routes, Route } from 'react-router-dom'; import { Routes, Route } from 'react-router-dom';

View file

@ -1,4 +1,5 @@
// Modal.test.tsx // Modal.test.tsx
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react'; import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import ConfirmDialog from '../../../components/ConfirmDialog/ConfirmDialog'; import ConfirmDialog from '../../../components/ConfirmDialog/ConfirmDialog';

View file

@ -1,4 +1,5 @@
// Editor.test.tsx // Editor.test.tsx
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react'; import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import Editor from '../../../components/Editor/Editor'; import Editor from '../../../components/Editor/Editor';

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import GIFTTemplatePreview from '../../../components/GiftTemplate/GIFTTemplatePreview'; import GIFTTemplatePreview from '../../../components/GiftTemplate/GIFTTemplatePreview';

View file

@ -32,7 +32,7 @@ describe('TextType', () => {
// Hint -- if the output changes because of a change in the code or library, you can update // Hint -- if the output changes because of a change in the code or library, you can update
// by running the test and copying the "Received string:" in jest output // by running the test and copying the "Received string:" in jest output
// when it fails (assuming the output is correct) // when it fails (assuming the output is correct)
const expectedOutput = '<span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding=\"application/x-tex\">E=mc^2</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.05764em;\">E</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.8641em;\"></span><span class=\"mord mathnormal\">m</span><span class=\"mord\"><span class=\"mord mathnormal\">c</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.8641em;\"><span style=\"top:-3.113em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\">2</span></span></span></span></span></span></span></span></span></span></span></span>'; const expectedOutput = '<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>';
expect(textType({ text: input })).toContain(expectedOutput); expect(textType({ text: input })).toContain(expectedOutput);
}); });
@ -42,19 +42,17 @@ describe('TextType', () => {
format: 'plain' format: 'plain'
}; };
// hint: katex-display is the class that indicates a separate equation // hint: katex-display is the class that indicates a separate equation
const expectedOutput = '<span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mi>a</mi><mo>+</mo><mi>b</mi><mo>=</mo><mi>c</mi></mrow><annotation encoding=\"application/x-tex\">a + b = c</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6667em;vertical-align:-0.0833em;\"></span><span class=\"mord mathnormal\">a</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span><span class=\"mbin\">+</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6944em;\"></span><span class=\"mord mathnormal\">b</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.4306em;\"></span><span class=\"mord mathnormal\">c</span></span></span></span> ? <span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding=\"application/x-tex\">E=mc^2</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.05764em;\">E</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.8641em;\"></span><span class=\"mord mathnormal\">m</span><span class=\"mord\"><span class=\"mord mathnormal\">c</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.8641em;\"><span style=\"top:-3.113em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\">2</span></span></span></span></span></span></span></span></span></span></span></span>'; const expectedOutput = '<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mo>+</mo><mi>b</mi><mo>=</mo><mi>c</mi></mrow><annotation encoding="application/x-tex">a + b = c</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">c</span></span></span></span> ? <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>';
expect(textType({ text: input })).toContain(expectedOutput); expect(textType({ text: input })).toContain(expectedOutput);
}); });
it('should format text with a katex matrix correctly', () => { it('should format text with a katex matrix correctly', () => {
const input: TextFormat = { const input: TextFormat = {
text: `Donnez le déterminant de la matrice suivante.$$\\begin\{pmatrix\} // eslint-disable-next-line no-useless-escape
a&b \\\\ text: `Donnez le déterminant de la matrice suivante.$$\\begin\{pmatrix\}\n a&b \\\\\n c&d\n\\end\{pmatrix\}`,
c&d
\\end\{pmatrix\}`,
format: 'plain' format: 'plain'
}; };
const expectedOutput = 'Donnez le déterminant de la matrice suivante.<span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow></mrow><annotation encoding=\"application/x-tex\"></annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"></span></span>\\begin{pmatrix}<br> a&b \\\\<br> c&d<br>\\end{pmatrix}'; const expectedOutput = 'Donnez le déterminant de la matrice suivante.<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow></mrow><annotation encoding="application/x-tex"></annotation></semantics></math></span><span class="katex-html" aria-hidden="true"></span></span>\\begin{pmatrix}<br> a&b \\\\<br> c&d<br>\\end{pmatrix}';
expect(textType({ text: input })).toContain(expectedOutput); expect(textType({ text: input })).toContain(expectedOutput);
}); });

View file

@ -1,4 +1,5 @@
//styles.test.tsx //styles.test.tsx
import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
@ -27,6 +28,7 @@ function convertStylesToObject(styles: string): React.CSSProperties {
styles.split(';').forEach((style) => { styles.split(';').forEach((style) => {
const [property, value] = style.split(':'); const [property, value] = style.split(':');
if (property && value) { if (property && value) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(styleObject as any)[property.trim()] = value.trim(); (styleObject as any)[property.trim()] = value.trim();
} }
}); });

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import AnswerIcon from '../../../../components/GiftTemplate/templates/AnswerIcon'; import AnswerIcon from '../../../../components/GiftTemplate/templates/AnswerIcon';

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import DragAndDrop from '../../../components/ImportModal/ImportModal'; import DragAndDrop from '../../../components/ImportModal/ImportModal';
@ -69,4 +70,4 @@ describe('DragAndDrop Component', () => {
target: { files: [file] }, target: { files: [file] },
}); });
}); });
}); });

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import LaunchQuizDialog from '../../../components/LaunchQuizDialog/LaunchQuizDialog'; import LaunchQuizDialog from '../../../components/LaunchQuizDialog/LaunchQuizDialog';

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import LoadingCircle from '../../../components/LoadingCircle/LoadingCircle'; import LoadingCircle from '../../../components/LoadingCircle/LoadingCircle';

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import MultipleChoiceQuestion from '../../../../components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion'; import MultipleChoiceQuestion from '../../../../components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion';

View file

@ -1,4 +1,5 @@
// NumericalQuestion.test.tsx // NumericalQuestion.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import NumericalQuestion from '../../../../components/Questions/NumericalQuestion/NumericalQuestion'; import NumericalQuestion from '../../../../components/Questions/NumericalQuestion/NumericalQuestion';

View file

@ -1,4 +1,5 @@
// Question.test.tsx // Question.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import Questions from '../../../components/Questions/Question'; import Questions from '../../../components/Questions/Question';

View file

@ -1,4 +1,5 @@
// ShortAnswerQuestion.test.tsx // ShortAnswerQuestion.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import ShortAnswerQuestion from '../../../../components/Questions/ShortAnswerQuestion/ShortAnswerQuestion'; import ShortAnswerQuestion from '../../../../components/Questions/ShortAnswerQuestion/ShortAnswerQuestion';
@ -11,6 +12,7 @@ describe('ShortAnswerQuestion Component', () => {
questionTitle: 'Sample Question', questionTitle: 'Sample Question',
choices: [ choices: [
{ {
id: '1',
feedback: { feedback: {
format: 'text', format: 'text',
text: 'Correct answer feedback' text: 'Correct answer feedback'
@ -22,6 +24,7 @@ describe('ShortAnswerQuestion Component', () => {
} }
}, },
{ {
id: '2',
feedback: null, feedback: null,
isCorrect: false, isCorrect: false,
text: { text: {
@ -58,7 +61,7 @@ describe('ShortAnswerQuestion Component', () => {
expect(submitButton).toBeDisabled(); expect(submitButton).toBeDisabled();
}); });
it('not submited answer if nothing is entered', () => { it('not submitted answer if nothing is entered', () => {
const submitButton = screen.getByText('Répondre'); const submitButton = screen.getByText('Répondre');
fireEvent.click(submitButton); fireEvent.click(submitButton);

View file

@ -1,4 +1,5 @@
// TrueFalseQuestion.test.tsx // TrueFalseQuestion.test.tsx
import React from 'react';
import { render, fireEvent, screen, act } from '@testing-library/react'; import { render, fireEvent, screen, act } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import TrueFalseQuestion from '../../../../components/Questions/TrueFalseQuestion/TrueFalseQuestion'; import TrueFalseQuestion from '../../../../components/Questions/TrueFalseQuestion/TrueFalseQuestion';

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import ReturnButton from '../../../components/ReturnButton/ReturnButton'; import ReturnButton from '../../../components/ReturnButton/ReturnButton';

View file

@ -1,4 +1,5 @@
// Importez le type UserType s'il n'est pas déjà importé // Importez le type UserType s'il n'est pas déjà importé
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import StudentWaitPage from '../../../components/StudentWaitPage/StudentWaitPage'; import StudentWaitPage from '../../../components/StudentWaitPage/StudentWaitPage';

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react'; import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
@ -32,4 +33,4 @@ describe('Home', () => {
fireEvent.click(teacherButton); fireEvent.click(teacherButton);
expect(window.location.pathname).toBe('/teacher/dashboard'); expect(window.location.pathname).toBe('/teacher/dashboard');
}); });
}); });

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen, fireEvent, act } from '@testing-library/react'; import { render, screen, fireEvent, act } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { parse } from 'gift-pegjs'; import { parse } from 'gift-pegjs';

View file

@ -1,4 +1,5 @@
//TeacherModeQuiz.test.tsx //TeacherModeQuiz.test.tsx
import React from 'react';
import { render, fireEvent, act } from '@testing-library/react'; import { render, fireEvent, act } from '@testing-library/react';
import { screen } from '@testing-library/dom'; import { screen } from '@testing-library/dom';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';

View file

@ -1,3 +1,4 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';

View file

@ -1,4 +1,5 @@
// GoBackButton.tsx // GoBackButton.tsx
import React from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import ConfirmDialog from '../ConfirmDialog/ConfirmDialog'; import ConfirmDialog from '../ConfirmDialog/ConfirmDialog';
@ -33,7 +34,7 @@ const DisconnectButton: React.FC<Props> = ({
}; };
const handleOnReturn = () => { const handleOnReturn = () => {
if (!!onReturn) { if (onReturn) {
onReturn(); onReturn();
} else { } else {
navigate(-1); navigate(-1);

View file

@ -1,20 +1,16 @@
import * as React from 'react'; import * as React from 'react';
import './footer.css'; import './footer.css';
interface FooterProps { type FooterProps = object; //empty object
} const Footer: React.FC<FooterProps> = () => {
const Footer: React.FC<FooterProps> = ({ }) => {
return ( return (
<div className="footer"> <div className="footer">
<div className="footer-content"> <div className="footer-content">
Réalisé avec à Montréal par des finissantes de l'ETS Réalisé avec à Montréal par des finissantes de l&apos;ETS
</div> </div>
<div className="footer-links"> <div className="footer-links">
<a href="https://github.com/louis-antoine-etsmtl/ETS-PFE042-EvalueTonSavoir-Frontend/tree/main">Frontend GitHub</a> <a href="https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/">GitHub</a>
<span className="divider">|</span>
<a href="https://github.com/louis-antoine-etsmtl/ETS-PFE042-EvalueTonSavoir-Backend">Backend GitHub</a>
<span className="divider">|</span> <span className="divider">|</span>
<a href="https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/wiki">Wiki GitHub</a> <a href="https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/wiki">Wiki GitHub</a>
</div> </div>

View file

@ -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"; 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 ( return (
<div className="gift-cheat-sheet"> <div className="gift-cheat-sheet">
<h2 className="subtitle">Informations pratiques sur l'éditeur</h2> <h2 className="subtitle">Informations pratiques sur l&apos;éditeur</h2>
<span> <span>
L'éditeur utilise le format GIFT (General Import Format Template) créé pour la L&apos;é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 plateforme Moodle afin de générer les mini-tests. Ci-dessous vous pouvez retrouver la
syntaxe pour chaque type de question&nbsp;: syntaxe pour chaque type de question&nbsp;:
</span> </span>
@ -126,7 +126,7 @@ const GiftCheatSheet: React.FC = () => {
<h4> 7. Paramètres optionnels </h4> <h4> 7. Paramètres optionnels </h4>
<p> <p>
Si vous souhaitez utiliser certains caractères spéciaux dans vos énoncés, 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: devant:
</p> </p>
<pre> <pre>
@ -140,9 +140,9 @@ const GiftCheatSheet: React.FC = () => {
<h4> 8. LaTeX et Markdown</h4> <h4> 8. LaTeX et Markdown</h4>
<p> <p>
Les formats LaTeX et Markdown sont supportés dans cette application. Vous devez cependant penser 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.
</p> </p>
<p>Exemple d'équation:</p> <p>Exemple d&apos;équation:</p>
<pre> <pre>
<code className="question-code-block selectable-text">{'$$x\\= \\frac\\{y^2\\}\\{4\\}$$'}</code> <code className="question-code-block selectable-text">{'$$x\\= \\frac\\{y^2\\}\\{4\\}$$'}</code>
<code className="question-code-block selectable-text">{'\n$x\\= \\frac\\{y^2\\}\\{4\\}$'}</code> <code className="question-code-block selectable-text">{'\n$x\\= \\frac\\{y^2\\}\\{4\\}$'}</code>
@ -167,16 +167,16 @@ const GiftCheatSheet: React.FC = () => {
{'")'} {'")'}
</code> </code>
</pre> </pre>
<p>Exemple d'une question Vrai/Faux avec l'image d'un chat:</p> <p>Exemple d&apos;une question Vrai/Faux avec l&apos;image d&apos;un chat:</p>
<pre> <pre>
<code className="question-code-block"> <code className="question-code-block">
{'[markdown]Ceci est un chat: \n![Image de chat](https\\://www.example.com\\:8000/chat.jpg "Chat mignon")\n{T}'} {'[markdown]Ceci est un chat: \n![Image de chat](https\\://www.example.com\\:8000/chat.jpg "Chat mignon")\n{T}'}
</code> </code>
</pre> </pre>
<p>Note&nbsp;: 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.</p> <p>Note&nbsp;: les images étant spécifiées avec la syntaxe Markdown dans GIFT, on doit échapper les caractères spéciales (:) dans l&apos;URL de l&apos;image.</p>
<p>Note&nbsp;: 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).</p> <p>Note&nbsp;: 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).</p>
<p style={{ color: 'red' }}> <p style={{ color: 'red' }}>
Attention: l'ancienne fonctionnalité avec les balises <code>{'<img>'}</code> n'est plus Attention: l&apos;ancienne fonctionnalité avec les balises <code>{'<img>'}</code> n&apos;est plus
supportée. supportée.
</p> </p>
</div> </div>
@ -184,7 +184,7 @@ const GiftCheatSheet: React.FC = () => {
<div className="question-type"> <div className="question-type">
<h4> 10. Informations supplémentaires </h4> <h4> 10. Informations supplémentaires </h4>
<p> <p>
GIFT supporte d'autres formats de questions que nous ne gérons pas sur cette GIFT supporte d&apos;autres formats de questions que nous ne gérons pas sur cette
application. application.
</p> </p>
<p>Vous pouvez retrouver la Documentation de GIFT (en anglais):</p> <p>Vous pouvez retrouver la Documentation de GIFT (en anglais):</p>

View file

@ -30,7 +30,7 @@ export function formatLatex(text: string): string {
*/ */
export default function textType({ text }: TextTypeOptions) { export default function textType({ text }: TextTypeOptions) {
const formatText = formatLatex(text.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped const formatText = formatLatex(text.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped
let parsedText = '';
switch (text.format) { switch (text.format) {
case 'moodle': case 'moodle':
case 'plain': case 'plain':
@ -40,7 +40,7 @@ export default function textType({ text }: TextTypeOptions) {
// Strip outer paragraph tags (not a great approach with regex) // Strip outer paragraph tags (not a great approach with regex)
return formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2'); return formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
case 'markdown': 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>)(.*?)(<\/p>)$/gm, '$2'); return parsedText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
default: default:
throw new Error(`Unsupported text format: ${text.format}`); throw new Error(`Unsupported text format: ${text.format}`);

View file

@ -168,7 +168,7 @@ const DragAndDrop: React.FC<Props> = ({ handleOnClose, handleOnImport, open, sel
<DialogContentText sx={{ textAlign: 'center' }}> <DialogContentText sx={{ textAlign: 'center' }}>
Déposer des fichiers ici ou Déposer des fichiers ici ou
<br /> <br />
cliquez pour ouvrir l'explorateur des fichiers cliquez pour ouvrir l&apos;explorateur des fichiers
</DialogContentText> </DialogContentText>
</div> </div>
<Download color="primary" /> <Download color="primary" />

View file

@ -1,3 +1,4 @@
import React from 'react';
import { import {
Button, Button,
Dialog, Dialog,

View file

@ -300,7 +300,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuesti
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell className="sticky-column"> <TableCell className="sticky-column">
<div className="text-base text-bold">Nom d'utilisateur</div> <div className="text-base text-bold">Nom d&apos;utilisateur</div>
</TableCell> </TableCell>
{Array.from({ length: maxQuestions }, (_, index) => ( {Array.from({ length: maxQuestions }, (_, index) => (
<TableCell <TableCell

View file

@ -1,3 +1,4 @@
import React from 'react';
import { IconButton } from '@mui/material'; import { IconButton } from '@mui/material';
import { ChevronLeft, ChevronRight } from '@mui/icons-material'; import { ChevronLeft, ChevronRight } from '@mui/icons-material';

View file

@ -42,7 +42,7 @@ const Question: React.FC<QuestionProps> = ({
questionTypeComponent = ( questionTypeComponent = (
<MultipleChoiceQuestion <MultipleChoiceQuestion
questionStem={question.stem} questionStem={question.stem}
choices={question.choices} choices={question.choices.map((choice, index) => ({ ...choice, id: index.toString() }))}
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer} showAnswer={showAnswer}
globalFeedback={question.globalFeedback?.text} globalFeedback={question.globalFeedback?.text}
@ -78,7 +78,7 @@ const Question: React.FC<QuestionProps> = ({
questionTypeComponent = ( questionTypeComponent = (
<ShortAnswerQuestion <ShortAnswerQuestion
questionContent={question.stem} questionContent={question.stem}
choices={question.choices} choices={question.choices.map((choice, index) => ({ ...choice, id: index.toString() }))}
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer} showAnswer={showAnswer}
globalFeedback={question.globalFeedback?.text} globalFeedback={question.globalFeedback?.text}

View file

@ -10,6 +10,7 @@ type Choices = {
isCorrect: boolean; isCorrect: boolean;
text: { format: string; text: string }; text: { format: string; text: string };
weigth?: number; weigth?: number;
id: string;
}; };
interface Props { interface Props {
@ -33,7 +34,9 @@ const ShortAnswerQuestion: React.FC<Props> = (props) => {
<> <>
<div className="correct-answer-text mb-1"> <div className="correct-answer-text mb-1">
{choices.map((choice) => ( {choices.map((choice) => (
<div className="mb-1">{choice.text.text}</div> <div key={choice.id} className="mb-1">
{choice.text.text}
</div>
))} ))}
</div> </div>
{globalFeedback && <div className="global-feedback mb-2">{globalFeedback}</div>} {globalFeedback && <div className="global-feedback mb-2">{globalFeedback}</div>}

View file

@ -1,4 +1,5 @@
// GoBackButton.tsx // GoBackButton.tsx
import React from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import ConfirmDialog from '../ConfirmDialog/ConfirmDialog'; import ConfirmDialog from '../ConfirmDialog/ConfirmDialog';
@ -33,7 +34,7 @@ const ReturnButton: React.FC<Props> = ({
}; };
const handleOnReturn = () => { const handleOnReturn = () => {
if (!!onReturn) { if (onReturn) {
onReturn(); onReturn();
} else { } else {
navigate(-1); navigate(-1);

View file

@ -1,3 +1,4 @@
import React from 'react';
import { Box, Button, Chip } from '@mui/material'; import { Box, Button, Chip } from '@mui/material';
import { StudentType } from '../../Types/StudentType'; import { StudentType } from '../../Types/StudentType';
import { PlayArrow } from '@mui/icons-material'; import { PlayArrow } from '@mui/icons-material';

View file

@ -1,3 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from './App.tsx'; import App from './App.tsx';
@ -27,10 +28,16 @@ const theme = createTheme({
} }
}); });
ReactDOM.createRoot(document.getElementById('root')!).render( const rootElement = document.getElementById('root');
<BrowserRouter> if (rootElement) {
<ThemeProvider theme={theme}>
<App /> ReactDOM.createRoot(document.getElementById('root')!).render(
</ThemeProvider> <BrowserRouter>
</BrowserRouter> <ThemeProvider theme={theme}>
); <App />
</ThemeProvider>
</BrowserRouter>
);
} else {
console.error('Root element not found');
}

View file

@ -82,7 +82,7 @@ const Dashboard: React.FC = () => {
return; return;
} }
else { else {
let userFolders = await ApiService.getUserFolders(); const userFolders = await ApiService.getUserFolders();
setFolders(userFolders as FolderType[]); setFolders(userFolders as FolderType[]);
} }
@ -102,7 +102,7 @@ const Dashboard: React.FC = () => {
if (selectedFolderId == '') { if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
console.log("show all quizes") console.log("show all quizes")
var quizzes: QuizType[] = []; let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) { for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id); const folderQuizzes = await ApiService.getFolderContent(folder._id);
@ -155,8 +155,8 @@ const Dashboard: React.FC = () => {
await ApiService.duplicateQuiz(quiz._id); await ApiService.duplicateQuiz(quiz._id);
if (selectedFolderId == '') { if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
console.log("show all quizes") console.log("show all quizzes")
var quizzes: QuizType[] = []; let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) { for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id); const folderQuizzes = await ApiService.getFolderContent(folder._id);
@ -196,6 +196,7 @@ const Dashboard: React.FC = () => {
// questions[i] = QuestionService.ignoreImgTags(questions[i]); // questions[i] = QuestionService.ignoreImgTags(questions[i]);
const parsedItem = parse(questions[i]); const parsedItem = parse(questions[i]);
Template(parsedItem[0]); Template(parsedItem[0]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) { } catch (error) {
return false; return false;
} }
@ -216,7 +217,7 @@ const Dashboard: React.FC = () => {
//const { title, content } = selectedQuiz; //const { title, content } = selectedQuiz;
let quizContent = ""; let quizContent = "";
let title = selectedQuiz.title; const title = selectedQuiz.title;
console.log(selectedQuiz.content); console.log(selectedQuiz.content);
selectedQuiz.content.forEach((question, qIndex) => { selectedQuiz.content.forEach((question, qIndex) => {
const formattedQuestion = question.trim(); 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 const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
console.log("show all quizzes") console.log("show all quizzes")
var quizzes: QuizType[] = []; let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) { for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id); const folderQuizzes = await ApiService.getFolderContent(folder._id);

View file

@ -162,8 +162,10 @@ const QuizForm: React.FC = () => {
if (fileInputRef.current) { if (fileInputRef.current) {
fileInputRef.current.value = ''; fileInputRef.current.value = '';
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) { } catch (error) {
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`)
} }
}; };
@ -245,7 +247,7 @@ const QuizForm: React.FC = () => {
onClose={() => setDialogOpen(false)} > onClose={() => setDialogOpen(false)} >
<DialogTitle>Erreur</DialogTitle> <DialogTitle>Erreur</DialogTitle>
<DialogContent> <DialogContent>
Veuillez d'abord choisir une image à téléverser. Veuillez d&apos;abord choisir une image à téléverser.
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => setDialogOpen(false)} color="primary"> <Button onClick={() => setDialogOpen(false)} color="primary">

View file

@ -28,7 +28,7 @@ const Login: React.FC = () => {
const login = async () => { const login = async () => {
const result = await ApiService.login(email, password); const result = await ApiService.login(email, password);
if (result != true) { if (typeof result === "string") {
setConnectionError(result); setConnectionError(result);
return; return;
} }

View file

@ -124,8 +124,8 @@ const ManageRoom: React.FC = () => {
// This is here to make sure the correct value is sent when user join // This is here to make sure the correct value is sent when user join
if (socket) { if (socket) {
console.log(`Listening for user-joined in room ${roomName}`); console.log(`Listening for user-joined in room ${roomName}`);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
socket.on('user-joined', (_student: StudentType) => { socket.on('user-joined', (_student: StudentType) => {
if (quizMode === 'teacher') { if (quizMode === 'teacher') {
webSocketService.nextQuestion(roomName, currentQuestion); webSocketService.nextQuestion(roomName, currentQuestion);
} else if (quizMode === 'student') { } else if (quizMode === 'student') {

View file

@ -28,7 +28,7 @@ const Register: React.FC = () => {
const register = async () => { const register = async () => {
const result = await ApiService.register(email, password); const result = await ApiService.register(email, password);
if (result != true) { if (typeof result === 'string') {
setConnectionError(result); setConnectionError(result);
return; return;
} }
@ -70,7 +70,7 @@ const Register: React.FC = () => {
sx={{ marginBottom: `${connectionError && '2rem'}` }} sx={{ marginBottom: `${connectionError && '2rem'}` }}
disabled={!email || !password} disabled={!email || !password}
> >
S'inscrire S&apos;inscrire
</LoadingButton> </LoadingButton>
</LoginContainer> </LoginContainer>

View file

@ -27,7 +27,7 @@ const ResetPassword: React.FC = () => {
const reset = async () => { const reset = async () => {
const result = await ApiService.resetPassword(email); const result = await ApiService.resetPassword(email);
if (result != true) { if (typeof result === 'string') {
setConnectionError(result); setConnectionError(result);
return; return;
} }

View file

@ -4,6 +4,8 @@ import { FolderType } from 'src/Types/FolderType';
import { QuizType } from 'src/Types/QuizType'; import { QuizType } from 'src/Types/QuizType';
import { ENV_VARIABLES } from 'src/constants'; import { ENV_VARIABLES } from 'src/constants';
type ApiResponse = boolean | string;
class ApiService { class ApiService {
private BASE_URL: string; private BASE_URL: string;
private TTL: number; private TTL: number;
@ -17,7 +19,7 @@ class ApiService {
return `${this.BASE_URL}/api${endpoint}`; return `${this.BASE_URL}/api${endpoint}`;
} }
private constructRequestHeaders(): any { private constructRequestHeaders() {
if (this.isLoggedIn()) { if (this.isLoggedIn()) {
return { return {
Authorization: `Bearer ${this.getToken()}`, Authorization: `Bearer ${this.getToken()}`,
@ -86,7 +88,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async register(email: string, password: string): Promise<any> { public async register(email: string, password: string): Promise<ApiResponse> {
try { try {
if (!email || !password) { if (!email || !password) {
@ -122,7 +124,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async login(email: string, password: string): Promise<any> { public async login(email: string, password: string): Promise<ApiResponse> {
try { try {
if (!email || !password) { if (!email || !password) {
@ -146,8 +148,13 @@ class ApiService {
} catch (error) { } catch (error) {
console.log("Error details: ", error); console.log("Error details: ", error);
console.log("axios.isAxiosError(error): ", axios.isAxiosError(error));
if (axios.isAxiosError(error)) { if (axios.isAxiosError(error)) {
const err = error as AxiosError; 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; const data = err.response?.data as { error: string } | undefined;
return data?.error || 'Erreur serveur inconnue lors de la requête.'; 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, * @returns A error string if unsuccessful,
*/ */
public async resetPassword(email: string): Promise<any> { public async resetPassword(email: string): Promise<ApiResponse> {
try { try {
if (!email) { if (!email) {
@ -196,7 +203,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async changePassword(email: string, oldPassword: string, newPassword: string): Promise<any> { public async changePassword(email: string, oldPassword: string, newPassword: string): Promise<ApiResponse> {
try { try {
if (!email || !oldPassword || !newPassword) { if (!email || !oldPassword || !newPassword) {
@ -232,7 +239,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async deleteUser(email: string, password: string): Promise<any> { public async deleteUser(email: string, password: string): Promise<ApiResponse> {
try { try {
if (!email || !password) { if (!email || !password) {
@ -270,7 +277,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async createFolder(title: string): Promise<any> { public async createFolder(title: string): Promise<ApiResponse> {
try { try {
if (!title) { if (!title) {
@ -375,7 +382,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async deleteFolder(folderId: string): Promise<any> { public async deleteFolder(folderId: string): Promise<ApiResponse> {
try { try {
if (!folderId) { if (!folderId) {
@ -410,7 +417,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async renameFolder(folderId: string, newTitle: string): Promise<any> { public async renameFolder(folderId: string, newTitle: string): Promise<ApiResponse> {
try { try {
if (!folderId || !newTitle) { if (!folderId || !newTitle) {
@ -441,7 +448,7 @@ class ApiService {
} }
} }
public async duplicateFolder(folderId: string): Promise<any> { public async duplicateFolder(folderId: string): Promise<ApiResponse> {
try { try {
if (!folderId) { if (!folderId) {
throw new Error(`Le folderId et le nouveau titre sont requis.`); 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<any> { public async copyFolder(folderId: string, newTitle: string): Promise<ApiResponse> {
try { try {
if (!folderId || !newTitle) { if (!folderId || !newTitle) {
throw new Error(`Le folderId et le nouveau titre sont requis.`); throw new Error(`Le folderId et le nouveau titre sont requis.`);
@ -510,7 +517,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async createQuiz(title: string, content: string[], folderId: string): Promise<any> { public async createQuiz(title: string, content: string[], folderId: string): Promise<ApiResponse> {
try { try {
if (!title || !content || !folderId) { if (!title || !content || !folderId) {
@ -581,7 +588,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async deleteQuiz(quizId: string): Promise<any> { public async deleteQuiz(quizId: string): Promise<ApiResponse> {
try { try {
if (!quizId) { if (!quizId) {
@ -616,7 +623,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async updateQuiz(quizId: string, newTitle: string, newContent: string[]): Promise<any> { public async updateQuiz(quizId: string, newTitle: string, newContent: string[]): Promise<ApiResponse> {
try { try {
if (!quizId || !newTitle || !newContent) { if (!quizId || !newTitle || !newContent) {
@ -652,7 +659,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async moveQuiz(quizId: string, newFolderId: string): Promise<any> { public async moveQuiz(quizId: string, newFolderId: string): Promise<ApiResponse> {
try { try {
if (!quizId || !newFolderId) { if (!quizId || !newFolderId) {
@ -689,7 +696,7 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async duplicateQuiz(quizId: string): Promise<any> { public async duplicateQuiz(quizId: string): Promise<ApiResponse> {
const url: string = this.constructRequestUrl(`/quiz/duplicate`); 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}`); throw new Error(`La duplication du quiz a échoué. Status: ${result.status}`);
} }
return result; return result.status === 200;
} catch (error) { } catch (error) {
console.error("Error details: ", error); console.error("Error details: ", error);
@ -723,9 +730,9 @@ class ApiService {
* @returns true if successful * @returns true if successful
* @returns A error string if unsuccessful, * @returns A error string if unsuccessful,
*/ */
public async copyQuiz(quizId: string, newTitle: string, folderId: string): Promise<any> { public async copyQuiz(quizId: string, newTitle: string, folderId: string): Promise<ApiResponse> {
try { try {
console.log(quizId, newTitle), folderId; console.log(quizId, newTitle, folderId);
return "Route not implemented yet!"; return "Route not implemented yet!";
} catch (error) { } catch (error) {
@ -741,7 +748,7 @@ class ApiService {
} }
} }
async ShareQuiz(quizId: string, email: string): Promise<any> { async ShareQuiz(quizId: string, email: string): Promise<ApiResponse> {
try { try {
if (!quizId || !email) { if (!quizId || !email) {
throw new Error(`quizId and email are required.`); throw new Error(`quizId and email are required.`);
@ -800,7 +807,7 @@ class ApiService {
} }
} }
async receiveSharedQuiz(quizId: string, folderId: string): Promise<any> { async receiveSharedQuiz(quizId: string, folderId: string): Promise<ApiResponse> {
try { try {
if (!quizId || !folderId) { if (!quizId || !folderId) {
throw new Error(`quizId and folderId are required.`); throw new Error(`quizId and folderId are required.`);
@ -869,7 +876,8 @@ class ApiService {
if (axios.isAxiosError(error)) { if (axios.isAxiosError(error)) {
const err = error as AxiosError; const err = error as AxiosError;
const data = err.response?.data as { error: string } | undefined; 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.` return `ERROR : Une erreur inattendue s'est produite.`

View file

@ -1,4 +1,4 @@
export function escapeForGIFT(link: string): string { export function escapeForGIFT(link: string): string {
const specialChars = /[{}#~=<>\:]/g; const specialChars = /[{}#~=<>\\:]/g;
return link.replace(specialChars, (match) => `\\${match}`); return link.replace(specialChars, (match) => `\\${match}`);
} }

View file

@ -18,7 +18,7 @@ exports.USER_ALREADY_EXISTS = {
} }
exports.LOGIN_CREDENTIALS_ERROR = { exports.LOGIN_CREDENTIALS_ERROR = {
message: 'L\'email et le mot de passe ne correspondent pas.', message: 'L\'email et le mot de passe ne correspondent pas.',
code: 400 code: 401
} }
exports.GENERATE_PASSWORD_ERROR = { exports.GENERATE_PASSWORD_ERROR = {
message: 'Une erreur s\'est produite lors de la création d\'un nouveau mot de passe.', 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 badRequest(res, message) {400
// static unauthorized(res, message) {401 // static unauthorized(res, message) {401
// static notFound(res, message) {404 // static notFound(res, message) {404
// static serverError(res, message) {505 // static serverError(res, message) {505