From 226cc24532952091af6a64da81b040f0c31cbef3 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:12:47 -0500 Subject: [PATCH 01/25] =?UTF-8?q?Indiquer=20le=20num=C3=A9ro=20de=20la=20q?= =?UTF-8?q?uestion=20dans=20l'affichage=20enseignant=20Fixes=20#207?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/package-lock.json | 114 +++++++++--------- client/package.json | 4 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 67 ++++++---- .../pages/Teacher/ManageRoom/manageRoom.css | 4 +- 4 files changed, 106 insertions(+), 83 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index edc30d8..638fac1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,7 +14,7 @@ "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.1.0", + "@mui/icons-material": "^6.4.1", "@mui/lab": "^5.0.0-alpha.153", "@mui/material": "^6.1.0", "@types/uuid": "^9.0.7", @@ -2090,15 +2090,15 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", - "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.1", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, @@ -3352,9 +3352,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz", - "integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.1.tgz", + "integrity": "sha512-SfDLWMV5b5oXgDf3NTa2hCTPC1d2defhDH2WgFKmAiejC4mSfXYbyi+AFCLzpizauXhgBm8OaZy9BHKnrSpahQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -3362,9 +3362,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.6.tgz", - "integrity": "sha512-5r9urIL2lxXb/sPN3LFfFYEibsXJUb986HhhIeu1gOcte460pwdSiEhBSxkAuyT8Dj7jvu9MjqSBmSumQELo8A==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.1.tgz", + "integrity": "sha512-wsxFcUTQxt4s+7Bg4GgobqRjyaHLmZGNOs+HJpbwrwmLbT6mhIJxhpqsKzzWq9aDY8xIe7HCjhpH7XI5UD6teA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -3377,7 +3377,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.1.6", + "@mui/material": "^6.4.1", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -3461,22 +3461,22 @@ } }, "node_modules/@mui/material": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz", - "integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.1.tgz", + "integrity": "sha512-MFBfia6UiKxyoLeGkAh8M15bkeDmfnsUTMRJd/vTQue6YQ8AQ6lw9HqDthyYghzDEWIvZO/lQQzLrZE8XwNJLA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.1.6", - "@mui/system": "^6.1.6", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.6", + "@mui/core-downloads-tracker": "^6.4.1", + "@mui/system": "^6.4.1", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.1", "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.11", + "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.3.1", + "react-is": "^19.0.0", "react-transition-group": "^4.4.5" }, "engines": { @@ -3489,7 +3489,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.6", + "@mui/material-pigment-css": "^6.4.1", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3510,13 +3510,13 @@ } }, "node_modules/@mui/material/node_modules/@mui/private-theming": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz", - "integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.1.tgz", + "integrity": "sha512-DcT7mwK89owwgcEuiE7w458te4CIjHbYWW6Kn6PiR6eLtxBsoBYphA968uqsQAOBQDpbYxvkuFLwhgk4bxoN/Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.1.6", + "@mui/utils": "^6.4.1", "prop-types": "^15.8.1" }, "engines": { @@ -3537,14 +3537,14 @@ } }, "node_modules/@mui/material/node_modules/@mui/styled-engine": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz", - "integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz", + "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.1", - "@emotion/serialize": "^1.3.2", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3571,16 +3571,16 @@ } }, "node_modules/@mui/material/node_modules/@mui/system": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz", - "integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.1.tgz", + "integrity": "sha512-rgQzgcsHCTtzF9MZ+sL0tOhf2ZBLazpjrujClcb4Siju5lTrK0xX4PsiropActzCemNfM+mOu+0jezAVnfRK8g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.1.6", - "@mui/styled-engine": "^6.1.6", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.6", + "@mui/private-theming": "^6.4.1", + "@mui/styled-engine": "^6.4.0", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.1", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3611,17 +3611,17 @@ } }, "node_modules/@mui/material/node_modules/@mui/utils": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz", - "integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.1.tgz", + "integrity": "sha512-iQUDUeYh87SvR4lVojaRaYnQix8BbRV51MxaV6MBmqthecQoxwSbS5e2wnbDJUeFxY2ppV505CiqPLtd0OWkqw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/types": "^7.2.19", - "@types/prop-types": "^15.7.13", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.3.1" + "react-is": "^19.0.0" }, "engines": { "node": ">=14.0.0" @@ -3640,6 +3640,12 @@ } } }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, "node_modules/@mui/private-theming": { "version": "5.16.14", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz", @@ -3740,9 +3746,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.19", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", - "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", "license": "MIT", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -4650,9 +4656,9 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", "license": "MIT" }, "node_modules/@types/react": { @@ -4686,11 +4692,11 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", - "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", - "dependencies": { + "peerDependencies": { "@types/react": "*" } }, diff --git a/client/package.json b/client/package.json index 052063d..7457f22 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,7 @@ "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.1.0", + "@mui/icons-material": "^6.4.1", "@mui/lab": "^5.0.0-alpha.153", "@mui/material": "^6.1.0", "@types/uuid": "^9.0.7", @@ -69,4 +69,4 @@ "vite": "^5.4.5", "vite-plugin-environment": "^1.1.3" } -} \ No newline at end of file +} diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 95e8464..8b3cf65 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -8,6 +8,7 @@ import LiveResultsComponent from 'src/components/LiveResults/LiveResults'; // import { QuestionService } from '../../../services/QuestionService'; import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService'; import { QuizType } from '../../../Types/QuizType'; +import GroupIcon from '@mui/icons-material/Group'; import './manageRoom.css'; import { ENV_VARIABLES } from 'src/constants'; @@ -32,7 +33,8 @@ const ManageRoom: React.FC = () => { const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher'); const [connectingError, setConnectingError] = useState(''); const [currentQuestion, setCurrentQuestion] = useState(undefined); - + const [quizStarted, setQuizStarted] = useState(false); + useEffect(() => { if (quizId.id) { const fetchquiz = async () => { @@ -144,7 +146,7 @@ const ManageRoom: React.FC = () => { console.log('Quiz questions not found (cannot update answers without them).'); return; } - + // Update the students state using the functional form of setStudents setStudents((prevStudents) => { // print the list of current student names @@ -152,7 +154,7 @@ const ManageRoom: React.FC = () => { prevStudents.forEach((student) => { console.log(student.name); }); - + let foundStudent = false; const updatedStudents = prevStudents.map((student) => { console.log(`Comparing ${student.id} to ${idUser}`); @@ -172,7 +174,7 @@ const ManageRoom: React.FC = () => { updatedAnswers = [...student.answers, newAnswer]; } return { ...student, answers: updatedAnswers }; - } + } return student; }); if (!foundStudent) { @@ -315,13 +317,18 @@ const ManageRoom: React.FC = () => { if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) { // TODO: This error happens when token expires! Need to handle it properly console.log(`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`); + setQuizStarted(true); + return; } switch (quizMode) { case 'student': + setQuizStarted(true); return launchStudentMode(); case 'teacher': + setQuizStarted(true); return launchTeacherMode(); + } }; @@ -440,9 +447,19 @@ const ManageRoom: React.FC = () => { askConfirm message={`Êtes-vous sûr de vouloir quitter?`} /> -
-
Salle: {roomName}
-
Utilisateurs: {students.length}/60
+ + + +
+
+
Salle: {roomName}
+
+ {quizStarted && ( +
+ + {students.length}/60 +
+ )}
@@ -454,8 +471,8 @@ const ManageRoom: React.FC = () => { {quizQuestions ? (
-
{quiz?.title}
+ Question {Number(currentQuestion?.question.id)}/{quizQuestions?.length} {quizMode === 'teacher' && ( @@ -492,23 +509,23 @@ const ManageRoom: React.FC = () => {
{quizMode === 'teacher' && ( -
-
- -
-
- -
-
)} +
+
+ +
+
+ +
+
)}
diff --git a/client/src/pages/Teacher/ManageRoom/manageRoom.css b/client/src/pages/Teacher/ManageRoom/manageRoom.css index ffb83fa..ad870a9 100644 --- a/client/src/pages/Teacher/ManageRoom/manageRoom.css +++ b/client/src/pages/Teacher/ManageRoom/manageRoom.css @@ -18,8 +18,8 @@ display: flex; flex-direction: column; - justify-content: center; - align-items: center; + justify-content: flex-end; + align-items: flex-end; } From b49057a8ffd30926a35e8dbeaf847d8cda02b91f Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:23:15 -0500 Subject: [PATCH 02/25] Only show the current question for teacher mode --- client/src/pages/Teacher/ManageRoom/ManageRoom.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 8b3cf65..65c56cb 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -472,7 +472,11 @@ const ManageRoom: React.FC = () => {
{quiz?.title}
- Question {Number(currentQuestion?.question.id)}/{quizQuestions?.length} + {!isNaN(Number(currentQuestion?.question.id)) && ( + + Question {Number(currentQuestion?.question.id)}/{quizQuestions?.length} + + )} {quizMode === 'teacher' && ( From d7b33423e8390b04b7651ef24c4431daae82bf39 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:53:40 -0500 Subject: [PATCH 03/25] =?UTF-8?q?Probl=C3=A8me=20dans=20l'affichage=20des?= =?UTF-8?q?=20r=C3=A9troactions=20=20Katek=20pour=20les=20questions=20r?= =?UTF-8?q?=C3=A9ponses=20courtes=20et=20num=C3=A9riques=20Fixes=20#220?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/QuestionsDisplay/questionStyle.css | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/QuestionsDisplay/questionStyle.css b/client/src/components/QuestionsDisplay/questionStyle.css index 3958e92..d731d4c 100644 --- a/client/src/components/QuestionsDisplay/questionStyle.css +++ b/client/src/components/QuestionsDisplay/questionStyle.css @@ -27,7 +27,6 @@ } .question-wrapper .katex { - display: block; text-align: center; } From 44f36a3a6bbe0d5e2c06339395f796bf9d617ef8 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:47:05 -0500 Subject: [PATCH 04/25] =?UTF-8?q?Probl=C3=A8me=20dans=20l'affichage=20des?= =?UTF-8?q?=20r=C3=A9troactions=20=20Katek=20pour=20les=20pr=C3=A9visualis?= =?UTF-8?q?ations=20(=C3=89diteur=20de=20quiz)=20Fixes=20#221?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/QuestionsDisplay/questionStyle.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/QuestionsDisplay/questionStyle.css b/client/src/components/QuestionsDisplay/questionStyle.css index 3958e92..8bbe92c 100644 --- a/client/src/components/QuestionsDisplay/questionStyle.css +++ b/client/src/components/QuestionsDisplay/questionStyle.css @@ -120,9 +120,9 @@ } .feedback-container { - margin-left: 1.1rem; - display: inline-flex !important; /* override the parent */ + display: inline-block !important; /* override the parent */ align-items: center; + margin-left: 1.1rem; position: relative; padding: 0 0.5rem; background-color: hsl(43, 100%, 94%); From 8edc77d21729a08c34df77947ac6ee1f7336c231 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:21:32 -0500 Subject: [PATCH 05/25] =?UTF-8?q?[Katex]=20La=20L'affichage=20du=20tableau?= =?UTF-8?q?=20AVEC=20r=C3=A9ponses=20pose=20probl=C3=A8me=20lorsqu'il=20y?= =?UTF-8?q?=20a=20des=20=C3=A9quations=20Fixes=20#226?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LiveResults/LiveResults.test.tsx | 163 ++++++++++++++++++ .../components/LiveResults/LiveResults.tsx | 2 +- 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 client/src/__tests__/components/LiveResults/LiveResults.test.tsx diff --git a/client/src/__tests__/components/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx new file mode 100644 index 0000000..ce6244e --- /dev/null +++ b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import LiveResults from 'src/components/LiveResults/LiveResults'; +import { QuestionType } from 'src/Types/QuestionType'; +import { StudentType } from 'src/Types/StudentType'; +import { Socket } from 'socket.io-client'; +import { BaseQuestion,parse } from 'gift-pegjs'; + +const mockSocket: Socket = { + on: jest.fn(), + off: jest.fn(), + emit: jest.fn(), + connect: jest.fn(), + disconnect: jest.fn(), +} as unknown as Socket; + +const mockGiftQuestions = parse( + `::Sample Question 1:: Question stem + { + =Choice 1 + ~Choice 2 + }`); + +const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { + if (question.type !== "Category") + question.id = (index + 1).toString(); + const newMockQuestion = question; + return {question : newMockQuestion as BaseQuestion}; +}); + +const mockStudents: StudentType[] = [ + { id: '1', name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Choice 1', isCorrect: true }] }, + { id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: 'Choice 2', isCorrect: false }] }, +]; + +describe('LiveResults', () => { + test('renders the component with questions and students', () => { + render( + + ); + expect(screen.getByText(`Q${1}`)).toBeInTheDocument(); + + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); + + // Check if the component renders the students + mockStudents.forEach((student) => { + expect(screen.getByText(student.name)).toBeInTheDocument(); + }); + }); + + test('toggles the display of usernames', () => { + render( + + ); + + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); + + // Check if the usernames are shown again + mockStudents.forEach((student) => { + expect(screen.getByText(student.name)).toBeInTheDocument(); + }); + }); + +}); +test('calculates and displays the correct student grades', () => { + render( + + ); + + + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); + + // Check if the student grades are calculated and displayed correctly + mockStudents.forEach((student) => { + const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100; + expect(screen.getByText(`${grade.toFixed()} %`)).toBeInTheDocument(); + }); +}); + +test('calculates and displays the class average', () => { + render( + + ); + + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); + + // Calculate the class average + const totalGrades = mockStudents.reduce((total, student) => { + return total + (student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100); + }, 0); + const classAverage = totalGrades / mockStudents.length; + + // Check if the class average is displayed correctly + const classAverageElements = screen.getAllByText(`${classAverage.toFixed()} %`); + const classAverageElement = classAverageElements.find((element) => { + return element.closest('td')?.classList.contains('MuiTableCell-footer'); + }); + expect(classAverageElement).toBeInTheDocument(); +}); + +test('displays the correct answers per question', () => { + render( + + ); + + // Check if the correct answers per question are displayed correctly + mockQuestions.forEach((_, index) => { + const correctAnswers = mockStudents.filter(student => student.answers.some(answer => answer.idQuestion === index + 1 && answer.isCorrect)).length; + const correctAnswersPercentage = (correctAnswers / mockStudents.length) * 100; + const correctAnswersElements = screen.getAllByText(`${correctAnswersPercentage.toFixed()} %`); + const correctAnswersElement = correctAnswersElements.find((element) => { + return element.closest('td')?.classList.contains('MuiTableCell-root'); + }); + expect(correctAnswersElement).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 6bcd7ce..4784750 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -370,7 +370,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti } > {showCorrectAnswers ? ( -
{formatLatex(answerText)}
+
) : isCorrect ? ( ) : ( From 1aba1d609831a1ee0413c4b65f57c0aa67c3239d Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Tue, 4 Feb 2025 23:14:10 -0500 Subject: [PATCH 06/25] =?UTF-8?q?Indiquer=20le=20num=C3=A9ro=20de=20la=20q?= =?UTF-8?q?uestion=20dans=20l'affichage=20enseignant=20Fixes=20#207?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/ManageRoom/ManageRoom.test.tsx | 253 ++++++++++++++++++ .../LaunchQuizDialog/LaunchQuizDialog.tsx | 4 +- 2 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx diff --git a/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx b/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx new file mode 100644 index 0000000..684f92e --- /dev/null +++ b/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx @@ -0,0 +1,253 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { MemoryRouter, useNavigate, useParams } from 'react-router-dom'; +import ManageRoom from 'src/pages/Teacher/ManageRoom/ManageRoom'; +import { StudentType } from 'src/Types/StudentType'; +import { QuizType } from 'src/Types/QuizType'; +import webSocketService, { AnswerReceptionFromBackendType } from 'src/services/WebsocketService'; +import ApiService from 'src/services/ApiService'; +import { Socket } from 'socket.io-client'; + +jest.mock('src/services/WebsocketService'); +jest.mock('src/services/ApiService'); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn(), + useParams: jest.fn(), +})); + +const mockSocket = { + on: jest.fn(), + off: jest.fn(), + emit: jest.fn(), + connect: jest.fn(), + disconnect: jest.fn(), +} as unknown as Socket; + +const mockQuiz: QuizType = { + _id: 'test-quiz-id', + title: 'Test Quiz', + content: ['::Q1:: Question 1 { =Answer1 ~Answer2 }', '::Q2:: Question 2 { =Answer1 ~Answer2 }'], + folderId: 'folder-id', + folderName: 'folder-name', + userId: 'user-id', + created_at: new Date(), + updated_at: new Date() +}; + +const mockStudents: StudentType[] = [ + { id: '1', name: 'Student 1', answers: [] }, + { id: '2', name: 'Student 2', answers: [] }, +]; + +const mockAnswerData: AnswerReceptionFromBackendType = { + answer: 'Answer1', + idQuestion: 1, + idUser: '1', + username: 'Student 1', +}; + +describe('ManageRoom', () => { + const navigate = jest.fn(); + const useParamsMock = useParams as jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + (useNavigate as jest.Mock).mockReturnValue(navigate); + useParamsMock.mockReturnValue({ id: 'test-quiz-id' }); + (ApiService.getQuiz as jest.Mock).mockResolvedValue(mockQuiz); + (webSocketService.connect as jest.Mock).mockReturnValue(mockSocket); + }); + + test('prepares to launch quiz and fetches quiz data', async () => { + await act(async () => { + render( + + + + ); + }); + + await act(async () => { + const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; + createSuccessCallback('test-room-name'); + }); + + await waitFor(() => { + expect(ApiService.getQuiz).toHaveBeenCalledWith('test-quiz-id'); + }); + + const launchButton = screen.getByText('Lancer'); + fireEvent.click(launchButton); + + const rythmeButton = screen.getByText('Rythme du professeur'); + fireEvent.click(rythmeButton); + + const secondLaunchButton = screen.getAllByText('Lancer'); + fireEvent.click(secondLaunchButton[1]); + + await waitFor(() => { + expect(screen.getByText('Test Quiz')).toBeInTheDocument(); + expect(screen.getByText('Salle: test-room-name')).toBeInTheDocument(); + expect(screen.getByText('0/60')).toBeInTheDocument(); + expect(screen.getByText('Question 1/2')).toBeInTheDocument(); + }); + }); + + test('handles create-success event', async () => { + await act(async () => { + render( + + + + ); + }); + + await act(async () => { + const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; + createSuccessCallback('test-room-name'); + }); + + await waitFor(() => { + expect(screen.getByText('Salle: test-room-name')).toBeInTheDocument(); + }); + }); + + test('handles user-joined event', async () => { + await act(async () => { + render( + + + + ); + }); + + await act(async () => { + const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; + createSuccessCallback('test-room-name'); + }); + + await act(async () => { + const userJoinedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-joined')[1]; + userJoinedCallback(mockStudents[0]); + }); + + await waitFor(() => { + expect(screen.getByText('Student 1')).toBeInTheDocument(); + + }); + + const launchButton = screen.getByText('Lancer'); + fireEvent.click(launchButton); + + const rythmeButton = screen.getByText('Rythme du professeur'); + fireEvent.click(rythmeButton); + + const secondLaunchButton = screen.getAllByText('Lancer'); + fireEvent.click(secondLaunchButton[1]); + + await waitFor(() => { + expect(screen.getByText('1/60')).toBeInTheDocument(); + + }); + }); + + test('handles submit-answer-room event', async () => { + const consoleSpy = jest.spyOn(console, 'log'); + await act(async () => { + render( + + + + ); + }); + + await act(async () => { + const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; + createSuccessCallback('test-room-name'); + }); + + const launchButton = screen.getByText('Lancer'); + fireEvent.click(launchButton); + + const rythmeButton = screen.getByText('Rythme du professeur'); + fireEvent.click(rythmeButton); + + const secondLaunchButton = screen.getAllByText('Lancer'); + fireEvent.click(secondLaunchButton[1]); + + await act(async () => { + const userJoinedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-joined')[1]; + userJoinedCallback(mockStudents[0]); + }); + + await act(async () => { + const submitAnswerCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'submit-answer-room')[1]; + submitAnswerCallback(mockAnswerData); + }); + + await waitFor(() => { + expect(consoleSpy).toHaveBeenCalledWith('Received answer from Student 1 for question 1: Answer1'); + }); + + consoleSpy.mockRestore(); + }); + + test('handles next question', async () => { + await act(async () => { + render( + + + + ); + }); + + await act(async () => { + const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; + createSuccessCallback('test-room-name'); + }); + + const launchButton = screen.getByText('Lancer'); + fireEvent.click(launchButton); + + const rythmeButton = screen.getByText('Rythme du professeur'); + fireEvent.click(rythmeButton); + + const secondLaunchButton = screen.getAllByText('Lancer'); + fireEvent.click(secondLaunchButton[1]); + + const nextQuestionButton = screen.getByText('Prochaine question'); + fireEvent.click(nextQuestionButton); + + await waitFor(() => { + expect(screen.getByText('Question 2/2')).toBeInTheDocument(); + }); + }); + + test('handles disconnect', async () => { + await act(async () => { + render( + + + + ); + }); + + await act(async () => { + const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; + createSuccessCallback('test-room-name'); + }); + + const disconnectButton = screen.getByText('Quitter'); + fireEvent.click(disconnectButton); + + const confirmButton = screen.getAllByText('Confirmer'); + fireEvent.click(confirmButton[1]); + + await waitFor(() => { + expect(webSocketService.disconnect).toHaveBeenCalled(); + expect(navigate).toHaveBeenCalledWith('/teacher/dashboard'); + }); + }); +}); \ No newline at end of file diff --git a/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx b/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx index 3bd307b..3aaf401 100644 --- a/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx +++ b/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx @@ -47,10 +47,10 @@ const LaunchQuizDialog: React.FC = ({ open, handleOnClose, launchQuiz, se From 705b0f1ddb425e0d9ba809703172d6cce2770c3e Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:29:36 -0500 Subject: [PATCH 07/25] =?UTF-8?q?[Katex]=20La=20L'affichage=20du=20tableau?= =?UTF-8?q?=20AVEC=20r=C3=A9ponses=20pose=20probl=C3=A8me=20lorsqu'il=20y?= =?UTF-8?q?=20a=20des=20=C3=A9quations=20Fixes=20#226?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/LiveResults/LiveResults.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 4784750..4825142 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -4,6 +4,7 @@ import { Socket } from 'socket.io-client'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; import { QuestionType } from '../../Types/QuestionType'; +import DOMPurify from 'dompurify'; import './liveResult.css'; import { @@ -370,7 +371,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti } > {showCorrectAnswers ? ( -
+
) : isCorrect ? ( ) : ( From 99f4f5c7c394db0bd2f6941609f52820b9444678 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:50:42 -0500 Subject: [PATCH 08/25] =?UTF-8?q?Affichage=20du=20tableau=20AVEC=20r=C3=A9?= =?UTF-8?q?ponses=20longue=20devrait=20=C3=AAtre=20adapt=C3=A9s=20Fixes=20?= =?UTF-8?q?#228?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/LiveResults/LiveResults.tsx | 93 ++++++++++++++++++- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 6bcd7ce..4731aa8 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -7,6 +7,7 @@ import { QuestionType } from '../../Types/QuestionType'; import './liveResult.css'; import { + Button, FormControlLabel, FormGroup, Paper, @@ -45,6 +46,36 @@ interface LiveResultsProps { const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); + const [showFullAnswer, setShowFullAnswer] = useState(false); + const [selectedAnswer, setSelectedAnswer] = useState(''); + + const handleShowAnswer = (answer: string) => { + setSelectedAnswer(answer); + setShowFullAnswer(true); + }; + + const renderAnswerCell = (answer: string) => { + if (!answer) return null; + const shortAnswer = answer.length > 20 ? answer.slice(0, 20) : answer; + + return ( + <> + {shortAnswer} + {answer.length > 20 && ( + + )} + + ); + }; // const [students, setStudents] = useState(initialStudents); // const [studentResultsMap, setStudentResultsMap] = useState>(new Map()); @@ -265,6 +296,8 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti // } return ( + +
Résultats du quiz
@@ -304,6 +337,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti {Array.from({ length: maxQuestions }, (_, index) => ( = ({ questions, showSelectedQuesti ); const answerText = answer ? answer.answer.toString() : ''; const isCorrect = answer ? answer.isCorrect : false; - return ( = ({ questions, showSelectedQuesti } > {showCorrectAnswers ? ( -
{formatLatex(answerText)}
+
{renderAnswerCell(formatLatex(answerText))}
) : isCorrect ? ( ) : ( @@ -435,7 +469,60 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti -
+
+ {showFullAnswer && ( +
setShowFullAnswer(false)} + style={{ + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: 'rgba(0,0,0,0.3)', + zIndex: 9999,}}> + e.stopPropagation()} + style={{ + position: 'fixed', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + border: 'none', + padding: '1rem', + background: '#fff', + boxShadow: '0 2px 10px rgba(0, 0, 0, 0.3)', + minWidth: '300px', + minHeight: '200px', + }} + > +
+

{selectedAnswer}

+ +
+
+
+)}
); }; From 4356769d439afc67013cb987c0153e0d133eb0ee Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 5 Feb 2025 18:48:52 -0500 Subject: [PATCH 09/25] =?UTF-8?q?Fen=C3=AAtre=20de=20r=C3=A9troaction=20n'?= =?UTF-8?q?est=20pas=20compatible=20avec=20les=20longues=20r=C3=A9ponses?= =?UTF-8?q?=20Fixes=20#230?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/package-lock.json | 199 ++++++------------ .../TeacherModeQuiz/TeacherModeQuiz.tsx | 28 ++- 2 files changed, 96 insertions(+), 131 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 8105575..627aa00 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -2549,7 +2549,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2568,7 +2568,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2578,7 +2578,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.5", @@ -2593,7 +2593,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2604,7 +2604,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2617,7 +2617,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2630,7 +2630,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2654,7 +2654,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2665,7 +2665,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=18" @@ -2678,7 +2678,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2691,7 +2691,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2701,7 +2701,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2711,7 +2711,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.10.0", @@ -2818,7 +2818,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2828,7 +2828,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2842,7 +2842,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2856,7 +2856,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -3853,7 +3853,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3867,7 +3866,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3881,7 +3879,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3895,7 +3892,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3909,7 +3905,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3923,7 +3918,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3937,7 +3931,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3951,7 +3944,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3965,7 +3957,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3979,7 +3970,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3993,7 +3983,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4007,7 +3996,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4021,7 +4009,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4035,7 +4022,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4049,7 +4035,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4063,7 +4048,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4077,7 +4061,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4091,7 +4074,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4132,7 +4114,7 @@ "version": "1.7.40", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.40.tgz", "integrity": "sha512-0HIzM5vigVT5IvNum+pPuST9p8xFhN6mhdIKju7qYYeNuZG78lwms/2d8WgjTJJlzp6JlPguXGrMMNzjQw0qNg==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4174,7 +4156,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4191,7 +4172,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4208,7 +4188,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4225,7 +4204,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4242,7 +4220,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4259,7 +4236,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4276,7 +4252,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4293,7 +4268,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4310,7 +4284,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4327,7 +4300,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4341,14 +4313,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -4542,7 +4514,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -4642,7 +4613,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/katex": { @@ -5023,7 +4994,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5057,7 +5028,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5132,7 +5103,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, + "devOptional": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -5946,7 +5917,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6117,7 +6088,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -6667,7 +6638,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -6825,7 +6796,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -6842,7 +6813,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6855,7 +6826,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -6869,7 +6840,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6880,7 +6851,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6893,7 +6864,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6906,7 +6877,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", @@ -6924,7 +6895,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6950,7 +6921,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -6963,7 +6934,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -7056,7 +7027,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -7091,14 +7062,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fastq": { @@ -7124,7 +7095,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -7178,7 +7149,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -7195,7 +7166,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -7209,7 +7180,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -7474,7 +7445,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7762,7 +7733,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7808,7 +7779,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -8311,7 +8282,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -9349,7 +9320,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9419,7 +9390,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -9432,14 +9403,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json5": { @@ -9511,7 +9482,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -9541,7 +9512,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -9561,7 +9532,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -9598,7 +9569,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/longest-streak": { @@ -10375,7 +10346,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/node-int64": { @@ -10555,7 +10526,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -10591,7 +10562,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -10607,7 +10578,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -10675,7 +10646,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -10837,7 +10808,6 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -10866,7 +10836,6 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, "funding": [ { "type": "github", @@ -10885,7 +10854,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -11374,7 +11343,6 @@ "version": "4.24.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -11576,7 +11544,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11589,7 +11557,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11735,7 +11703,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -11966,7 +11933,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -12234,7 +12201,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -12346,7 +12313,6 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12580,7 +12546,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -12669,7 +12635,6 @@ "version": "5.4.14", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", - "dev": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -12807,7 +12772,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12824,7 +12788,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12841,7 +12804,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12858,7 +12820,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12875,7 +12836,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12892,7 +12852,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12909,7 +12868,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12926,7 +12884,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12943,7 +12900,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12960,7 +12916,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12977,7 +12932,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12994,7 +12948,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13011,7 +12964,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13028,7 +12980,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13045,7 +12996,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13062,7 +13012,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13079,7 +13028,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13096,7 +13044,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13113,7 +13060,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13130,7 +13076,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13147,7 +13092,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13164,7 +13108,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13181,7 +13124,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13195,7 +13137,6 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -13405,7 +13346,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -13509,7 +13450,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13666,7 +13607,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx index ae2d382..4dfdf6a 100644 --- a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx +++ b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx @@ -23,8 +23,22 @@ const TeacherModeQuiz: React.FC = ({ }) => { const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false); const [isFeedbackDialogOpen, setIsFeedbackDialogOpen] = useState(false); - const [feedbackMessage, setFeedbackMessage] = useState(''); + const [feedbackMessage, setFeedbackMessage] = useState(''); + + const renderFeedbackMessage = (answer: string) => { + if(answer === 'true' || answer === 'false'){ + return ( + Votre réponse est: {answer==="true" ? 'Vrai' : 'Faux'} + ) + } + else{ + return ( + + Votre réponse est: {answer.toString()} + + );} + }; useEffect(() => { setIsAnswerSubmitted(false); }, [questionInfos]); @@ -32,7 +46,7 @@ const TeacherModeQuiz: React.FC = ({ const handleOnSubmitAnswer = (answer: string | number | boolean) => { const idQuestion = Number(questionInfos.question.id) || -1; submitAnswer(answer, idQuestion); - setFeedbackMessage(`Votre réponse est "${answer.toString()}".`); + setFeedbackMessage(renderFeedbackMessage(answer.toString())); setIsFeedbackDialogOpen(true); }; @@ -74,7 +88,17 @@ const TeacherModeQuiz: React.FC = ({ > Rétroaction +
{feedbackMessage} +
Question :
+
+ Date: Wed, 5 Feb 2025 18:53:05 -0500 Subject: [PATCH 10/25] Undo package-lock changes --- client/package-lock.json | 313 +++++++++++++++++++++++---------------- 1 file changed, 189 insertions(+), 124 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 627aa00..3c24ffc 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,7 +14,7 @@ "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.1.0", + "@mui/icons-material": "^6.4.1", "@mui/lab": "^5.0.0-alpha.153", "@mui/material": "^6.1.0", "@types/uuid": "^9.0.7", @@ -2093,15 +2093,15 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", - "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.1", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, @@ -2549,7 +2549,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2568,7 +2568,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2578,7 +2578,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.5", @@ -2593,7 +2593,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2604,7 +2604,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2617,7 +2617,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2630,7 +2630,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2654,7 +2654,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2665,7 +2665,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2678,7 +2678,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2691,7 +2691,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2701,7 +2701,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2711,7 +2711,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.10.0", @@ -2818,7 +2818,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2828,7 +2828,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2842,7 +2842,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2856,7 +2856,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -3355,9 +3355,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz", - "integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.1.tgz", + "integrity": "sha512-SfDLWMV5b5oXgDf3NTa2hCTPC1d2defhDH2WgFKmAiejC4mSfXYbyi+AFCLzpizauXhgBm8OaZy9BHKnrSpahQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -3365,9 +3365,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.6.tgz", - "integrity": "sha512-5r9urIL2lxXb/sPN3LFfFYEibsXJUb986HhhIeu1gOcte460pwdSiEhBSxkAuyT8Dj7jvu9MjqSBmSumQELo8A==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.1.tgz", + "integrity": "sha512-wsxFcUTQxt4s+7Bg4GgobqRjyaHLmZGNOs+HJpbwrwmLbT6mhIJxhpqsKzzWq9aDY8xIe7HCjhpH7XI5UD6teA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -3380,7 +3380,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.1.6", + "@mui/material": "^6.4.1", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -3464,22 +3464,22 @@ } }, "node_modules/@mui/material": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz", - "integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.1.tgz", + "integrity": "sha512-MFBfia6UiKxyoLeGkAh8M15bkeDmfnsUTMRJd/vTQue6YQ8AQ6lw9HqDthyYghzDEWIvZO/lQQzLrZE8XwNJLA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.1.6", - "@mui/system": "^6.1.6", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.6", + "@mui/core-downloads-tracker": "^6.4.1", + "@mui/system": "^6.4.1", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.1", "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.11", + "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.3.1", + "react-is": "^19.0.0", "react-transition-group": "^4.4.5" }, "engines": { @@ -3492,7 +3492,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.6", + "@mui/material-pigment-css": "^6.4.1", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3513,13 +3513,13 @@ } }, "node_modules/@mui/material/node_modules/@mui/private-theming": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz", - "integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.1.tgz", + "integrity": "sha512-DcT7mwK89owwgcEuiE7w458te4CIjHbYWW6Kn6PiR6eLtxBsoBYphA968uqsQAOBQDpbYxvkuFLwhgk4bxoN/Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.1.6", + "@mui/utils": "^6.4.1", "prop-types": "^15.8.1" }, "engines": { @@ -3540,14 +3540,14 @@ } }, "node_modules/@mui/material/node_modules/@mui/styled-engine": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz", - "integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz", + "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.1", - "@emotion/serialize": "^1.3.2", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3574,16 +3574,16 @@ } }, "node_modules/@mui/material/node_modules/@mui/system": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz", - "integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.1.tgz", + "integrity": "sha512-rgQzgcsHCTtzF9MZ+sL0tOhf2ZBLazpjrujClcb4Siju5lTrK0xX4PsiropActzCemNfM+mOu+0jezAVnfRK8g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.1.6", - "@mui/styled-engine": "^6.1.6", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.6", + "@mui/private-theming": "^6.4.1", + "@mui/styled-engine": "^6.4.0", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.1", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3614,17 +3614,17 @@ } }, "node_modules/@mui/material/node_modules/@mui/utils": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz", - "integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.1.tgz", + "integrity": "sha512-iQUDUeYh87SvR4lVojaRaYnQix8BbRV51MxaV6MBmqthecQoxwSbS5e2wnbDJUeFxY2ppV505CiqPLtd0OWkqw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/types": "^7.2.19", - "@types/prop-types": "^15.7.13", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.3.1" + "react-is": "^19.0.0" }, "engines": { "node": ">=14.0.0" @@ -3643,6 +3643,12 @@ } } }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, "node_modules/@mui/private-theming": { "version": "5.16.14", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz", @@ -3743,9 +3749,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.19", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", - "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", "license": "MIT", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3853,6 +3859,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3866,6 +3873,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3879,6 +3887,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3892,6 +3901,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3905,6 +3915,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3918,6 +3929,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3931,6 +3943,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3944,6 +3957,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3957,6 +3971,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3970,6 +3985,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3983,6 +3999,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3996,6 +4013,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4009,6 +4027,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4022,6 +4041,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4035,6 +4055,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4048,6 +4069,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4061,6 +4083,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4074,6 +4097,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4114,7 +4138,7 @@ "version": "1.7.40", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.40.tgz", "integrity": "sha512-0HIzM5vigVT5IvNum+pPuST9p8xFhN6mhdIKju7qYYeNuZG78lwms/2d8WgjTJJlzp6JlPguXGrMMNzjQw0qNg==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4156,6 +4180,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4172,6 +4197,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4188,6 +4214,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4204,6 +4231,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4220,6 +4248,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4236,6 +4265,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4252,6 +4282,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4268,6 +4299,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4284,6 +4316,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4300,6 +4333,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4313,14 +4347,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -4514,6 +4548,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -4613,7 +4648,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/katex": { @@ -4653,9 +4688,9 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", "license": "MIT" }, "node_modules/@types/react": { @@ -4689,11 +4724,11 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", - "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", - "dependencies": { + "peerDependencies": { "@types/react": "*" } }, @@ -4994,7 +5029,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5028,7 +5063,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5103,7 +5138,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "devOptional": true, + "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -5917,7 +5952,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6088,7 +6123,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -6638,7 +6673,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -6796,7 +6831,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -6813,7 +6848,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6826,7 +6861,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -6840,7 +6875,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6851,7 +6886,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6864,7 +6899,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6877,7 +6912,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", @@ -6895,7 +6930,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6921,7 +6956,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -6934,7 +6969,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -7027,7 +7062,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -7062,14 +7097,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fastq": { @@ -7095,7 +7130,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -7149,7 +7184,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -7166,7 +7201,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -7180,7 +7215,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -7445,7 +7480,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7733,7 +7768,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7779,7 +7814,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -8282,7 +8317,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -9320,7 +9355,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9390,7 +9425,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -9403,14 +9438,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json5": { @@ -9482,7 +9517,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -9512,7 +9547,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -9532,7 +9567,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -9569,7 +9604,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/longest-streak": { @@ -10346,7 +10381,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/node-int64": { @@ -10526,7 +10561,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -10562,7 +10597,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -10578,7 +10613,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -10646,7 +10681,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10808,6 +10843,7 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10836,6 +10872,7 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, "funding": [ { "type": "github", @@ -10854,7 +10891,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -11343,6 +11380,7 @@ "version": "4.24.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -11544,7 +11582,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11557,7 +11595,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11703,6 +11741,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -11933,7 +11972,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12201,7 +12240,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -12313,6 +12352,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12546,7 +12586,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -12635,6 +12675,7 @@ "version": "5.4.14", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "dev": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -12772,6 +12813,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12788,6 +12830,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12804,6 +12847,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12820,6 +12864,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12836,6 +12881,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12852,6 +12898,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12868,6 +12915,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12884,6 +12932,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12900,6 +12949,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12916,6 +12966,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12932,6 +12983,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12948,6 +13000,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12964,6 +13017,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12980,6 +13034,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12996,6 +13051,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13012,6 +13068,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13028,6 +13085,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13044,6 +13102,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13060,6 +13119,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13076,6 +13136,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13092,6 +13153,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13108,6 +13170,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13124,6 +13187,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13137,6 +13201,7 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -13346,7 +13411,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -13450,7 +13515,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13607,7 +13672,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" From fc623582e00cb10cef99ccde534fead19f2aad6a Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:45:55 -0500 Subject: [PATCH 11/25] =?UTF-8?q?[Rythme=20enseignant]=20Fen=C3=AAtre=20de?= =?UTF-8?q?=20r=C3=A9troaction=20reste=20ouverte=20pour=20les=20prochaines?= =?UTF-8?q?=20questions=20si=20l'=C3=A9tudiant=20ne=20l'enl=C3=A8ve=20pas?= =?UTF-8?q?=20Fixes=20#222?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx index ae2d382..dea9af3 100644 --- a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx +++ b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx @@ -26,8 +26,11 @@ const TeacherModeQuiz: React.FC = ({ const [feedbackMessage, setFeedbackMessage] = useState(''); useEffect(() => { + // Close the feedback dialog when the question changes + handleFeedbackDialogClose(); setIsAnswerSubmitted(false); - }, [questionInfos]); + + }, [questionInfos.question]); const handleOnSubmitAnswer = (answer: string | number | boolean) => { const idQuestion = Number(questionInfos.question.id) || -1; From 3a592ef2dc0df85c8d6a580bf5af55cefe821d91 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:56:04 -0500 Subject: [PATCH 12/25] =?UTF-8?q?Fen=C3=AAtre=20de=20r=C3=A9troaction=20n'?= =?UTF-8?q?est=20pas=20compatible=20avec=20les=20longues=20r=C3=A9ponses?= =?UTF-8?q?=20Fixes=20#230?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx index f3b3e57..0332f1a 100644 --- a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx @@ -53,7 +53,7 @@ describe('TeacherModeQuiz', () => { fireEvent.click(screen.getByText('Répondre')); }); expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); - expect(screen.getByText('Votre réponse est "Option A".')).toBeInTheDocument(); + expect(screen.getByText('Votre réponse est:')).toBeInTheDocument(); }); test('handles disconnect button click', () => { From a539284813e8a54fb2958f9ac195758e76725ad9 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Thu, 6 Feb 2025 12:52:53 -0500 Subject: [PATCH 13/25] =?UTF-8?q?[Katex]=20La=20L'affichage=20du=20tableau?= =?UTF-8?q?=20AVEC=20r=C3=A9ponses=20pose=20probl=C3=A8me=20lorsqu'il=20y?= =?UTF-8?q?=20a=20des=20=C3=A9quations=20Fixes=20#226?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/LiveResults/LiveResults.tsx | 188 +----------------- 1 file changed, 2 insertions(+), 186 deletions(-) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 4825142..d1e560a 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -4,7 +4,6 @@ import { Socket } from 'socket.io-client'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; import { QuestionType } from '../../Types/QuestionType'; -import DOMPurify from 'dompurify'; import './liveResult.css'; import { @@ -21,7 +20,7 @@ import { TableRow } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; -import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate'; +import { FormattedTextTemplate } from '../GiftTemplate/templates/TextTypeTemplate'; interface LiveResultsProps { socket: Socket | null; @@ -31,117 +30,13 @@ interface LiveResultsProps { students: StudentType[] } -// interface Answer { -// answer: string | number | boolean; -// isCorrect: boolean; -// idQuestion: number; -// } - -// interface StudentResult { -// username: string; -// idUser: string; -// answers: Answer[]; -// } const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - // const [students, setStudents] = useState(initialStudents); - // const [studentResultsMap, setStudentResultsMap] = useState>(new Map()); const maxQuestions = questions.length; - // useEffect(() => { - // // Initialize the map with the current students - // const newStudentResultsMap = new Map(); - - // for (const student of students) { - // newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] }); - // } - - // setStudentResultsMap(newStudentResultsMap); - // }, []) - - // update when students change - // useEffect(() => { - // // studentResultsMap is inconsistent with students -- need to update - - // for (const student of students as StudentType[]) { - // } - - // }, [students]) - - // useEffect(() => { - // if (socket) { - // const submitAnswerHandler = ({ - // idUser, - // answer, - // idQuestion - // }: { - // idUser: string; - // username: string; - // answer: string | number | boolean; - // idQuestion: number; - // }) => { - // console.log(`Received answer from ${idUser} for question ${idQuestion}: ${answer}`); - - // // print the list of current student names - // console.log('Current students:'); - // students.forEach((student) => { - // console.log(student.name); - // }); - - // // Update the students state using the functional form of setStudents - // setStudents((prevStudents) => { - // let foundStudent = false; - // const updatedStudents = prevStudents.map((student) => { - // if (student.id === idUser) { - // foundStudent = true; - // const updatedAnswers = student.answers.map((ans) => { - // const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion), idQuestion }; - // console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`); - // return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans); - // } - // ); - // return { ...student, answers: updatedAnswers }; - // } - // return student; - // }); - // if (!foundStudent) { - // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // } - // return updatedStudents; - // }); - - - // // make a copy of the students array so we can update it - // // const updatedStudents = [...students]; - - // // const student = updatedStudents.find((student) => student.id === idUser); - // // if (!student) { - // // // this is a bad thing if an answer was submitted but the student isn't in the list - // // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // // return; - // // } - - // // const isCorrect = checkIfIsCorrect(answer, idQuestion); - // // const newAnswer: Answer = { answer, isCorrect, idQuestion }; - // // student.answers.push(newAnswer); - // // // print list of answers - // // console.log('Answers:'); - // // student.answers.forEach((answer) => { - // // console.log(answer.answer); - // // }); - // // setStudents(updatedStudents); // update the state - // }; - - // socket.on('submit-answer', submitAnswerHandler); - // return () => { - // socket.off('submit-answer'); - // }; - // } - // }, [socket]); - const getStudentGrade = (student: StudentType): number => { if (student.answers.length === 0) { return 0; @@ -186,85 +81,6 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti ); }; - // (studentResults.filter((student) => - // student.answers.some( - // (answer) => - // parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect - // ) - // ).length / - // studentResults.length) * - // 100 - // ); - // }; - - // function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean { - // const questionInfo = questions.find((q) => - // q.question.id ? q.question.id === idQuestion.toString() : false - // ) as QuestionType | undefined; - - // const answerText = answer.toString(); - // if (questionInfo) { - // const question = questionInfo.question as GIFTQuestion; - // if (question.type === 'TF') { - // return ( - // (question.isTrue && answerText == 'true') || - // (!question.isTrue && answerText == 'false') - // ); - // } else if (question.type === 'MC') { - // return question.choices.some( - // (choice) => choice.isCorrect && choice.text.text === answerText - // ); - // } else if (question.type === 'Numerical') { - // if (question.choices && !Array.isArray(question.choices)) { - // if ( - // question.choices.type === 'high-low' && - // question.choices.numberHigh && - // question.choices.numberLow - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= question.choices.numberHigh && - // answerNumber >= question.choices.numberLow - // ); - // } - // } - // } - // if (question.choices && Array.isArray(question.choices)) { - // if ( - // question.choices[0].text.type === 'range' && - // question.choices[0].text.number && - // question.choices[0].text.range - // ) { - // const answerNumber = parseFloat(answerText); - // const range = question.choices[0].text.range; - // const correctAnswer = question.choices[0].text.number; - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= correctAnswer + range && - // answerNumber >= correctAnswer - range - // ); - // } - // } - // if ( - // question.choices[0].text.type === 'simple' && - // question.choices[0].text.number - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return answerNumber === question.choices[0].text.number; - // } - // } - // } - // } else if (question.type === 'Short') { - // return question.choices.some( - // (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase() - // ); - // } - // } - // return false; - // } - return (
@@ -371,7 +187,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti } > {showCorrectAnswers ? ( -
+
) : isCorrect ? ( ) : ( From 4ab2d1fd2583c7f00cb7a989f75bc3502f331ce1 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:03:21 -0500 Subject: [PATCH 14/25] =?UTF-8?q?Fen=C3=AAtre=20de=20r=C3=A9troaction=20n'?= =?UTF-8?q?est=20pas=20compatible=20avec=20les=20longues=20r=C3=A9ponses?= =?UTF-8?q?=20Fixes=20#230?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GiftTemplate/GIFTTemplatePreview.tsx | 4 +- .../templates/TextTypeTemplate.ts | 2 +- .../components/LiveResults/LiveResults.tsx | 187 +----------------- 3 files changed, 5 insertions(+), 188 deletions(-) diff --git a/client/src/components/GiftTemplate/GIFTTemplatePreview.tsx b/client/src/components/GiftTemplate/GIFTTemplatePreview.tsx index 51dbd3f..8b2107c 100644 --- a/client/src/components/GiftTemplate/GIFTTemplatePreview.tsx +++ b/client/src/components/GiftTemplate/GIFTTemplatePreview.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import Template, { ErrorTemplate } from './templates'; import { parse } from 'gift-pegjs'; import './styles.css'; -import DOMPurify from 'dompurify'; +import { FormattedTextTemplate } from './templates/TextTypeTemplate'; interface GIFTTemplatePreviewProps { questions: string[]; @@ -74,7 +74,7 @@ const GIFTTemplatePreview: React.FC = ({
{error}
) : isPreviewReady ? (
-
+
) : (
Chargement de la prévisualisation...
diff --git a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts index 8a3e24b..c86b25e 100644 --- a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts +++ b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts @@ -4,7 +4,7 @@ import katex from 'katex'; import { TextFormat } from 'gift-pegjs'; import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc. -export function formatLatex(text: string): string { +function formatLatex(text: string): string { return text .replace(/\$\$(.*?)\$\$/g, (_, inner) => katex.renderToString(inner, { displayMode: true })) .replace(/\$(.*?)\$/g, (_, inner) => katex.renderToString(inner, { displayMode: false })) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 6bcd7ce..d1e560a 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -20,7 +20,7 @@ import { TableRow } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; -import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate'; +import { FormattedTextTemplate } from '../GiftTemplate/templates/TextTypeTemplate'; interface LiveResultsProps { socket: Socket | null; @@ -30,117 +30,13 @@ interface LiveResultsProps { students: StudentType[] } -// interface Answer { -// answer: string | number | boolean; -// isCorrect: boolean; -// idQuestion: number; -// } - -// interface StudentResult { -// username: string; -// idUser: string; -// answers: Answer[]; -// } const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - // const [students, setStudents] = useState(initialStudents); - // const [studentResultsMap, setStudentResultsMap] = useState>(new Map()); const maxQuestions = questions.length; - // useEffect(() => { - // // Initialize the map with the current students - // const newStudentResultsMap = new Map(); - - // for (const student of students) { - // newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] }); - // } - - // setStudentResultsMap(newStudentResultsMap); - // }, []) - - // update when students change - // useEffect(() => { - // // studentResultsMap is inconsistent with students -- need to update - - // for (const student of students as StudentType[]) { - // } - - // }, [students]) - - // useEffect(() => { - // if (socket) { - // const submitAnswerHandler = ({ - // idUser, - // answer, - // idQuestion - // }: { - // idUser: string; - // username: string; - // answer: string | number | boolean; - // idQuestion: number; - // }) => { - // console.log(`Received answer from ${idUser} for question ${idQuestion}: ${answer}`); - - // // print the list of current student names - // console.log('Current students:'); - // students.forEach((student) => { - // console.log(student.name); - // }); - - // // Update the students state using the functional form of setStudents - // setStudents((prevStudents) => { - // let foundStudent = false; - // const updatedStudents = prevStudents.map((student) => { - // if (student.id === idUser) { - // foundStudent = true; - // const updatedAnswers = student.answers.map((ans) => { - // const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion), idQuestion }; - // console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`); - // return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans); - // } - // ); - // return { ...student, answers: updatedAnswers }; - // } - // return student; - // }); - // if (!foundStudent) { - // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // } - // return updatedStudents; - // }); - - - // // make a copy of the students array so we can update it - // // const updatedStudents = [...students]; - - // // const student = updatedStudents.find((student) => student.id === idUser); - // // if (!student) { - // // // this is a bad thing if an answer was submitted but the student isn't in the list - // // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // // return; - // // } - - // // const isCorrect = checkIfIsCorrect(answer, idQuestion); - // // const newAnswer: Answer = { answer, isCorrect, idQuestion }; - // // student.answers.push(newAnswer); - // // // print list of answers - // // console.log('Answers:'); - // // student.answers.forEach((answer) => { - // // console.log(answer.answer); - // // }); - // // setStudents(updatedStudents); // update the state - // }; - - // socket.on('submit-answer', submitAnswerHandler); - // return () => { - // socket.off('submit-answer'); - // }; - // } - // }, [socket]); - const getStudentGrade = (student: StudentType): number => { if (student.answers.length === 0) { return 0; @@ -185,85 +81,6 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti ); }; - // (studentResults.filter((student) => - // student.answers.some( - // (answer) => - // parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect - // ) - // ).length / - // studentResults.length) * - // 100 - // ); - // }; - - // function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean { - // const questionInfo = questions.find((q) => - // q.question.id ? q.question.id === idQuestion.toString() : false - // ) as QuestionType | undefined; - - // const answerText = answer.toString(); - // if (questionInfo) { - // const question = questionInfo.question as GIFTQuestion; - // if (question.type === 'TF') { - // return ( - // (question.isTrue && answerText == 'true') || - // (!question.isTrue && answerText == 'false') - // ); - // } else if (question.type === 'MC') { - // return question.choices.some( - // (choice) => choice.isCorrect && choice.text.text === answerText - // ); - // } else if (question.type === 'Numerical') { - // if (question.choices && !Array.isArray(question.choices)) { - // if ( - // question.choices.type === 'high-low' && - // question.choices.numberHigh && - // question.choices.numberLow - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= question.choices.numberHigh && - // answerNumber >= question.choices.numberLow - // ); - // } - // } - // } - // if (question.choices && Array.isArray(question.choices)) { - // if ( - // question.choices[0].text.type === 'range' && - // question.choices[0].text.number && - // question.choices[0].text.range - // ) { - // const answerNumber = parseFloat(answerText); - // const range = question.choices[0].text.range; - // const correctAnswer = question.choices[0].text.number; - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= correctAnswer + range && - // answerNumber >= correctAnswer - range - // ); - // } - // } - // if ( - // question.choices[0].text.type === 'simple' && - // question.choices[0].text.number - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return answerNumber === question.choices[0].text.number; - // } - // } - // } - // } else if (question.type === 'Short') { - // return question.choices.some( - // (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase() - // ); - // } - // } - // return false; - // } - return (
@@ -370,7 +187,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti } > {showCorrectAnswers ? ( -
{formatLatex(answerText)}
+
) : isCorrect ? ( ) : ( From c396b60734f395665a44086bdd2c50c7e7793413 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:09:17 -0500 Subject: [PATCH 15/25] Faire l'appel de TextTypeTemplate au lieu de format() et sanitize() Fixes #233 --- .../GiftTemplate/GIFTTemplatePreview.tsx | 4 +- .../templates/TextTypeTemplate.ts | 2 +- .../components/LiveResults/LiveResults.tsx | 187 +----------------- 3 files changed, 5 insertions(+), 188 deletions(-) diff --git a/client/src/components/GiftTemplate/GIFTTemplatePreview.tsx b/client/src/components/GiftTemplate/GIFTTemplatePreview.tsx index 51dbd3f..f4ab035 100644 --- a/client/src/components/GiftTemplate/GIFTTemplatePreview.tsx +++ b/client/src/components/GiftTemplate/GIFTTemplatePreview.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import Template, { ErrorTemplate } from './templates'; import { parse } from 'gift-pegjs'; import './styles.css'; -import DOMPurify from 'dompurify'; +import { FormattedTextTemplate } from './templates/TextTypeTemplate'; interface GIFTTemplatePreviewProps { questions: string[]; @@ -74,7 +74,7 @@ const GIFTTemplatePreview: React.FC = ({
{error}
) : isPreviewReady ? (
-
+
) : (
Chargement de la prévisualisation...
diff --git a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts index 8a3e24b..c86b25e 100644 --- a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts +++ b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts @@ -4,7 +4,7 @@ import katex from 'katex'; import { TextFormat } from 'gift-pegjs'; import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc. -export function formatLatex(text: string): string { +function formatLatex(text: string): string { return text .replace(/\$\$(.*?)\$\$/g, (_, inner) => katex.renderToString(inner, { displayMode: true })) .replace(/\$(.*?)\$/g, (_, inner) => katex.renderToString(inner, { displayMode: false })) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 6bcd7ce..d1e560a 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -20,7 +20,7 @@ import { TableRow } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; -import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate'; +import { FormattedTextTemplate } from '../GiftTemplate/templates/TextTypeTemplate'; interface LiveResultsProps { socket: Socket | null; @@ -30,117 +30,13 @@ interface LiveResultsProps { students: StudentType[] } -// interface Answer { -// answer: string | number | boolean; -// isCorrect: boolean; -// idQuestion: number; -// } - -// interface StudentResult { -// username: string; -// idUser: string; -// answers: Answer[]; -// } const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - // const [students, setStudents] = useState(initialStudents); - // const [studentResultsMap, setStudentResultsMap] = useState>(new Map()); const maxQuestions = questions.length; - // useEffect(() => { - // // Initialize the map with the current students - // const newStudentResultsMap = new Map(); - - // for (const student of students) { - // newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] }); - // } - - // setStudentResultsMap(newStudentResultsMap); - // }, []) - - // update when students change - // useEffect(() => { - // // studentResultsMap is inconsistent with students -- need to update - - // for (const student of students as StudentType[]) { - // } - - // }, [students]) - - // useEffect(() => { - // if (socket) { - // const submitAnswerHandler = ({ - // idUser, - // answer, - // idQuestion - // }: { - // idUser: string; - // username: string; - // answer: string | number | boolean; - // idQuestion: number; - // }) => { - // console.log(`Received answer from ${idUser} for question ${idQuestion}: ${answer}`); - - // // print the list of current student names - // console.log('Current students:'); - // students.forEach((student) => { - // console.log(student.name); - // }); - - // // Update the students state using the functional form of setStudents - // setStudents((prevStudents) => { - // let foundStudent = false; - // const updatedStudents = prevStudents.map((student) => { - // if (student.id === idUser) { - // foundStudent = true; - // const updatedAnswers = student.answers.map((ans) => { - // const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion), idQuestion }; - // console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`); - // return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans); - // } - // ); - // return { ...student, answers: updatedAnswers }; - // } - // return student; - // }); - // if (!foundStudent) { - // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // } - // return updatedStudents; - // }); - - - // // make a copy of the students array so we can update it - // // const updatedStudents = [...students]; - - // // const student = updatedStudents.find((student) => student.id === idUser); - // // if (!student) { - // // // this is a bad thing if an answer was submitted but the student isn't in the list - // // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // // return; - // // } - - // // const isCorrect = checkIfIsCorrect(answer, idQuestion); - // // const newAnswer: Answer = { answer, isCorrect, idQuestion }; - // // student.answers.push(newAnswer); - // // // print list of answers - // // console.log('Answers:'); - // // student.answers.forEach((answer) => { - // // console.log(answer.answer); - // // }); - // // setStudents(updatedStudents); // update the state - // }; - - // socket.on('submit-answer', submitAnswerHandler); - // return () => { - // socket.off('submit-answer'); - // }; - // } - // }, [socket]); - const getStudentGrade = (student: StudentType): number => { if (student.answers.length === 0) { return 0; @@ -185,85 +81,6 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti ); }; - // (studentResults.filter((student) => - // student.answers.some( - // (answer) => - // parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect - // ) - // ).length / - // studentResults.length) * - // 100 - // ); - // }; - - // function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean { - // const questionInfo = questions.find((q) => - // q.question.id ? q.question.id === idQuestion.toString() : false - // ) as QuestionType | undefined; - - // const answerText = answer.toString(); - // if (questionInfo) { - // const question = questionInfo.question as GIFTQuestion; - // if (question.type === 'TF') { - // return ( - // (question.isTrue && answerText == 'true') || - // (!question.isTrue && answerText == 'false') - // ); - // } else if (question.type === 'MC') { - // return question.choices.some( - // (choice) => choice.isCorrect && choice.text.text === answerText - // ); - // } else if (question.type === 'Numerical') { - // if (question.choices && !Array.isArray(question.choices)) { - // if ( - // question.choices.type === 'high-low' && - // question.choices.numberHigh && - // question.choices.numberLow - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= question.choices.numberHigh && - // answerNumber >= question.choices.numberLow - // ); - // } - // } - // } - // if (question.choices && Array.isArray(question.choices)) { - // if ( - // question.choices[0].text.type === 'range' && - // question.choices[0].text.number && - // question.choices[0].text.range - // ) { - // const answerNumber = parseFloat(answerText); - // const range = question.choices[0].text.range; - // const correctAnswer = question.choices[0].text.number; - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= correctAnswer + range && - // answerNumber >= correctAnswer - range - // ); - // } - // } - // if ( - // question.choices[0].text.type === 'simple' && - // question.choices[0].text.number - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return answerNumber === question.choices[0].text.number; - // } - // } - // } - // } else if (question.type === 'Short') { - // return question.choices.some( - // (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase() - // ); - // } - // } - // return false; - // } - return (
@@ -370,7 +187,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti } > {showCorrectAnswers ? ( -
{formatLatex(answerText)}
+
) : isCorrect ? ( ) : ( From df617db28ba31001c0608fc907c157533f566478 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:47:33 -0500 Subject: [PATCH 16/25] Refactoriser la classe LiveResult Fixes #236 --- .../components/LiveResults/LiveResults.tsx | 389 +----------------- .../LiveResults/LiveResultsTable.tsx | 215 ++++++++++ 2 files changed, 224 insertions(+), 380 deletions(-) create mode 100644 client/src/components/LiveResults/LiveResultsTable.tsx diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 6bcd7ce..a230630 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -1,26 +1,16 @@ // LiveResults.tsx -import React, { useMemo, useState } from 'react'; +import React, { useState } from 'react'; import { Socket } from 'socket.io-client'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; import { QuestionType } from '../../Types/QuestionType'; import './liveResult.css'; import { FormControlLabel, FormGroup, - Paper, Switch, - Table, - TableBody, - TableCell, - TableContainer, - TableFooter, - TableHead, - TableRow } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; -import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate'; +import LiveResultsTable from './LiveResultsTable'; interface LiveResultsProps { socket: Socket | null; @@ -30,239 +20,11 @@ interface LiveResultsProps { students: StudentType[] } -// interface Answer { -// answer: string | number | boolean; -// isCorrect: boolean; -// idQuestion: number; -// } - -// interface StudentResult { -// username: string; -// idUser: string; -// answers: Answer[]; -// } const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - // const [students, setStudents] = useState(initialStudents); - // const [studentResultsMap, setStudentResultsMap] = useState>(new Map()); - const maxQuestions = questions.length; - - // useEffect(() => { - // // Initialize the map with the current students - // const newStudentResultsMap = new Map(); - - // for (const student of students) { - // newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] }); - // } - - // setStudentResultsMap(newStudentResultsMap); - // }, []) - - // update when students change - // useEffect(() => { - // // studentResultsMap is inconsistent with students -- need to update - - // for (const student of students as StudentType[]) { - // } - - // }, [students]) - - // useEffect(() => { - // if (socket) { - // const submitAnswerHandler = ({ - // idUser, - // answer, - // idQuestion - // }: { - // idUser: string; - // username: string; - // answer: string | number | boolean; - // idQuestion: number; - // }) => { - // console.log(`Received answer from ${idUser} for question ${idQuestion}: ${answer}`); - - // // print the list of current student names - // console.log('Current students:'); - // students.forEach((student) => { - // console.log(student.name); - // }); - - // // Update the students state using the functional form of setStudents - // setStudents((prevStudents) => { - // let foundStudent = false; - // const updatedStudents = prevStudents.map((student) => { - // if (student.id === idUser) { - // foundStudent = true; - // const updatedAnswers = student.answers.map((ans) => { - // const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion), idQuestion }; - // console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`); - // return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans); - // } - // ); - // return { ...student, answers: updatedAnswers }; - // } - // return student; - // }); - // if (!foundStudent) { - // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // } - // return updatedStudents; - // }); - - - // // make a copy of the students array so we can update it - // // const updatedStudents = [...students]; - - // // const student = updatedStudents.find((student) => student.id === idUser); - // // if (!student) { - // // // this is a bad thing if an answer was submitted but the student isn't in the list - // // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // // return; - // // } - - // // const isCorrect = checkIfIsCorrect(answer, idQuestion); - // // const newAnswer: Answer = { answer, isCorrect, idQuestion }; - // // student.answers.push(newAnswer); - // // // print list of answers - // // console.log('Answers:'); - // // student.answers.forEach((answer) => { - // // console.log(answer.answer); - // // }); - // // setStudents(updatedStudents); // update the state - // }; - - // socket.on('submit-answer', submitAnswerHandler); - // return () => { - // socket.off('submit-answer'); - // }; - // } - // }, [socket]); - - const getStudentGrade = (student: StudentType): number => { - if (student.answers.length === 0) { - return 0; - } - - const uniqueQuestions = new Set(); - let correctAnswers = 0; - - for (const answer of student.answers) { - const { idQuestion, isCorrect } = answer; - - if (!uniqueQuestions.has(idQuestion)) { - uniqueQuestions.add(idQuestion); - - if (isCorrect) { - correctAnswers++; - } - } - } - - return (correctAnswers / questions.length) * 100; - }; - - const classAverage: number = useMemo(() => { - let classTotal = 0; - - students.forEach((student) => { - classTotal += getStudentGrade(student); - }); - - return classTotal / students.length; - }, [students]); - - const getCorrectAnswersPerQuestion = (index: number): number => { - return ( - (students.filter((student) => - student.answers.some( - (answer) => - parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect - ) - ).length / students.length) * 100 - ); - }; - - // (studentResults.filter((student) => - // student.answers.some( - // (answer) => - // parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect - // ) - // ).length / - // studentResults.length) * - // 100 - // ); - // }; - - // function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean { - // const questionInfo = questions.find((q) => - // q.question.id ? q.question.id === idQuestion.toString() : false - // ) as QuestionType | undefined; - - // const answerText = answer.toString(); - // if (questionInfo) { - // const question = questionInfo.question as GIFTQuestion; - // if (question.type === 'TF') { - // return ( - // (question.isTrue && answerText == 'true') || - // (!question.isTrue && answerText == 'false') - // ); - // } else if (question.type === 'MC') { - // return question.choices.some( - // (choice) => choice.isCorrect && choice.text.text === answerText - // ); - // } else if (question.type === 'Numerical') { - // if (question.choices && !Array.isArray(question.choices)) { - // if ( - // question.choices.type === 'high-low' && - // question.choices.numberHigh && - // question.choices.numberLow - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= question.choices.numberHigh && - // answerNumber >= question.choices.numberLow - // ); - // } - // } - // } - // if (question.choices && Array.isArray(question.choices)) { - // if ( - // question.choices[0].text.type === 'range' && - // question.choices[0].text.number && - // question.choices[0].text.range - // ) { - // const answerNumber = parseFloat(answerText); - // const range = question.choices[0].text.range; - // const correctAnswer = question.choices[0].text.number; - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= correctAnswer + range && - // answerNumber >= correctAnswer - range - // ); - // } - // } - // if ( - // question.choices[0].text.type === 'simple' && - // question.choices[0].text.number - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return answerNumber === question.choices[0].text.number; - // } - // } - // } - // } else if (question.type === 'Short') { - // return question.choices.some( - // (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase() - // ); - // } - // } - // return false; - // } return (
@@ -295,146 +57,13 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti
- - - - - -
Nom d'utilisateur
-
- {Array.from({ length: maxQuestions }, (_, index) => ( - showSelectedQuestion(index)} - > -
{`Q${index + 1}`}
-
- ))} - -
% réussite
-
-
-
- - {students.map((student) => ( - - -
- {showUsernames ? student.name : '******'} -
-
- {Array.from({ length: maxQuestions }, (_, index) => { - const answer = student.answers.find( - (answer) => parseInt(answer.idQuestion.toString()) === index + 1 - ); - const answerText = answer ? answer.answer.toString() : ''; - const isCorrect = answer ? answer.isCorrect : false; - - return ( - - {showCorrectAnswers ? ( -
{formatLatex(answerText)}
- ) : isCorrect ? ( - - ) : ( - answerText !== '' && ( - - ) - )} -
- ); - })} - - {getStudentGrade(student).toFixed()} % - -
- ))} -
- - - -
% réussite
-
- {Array.from({ length: maxQuestions }, (_, index) => ( - - {students.length > 0 - ? `${getCorrectAnswersPerQuestion(index).toFixed()} %` - : '-'} - - ))} - - {students.length > 0 ? `${classAverage.toFixed()} %` : '-'} - -
-
-
-
+
); diff --git a/client/src/components/LiveResults/LiveResultsTable.tsx b/client/src/components/LiveResults/LiveResultsTable.tsx new file mode 100644 index 0000000..bb81b80 --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable.tsx @@ -0,0 +1,215 @@ +import React, { useMemo } from 'react'; +import { Paper, Table, TableBody, TableCell, TableContainer, TableFooter, TableHead, TableRow } from '@mui/material'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; +import { StudentType } from 'src/Types/StudentType'; +import { QuestionType } from '../../Types/QuestionType'; +import { FormattedTextTemplate } from '../GiftTemplate/templates/TextTypeTemplate'; + +interface LiveResultsTableProps { + students: StudentType[]; + questions: QuestionType[]; + showCorrectAnswers: boolean; + showSelectedQuestion: (index: number) => void; + showUsernames: boolean; +} + +const LiveResultsTable: React.FC = ({ + questions, + students, + showCorrectAnswers, + showSelectedQuestion, + showUsernames +}) => { + + const maxQuestions = questions.length; + + const getStudentGrade = (student: StudentType): number => { + if (student.answers.length === 0) { + return 0; + } + + const uniqueQuestions = new Set(); + let correctAnswers = 0; + + for (const answer of student.answers) { + const { idQuestion, isCorrect } = answer; + + if (!uniqueQuestions.has(idQuestion)) { + uniqueQuestions.add(idQuestion); + + if (isCorrect) { + correctAnswers++; + } + } + } + + return (correctAnswers / questions.length) * 100; + }; + + const classAverage: number = useMemo(() => { + let classTotal = 0; + + students.forEach((student) => { + classTotal += getStudentGrade(student); + }); + + return classTotal / students.length; + }, [students]); + + const getCorrectAnswersPerQuestion = (index: number): number => { + return ( + (students.filter((student) => + student.answers.some( + (answer) => + parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect + ) + ).length / students.length) * 100 + ); + }; + + return ( + + + + + +
Nom d'utilisateur
+
+ {Array.from({ length: maxQuestions }, (_, index) => ( + showSelectedQuestion(index)} + > +
{`Q${index + 1}`}
+
+ ))} + +
% réussite
+
+
+
+ + {students.map((student) => ( + + +
+ {showUsernames ? student.name : '******'} +
+
+ {Array.from({ length: maxQuestions }, (_, index) => { + const answer = student.answers.find( + (answer) => parseInt(answer.idQuestion.toString()) === index + 1 + ); + const answerText = answer ? answer.answer.toString() : ''; + const isCorrect = answer ? answer.isCorrect : false; + + return ( + + {showCorrectAnswers ? ( +
+ ) : isCorrect ? ( + + ) : ( + answerText !== '' && ( + + ) + )} +
+ ); + })} + + {getStudentGrade(student).toFixed()} % + +
+ ))} +
+ + + +
% réussite
+
+ {Array.from({ length: maxQuestions }, (_, index) => ( + + {students.length > 0 + ? `${getCorrectAnswersPerQuestion(index).toFixed()} %` + : '-'} + + ))} + + {students.length > 0 ? `${classAverage.toFixed()} %` : '-'} + +
+
+
+
+ ); +}; + +export default LiveResultsTable; \ No newline at end of file From 3aa51900d71f56d6fc25394ea9db9beba4a688c5 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:33:38 -0500 Subject: [PATCH 17/25] =?UTF-8?q?Cr=C3=A9ation=20de=20classes=20de=20tests?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LiveResults/LiveResults.test.tsx | 95 +++++++++++++++ .../LiveResults/LiveResultsTable.test.tsx | 110 ++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx create mode 100644 client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx new file mode 100644 index 0000000..3431b73 --- /dev/null +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import LiveResults from 'src/components/LiveResults/LiveResults'; +import { QuestionType } from 'src/Types/QuestionType'; +import { StudentType } from 'src/Types/StudentType'; +import { BaseQuestion, parse } from 'gift-pegjs'; + +const mockGiftQuestions = parse( + `::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2} + + ::Sample Question 2:: Sample Question 2 {T}`); + +const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { + if (question.type !== "Category") + question.id = (index + 1).toString(); + const newMockQuestion = question; + return {question : newMockQuestion as BaseQuestion}; +}); + +const mockStudents: StudentType[] = [ + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, +]; + +const mockShowSelectedQuestion = jest.fn(); + +describe('LiveResults', () => { + test('renders LiveResults component', () => { + render( + + ); + + expect(screen.getByText('Résultats du quiz')).toBeInTheDocument(); + }); + + test('toggles show usernames switch', () => { + render( + + ); + + const switchElement = screen.getByLabelText('Afficher les noms'); + expect(switchElement).toBeInTheDocument(); + + fireEvent.click(switchElement); + expect(switchElement).toBeChecked(); + }); + + test('toggles show correct answers switch', () => { + render( + + ); + + const switchElement = screen.getByLabelText('Afficher les réponses'); + expect(switchElement).toBeInTheDocument(); + + fireEvent.click(switchElement); + expect(switchElement).toBeChecked(); + }); + + test('calls showSelectedQuestion when a table cell is clicked', () => { + render( + + ); + + const tableCell = screen.getByText('Q1'); + fireEvent.click(tableCell); + + expect(mockShowSelectedQuestion).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx new file mode 100644 index 0000000..7f4c8a0 --- /dev/null +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { StudentType } from 'src/Types/StudentType'; +import LiveResultsTable from 'src/components/LiveResults/LiveResultsTable'; +import { QuestionType } from 'src/Types/QuestionType'; +import { BaseQuestion, parse } from 'gift-pegjs'; + +const mockGiftQuestions = parse( + `::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2} + + ::Sample Question 2:: Sample Question 2 {T}`); + +const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { + if (question.type !== "Category") + question.id = (index + 1).toString(); + const newMockQuestion = question; + return {question : newMockQuestion as BaseQuestion}; +}); + + +const mockStudents: StudentType[] = [ + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, +]; + +const mockShowSelectedQuestion = jest.fn(); + +describe('LiveResultsTable', () => { + test('renders LiveResultsTable component', () => { + render( + + ); + + expect(screen.getByText('Student 1')).toBeInTheDocument(); + expect(screen.getByText('Student 2')).toBeInTheDocument(); + }); + + test('displays correct and incorrect answers', () => { + render( + + ); + + expect(screen.getByText('Answer 1')).toBeInTheDocument(); + expect(screen.getByText('Answer 2')).toBeInTheDocument(); + }); + + test('calls showSelectedQuestion when a table cell is clicked', () => { + render( + + ); + + const tableCell = screen.getByText('Q1'); + fireEvent.click(tableCell); + + expect(mockShowSelectedQuestion).toHaveBeenCalled(); + }); + + test('calculates and displays student grades', () => { + render( + + ); + + //50% because only one of the two questions have been answered (getALLByText, because there are a value 50% for the %reussite de la question + // and a second one for the student grade) + const gradeElements = screen.getAllByText('50 %'); + expect(gradeElements.length).toBe(2); + + const gradeElements2 = screen.getAllByText('0 %'); + expect(gradeElements2.length).toBe(2); }); + + test('calculates and displays class average', () => { + render( + + ); + + //1 good answer out of 4 possible good answers (the second question has not been answered) + expect(screen.getByText('25 %')).toBeInTheDocument(); + }); +}); \ No newline at end of file From a2650d89f1a317e364d753580fc532dd83de74de Mon Sep 17 00:00:00 2001 From: "Christopher (Cris) Fuhrman" Date: Fri, 7 Feb 2025 19:36:11 -0500 Subject: [PATCH 18/25] Revert "PFEH2025 - merge entre main et dev-it2-PFEA2024 pour activation SSO" --- .github/workflows/tests.yml | 60 +- .gitignore | 3 - EvalueTonSavoir.code-workspace | 33 -- client/.eslintrc.cjs | 19 + client/babel.config.cjs | 2 +- client/eslint.config.js | 93 +-- client/jest.config.cjs | 2 +- client/jest.setup.cjs | 2 +- client/package-lock.json | 280 +++++---- client/package.json | 4 - client/src/App.tsx | 96 +-- .../src/__tests__/Types/StudentType.test.tsx | 2 +- .../GiftTemplate/GIFTTemplatePreview.test.tsx | 75 +-- .../components/GiftTemplate/TextType.test.ts | 4 +- .../GiftTemplate/constants/styles.test.tsx | 2 +- .../services/WebsocketService.test.tsx | 14 +- client/src/components/Header/Header.tsx | 14 +- .../StudentModeQuiz/StudentModeQuiz.tsx | 2 +- .../StudentWaitPage/StudentWaitPage.tsx | 2 +- .../TeacherModeQuiz/TeacherModeQuiz.tsx | 2 +- client/src/constants.tsx | 6 +- client/src/pages/AuthManager/AuthDrawer.tsx | 61 -- client/src/pages/AuthManager/authDrawer.css | 48 -- .../AuthManager/callback/AuthCallback.tsx | 27 - .../providers/OAuth-Oidc/ButtonAuth.tsx | 27 - .../providers/SimpleLogin/Register.tsx | 114 ---- .../providers/SimpleLogin/ResetPassword.tsx | 68 --- .../AuthManager/providers/css/buttonAuth.css | 23 - .../AuthManager/providers/css/simpleLogin.css | 17 - client/src/pages/Home/home.css | 19 - .../src/pages/Student/JoinRoom/JoinRoom.tsx | 8 +- .../src/pages/Teacher/Dashboard/Dashboard.tsx | 4 +- .../pages/Teacher/EditorQuiz/EditorQuiz.tsx | 3 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 3 +- .../Register/Register.tsx} | 169 +++--- client/src/pages/Teacher/Share/Share.tsx | 2 +- client/src/services/ApiService.tsx | 163 ++---- client/src/services/AuthService.tsx | 28 - docker-compose-auth.yaml | 96 --- docker-compose.yaml | 41 +- oauth-tester/config.json | 96 --- server/.env.example | 8 +- server/.gitignore | 1 - server/__tests__/auth.test.js | 245 -------- server/__tests__/users.test.js | 2 +- server/app.js | 24 +- server/auth/auth-manager.js | 69 --- .../auth/modules/passport-providers/oauth.js | 100 ---- .../auth/modules/passport-providers/oidc.js | 106 ---- server/auth/modules/passportjs.js | 65 --- server/auth/modules/simpleauth.js | 126 ---- server/auth_config.json.example | 26 - server/config/auth.js | 193 ------ server/constants/errorCodes.js | 7 - server/controllers/auth.js | 36 -- server/middleware/jwtToken.js | 8 +- server/models/authProvider.js | 44 -- server/models/authUserAssociation.js | 59 -- server/models/users.js | 273 ++++----- server/package-lock.json | 548 +++--------------- server/package.json | 8 +- .../passport-openidconnect+0.1.2.patch | 12 - server/routers/auth.js | 9 - server/routers/users.js | 3 +- server/utils.js | 35 -- 65 files changed, 603 insertions(+), 3138 deletions(-) delete mode 100644 EvalueTonSavoir.code-workspace create mode 100644 client/.eslintrc.cjs delete mode 100644 client/src/pages/AuthManager/AuthDrawer.tsx delete mode 100644 client/src/pages/AuthManager/authDrawer.css delete mode 100644 client/src/pages/AuthManager/callback/AuthCallback.tsx delete mode 100644 client/src/pages/AuthManager/providers/OAuth-Oidc/ButtonAuth.tsx delete mode 100644 client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx delete mode 100644 client/src/pages/AuthManager/providers/SimpleLogin/ResetPassword.tsx delete mode 100644 client/src/pages/AuthManager/providers/css/buttonAuth.css delete mode 100644 client/src/pages/AuthManager/providers/css/simpleLogin.css rename client/src/pages/{AuthManager/providers/SimpleLogin/Login.tsx => Teacher/Register/Register.tsx} (63%) delete mode 100644 client/src/services/AuthService.tsx delete mode 100644 docker-compose-auth.yaml delete mode 100644 oauth-tester/config.json delete mode 100644 server/.gitignore delete mode 100644 server/__tests__/auth.test.js delete mode 100644 server/auth/auth-manager.js delete mode 100644 server/auth/modules/passport-providers/oauth.js delete mode 100644 server/auth/modules/passport-providers/oidc.js delete mode 100644 server/auth/modules/passportjs.js delete mode 100644 server/auth/modules/simpleauth.js delete mode 100644 server/auth_config.json.example delete mode 100644 server/config/auth.js delete mode 100644 server/controllers/auth.js delete mode 100644 server/models/authProvider.js delete mode 100644 server/models/authUserAssociation.js delete mode 100644 server/patches/passport-openidconnect+0.1.2.patch delete mode 100644 server/routers/auth.js delete mode 100644 server/utils.js diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8af0e3b..d916699 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,45 +8,29 @@ on: branches: - main -env: - MONGO_URI: mongodb://localhost:27017 - MONGO_DATABASE: evaluetonsavoir - jobs: - lint-and-tests: + tests: + runs-on: ubuntu-latest + + steps: + - name: Check Out Repo + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Dependencies, lint and Run Tests + run: | + echo "Installing dependencies..." + npm ci + echo "Running ESLint..." + npx eslint . + echo "Running tests..." + npm test + working-directory: ${{ matrix.directory }} + strategy: matrix: directory: [client, server] - fail-fast: false - - runs-on: ubuntu-latest - timeout-minutes: 10 - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: ${{ matrix.directory }}/package-lock.json - - - name: Process ${{ matrix.directory }} - working-directory: ${{ matrix.directory }} - timeout-minutes: 5 - run: | - echo "::group::Installing dependencies for ${{ matrix.directory }}" - npm ci - echo "::endgroup::" - - echo "::group::Running ESLint" - npx eslint . || { - echo "ESLint failed with exit code $?" - exit 1 - } - echo "::endgroup::" - - echo "::group::Running Tests" - npm test - echo "::endgroup::" - diff --git a/.gitignore b/.gitignore index d4eb19a..6e8de7b 100644 --- a/.gitignore +++ b/.gitignore @@ -122,9 +122,6 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test -.env -launch.json - # yarn v2 .yarn/cache .yarn/unplugged diff --git a/EvalueTonSavoir.code-workspace b/EvalueTonSavoir.code-workspace deleted file mode 100644 index 2ee3b1c..0000000 --- a/EvalueTonSavoir.code-workspace +++ /dev/null @@ -1,33 +0,0 @@ -{ - "folders": [ - { - "path": "." - }, - { - "name": "server", - "path": "server" - }, - { - "name": "client", - "path": "client" - } - ], - "settings": { - "jest.disabledWorkspaceFolders": [ - "EvalueTonSavoir" - ] - }, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - }, - "eslint.validate": [ - "javascript", - "typescript", - "javascriptreact", - "typescriptreact" - ], - // use the same eslint config as `npx eslint` - "eslint.experimental.useFlatConfig": true, - "eslint.nodePath": "./node_modules" - -} diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs new file mode 100644 index 0000000..43f6c4c --- /dev/null +++ b/client/.eslintrc.cjs @@ -0,0 +1,19 @@ +// eslint-disable-next-line no-undef +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 }, + ], + }, +} diff --git a/client/babel.config.cjs b/client/babel.config.cjs index eae7944..2bda178 100644 --- a/client/babel.config.cjs +++ b/client/babel.config.cjs @@ -1,4 +1,4 @@ - +/* eslint-disable no-undef */ module.exports = { presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'] }; diff --git a/client/eslint.config.js b/client/eslint.config.js index 1f5e231..ed8593c 100644 --- a/client/eslint.config.js +++ b/client/eslint.config.js @@ -1,78 +1,29 @@ -import react from "eslint-plugin-react"; -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import typescriptParser from "@typescript-eslint/parser"; import globals from "globals"; import pluginJs from "@eslint/js"; -import jest from "eslint-plugin-jest"; -import reactRefresh from "eslint-plugin-react-refresh"; -import unusedImports from "eslint-plugin-unused-imports"; -import eslintComments from "eslint-plugin-eslint-comments"; +import tseslint from "typescript-eslint"; +import pluginReact from "eslint-plugin-react"; /** @type {import('eslint').Linter.Config[]} */ export default [ - { - ignores: ["node_modules", "dist/**/*"], + { + files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], + languageOptions: { + globals: globals.browser, }, - { - files: ["**/*.{js,jsx,mjs,cjs,ts,tsx}"], - languageOptions: { - parser: typescriptParser, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - ...globals.serviceworker, - ...globals.browser, - ...globals.jest, - ...globals.node, - process: "readonly", - }, - }, - plugins: { - "@typescript-eslint": typescriptEslint, - react, - jest, - "react-refresh": reactRefresh, - "unused-imports": unusedImports, - "eslint-comments": eslintComments - }, - rules: { - // Auto-fix unused variables - "@typescript-eslint/no-unused-vars": "off", - "no-unused-vars": "off", - "unused-imports/no-unused-vars": [ - "warn", - { - "vars": "all", - "varsIgnorePattern": "^_", - "args": "after-used", - "argsIgnorePattern": "^_", - "destructuredArrayIgnorePattern": "^_" - } - ], - - // Handle directive comments - "eslint-comments/no-unused-disable": "warn", - "eslint-comments/no-unused-enable": "warn", - - // Jest configurations - "jest/valid-expect": ["error", { "alwaysAwait": true }], - "jest/prefer-to-have-length": "warn", - "jest/no-disabled-tests": "off", - "jest/no-focused-tests": "error", - "jest/no-identical-title": "error", - - // React refresh - "react-refresh/only-export-components": ["warn", { - allowConstantExport: true - }], - }, - settings: { - react: { - version: "detect", - }, - }, - } + 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, ]; diff --git a/client/jest.config.cjs b/client/jest.config.cjs index b2d35cc..6c635c8 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,4 +1,4 @@ - +/* eslint-disable no-undef */ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { diff --git a/client/jest.setup.cjs b/client/jest.setup.cjs index 3b56b65..30fd66a 100644 --- a/client/jest.setup.cjs +++ b/client/jest.setup.cjs @@ -1,3 +1,3 @@ - +/* eslint-disable no-undef */ process.env.VITE_BACKEND_URL = 'http://localhost:4000/'; process.env.VITE_BACKEND_SOCKET_URL = 'https://ets-glitch-backend.glitch.me/'; diff --git a/client/package-lock.json b/client/package-lock.json index 57b7252..3c24ffc 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -23,7 +23,6 @@ "esbuild": "^0.23.1", "gift-pegjs": "^2.0.0-beta.1", "jest-environment-jsdom": "^29.7.0", - "jwt-decode": "^4.0.0", "katex": "^0.16.11", "marked": "^14.1.2", "nanoid": "^5.0.2", @@ -54,12 +53,9 @@ "@typescript-eslint/parser": "^8.5.0", "@vitejs/plugin-react-swc": "^3.7.2", "eslint": "^9.18.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-jest": "^28.11.0", "eslint-plugin-react": "^7.37.3", "eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912", "eslint-plugin-react-refresh": "^0.4.12", - "eslint-plugin-unused-imports": "^4.1.4", "globals": "^15.14.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", @@ -2553,7 +2549,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2572,7 +2568,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2582,7 +2578,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.5", @@ -2597,7 +2593,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2608,7 +2604,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2621,7 +2617,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2634,7 +2630,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2658,7 +2654,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2669,7 +2665,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2682,7 +2678,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2695,7 +2691,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2705,7 +2701,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2715,7 +2711,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.10.0", @@ -2822,7 +2818,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2832,7 +2828,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2846,7 +2842,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2860,7 +2856,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -3863,6 +3859,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3876,6 +3873,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3889,6 +3887,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3902,6 +3901,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3915,6 +3915,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3928,6 +3929,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3941,6 +3943,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3954,6 +3957,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3967,6 +3971,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3980,6 +3985,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3993,6 +3999,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4006,6 +4013,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4019,6 +4027,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4032,6 +4041,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4045,6 +4055,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4058,6 +4069,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4071,6 +4083,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4084,6 +4097,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4124,7 +4138,7 @@ "version": "1.7.40", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.40.tgz", "integrity": "sha512-0HIzM5vigVT5IvNum+pPuST9p8xFhN6mhdIKju7qYYeNuZG78lwms/2d8WgjTJJlzp6JlPguXGrMMNzjQw0qNg==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4166,6 +4180,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4182,6 +4197,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4198,6 +4214,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4214,6 +4231,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4230,6 +4248,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4246,6 +4265,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4262,6 +4282,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4278,6 +4299,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4294,6 +4316,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4310,6 +4333,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4323,14 +4347,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -4524,6 +4548,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -4623,7 +4648,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/katex": { @@ -5004,7 +5029,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5038,7 +5063,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5113,7 +5138,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "devOptional": true, + "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -5927,7 +5952,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6098,7 +6123,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -6648,7 +6673,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -6704,59 +6729,6 @@ } } }, - "node_modules/eslint-plugin-eslint-comments": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", - "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5", - "ignore": "^5.0.5" - }, - "engines": { - "node": ">=6.5.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-plugin-jest": { - "version": "28.11.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.11.0.tgz", - "integrity": "sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "engines": { - "node": "^16.10.0 || ^18.12.0 || >=20.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", - "jest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, "node_modules/eslint-plugin-react": { "version": "7.37.4", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", @@ -6855,26 +6827,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-unused-imports": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz", - "integrity": "sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==", - "dev": true, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", - "eslint": "^9.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - } - } - }, "node_modules/eslint-scope": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -6891,7 +6848,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6904,7 +6861,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -6918,7 +6875,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6929,7 +6886,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6942,7 +6899,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6955,7 +6912,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", @@ -6973,7 +6930,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6999,7 +6956,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -7012,7 +6969,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -7105,7 +7062,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -7140,14 +7097,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fastq": { @@ -7173,7 +7130,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -7227,7 +7184,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -7244,7 +7201,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -7258,7 +7215,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -7523,7 +7480,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7811,7 +7768,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7857,7 +7814,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -8360,7 +8317,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -9398,7 +9355,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9468,7 +9425,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -9481,14 +9438,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json5": { @@ -9530,6 +9487,7 @@ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -9540,14 +9498,6 @@ "node": ">=4.0" } }, - "node_modules/jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", - "engines": { - "node": ">=18" - } - }, "node_modules/katex": { "version": "0.16.21", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", @@ -9567,7 +9517,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -9597,7 +9547,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -9617,7 +9567,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -9654,7 +9604,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/longest-streak": { @@ -10431,7 +10381,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/node-int64": { @@ -10611,7 +10561,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -10647,7 +10597,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -10663,7 +10613,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -10731,7 +10681,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10893,6 +10843,7 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10921,6 +10872,7 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, "funding": [ { "type": "github", @@ -10939,7 +10891,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -11428,6 +11380,7 @@ "version": "4.24.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -11629,7 +11582,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11642,7 +11595,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11788,6 +11741,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12018,7 +11972,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12286,7 +12240,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -12398,6 +12352,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12631,7 +12586,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -12720,6 +12675,7 @@ "version": "5.4.14", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "dev": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -12857,6 +12813,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12873,6 +12830,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12889,6 +12847,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12905,6 +12864,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12921,6 +12881,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12937,6 +12898,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12953,6 +12915,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12969,6 +12932,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12985,6 +12949,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13001,6 +12966,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13017,6 +12983,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13033,6 +13000,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13049,6 +13017,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13065,6 +13034,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13081,6 +13051,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13097,6 +13068,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13113,6 +13085,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13129,6 +13102,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13145,6 +13119,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13161,6 +13136,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13177,6 +13153,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13193,6 +13170,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13209,6 +13187,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13222,6 +13201,7 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -13431,7 +13411,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -13535,7 +13515,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13692,7 +13672,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/client/package.json b/client/package.json index 4a49162..2a99173 100644 --- a/client/package.json +++ b/client/package.json @@ -27,7 +27,6 @@ "esbuild": "^0.23.1", "gift-pegjs": "^2.0.0-beta.1", "jest-environment-jsdom": "^29.7.0", - "jwt-decode": "^4.0.0", "katex": "^0.16.11", "marked": "^14.1.2", "nanoid": "^5.0.2", @@ -58,12 +57,9 @@ "@typescript-eslint/parser": "^8.5.0", "@vitejs/plugin-react-swc": "^3.7.2", "eslint": "^9.18.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-jest": "^28.11.0", "eslint-plugin-react": "^7.37.3", "eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912", "eslint-plugin-react-refresh": "^0.4.12", - "eslint-plugin-unused-imports": "^4.1.4", "globals": "^15.14.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", diff --git a/client/src/App.tsx b/client/src/App.tsx index 9b16e2f..8f8ecf8 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { useEffect, useState } from 'react'; -import { Routes, Route, Navigate, useLocation } from 'react-router-dom'; +// App.tsx +import { Routes, Route } from 'react-router-dom'; // Page main import Home from './pages/Home/Home'; @@ -8,55 +8,37 @@ import Home from './pages/Home/Home'; // Pages espace enseignant import Dashboard from './pages/Teacher/Dashboard/Dashboard'; import Share from './pages/Teacher/Share/Share'; -import Register from './pages/AuthManager/providers/SimpleLogin/Register'; -import ResetPassword from './pages/AuthManager/providers/SimpleLogin/ResetPassword'; +import Login from './pages/Teacher/Login/Login'; +import Register from './pages/Teacher/Register/Register'; +import ResetPassword from './pages/Teacher/ResetPassword/ResetPassword'; import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom'; import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz'; // Pages espace étudiant import JoinRoom from './pages/Student/JoinRoom/JoinRoom'; -// Pages authentification selection -import AuthDrawer from './pages/AuthManager/AuthDrawer'; - // Header/Footer import import Header from './components/Header/Header'; import Footer from './components/Footer/Footer'; import ApiService from './services/ApiService'; -import OAuthCallback from './pages/AuthManager/callback/AuthCallback'; -const App: React.FC = () => { - const [isAuthenticated, setIsAuthenticated] = useState(ApiService.isLoggedIn()); - const [isTeacherAuthenticated, setIsTeacherAuthenticated] = useState(ApiService.isLoggedInTeacher()); - const [isRoomRequireAuthentication, setRoomsRequireAuth] = useState(null); - const location = useLocation(); +const handleLogout = () => { + ApiService.logout(); +} - // Check login status every time the route changes - useEffect(() => { - const checkLoginStatus = () => { - setIsAuthenticated(ApiService.isLoggedIn()); - setIsTeacherAuthenticated(ApiService.isLoggedInTeacher()); - }; - - const fetchAuthenticatedRooms = async () => { - const data = await ApiService.getRoomsRequireAuth(); - setRoomsRequireAuth(data); - }; - - checkLoginStatus(); - fetchAuthenticatedRooms(); - }, [location]); - - const handleLogout = () => { - ApiService.logout(); - setIsAuthenticated(false); - setIsTeacherAuthenticated(false); - }; +const isLoggedIn = () => { + return ApiService.isLoggedIn(); +} +function App() { return (
-
+ +
+
@@ -64,46 +46,22 @@ const App: React.FC = () => { } /> {/* Pages espace enseignant */} - : } - /> - : } - /> - : } - /> - : } - /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> {/* Pages espace étudiant */} - : } - /> - - {/* Pages authentification */} - } /> - - {/* Pages enregistrement */} - } /> - - {/* Pages rest password */} - } /> - - {/* Pages authentification sélection */} - } /> + } />
-
+
); -}; +} export default App; diff --git a/client/src/__tests__/Types/StudentType.test.tsx b/client/src/__tests__/Types/StudentType.test.tsx index 2f9efbe..4e7c849 100644 --- a/client/src/__tests__/Types/StudentType.test.tsx +++ b/client/src/__tests__/Types/StudentType.test.tsx @@ -12,6 +12,6 @@ describe('StudentType', () => { expect(user.name).toBe('Student'); expect(user.id).toBe('123'); - expect(user.answers).toHaveLength(0); + expect(user.answers.length).toBe(0); }); }); diff --git a/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx b/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx index 967d33c..586c8d1 100644 --- a/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx @@ -5,64 +5,47 @@ import GIFTTemplatePreview from 'src/components/GiftTemplate/GIFTTemplatePreview describe('GIFTTemplatePreview Component', () => { test('renders error message when questions contain invalid syntax', () => { - render(); - const errorMessage = screen.getByText(/Title ::, Category, Description, or Question formatted stem but ":" found./i); - expect(errorMessage).toBeInTheDocument(); + render(); + const errorMessage = screen.findByText(/Erreur inconnue/i, {}, { timeout: 5000 }); + expect(errorMessage).resolves.toBeInTheDocument(); }); - test('renders preview when valid questions are provided', () => { const questions = [ - 'Stem1 {=ans1 ~ans2 ~ans3}', + 'Question 1 { A | B | C }', + 'Question 2 { D | E | F }', ]; render(); const previewContainer = screen.getByTestId('preview-container'); expect(previewContainer).toBeInTheDocument(); - // const question1 = screen.getByText('Stem1'); - const mcQuestion1 = screen.getByText('Stem1'); - const ans1 = screen.getByText('ans1'); - const ans2 = screen.getByText('ans2'); - const ans3 = screen.getByText('ans3'); - expect(mcQuestion1).toBeInTheDocument(); - expect(ans1).toBeInTheDocument(); - expect(ans2).toBeInTheDocument(); - expect(ans3).toBeInTheDocument(); - - // each answer should have a radio button before it - const radioButtons = screen.getAllByRole('radio'); - expect(radioButtons).toHaveLength(3); - // ans1 should be the
); }; diff --git a/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx b/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx index dd34ec1..eb70432 100644 --- a/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx +++ b/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx @@ -12,7 +12,7 @@ import { Question } from 'gift-pegjs'; interface StudentModeQuizProps { questions: QuestionType[]; - submitAnswer: (_answer: string | number | boolean, _idQuestion: number) => void; + submitAnswer: (answer: string | number | boolean, idQuestion: number) => void; disconnectWebSocket: () => void; } diff --git a/client/src/components/StudentWaitPage/StudentWaitPage.tsx b/client/src/components/StudentWaitPage/StudentWaitPage.tsx index 20e072d..9989b71 100644 --- a/client/src/components/StudentWaitPage/StudentWaitPage.tsx +++ b/client/src/components/StudentWaitPage/StudentWaitPage.tsx @@ -9,7 +9,7 @@ import './studentWaitPage.css'; interface Props { students: StudentType[]; launchQuiz: () => void; - setQuizMode: (_mode: 'student' | 'teacher') => void; + setQuizMode: (mode: 'student' | 'teacher') => void; } const StudentWaitPage: React.FC = ({ students, launchQuiz, setQuizMode }) => { diff --git a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx index ec552d6..dea9af3 100644 --- a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx +++ b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx @@ -12,7 +12,7 @@ import { Question } from 'gift-pegjs'; interface TeacherModeQuizProps { questionInfos: QuestionType; - submitAnswer: (_answer: string | number | boolean, _idQuestion: number) => void; + submitAnswer: (answer: string | number | boolean, idQuestion: number) => void; disconnectWebSocket: () => void; } diff --git a/client/src/constants.tsx b/client/src/constants.tsx index dccc503..1fc104b 100644 --- a/client/src/constants.tsx +++ b/client/src/constants.tsx @@ -1,11 +1,11 @@ // constants.tsx const ENV_VARIABLES = { MODE: 'production', - VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || "", - BACKEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.BACKEND_PORT}`:''}` : process.env.VITE_BACKEND_URL || '', - FRONTEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.PORT}`:''}` : '' + VITE_BACKEND_URL: import.meta.env.VITE_BACKEND_URL || "", + VITE_BACKEND_SOCKET_URL: import.meta.env.VITE_BACKEND_SOCKET_URL || "", }; console.log(`ENV_VARIABLES.VITE_BACKEND_URL=${ENV_VARIABLES.VITE_BACKEND_URL}`); +console.log(`ENV_VARIABLES.VITE_BACKEND_SOCKET_URL=${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`); export { ENV_VARIABLES }; diff --git a/client/src/pages/AuthManager/AuthDrawer.tsx b/client/src/pages/AuthManager/AuthDrawer.tsx deleted file mode 100644 index 093b7aa..0000000 --- a/client/src/pages/AuthManager/AuthDrawer.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import './authDrawer.css'; -import SimpleLogin from './providers/SimpleLogin/Login'; -import authService from '../../services/AuthService'; -import { ENV_VARIABLES } from '../../constants'; -import ButtonAuth from './providers/OAuth-Oidc/ButtonAuth'; - -const AuthSelection: React.FC = () => { - const [authData, setAuthData] = useState(null); // Stocke les données d'auth - const navigate = useNavigate(); - - ENV_VARIABLES.VITE_BACKEND_URL; - // Récupérer les données d'authentification depuis l'API - useEffect(() => { - const fetchData = async () => { - const data = await authService.fetchAuthData(); - setAuthData(data); - }; - - fetchData(); - }, []); - - return ( -
-

Connexion

- - {/* Formulaire de connexion Simple Login */} - {authData && authData['simpleauth'] && ( -
- -
- )} - - {/* Conteneur OAuth/OIDC */} - {authData && Object.keys(authData).some(key => authData[key].type === 'oidc' || authData[key].type === 'oauth') && ( -
- {Object.keys(authData).map((providerKey) => { - const providerType = authData[providerKey].type; - if (providerType === 'oidc' || providerType === 'oauth') { - return ( - - ); - } - return null; - })} -
- )} - -
- -
-
- ); -}; - -export default AuthSelection; diff --git a/client/src/pages/AuthManager/authDrawer.css b/client/src/pages/AuthManager/authDrawer.css deleted file mode 100644 index 1543fc2..0000000 --- a/client/src/pages/AuthManager/authDrawer.css +++ /dev/null @@ -1,48 +0,0 @@ -.auth-selection-page { - display: flex; - flex-direction: column; - align-items: center; - padding: 20px; - } - h1 { - margin-bottom: 20px; - } - .form-container{ - border: 1px solid #ccc; - border-radius: 8px; - padding: 15px; - margin: 10px 0; - width: 400px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - text-align: center; - } - form { - display: flex; - flex-direction: column; - } - input { - margin: 5px 0; - padding: 10px; - border: 1px solid #ccc; - border-radius: 4px; - } - button { - padding: 10px; - border: none; - border-radius: 4px; - background-color: #5271ff; - color: white; - cursor: pointer; - } - button:hover { - background-color: #5271ff; - } - .home-button-container{ - background: none; - color: black; - } - .home-button-container:hover{ - background: none; - color: black; - text-decoration: underline; - } \ No newline at end of file diff --git a/client/src/pages/AuthManager/callback/AuthCallback.tsx b/client/src/pages/AuthManager/callback/AuthCallback.tsx deleted file mode 100644 index 6206294..0000000 --- a/client/src/pages/AuthManager/callback/AuthCallback.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import apiService from '../../../services/ApiService'; - -const OAuthCallback: React.FC = () => { - const navigate = useNavigate(); - const location = useLocation(); - - useEffect(() => { - const searchParams = new URLSearchParams(location.search); - const user = searchParams.get('user'); - const username = searchParams.get('username'); - - if (user) { - apiService.saveToken(user); - apiService.saveUsername(username || ""); - navigate('/'); - } else { - navigate('/login'); - } - }, []); - - return
Loading...
; -}; - -export default OAuthCallback; diff --git a/client/src/pages/AuthManager/providers/OAuth-Oidc/ButtonAuth.tsx b/client/src/pages/AuthManager/providers/OAuth-Oidc/ButtonAuth.tsx deleted file mode 100644 index c8f4efc..0000000 --- a/client/src/pages/AuthManager/providers/OAuth-Oidc/ButtonAuth.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { ENV_VARIABLES } from '../../../../constants'; -import '../css/buttonAuth.css'; - -interface ButtonAuthContainerProps { - providerName: string; - providerType: 'oauth' | 'oidc'; -} - -const handleAuthLogin = (provider: string) => { - window.location.href = `${ENV_VARIABLES.BACKEND_URL}/api/auth/${provider}`; -}; - -const ButtonAuth: React.FC = ({ providerName, providerType }) => { - return ( - <> -
-

Se connecter avec {providerType.toUpperCase()}

- -
- - ); -}; - -export default ButtonAuth; \ No newline at end of file diff --git a/client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx b/client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx deleted file mode 100644 index d33527d..0000000 --- a/client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx +++ /dev/null @@ -1,114 +0,0 @@ -// JoinRoom.tsx -import React, { useEffect, useState } from 'react'; - -import { TextField, FormLabel, RadioGroup, FormControlLabel, Radio, Box } from '@mui/material'; -import LoadingButton from '@mui/lab/LoadingButton'; - -import LoginContainer from '../../../../components/LoginContainer/LoginContainer'; -import ApiService from '../../../../services/ApiService'; - -const Register: React.FC = () => { - - const [name, setName] = useState(''); // State for name - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [roles, setRoles] = useState(['student']); // Set 'student' as the default role - - const [connectionError, setConnectionError] = useState(''); - const [isConnecting] = useState(false); - - useEffect(() => { - return () => { }; - }, []); - - const handleRoleChange = (role: string) => { - setRoles([role]); // Update the roles array to contain the selected role - }; - - const isValidEmail = (email: string) => { - // Basic email format validation - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - }; - - const register = async () => { - if (!isValidEmail(email)) { - setConnectionError("Veuillez entrer une adresse email valide."); - return; - } - - const result = await ApiService.register(name, email, password, roles); - - if (result !== true) { - setConnectionError(result); - return; - } - }; - - return ( - - setName(e.target.value)} - placeholder="Votre nom" - sx={{ marginBottom: '1rem' }} - fullWidth - /> - - setEmail(e.target.value)} - placeholder="Adresse courriel" - sx={{ marginBottom: '1rem' }} - fullWidth - type="email" - error={!!connectionError && !isValidEmail(email)} - helperText={connectionError && !isValidEmail(email) ? "Adresse email invalide." : ""} - /> - - setPassword(e.target.value)} - placeholder="Mot de passe" - sx={{ marginBottom: '1rem' }} - fullWidth - /> - - - Choisir votre rôle - handleRoleChange(e.target.value)} - > - } label="Étudiant" /> - } label="Professeur" /> - - - - - S'inscrire - - - ); -}; - -export default Register; diff --git a/client/src/pages/AuthManager/providers/SimpleLogin/ResetPassword.tsx b/client/src/pages/AuthManager/providers/SimpleLogin/ResetPassword.tsx deleted file mode 100644 index c33c9fa..0000000 --- a/client/src/pages/AuthManager/providers/SimpleLogin/ResetPassword.tsx +++ /dev/null @@ -1,68 +0,0 @@ - -import { useNavigate } from 'react-router-dom'; - -// JoinRoom.tsx -import React, { useEffect, useState } from 'react'; - -import { TextField } from '@mui/material'; -import LoadingButton from '@mui/lab/LoadingButton'; - -import LoginContainer from '../../../../components/LoginContainer/LoginContainer' -import ApiService from '../../../../services/ApiService'; - -const ResetPassword: React.FC = () => { - const navigate = useNavigate(); - - const [email, setEmail] = useState(''); - - const [connectionError, setConnectionError] = useState(''); - const [isConnecting] = useState(false); - - useEffect(() => { - return () => { - - }; - }, []); - - const reset = async () => { - const result = await ApiService.resetPassword(email); - - if (!result) { - setConnectionError(result.toString()); - return; - } - - navigate("/login") - }; - - - return ( - - - setEmail(e.target.value)} - placeholder="Adresse courriel" - sx={{ marginBottom: '1rem' }} - fullWidth - /> - - - Réinitialiser le mot de passe - - - - ); -}; - -export default ResetPassword; diff --git a/client/src/pages/AuthManager/providers/css/buttonAuth.css b/client/src/pages/AuthManager/providers/css/buttonAuth.css deleted file mode 100644 index 98476ec..0000000 --- a/client/src/pages/AuthManager/providers/css/buttonAuth.css +++ /dev/null @@ -1,23 +0,0 @@ -.provider-btn { - background-color: #ffffff; - border: 1px solid #ccc; - color: black; - margin: 4px 0 4px 0; -} - -.provider-btn:hover { - background-color: #dbdbdb; - border: 1px solid #ccc; - color: black; - margin: 4px 0 4px 0; -} - -.button-container { - border: 1px solid #ccc; - border-radius: 8px; - padding: 15px; - margin: 10px 0; - width: 400px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - text-align: center; -} \ No newline at end of file diff --git a/client/src/pages/AuthManager/providers/css/simpleLogin.css b/client/src/pages/AuthManager/providers/css/simpleLogin.css deleted file mode 100644 index ddbebdb..0000000 --- a/client/src/pages/AuthManager/providers/css/simpleLogin.css +++ /dev/null @@ -1,17 +0,0 @@ -.login-links { - padding-top: 10px; - width: 100%; - display: flex; - flex-direction: column; - align-items: center; -} - -.login-links a { - padding: 4px; - color: #333; - text-decoration: none; -} - -.login-links a:hover { - text-decoration: underline; -} diff --git a/client/src/pages/Home/home.css b/client/src/pages/Home/home.css index 8a6a1a7..1fc8a8d 100644 --- a/client/src/pages/Home/home.css +++ b/client/src/pages/Home/home.css @@ -61,25 +61,6 @@ align-items: end; } -.auth-selection-btn { - position: absolute; - top: 20px; - right: 20px; -} -.auth-btn { - padding: 10px 20px; - background-color: #5271ff; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; - font-size: 14px; - transition: background-color 0.3s ease; -} -.auth-btn:hover { - background-color: #5976fa; -} - @media only screen and (max-width: 768px) { .btn-container { flex-direction: column; diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx index 65bc699..f0ac8d7 100644 --- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx +++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx @@ -15,11 +15,9 @@ import LoadingButton from '@mui/lab/LoadingButton'; import LoginContainer from 'src/components/LoginContainer/LoginContainer' -import ApiService from '../../../services/ApiService' - const JoinRoom: React.FC = () => { const [roomName, setRoomName] = useState(''); - const [username, setUsername] = useState(ApiService.getUsername()); + const [username, setUsername] = useState(''); const [socket, setSocket] = useState(null); const [isWaitingForTeacher, setIsWaitingForTeacher] = useState(false); const [question, setQuestion] = useState(); @@ -36,8 +34,8 @@ const JoinRoom: React.FC = () => { }, []); const handleCreateSocket = () => { - console.log(`JoinRoom: handleCreateSocket: ${ENV_VARIABLES.VITE_BACKEND_URL}`); - const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); + console.log(`JoinRoom: handleCreateSocket: ${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`); + const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); socket.on('join-success', () => { setIsWaitingForTeacher(true); diff --git a/client/src/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx index 3db6250..f920d12 100644 --- a/client/src/pages/Teacher/Dashboard/Dashboard.tsx +++ b/client/src/pages/Teacher/Dashboard/Dashboard.tsx @@ -78,7 +78,7 @@ const Dashboard: React.FC = () => { useEffect(() => { const fetchData = async () => { if (!ApiService.isLoggedIn()) { - navigate("/login"); + navigate("/teacher/login"); return; } else { @@ -196,8 +196,8 @@ 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) { - console.error('Error parsing question:', error); return false; } } diff --git a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx index 1b5ff0a..453d5c9 100644 --- a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx +++ b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx @@ -190,8 +190,9 @@ 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${error}\nVeuillez réessayer plus tard.`) + window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) } }; diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index e69dcea..7af12f9 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -86,7 +86,7 @@ const ManageRoom: React.FC = () => { const createWebSocketRoom = () => { console.log('Creating WebSocket room...'); setConnectingError(''); - const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); + const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); socket.on('connect', () => { webSocketService.createRoom(); @@ -127,6 +127,7 @@ 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); diff --git a/client/src/pages/AuthManager/providers/SimpleLogin/Login.tsx b/client/src/pages/Teacher/Register/Register.tsx similarity index 63% rename from client/src/pages/AuthManager/providers/SimpleLogin/Login.tsx rename to client/src/pages/Teacher/Register/Register.tsx index 6356d7a..e09b316 100644 --- a/client/src/pages/AuthManager/providers/SimpleLogin/Login.tsx +++ b/client/src/pages/Teacher/Register/Register.tsx @@ -1,88 +1,81 @@ -import { Link } from 'react-router-dom'; - -// JoinRoom.tsx -import React, { useEffect, useState } from 'react'; - -import '../css/simpleLogin.css'; -import { TextField } from '@mui/material'; -import LoadingButton from '@mui/lab/LoadingButton'; - -import LoginContainer from '../../../../components/LoginContainer/LoginContainer' -import ApiService from '../../../../services/ApiService'; - -const SimpleLogin: React.FC = () => { - - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - - const [connectionError, setConnectionError] = useState(''); - const [isConnecting] = useState(false); - - useEffect(() => { - return () => { - - }; - }, []); - - const login = async () => { - const result = await ApiService.login(email, password); - if (result !== true) { - setConnectionError(result); - return; - } - }; - - - return ( - - - setEmail(e.target.value)} - placeholder="Nom d'utilisateur" - sx={{ marginBottom: '1rem' }} - fullWidth - /> - - setPassword(e.target.value)} - placeholder="Nom de la salle" - sx={{ marginBottom: '1rem' }} - fullWidth - /> - - - Login - - -
- - - Réinitialiser le mot de passe - - - - Créer un compte - - -
- -
- ); -}; - -export default SimpleLogin; + +import { useNavigate } from 'react-router-dom'; + +// JoinRoom.tsx +import React, { useEffect, useState } from 'react'; + +import { TextField } from '@mui/material'; +import LoadingButton from '@mui/lab/LoadingButton'; + +import LoginContainer from 'src/components/LoginContainer/LoginContainer' +import ApiService from '../../../services/ApiService'; + +const Register: React.FC = () => { + const navigate = useNavigate(); + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const [connectionError, setConnectionError] = useState(''); + const [isConnecting] = useState(false); + + useEffect(() => { + return () => { + + }; + }, []); + + const register = async () => { + const result = await ApiService.register(email, password); + + if (typeof result === 'string') { + setConnectionError(result); + return; + } + + navigate("/teacher/login") + }; + + + return ( + + + setEmail(e.target.value)} + placeholder="Adresse courriel" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + setPassword(e.target.value)} + placeholder="Mot de passe" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + + S'inscrire + + + + + ); +}; + +export default Register; diff --git a/client/src/pages/Teacher/Share/Share.tsx b/client/src/pages/Teacher/Share/Share.tsx index 0dc4fe7..31bb72c 100644 --- a/client/src/pages/Teacher/Share/Share.tsx +++ b/client/src/pages/Teacher/Share/Share.tsx @@ -33,7 +33,7 @@ const Share: React.FC = () => { if (!ApiService.isLoggedIn()) { window.alert(`Vous n'êtes pas connecté.\nVeuillez vous connecter et revenir à ce lien`); - navigate("/login"); + navigate("/teacher/login"); return; } diff --git a/client/src/services/ApiService.tsx b/client/src/services/ApiService.tsx index 6d6e561..ef124b4 100644 --- a/client/src/services/ApiService.tsx +++ b/client/src/services/ApiService.tsx @@ -1,9 +1,8 @@ import axios, { AxiosError, AxiosResponse } from 'axios'; -import { jwtDecode } from 'jwt-decode'; -import { ENV_VARIABLES } from '../constants'; import { FolderType } from 'src/Types/FolderType'; import { QuizType } from 'src/Types/QuizType'; +import { ENV_VARIABLES } from 'src/constants'; type ApiResponse = boolean | string; @@ -35,7 +34,7 @@ class ApiService { } // Helpers - public saveToken(token: string): void { + private saveToken(token: string): void { const now = new Date(); const object = { @@ -79,71 +78,7 @@ class ApiService { return true; } - public isLoggedInTeacher(): boolean { - const token = this.getToken(); - - - if (token == null) { - return false; - } - - try { - const decodedToken = jwtDecode(token) as { roles: string[] }; - - const userRoles = decodedToken.roles; - const requiredRole = 'teacher'; - - if (!userRoles || !userRoles.includes(requiredRole)) { - return false; - } - - // Update token expiry - this.saveToken(token); - - return true; - } catch (error) { - console.error("Error decoding token:", error); - return false; - } - } - - public saveUsername(username: string): void { - if (!username || username.length === 0) { - return; - } - - const object = { - username: username - } - - localStorage.setItem("username", JSON.stringify(object)); - } - - public getUsername(): string { - const objectStr = localStorage.getItem("username"); - - if (!objectStr) { - return ""; - } - - const object = JSON.parse(objectStr) - - return object.username; - } - - // Route to know if rooms need authentication to join - public async getRoomsRequireAuth(): Promise { - const url: string = this.constructRequestUrl(`/auth/getRoomsRequireAuth`); - const result: AxiosResponse = await axios.get(url); - - if (result.status == 200) { - return result.data.roomsRequireAuth; - } - return false; - } - public logout(): void { - localStorage.removeItem("username"); return localStorage.removeItem("jwt"); } @@ -153,25 +88,21 @@ class ApiService { * @returns true if successful * @returns A error string if unsuccessful, */ - public async register(name: string, email: string, password: string, roles: string[]): Promise { + public async register(email: string, password: string): Promise { try { if (!email || !password) { throw new Error(`L'email et le mot de passe sont requis.`); } - const url: string = this.constructRequestUrl(`/auth/simple-auth/register`); + const url: string = this.constructRequestUrl(`/user/register`); const headers = this.constructRequestHeaders(); - const body = { name, email, password, roles }; + const body = { email, password }; const result: AxiosResponse = await axios.post(url, body, { headers: headers }); - console.log(result); - if (result.status == 200) { - window.location.href = result.request.responseURL; - } - else { - throw new Error(`La connexion a échoué. Status: ${result.status}`); + if (result.status !== 200) { + throw new Error(`L'enregistrement a échoué. Status: ${result.status}`); } return true; @@ -193,52 +124,44 @@ class ApiService { * @returns true if successful * @returns A error string if unsuccessful, */ - /** - * @returns true if successful - * @returns An error string if unsuccessful - */ -public async login(email: string, password: string): Promise { - try { - if (!email || !password) { - throw new Error("L'email et le mot de passe sont requis."); - } + public async login(email: string, password: string): Promise { + try { - const url: string = this.constructRequestUrl(`/auth/simple-auth/login`); - const headers = this.constructRequestHeaders(); - const body = { email, password }; - - const result: AxiosResponse = await axios.post(url, body, { headers: headers }); - - // If login is successful, redirect the user - if (result.status === 200) { - window.location.href = result.request.responseURL; - return true; - } else { - throw new Error(`La connexion a échoué. Statut: ${result.status}`); - } - } catch (error) { - console.log("Error details:", error); - - // Handle Axios-specific errors - if (axios.isAxiosError(error)) { - const err = error as AxiosError; - const responseData = err.response?.data as { message?: string } | undefined; - - // If there is a message field in the response, print it - if (responseData?.message) { - console.log("Backend error message:", responseData.message); - return responseData.message; + if (!email || !password) { + throw new Error(`L'email et le mot de passe sont requis.`); } - // If no message is found, return a fallback message - return "Erreur serveur inconnue lors de la requête."; + const url: string = this.constructRequestUrl(`/user/login`); + const headers = this.constructRequestHeaders(); + const body = { email, password }; + + const result: AxiosResponse = await axios.post(url, body, { headers: headers }); + + if (result.status !== 200) { + throw new Error(`La connexion a échoué. Status: ${result.status}`); + } + + this.saveToken(result.data.token); + + return true; + + } 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.'; + } + + return `Une erreur inattendue s'est produite.` } - - // Handle other non-Axios errors - return "Une erreur inattendue s'est produite."; } -} - /** * @returns true if successful @@ -251,7 +174,7 @@ public async login(email: string, password: string): Promise { throw new Error(`L'email est requis.`); } - const url: string = this.constructRequestUrl(`/auth/simple-auth/reset-password`); + const url: string = this.constructRequestUrl(`/user/reset-password`); const headers = this.constructRequestHeaders(); const body = { email }; @@ -287,7 +210,7 @@ public async login(email: string, password: string): Promise { throw new Error(`L'email, l'ancien et le nouveau mot de passe sont requis.`); } - const url: string = this.constructRequestUrl(`/auth/simple-auth/change-password`); + const url: string = this.constructRequestUrl(`/user/change-password`); const headers = this.constructRequestHeaders(); const body = { email, oldPassword, newPassword }; @@ -968,4 +891,4 @@ public async login(email: string, password: string): Promise { } const apiService = new ApiService(); -export default apiService; \ No newline at end of file +export default apiService; diff --git a/client/src/services/AuthService.tsx b/client/src/services/AuthService.tsx deleted file mode 100644 index bca616d..0000000 --- a/client/src/services/AuthService.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { ENV_VARIABLES } from '../constants'; - -class AuthService { - - private BASE_URL: string; - - constructor() { - this.BASE_URL = ENV_VARIABLES.VITE_BACKEND_URL; - } - - private constructRequestUrl(endpoint: string): string { - return `${this.BASE_URL}/api${endpoint}`; - } - - async fetchAuthData(){ - try { - const response = await fetch(this.constructRequestUrl('/auth/getActiveAuth')); - const data = await response.json(); - return data.authActive; - } catch (error) { - console.error('Erreur lors de la récupération des données d\'auth:', error); - } - }; - -} - -const authService = new AuthService(); -export default authService; \ No newline at end of file diff --git a/docker-compose-auth.yaml b/docker-compose-auth.yaml deleted file mode 100644 index 749c6b4..0000000 --- a/docker-compose-auth.yaml +++ /dev/null @@ -1,96 +0,0 @@ -version: '3' - -services: - - frontend: - build: - context: ./client - dockerfile: Dockerfile - container_name: frontend - ports: - - "5173:5173" - restart: always - - backend: - build: - context: ./server - dockerfile: Dockerfile - container_name: backend - ports: - - "3000:3000" - environment: - PORT: 3000 - MONGO_URI: "mongodb://mongo:27017/evaluetonsavoir" - MONGO_DATABASE: evaluetonsavoir - EMAIL_SERVICE: gmail - SENDER_EMAIL: infoevaluetonsavoir@gmail.com - EMAIL_PSW: 'vvml wmfr dkzb vjzb' - JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe - SESSION_Secret: 'lookMomImQuizzing' - SITE_URL: http://localhost - FRONTEND_PORT: 5173 - USE_PORTS: false - AUTHENTICATED_ROOMS: false - volumes: - - ./server/auth_config.json:/usr/src/app/serveur/config/auth_config.json - depends_on: - - mongo - - keycloak - restart: always - - # Ce conteneur sert de routeur pour assurer le bon fonctionnement de l'application - nginx: - image: fuhrmanator/evaluetonsavoir-routeur:latest - container_name: nginx - ports: - - "80:80" - depends_on: - - backend - - frontend - restart: always - - # Ce conteneur est la base de données principale pour l'application - mongo: - image: mongo - container_name: mongo - ports: - - "27017:27017" - tty: true - volumes: - - mongodb_data:/data/db - restart: always - - # Ce conteneur assure que l'application est à jour en allant chercher s'il y a des mises à jours à chaque heure - watchtower: - image: containrrr/watchtower - container_name: watchtower - volumes: - - /var/run/docker.sock:/var/run/docker.sock - environment: - - TZ=America/Montreal - - WATCHTOWER_CLEANUP=true - - WATCHTOWER_DEBUG=true - - WATCHTOWER_INCLUDE_RESTARTING=true - - WATCHTOWER_SCHEDULE=0 0 5 * * * # At 5 am everyday - restart: always - - keycloak: - container_name: keycloak - image: quay.io/keycloak/keycloak:latest - environment: - KEYCLOAK_ADMIN: admin - KEYCLOAK_ADMIN_PASSWORD: admin123 - KC_HEALTH_ENABLED: 'true' - KC_FEATURES: preview - ports: - - "8080:8080" - volumes: - - ./oauth-tester/config.json:/opt/keycloak/data/import/realm-config.json - command: - - start-dev - - --import-realm - - --hostname-strict=false - -volumes: - mongodb_data: - external: false diff --git a/docker-compose.yaml b/docker-compose.yaml index 0d8d61a..24bd3a6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,20 +1,19 @@ -version: '3' - services: frontend: - build: - context: ./client - dockerfile: Dockerfile + image: fuhrmanator/evaluetonsavoir-frontend:latest container_name: frontend + environment: + # Define empty VITE_BACKEND_URL because it's production + - VITE_BACKEND_URL= + # Define empty VITE_BACKEND_SOCKET_URL so it will default to window.location.host + - VITE_BACKEND_SOCKET_URL= ports: - "5173:5173" restart: always backend: - build: - context: ./server - dockerfile: Dockerfile + image: fuhrmanator/evaluetonsavoir-backend:latest container_name: backend ports: - "3000:3000" @@ -26,16 +25,9 @@ services: SENDER_EMAIL: infoevaluetonsavoir@gmail.com EMAIL_PSW: 'vvml wmfr dkzb vjzb' JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe - SESSION_Secret: 'lookMomImQuizzing' - SITE_URL: http://localhost - FRONTEND_PORT: 5173 - USE_PORTS: false - AUTHENTICATED_ROOMS: false - volumes: - - ./server/auth_config.json:/usr/src/app/serveur/config/auth_config.json + FRONTEND_URL: "http://localhost:5173" depends_on: - mongo - - keycloak restart: always # Ce conteneur sert de routeur pour assurer le bon fonctionnement de l'application @@ -87,23 +79,6 @@ services: - WATCHTOWER_INCLUDE_RESTARTING=true restart: "no" - keycloak: - container_name: keycloak - image: quay.io/keycloak/keycloak:latest - environment: - KEYCLOAK_ADMIN: admin - KEYCLOAK_ADMIN_PASSWORD: admin123 - KC_HEALTH_ENABLED: 'true' - KC_FEATURES: preview - ports: - - "8080:8080" - volumes: - - ./oauth-tester/config.json:/opt/keycloak/data/import/realm-config.json - command: - - start-dev - - --import-realm - - --hostname-strict=false - volumes: mongodb_data: external: false diff --git a/oauth-tester/config.json b/oauth-tester/config.json deleted file mode 100644 index ef8f778..0000000 --- a/oauth-tester/config.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "id": "test-realm", - "realm": "EvalueTonSavoir", - "enabled": true, - "users": [ - { - "username": "teacher", - "enabled": true, - "credentials": [ - { - "type": "password", - "value": "teacher123", - "temporary": false - } - ], - "groups": ["teachers"] - }, - { - "username": "student", - "enabled": true, - "credentials": [ - { - "type": "password", - "value": "student123", - "temporary": false - } - ], - "groups": ["students"] - } - ], - "groups": [ - { - "name": "teachers", - "attributes": { - "role": ["teacher"] - } - }, - { - "name": "students", - "attributes": { - "role": ["student"] - } - } - ], - "roles": { - "realm": [ - { - "name": "teacher", - "description": "Teacher role" - }, - { - "name": "student", - "description": "Student role" - } - ] - }, - "clients": [ - { - "clientId": "evaluetonsavoir-client", - "enabled": true, - "publicClient": false, - "clientAuthenticatorType": "client-secret", - "secret": "your-secret-key-123", - "redirectUris": ["http://localhost:5173/*","http://localhost/*"], - "webOrigins": ["http://localhost:5173","http://localhost/"] - } - ], - "clientScopes": [ - { - "name": "group", - "description": "Group scope for access control", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "name": "group mapper", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "group", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "group", - "jsonType.label": "String" - } - } - ] - } - ], - "defaultDefaultClientScopes": ["group"] -} \ No newline at end of file diff --git a/server/.env.example b/server/.env.example index 3ab7212..8608d36 100644 --- a/server/.env.example +++ b/server/.env.example @@ -14,10 +14,4 @@ EMAIL_PSW='vvml wmfr dkzb vjzb' JWT_SECRET=TOKEN! # Pour creer les liens images -SESSION_Secret='session_secret' - -SITE_URL=http://localhost -FRONTEND_PORT=5173 -USE_PORTS=false - -AUTHENTICATED_ROOMS=false +FRONTEND_URL=http://localhost:5173 diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index 47c9c3b..0000000 --- a/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -auth_config.json \ No newline at end of file diff --git a/server/__tests__/auth.test.js b/server/__tests__/auth.test.js deleted file mode 100644 index ae5a192..0000000 --- a/server/__tests__/auth.test.js +++ /dev/null @@ -1,245 +0,0 @@ - -const AuthConfig = require("../config/auth.js"); -const AuthManager = require("../auth/auth-manager.js"); - -const mockConfig = { - auth: { - passportjs: [ - { - provider1: { - type: "oauth", - OAUTH_AUTHORIZATION_URL: "https://www.testurl.com/oauth2/authorize", - OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", - OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", - OAUTH_CLIENT_ID: "your_oauth_client_id", - OAUTH_CLIENT_SECRET: "your_oauth_client_secret", - OAUTH_ADD_SCOPE: "scopes", - OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", - OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", - }, - }, - { - provider2: { - type: "oidc", - OIDC_CLIENT_ID: "your_oidc_client_id", - OIDC_CLIENT_SECRET: "your_oidc_client_secret", - OIDC_CONFIG_URL: "https://your-issuer.com", - OIDC_ADD_SCOPE: "groups", - OIDC_ROLE_TEACHER_VALUE: "teacher-claim-value", - OIDC_ROLE_STUDENT_VALUE: "student-claim-value", - }, - }, - ], - "simpleauth": { - enabled: true, - name: "provider3", - SESSION_SECRET: "your_session_secret", - }, - }, -}; - -// Créez une instance de AuthConfig en utilisant la configuration mockée -describe( - "AuthConfig Class Tests", - () => { - let authConfigInstance; - - // Initialisez l'instance avec la configuration mockée - beforeAll(() => { - authConfigInstance = new AuthConfig(); - authConfigInstance.loadConfigTest(mockConfig); // On injecte la configuration mockée - }); - - it("devrait retourner la configuration PassportJS", () => { - const config = authConfigInstance.getPassportJSConfig(); - expect(config).toHaveProperty("provider1"); - expect(config).toHaveProperty("provider2"); - }); - - it("devrait retourner la configuration Simple Login", () => { - const config = authConfigInstance.getSimpleLoginConfig(); - expect(config).toHaveProperty("name", "provider3"); - expect(config).toHaveProperty("SESSION_SECRET", "your_session_secret"); - }); - - it("devrait retourner les providers OAuth", () => { - const oauthProviders = authConfigInstance.getOAuthProviders(); - expect(Array.isArray(oauthProviders)).toBe(true); - expect(oauthProviders.length).toBe(1); // Il y a un seul provider OAuth - expect(oauthProviders[0]).toHaveProperty("provider1"); - }); - - it("devrait valider la configuration des providers", () => { - expect(() => authConfigInstance.validateProvidersConfig()).not.toThrow(); - }); - - it("devrait lever une erreur si une configuration manque", () => { - const invalidMockConfig = { - auth: { - passportjs: [ - { - provider1: { - type: "oauth", - OAUTH_CLIENT_ID: "your_oauth_client_id", // Il manque des champs nécessaires - }, - }, - ], - }, - }; - - const instanceWithInvalidConfig = new AuthConfig(); - instanceWithInvalidConfig.loadConfigTest(invalidMockConfig); - - // Vérifiez que l'erreur est lancée avec les champs manquants corrects - expect(() => instanceWithInvalidConfig.validateProvidersConfig()).toThrow( - new Error(`Configuration invalide pour les providers suivants : [ - { - "provider": "provider1", - "missingFields": [ - "OAUTH_AUTHORIZATION_URL", - "OAUTH_TOKEN_URL", - "OAUTH_USERINFO_URL", - "OAUTH_CLIENT_SECRET", - "OAUTH_ROLE_TEACHER_VALUE", - "OAUTH_ROLE_STUDENT_VALUE" - ] - } -]`) - ); - }); - }, - - describe("Auth Module Registration", () => { - let expressMock = jest.mock("express"); - expressMock.use = () => {} - expressMock.get = () => {} - - let authConfigInstance; - let authmanagerInstance; - - // Initialisez l'instance avec la configuration mockée - beforeAll(() => { - authConfigInstance = new AuthConfig(); - }); - - it("should load valid modules", () => { - const logSpy = jest.spyOn(global.console, "error"); - const validModule = { - auth: { - passportjs: [ - { - provider1: { - type: "oauth", - OAUTH_AUTHORIZATION_URL: - "https://www.testurl.com/oauth2/authorize", - OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", - OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", - OAUTH_CLIENT_ID: "your_oauth_client_id", - OAUTH_CLIENT_SECRET: "your_oauth_client_secret", - OAUTH_ADD_SCOPE: "scopes", - OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", - OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", - }, - provider2: { - type: "oauth", - OAUTH_AUTHORIZATION_URL: - "https://www.testurl.com/oauth2/authorize", - OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", - OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", - OAUTH_CLIENT_ID: "your_oauth_client_id", - OAUTH_CLIENT_SECRET: "your_oauth_client_secret", - OAUTH_ADD_SCOPE: "scopes", - OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", - OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", - }, - }, - ], - }, - }; - authConfigInstance.loadConfigTest(validModule); // On injecte la configuration mockée - authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); - authmanagerInstance.getUserModel(); - expect(logSpy).toHaveBeenCalledTimes(0); - logSpy.mockClear(); - }); - - it("should not load invalid modules", () => { - const logSpy = jest.spyOn(global.console, "error"); - const invalidModule = { - auth: { - ModuleX:{} - }, - }; - authConfigInstance.loadConfigTest(invalidModule); // On injecte la configuration mockée - authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); - expect(logSpy).toHaveBeenCalledTimes(1); - logSpy.mockClear(); - }); - - - it("should not load invalid provider from passport", () => { - const logSpy = jest.spyOn(global.console, "error"); - const validModuleInvalidProvider = { - auth: { - passportjs: [ - { - provider1: { - type: "x", - OAUTH_AUTHORIZATION_URL: - "https://www.testurl.com/oauth2/authorize", - OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", - OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", - OAUTH_CLIENT_ID: "your_oauth_client_id", - OAUTH_CLIENT_SECRET: "your_oauth_client_secret", - OAUTH_ADD_SCOPE: "scopes", - OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", - OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", - }, - }, - ], - }, - }; - authConfigInstance.loadConfigTest(validModuleInvalidProvider); // On injecte la configuration mockée - authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); - expect(logSpy).toHaveBeenCalledTimes(4); - logSpy.mockClear(); - }); - }) -); - -describe( - "Rooms requiring authentication", () => { - // Making a copy of env variables to restore them later - const OLD_ENV_VARIABLES = process.env; - - let authConfigInstance; - - beforeAll(() => { - authConfigInstance = new AuthConfig(); - }); - - // Clearing cache just in case - beforeEach(() => { - jest.resetModules(); - process.env = { ...OLD_ENV_VARIABLES }; - }); - - // Resetting the old values - afterAll(() => { - process.env = OLD_ENV_VARIABLES; - }); - - // tests cases as [environment variable value, expected value] - const cases = [["true", true], ["false", false], ["", false], ["other_than_true_false", false]]; - test.each(cases)( - "Given %p as AUTHENTICATED_ROOMS environment variable value, returns %p", - (envVarArg, expectedResult) => { - process.env.AUTHENTICATED_ROOMS = envVarArg; - const isAuthRequired = authConfigInstance.getRoomsRequireAuth(); - - expect(isAuthRequired).toEqual(expectedResult); - } - ); - - } -) diff --git a/server/__tests__/users.test.js b/server/__tests__/users.test.js index 7f326ee..2c6b4eb 100644 --- a/server/__tests__/users.test.js +++ b/server/__tests__/users.test.js @@ -32,7 +32,7 @@ describe('Users', () => { users = new Users(db, foldersModel); }); - it.skip('should register a new user', async () => { + it('should register a new user', async () => { db.collection().findOne.mockResolvedValue(null); // No user found db.collection().insertOne.mockResolvedValue({ insertedId: new ObjectId() }); bcrypt.hash.mockResolvedValue('hashedPassword'); diff --git a/server/app.js b/server/app.js index d2634c7..570ee8b 100644 --- a/server/app.js +++ b/server/app.js @@ -39,25 +39,17 @@ module.exports.images = imagesControllerInstance; const userRouter = require('./routers/users.js'); const folderRouter = require('./routers/folders.js'); const quizRouter = require('./routers/quiz.js'); -const imagesRouter = require('./routers/images.js') -const AuthManager = require('./auth/auth-manager.js') -const authRouter = require('./routers/auth.js') +const imagesRouter = require('./routers/images.js'); // Setup environment dotenv.config(); - -// Setup urls from configs -const use_ports = (process.env['USE_PORTS'] || 'false').toLowerCase() == "true" -process.env['FRONTEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['FRONTEND_PORT']}`:"") -process.env['BACKEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['PORT']}`:"") - +const isDev = process.env.NODE_ENV === 'development'; const errorHandler = require("./middleware/errorHandler.js"); // Start app const app = express(); const cors = require("cors"); const bodyParser = require('body-parser'); -let isDev = process.env.NODE_ENV === 'development'; const configureServer = (httpServer, isDev) => { console.log(`Configuring server with isDev: ${isDev}`); @@ -92,19 +84,7 @@ app.use('/api/user', userRouter); app.use('/api/folder', folderRouter); app.use('/api/quiz', quizRouter); app.use('/api/image', imagesRouter); -app.use('/api/auth', authRouter); -// Add Auths methods -const session = require('express-session'); -app.use(session({ - secret: process.env['SESSION_Secret'], - resave: false, - saveUninitialized: false, - cookie: { secure: process.env.NODE_ENV === 'production' } -})); - -let authManager = new AuthManager(app,null,userModel); -authManager.getUserModel(); app.use(errorHandler); // Start server diff --git a/server/auth/auth-manager.js b/server/auth/auth-manager.js deleted file mode 100644 index c38c44d..0000000 --- a/server/auth/auth-manager.js +++ /dev/null @@ -1,69 +0,0 @@ -const fs = require('fs'); -const AuthConfig = require('../config/auth.js'); -const jwt = require('../middleware/jwtToken.js'); -const emailer = require('../config/email.js'); -const { MISSING_REQUIRED_PARAMETER } = require('../constants/errorCodes.js'); -const AppError = require('../middleware/AppError.js'); - -class AuthManager{ - constructor(expressapp,configs=null,userModel){ - this.modules = [] - this.app = expressapp - - this.configs = configs ?? (new AuthConfig()).loadConfig() - this.addModules() - this.registerAuths() - this.simpleregister = userModel; - } - - getUserModel(){ - return this.simpleregister; - } - - async addModules(){ - for(const module in this.configs.auth){ - this.addModule(module) - } - } - - async addModule(name){ - const modulePath = `${process.cwd()}/auth/modules/${name}.js` - - if(fs.existsSync(modulePath)){ - const Module = require(modulePath); - this.modules.push(new Module(this,this.configs.auth[name])); - console.info(`Module d'authentification '${name}' ajouté`) - } else{ - console.error(`Le module d'authentification ${name} n'as pas été chargé car il est introuvable`); - } - } - - async registerAuths(){ - for(const module of this.modules){ - try{ - module.registerAuth(this.app) - } catch(error){ - console.error(`L'enregistrement du module ${module} a échoué.`); - console.error(`Error: ${error} `); - } - } - } - - // eslint-disable-next-line no-unused-vars - async login(userInfo,req,res,next){ //passport and simpleauth use next - const tokenToSave = jwt.create(userInfo.email, userInfo._id,userInfo.roles); - res.redirect(`/auth/callback?user=${tokenToSave}&username=${userInfo.name}`); - console.info(`L'utilisateur '${userInfo.name}' vient de se connecter`) - } - - async register(userInfos){ - if (!userInfos.email || !userInfos.password) { - throw new AppError(MISSING_REQUIRED_PARAMETER); - } - const user = await this.simpleregister.register(userInfos); - emailer.registerConfirmation(user.email) - return user - } -} - -module.exports = AuthManager; \ No newline at end of file diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js deleted file mode 100644 index b05a5be..0000000 --- a/server/auth/modules/passport-providers/oauth.js +++ /dev/null @@ -1,100 +0,0 @@ -var OAuth2Strategy = require('passport-oauth2') -var authUserAssoc = require('../../../models/authUserAssociation') -var users = require('../../../models/users') -var { hasNestedValue } = require('../../../utils') - -class PassportOAuth { - constructor(passportjs, auth_name) { - this.passportjs = passportjs - this.auth_name = auth_name - } - - register(app, passport, endpoint, name, provider) { - const cb_url = `${process.env['BACKEND_URL']}${endpoint}/${name}/callback` - const self = this - const scope = 'openid profile email offline_access' + ` ${provider.OAUTH_ADD_SCOPE}`; - - passport.use(name, new OAuth2Strategy({ - authorizationURL: provider.OAUTH_AUTHORIZATION_URL, - tokenURL: provider.OAUTH_TOKEN_URL, - clientID: provider.OAUTH_CLIENT_ID, - clientSecret: provider.OAUTH_CLIENT_SECRET, - callbackURL: cb_url, - passReqToCallback: true - }, - async function (req, accessToken, refreshToken, params, profile, done) { - try { - const userInfoResponse = await fetch(provider.OAUTH_USERINFO_URL, { - headers: { 'Authorization': `Bearer ${accessToken}` } - }); - const userInfo = await userInfoResponse.json(); - - let received_user = { - auth_id: userInfo.sub, - email: userInfo.email, - name: userInfo.name, - roles: [] - }; - - if (hasNestedValue(userInfo, provider.OAUTH_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher') - if (hasNestedValue(userInfo, provider.OAUTH_ROLE_STUDENT_VALUE)) received_user.roles.push('student') - - const user_association = await authUserAssoc.find_user_association(self.auth_name, received_user.auth_id) - - let user_account - if (user_association) { - user_account = await users.getById(user_association.user_id) - } - else { - let user_id = await users.getId(received_user.email) - if (user_id) { - user_account = await users.getById(user_id); - } else { - received_user.password = users.generatePassword() - user_account = await self.passportjs.register(received_user) - } - await authUserAssoc.link(self.auth_name, received_user.auth_id, user_account._id) - } - - user_account.name = received_user.name - user_account.roles = received_user.roles - await users.editUser(user_account) - - // Store the tokens in the session - req.session.oauth2Tokens = { - accessToken: accessToken, - refreshToken: refreshToken, - expiresIn: params.expires_in - }; - - return done(null, user_account); - } catch (error) { - console.error(`Erreur dans la strategie OAuth2 '${name}' : ${error}`); - return done(error); - } - })); - - app.get(`${endpoint}/${name}`, (req, res, next) => { - passport.authenticate(name, { - scope: scope, - prompt: 'consent' - })(req, res, next); - }); - - app.get(`${endpoint}/${name}/callback`, - (req, res, next) => { - passport.authenticate(name, { failureRedirect: '/login' })(req, res, next); - }, - (req, res) => { - if (req.user) { - self.passportjs.authenticate(req.user, req, res) - } else { - res.status(401).json({ error: "L'authentification a échoué" }); - } - } - ); - console.info(`Ajout de la connexion : ${name}(OAuth)`) - } -} - -module.exports = PassportOAuth; diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js deleted file mode 100644 index 9ccebf1..0000000 --- a/server/auth/modules/passport-providers/oidc.js +++ /dev/null @@ -1,106 +0,0 @@ -var OpenIDConnectStrategy = require('passport-openidconnect'); -var authUserAssoc = require('../../../models/authUserAssociation'); -var users = require('../../../models/users'); -var { hasNestedValue } = require('../../../utils'); -const { MISSING_OIDC_PARAMETER } = require('../../../constants/errorCodes.js'); -const AppError = require('../../../middleware/AppError.js'); - -class PassportOpenIDConnect { - constructor(passportjs, auth_name) { - this.passportjs = passportjs - this.auth_name = auth_name - } - - async getConfigFromConfigURL(name, provider) { - try { - const config = await fetch(provider.OIDC_CONFIG_URL) - return await config.json() - } catch (error) { - console.error(`Error: ${error} `); - throw new AppError(MISSING_OIDC_PARAMETER(name)); - } - } - - async register(app, passport, endpoint, name, provider) { - - const config = await this.getConfigFromConfigURL(name, provider) - const cb_url = `${process.env['BACKEND_URL']}${endpoint}/${name}/callback` - const self = this - const scope = 'openid profile email ' + `${provider.OIDC_ADD_SCOPE}` - - passport.use(name, new OpenIDConnectStrategy({ - issuer: config.issuer, - authorizationURL: config.authorization_endpoint, - tokenURL: config.token_endpoint, - userInfoURL: config.userinfo_endpoint, - clientID: provider.OIDC_CLIENT_ID, - clientSecret: provider.OIDC_CLIENT_SECRET, - callbackURL: cb_url, - passReqToCallback: true, - scope: scope, - }, - // patch pour la librairie permet d'obtenir les groupes, PR en cours mais "morte" : https://github.com/jaredhanson/passport-openidconnect/pull/101 - async function (req, issuer, profile, times, tok, done) { - try { - const received_user = { - auth_id: profile.id, - email: profile.emails[0].value, - name: profile.name.givenName, - roles: [] - }; - - - if (hasNestedValue(profile, provider.OIDC_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher') - if (hasNestedValue(profile, provider.OIDC_ROLE_STUDENT_VALUE)) received_user.roles.push('student') - - const user_association = await authUserAssoc.find_user_association(self.auth_name, received_user.auth_id) - - let user_account - if (user_association) { - user_account = await users.getById(user_association.user_id) - } - else { - let user_id = await users.getId(received_user.email) - if (user_id) { - user_account = await users.getById(user_id); - } else { - received_user.password = users.generatePassword() - user_account = await self.passportjs.register(received_user) - } - await authUserAssoc.link(self.auth_name, received_user.auth_id, user_account._id) - } - - user_account.name = received_user.name - user_account.roles = received_user.roles - await users.editUser(user_account); - - return done(null, user_account); - } catch (error) { - console.error(`Error: ${error} `); - } - })); - - app.get(`${endpoint}/${name}`, (req, res, next) => { - passport.authenticate(name, { - scope: scope, - prompt: 'consent' - })(req, res, next); - }); - - app.get(`${endpoint}/${name}/callback`, - (req, res, next) => { - passport.authenticate(name, { failureRedirect: '/login' })(req, res, next); - }, - (req, res) => { - if (req.user) { - self.passportjs.authenticate(req.user, req, res) - } else { - res.status(401).json({ error: "L'authentification a échoué" }); - } - } - ); - console.info(`Ajout de la connexion : ${name}(OIDC)`) - } -} - -module.exports = PassportOpenIDConnect; diff --git a/server/auth/modules/passportjs.js b/server/auth/modules/passportjs.js deleted file mode 100644 index d88488b..0000000 --- a/server/auth/modules/passportjs.js +++ /dev/null @@ -1,65 +0,0 @@ -var passport = require('passport') -var authprovider = require('../../models/authProvider') - -class PassportJs{ - constructor(authmanager,settings){ - this.authmanager = authmanager - this.registeredProviders = {} - this.providers = settings - this.endpoint = "/api/auth" - } - - async registerAuth(expressapp){ - expressapp.use(passport.initialize()); - expressapp.use(passport.session()); - - for(const p of this.providers){ - for(const [name,provider] of Object.entries(p)){ - const auth_id = `passportjs_${provider.type}_${name}` - - if(!(provider.type in this.registeredProviders)){ - this.registerProvider(provider.type,auth_id) - } - try{ - this.registeredProviders[provider.type].register(expressapp,passport,this.endpoint,name,provider) - authprovider.create(auth_id) - } catch(error){ - console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`); - console.error(`Error: ${error} `); - } - } - } - - passport.serializeUser(function(user, done) { - done(null, user); - }); - - passport.deserializeUser(function(user, done) { - done(null, user); - }); - } - - async registerProvider(providerType,auth_id){ - try{ - const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js` - const Provider = require(providerPath); - this.registeredProviders[providerType]= new Provider(this,auth_id) - console.info(`Le type de connexion '${providerType}' a été ajouté dans passportjs.`) - } catch(error){ - console.error(`Le type de connexion '${providerType}' n'as pas pu être chargé dans passportjs.`); - console.error(`Error: ${error} `); - } - } - - - register(userInfos){ - return this.authmanager.register(userInfos) - } - - authenticate(userInfo,req,res,next){ - return this.authmanager.login(userInfo,req,res,next) - } - -} - -module.exports = PassportJs; \ No newline at end of file diff --git a/server/auth/modules/simpleauth.js b/server/auth/modules/simpleauth.js deleted file mode 100644 index b40f55d..0000000 --- a/server/auth/modules/simpleauth.js +++ /dev/null @@ -1,126 +0,0 @@ -const jwt = require('../../middleware/jwtToken.js'); -const emailer = require('../../config/email.js'); - -const model = require('../../models/users.js'); -const AppError = require('../../middleware/AppError.js'); -const { MISSING_REQUIRED_PARAMETER, LOGIN_CREDENTIALS_ERROR, GENERATE_PASSWORD_ERROR, UPDATE_PASSWORD_ERROR } = require('../../constants/errorCodes'); -const { name } = require('../../models/authProvider.js'); - -class SimpleAuth { - constructor(authmanager, settings) { - this.authmanager = authmanager - this.providers = settings - this.endpoint = "/api/auth/simple-auth" - } - - async registerAuth(expressapp) { - try { - expressapp.post(`${this.endpoint}/register`, (req, res) => this.register(this, req, res)); - expressapp.post(`${this.endpoint}/login`, (req, res, next) => this.authenticate(this, req, res, next)); - expressapp.post(`${this.endpoint}/reset-password`, (req, res, next) => this.resetPassword(this, req, res, next)); - expressapp.post(`${this.endpoint}/change-password`, jwt.authenticate, (req, res, next) => this.changePassword(this, req, res, next)); - } catch (error) { - console.error(`La connexion ${name} de type ${this.providers.type} n'as pu être chargé.`); - console.error(`Error: ${error} `); - } - } - - async register(self, req, res) { - try { - let userInfos = { - name: req.body.name, - email: req.body.email, - password: req.body.password, - roles: req.body.roles - } - let user = await self.authmanager.register(userInfos) - if (user) res.redirect("/login") - } - catch (error) { - return res.status(400).json({ - message: error.message - }); - } - } - - async authenticate(self, req, res, next) { - try { - const { email, password } = req.body; - - if (!email || !password) { - const error = new Error("Email or password is missing"); - error.statusCode = 400; - throw error; - } - - const userModel = self.authmanager.getUserModel(); - const user = userModel.login(email, password); - - await self.authmanager.login(user, req, res, next); - } catch (error) { - const statusCode = error.statusCode || 500; - const message = error.message || "An internal server error occurred"; - - console.error(error); - return res.status(statusCode).json({ message }); - } - } - - async resetPassword(self, req, res, next) { - try { - const { email } = req.body; - - if (!email) { - throw new AppError(MISSING_REQUIRED_PARAMETER); - } - - const newPassword = await model.resetPassword(email); - - if (!newPassword) { - throw new AppError(GENERATE_PASSWORD_ERROR); - } - - emailer.newPasswordConfirmation(email, newPassword); - - return res.status(200).json({ - message: 'Nouveau mot de passe envoyé par courriel.' - }); - } - catch (error) { - return next(error); - } - } - - async changePassword(self, req, res, next) { - try { - const { email, oldPassword, newPassword } = req.body; - - if (!email || !oldPassword || !newPassword) { - throw new AppError(MISSING_REQUIRED_PARAMETER); - } - - // verify creds first - const user = await model.login(email, oldPassword); - - if (!user) { - throw new AppError(LOGIN_CREDENTIALS_ERROR); - } - - const password = await model.changePassword(email, newPassword) - - if (!password) { - throw new AppError(UPDATE_PASSWORD_ERROR); - } - - return res.status(200).json({ - message: 'Mot de passe changé avec succès.' - }); - } - catch (error) { - return next(error); - } - } - -} - -module.exports = SimpleAuth; \ No newline at end of file diff --git a/server/auth_config.json.example b/server/auth_config.json.example deleted file mode 100644 index 2a8fb11..0000000 --- a/server/auth_config.json.example +++ /dev/null @@ -1,26 +0,0 @@ -{ - "auth": { - "passportjs": - [ - { - "oidc_local": { - "type": "oidc", - "OIDC_CONFIG_URL": "http://localhost:8080/realms/EvalueTonSavoir/.well-known/openid-configuration", - "OIDC_CLIENT_ID": "evaluetonsavoir-client", - "OIDC_CLIENT_SECRET": "your-secret-key-123", - "OIDC_ADD_SCOPE": "group", - "OIDC_ROLE_TEACHER_VALUE": "teachers", - "OIDC_ROLE_STUDENT_VALUE": "students" - } - } - ], - "simple-login": { - "enabled": true, - "name": "provider3", - "SESSION_SECRET": "your_session_secret" - }, - "Module X":{ - - } - } -} \ No newline at end of file diff --git a/server/config/auth.js b/server/config/auth.js deleted file mode 100644 index 72e89ed..0000000 --- a/server/config/auth.js +++ /dev/null @@ -1,193 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const pathAuthConfig = './auth_config.json'; - -const configPath = path.join(process.cwd(), pathAuthConfig); - -class AuthConfig { - - config = null; - - - // Méthode pour lire le fichier de configuration JSON - loadConfig() { - try { - const configData = fs.readFileSync(configPath, 'utf-8'); - this.config = JSON.parse(configData); - } catch (error) { - console.error("Erreur lors de la lecture du fichier de configuration. Ne pas se fier si vous n'avez pas mit de fichier de configuration."); - this.config = {}; - throw error; - } - return this.config - } - - // Méthode pour load le fichier de test - loadConfigTest(mockConfig) { - this.config = mockConfig; - } - - // Méthode pour retourner la configuration des fournisseurs PassportJS - getPassportJSConfig() { - if (this.config && this.config.auth && this.config.auth.passportjs) { - const passportConfig = {}; - - this.config.auth.passportjs.forEach(provider => { - const providerName = Object.keys(provider)[0]; - passportConfig[providerName] = provider[providerName]; - }); - - return passportConfig; - } else { - return { error: "Aucune configuration PassportJS disponible." }; - } - } - - // Méthode pour retourner la configuration de Simple Login - getSimpleLoginConfig() { - if (this.config && this.config.auth && this.config.auth["simpleauth"]) { - return this.config.auth["simpleauth"]; - } else { - return { error: "Aucune configuration Simple Login disponible." }; - } - } - - // Méthode pour retourner tous les providers de type OAuth - getOAuthProviders() { - if (this.config && this.config.auth && this.config.auth.passportjs) { - const oauthProviders = this.config.auth.passportjs.filter(provider => { - const providerName = Object.keys(provider)[0]; - return provider[providerName].type === 'oauth'; - }); - - if (oauthProviders.length > 0) { - return oauthProviders; - } else { - return { error: "Aucun fournisseur OAuth disponible." }; - } - } else { - return { error: "Aucune configuration PassportJS disponible." }; - } - } - - // Méthode pour retourner tous les providers de type OIDC - getOIDCProviders() { - if (this.config && this.config.auth && this.config.auth.passportjs) { - const oidcProviders = this.config.auth.passportjs.filter(provider => { - const providerName = Object.keys(provider)[0]; - return provider[providerName].type === 'oidc'; - }); - - if (oidcProviders.length > 0) { - return oidcProviders; - } else { - return { error: "Aucun fournisseur OIDC disponible." }; - } - } else { - return { error: "Aucune configuration PassportJS disponible." }; - } - } - - // Méthode pour vérifier si tous les providers ont les variables nécessaires - validateProvidersConfig() { - const requiredOAuthFields = [ - 'OAUTH_AUTHORIZATION_URL', 'OAUTH_TOKEN_URL','OAUTH_USERINFO_URL', 'OAUTH_CLIENT_ID', 'OAUTH_CLIENT_SECRET', 'OAUTH_ROLE_TEACHER_VALUE', 'OAUTH_ROLE_STUDENT_VALUE' - ]; - - const requiredOIDCFields = [ - 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_CONFIG_URL', 'OIDC_ROLE_TEACHER_VALUE', 'OIDC_ROLE_STUDENT_VALUE','OIDC_ADD_SCOPE' - ]; - - const missingFieldsReport = []; - - if (this.config && this.config.auth && this.config.auth.passportjs) { - this.config.auth.passportjs.forEach(provider => { - const providerName = Object.keys(provider)[0]; - const providerConfig = provider[providerName]; - - let missingFields = []; - - // Vérification des providers de type OAuth - if (providerConfig.type === 'oauth') { - missingFields = requiredOAuthFields.filter(field => !(field in providerConfig)); - } - // Vérification des providers de type OIDC - else if (providerConfig.type === 'oidc') { - missingFields = requiredOIDCFields.filter(field => !(field in providerConfig)); - } - - // Si des champs manquent, on les ajoute au rapport - if (missingFields.length > 0) { - missingFieldsReport.push({ - provider: providerName, - missingFields: missingFields - }); - } - }); - - // Si des champs manquent, lever une exception - if (missingFieldsReport.length > 0) { - throw new Error(`Configuration invalide pour les providers suivants : ${JSON.stringify(missingFieldsReport, null, 2)}`); - } else { - console.log("Configuration auth_config.json: Tous les providers ont les variables nécessaires.") - return { success: "Tous les providers ont les variables nécessaires." }; - } - } else { - throw new Error("Aucune configuration PassportJS disponible."); - } - } - - // Méthode pour retourner la configuration des fournisseurs PassportJS pour le frontend - getActiveAuth() { - if (this.config && this.config.auth) { - const passportConfig = {}; - - // Gestion des providers PassportJS - if (this.config.auth.passportjs) { - this.config.auth.passportjs.forEach(provider => { - const providerName = Object.keys(provider)[0]; - const providerConfig = provider[providerName]; - - passportConfig[providerName] = {}; - - if (providerConfig.type === 'oauth') { - passportConfig[providerName] = { - type: providerConfig.type - }; - } else if (providerConfig.type === 'oidc') { - passportConfig[providerName] = { - type: providerConfig.type, - }; - } - }); - } - - // Gestion du Simple Login - if (this.config.auth["simpleauth"] && this.config.auth["simpleauth"].enabled) { - passportConfig['simpleauth'] = { - type: "simpleauth", - name: this.config.auth["simpleauth"].name - }; - } - - return passportConfig; - } else { - return { error: "Aucune configuration d'authentification disponible." }; - } - } - - // Check if students must be authenticated to join a room - getRoomsRequireAuth() { - const roomRequireAuth = process.env.AUTHENTICATED_ROOMS; - - if (!roomRequireAuth || roomRequireAuth !== "true") { - return false; - } - - return true; - } - - -} - -module.exports = AuthConfig; diff --git a/server/constants/errorCodes.js b/server/constants/errorCodes.js index 36ae657..41147ae 100644 --- a/server/constants/errorCodes.js +++ b/server/constants/errorCodes.js @@ -12,13 +12,6 @@ exports.MISSING_REQUIRED_PARAMETER = { code: 400 } -exports.MISSING_OIDC_PARAMETER = (name) => { - return { - message: `Les informations de connexions de la connexion OIDC ${name} n'ont pu être chargées.`, - code: 400 - } -} - exports.USER_ALREADY_EXISTS = { message: 'L\'utilisateur existe déjà.', code: 400 diff --git a/server/controllers/auth.js b/server/controllers/auth.js deleted file mode 100644 index 3696e1e..0000000 --- a/server/controllers/auth.js +++ /dev/null @@ -1,36 +0,0 @@ -const AuthConfig = require('../config/auth.js'); - -class authController { - - async getActive(req, res, next) { - try { - - const authC = new AuthConfig(); - authC.loadConfig(); - - const authActive = authC.getActiveAuth(); - - const response = { - authActive - }; - return res.json(response); - } - catch (error) { - return next(error); // Gérer l'erreur - } - } - - async getRoomsRequireAuth(req, res) { - const authC = new AuthConfig(); - const roomsRequireAuth = authC.getRoomsRequireAuth(); - - const response = { - roomsRequireAuth - } - - return res.json(response); - } - -} - -module.exports = new authController; \ No newline at end of file diff --git a/server/middleware/jwtToken.js b/server/middleware/jwtToken.js index 75ad458..292e591 100644 --- a/server/middleware/jwtToken.js +++ b/server/middleware/jwtToken.js @@ -7,8 +7,8 @@ dotenv.config(); class Token { - create(email, userId, roles) { - return jwt.sign({ email, userId, roles }, process.env.JWT_SECRET); + create(email, userId) { + return jwt.sign({ email, userId }, process.env.JWT_SECRET); } authenticate(req, res, next) { @@ -25,11 +25,11 @@ class Token { req.user = payload; }); - + } catch (error) { return next(error); } - + return next(); } } diff --git a/server/models/authProvider.js b/server/models/authProvider.js deleted file mode 100644 index ab92da4..0000000 --- a/server/models/authProvider.js +++ /dev/null @@ -1,44 +0,0 @@ -const db = require('../config/db.js') -const { ObjectId } = require('mongodb'); - -class AuthProvider { - constructor(name) { - this._id = new ObjectId(); - this.name = name; - } - - async getId(name){ - await db.connect() - const conn = db.getConnection(); - - const collection = conn.collection('authprovider'); - - const existingauth = await collection.findOne({ name:name }); - - if(existingauth){ - return existingauth._id - } - return null - } - - async create(name) { - await db.connect() - const conn = db.getConnection(); - - const collection = conn.collection('authprovider'); - - const existingauth = await collection.findOne({ name:name }); - - if(existingauth){ - return existingauth._id; - } - - const newProvider = { - name:name - } - const result = await collection.insertOne(newProvider); - return result.insertedId; - } -} - -module.exports = new AuthProvider; \ No newline at end of file diff --git a/server/models/authUserAssociation.js b/server/models/authUserAssociation.js deleted file mode 100644 index b6c1e4d..0000000 --- a/server/models/authUserAssociation.js +++ /dev/null @@ -1,59 +0,0 @@ -const authProvider = require('./authProvider.js') -const db = require('../config/db.js') -const { ObjectId } = require('mongodb'); - - -class AuthUserAssociation { - constructor(authProviderId, authId, userId) { - this._id = new ObjectId(); - this.authProvider_id = authProviderId; - this.auth_id = authId; - this.user_id = userId; - this.connected = false; - } - - async find_user_association(provider_name,auth_id){ - await db.connect() - const conn = db.getConnection(); - - const collection = conn.collection('authUserAssociation'); - const provider_id = await authProvider.getId(provider_name) - - const userAssociation = await collection.findOne({ authProvider_id: provider_id, auth_id: auth_id }); - return userAssociation - } - - async link(provider_name,auth_id,user_id){ - await db.connect() - const conn = db.getConnection(); - - const collection = conn.collection('authUserAssociation'); - const provider_id = await authProvider.getId(provider_name) - - const userAssociation = await collection.findOne({ authProvider_id: provider_id, user_id: user_id }); - - if(!userAssociation){ - return await collection.insertOne({ - _id:ObjectId, - authProvider_id:provider_id, - auth_id:auth_id, - user_id:user_id, - }) - } - } - - async unlink(provider_name,user_id){ - await db.connect() - const conn = db.getConnection(); - - const collection = conn.collection('authUserAssociation'); - const provider_id = await authProvider.getId(provider_name) - - const userAssociation = await collection.findOne({ authProvider_id: provider_id, user_id: user_id }); - - if(userAssociation){ - return await collection.deleteOne(userAssociation) - } else return null - } - } -module.exports = new AuthUserAssociation; \ No newline at end of file diff --git a/server/models/users.js b/server/models/users.js index f18e708..1a04d86 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -1,194 +1,125 @@ -const bcrypt = require("bcrypt"); -const AppError = require("../middleware/AppError.js"); -const { USER_ALREADY_EXISTS } = require("../constants/errorCodes"); +//user +const bcrypt = require('bcrypt'); +const AppError = require('../middleware/AppError.js'); +const { USER_ALREADY_EXISTS } = require('../constants/errorCodes'); class Users { - - constructor(db, foldersModel) { - this.db = db; - this.folders = foldersModel; - } - - async hashPassword(password) { - return await bcrypt.hash(password, 10); - } - - generatePassword() { - return Math.random().toString(36).slice(-8); - } - - async verify(password, hash) { - return await bcrypt.compare(password, hash); - } - - async register(userInfos) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const userCollection = conn.collection("users"); - - const existingUser = await userCollection.findOne({ email: userInfos.email }); - - if (existingUser) { - throw new AppError(USER_ALREADY_EXISTS); + constructor(db, foldersModel) { + // console.log("Users constructor: db", db) + this.db = db; + this.folders = foldersModel; } - - let newUser = { - name: userInfos.name ?? userInfos.email, - email: userInfos.email, - password: await this.hashPassword(userInfos.password), - created_at: new Date(), - roles: userInfos.roles - }; - - let created_user = await userCollection.insertOne(newUser); - let user = await this.getById(created_user.insertedId) - - const folderTitle = "Dossier par Défaut"; - const userId = newUser._id ? newUser._id.toString() : 'x'; - await this.folders.create(folderTitle, userId); - - // TODO: verif if inserted properly... - return user; - } - - async login(userid) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const userCollection = conn.collection("users"); - const user = await userCollection.findOne({ _id: userid }); - - if (!user) { - return false; + async hashPassword(password) { + return await bcrypt.hash(password, 10) } - return user; - } -/* - async login(email, password) { - try { - await this.db.connect(); - const conn = this.db.getConnection(); - const userCollection = conn.collection("users"); - - const user = await userCollection.findOne({ email: email }); - - if (!user) { - const error = new Error("User not found"); - error.statusCode = 404; - throw error; - } - - const passwordMatch = await this.verify(password, user.password); - - if (!passwordMatch) { - const error = new Error("Password does not match"); - error.statusCode = 401; - throw error; - } - - return user; - } catch (error) { - console.error(error); - throw error; - } - } -*/ - async resetPassword(email) { - const newPassword = this.generatePassword(); - - return await this.changePassword(email, newPassword); - } - - async changePassword(email, newPassword) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const userCollection = conn.collection("users"); - - const hashedPassword = await this.hashPassword(newPassword); - - const result = await userCollection.updateOne( - { email }, - { $set: { password: hashedPassword } } - ); - - if (result.modifiedCount != 1) return null; - - return newPassword; - } - - async delete(email) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const userCollection = conn.collection("users"); - - const result = await userCollection.deleteOne({ email }); - - if (result.deletedCount != 1) return false; - - return true; - } - - async getId(email) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const userCollection = conn.collection("users"); - - const user = await userCollection.findOne({ email: email }); - - if (!user) { - return false; + generatePassword() { + return Math.random().toString(36).slice(-8); } - return user._id; - } - - async getById(id) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const userCollection = conn.collection("users"); - - const user = await userCollection.findOne({ _id: id }); - - if (!user) { - return false; + async verify(password, hash) { + return await bcrypt.compare(password, hash) } - return user; - } + async register(email, password) { + await this.db.connect() + const conn = this.db.getConnection(); + + const userCollection = conn.collection('users'); - async editUser(userInfo) { - await this.db.connect(); - const conn = this.db.getConnection(); + const existingUser = await userCollection.findOne({ email: email }); - const userCollection = conn.collection("users"); + if (existingUser) { + throw new AppError(USER_ALREADY_EXISTS); + } - const user = await userCollection.findOne({ _id: userInfo.id }); + const newUser = { + email: email, + password: await this.hashPassword(password), + created_at: new Date() + }; - if (!user) { - return false; + const result = await userCollection.insertOne(newUser); + // console.log("userCollection.insertOne() result", result); + const userId = result.insertedId.toString(); + + const folderTitle = 'Dossier par Défaut'; + await this.folders.create(folderTitle, userId); + + return result; } - const updatedFields = { ...userInfo }; - delete updatedFields.id; + async login(email, password) { + await this.db.connect() + const conn = this.db.getConnection(); - const result = await userCollection.updateOne( - { _id: userInfo.id }, - { $set: updatedFields } - ); + const userCollection = conn.collection('users'); - if (result.modifiedCount === 1) { - return true; + const user = await userCollection.findOne({ email: email }); + + if (!user) { + return false; + } + + const passwordMatch = await this.verify(password, user.password); + + if (!passwordMatch) { + return false; + } + + return user; + } + + async resetPassword(email) { + const newPassword = this.generatePassword(); + + return await this.changePassword(email, newPassword); + } + + async changePassword(email, newPassword) { + await this.db.connect() + const conn = this.db.getConnection(); + + const userCollection = conn.collection('users'); + + const hashedPassword = await this.hashPassword(newPassword); + + const result = await userCollection.updateOne({ email }, { $set: { password: hashedPassword } }); + + if (result.modifiedCount != 1) return null; + + return newPassword + } + + async delete(email) { + await this.db.connect() + const conn = this.db.getConnection(); + + const userCollection = conn.collection('users'); + + const result = await userCollection.deleteOne({ email }); + + if (result.deletedCount != 1) return false; + + return true; + } + + async getId(email) { + await this.db.connect() + const conn = this.db.getConnection(); + + const userCollection = conn.collection('users'); + + const user = await userCollection.findOne({ email: email }); + + if (!user) { + return false; + } + + return user._id; } - return false; - } } module.exports = Users; diff --git a/server/package-lock.json b/server/package-lock.json index 1de4aeb..3d4122a 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -7,22 +7,16 @@ "": { "name": "ets-pfe004-evaluetonsavoir-backend", "version": "1.0.0", - "hasInstallScript": true, "license": "MIT", "dependencies": { "bcrypt": "^5.1.1", "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", - "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.9", - "passport": "^0.7.0", - "passport-oauth2": "^1.8.0", - "passport-openidconnect": "^0.1.2", - "patch-package": "^8.0.0", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, @@ -1624,11 +1618,6 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" - }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1745,6 +1734,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1816,14 +1806,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -1953,14 +1935,6 @@ "node": "^4.5.0 || >= 5.9" } }, - "node_modules/base64url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", - "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -2019,6 +1993,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -2105,41 +2080,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dependencies": { "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -2190,6 +2139,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2205,6 +2155,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -2213,6 +2164,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2268,6 +2220,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, "funding": [ { "type": "github", @@ -2318,6 +2271,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2328,7 +2282,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/color-support": { "version": "1.1.3", @@ -2514,6 +2469,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2656,19 +2612,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -2813,9 +2756,12 @@ } }, "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, "engines": { "node": ">= 0.4" } @@ -2828,17 +2774,6 @@ "node": ">= 0.4" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3247,37 +3182,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3330,6 +3234,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3367,14 +3272,6 @@ "node": ">=8" } }, - "node_modules/find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "dependencies": { - "micromatch": "^4.0.2" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -3441,20 +3338,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -3482,6 +3365,20 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3528,20 +3425,15 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "get-proto": "^1.0.0", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -3559,18 +3451,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -3628,11 +3508,11 @@ } }, "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3641,7 +3521,8 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/has-flag": { "version": "3.0.0", @@ -3663,10 +3544,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -3680,9 +3572,9 @@ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "dependencies": { "function-bind": "^1.1.2" }, @@ -3896,20 +3788,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3952,6 +3830,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -3968,17 +3847,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3987,7 +3855,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -4808,24 +4677,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-stable-stringify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz", - "integrity": "sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "isarray": "^2.0.5", - "jsonify": "^0.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -4833,11 +4684,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-stable-stringify/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4850,25 +4696,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", - "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -4924,14 +4751,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/klaw-sync": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", - "dependencies": { - "graceful-fs": "^4.1.11" - } - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -5059,14 +4878,6 @@ "tmpl": "1.0.5" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -5106,6 +4917,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -5468,11 +5280,6 @@ "set-blocking": "^2.0.0" } }, - "node_modules/oauth": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.0.tgz", - "integrity": "sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5492,14 +5299,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -5511,14 +5310,6 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5542,21 +5333,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5575,14 +5351,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -5658,115 +5426,6 @@ "node": ">= 0.8" } }, - "node_modules/passport": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", - "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", - "dependencies": { - "passport-strategy": "1.x.x", - "pause": "0.0.1", - "utils-merge": "^1.0.1" - }, - "engines": { - "node": ">= 0.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" - } - }, - "node_modules/passport-oauth2": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", - "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", - "dependencies": { - "base64url": "3.x.x", - "oauth": "0.10.x", - "passport-strategy": "1.x.x", - "uid2": "0.0.x", - "utils-merge": "1.x.x" - }, - "engines": { - "node": ">= 0.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" - } - }, - "node_modules/passport-openidconnect": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/passport-openidconnect/-/passport-openidconnect-0.1.2.tgz", - "integrity": "sha512-JX3rTyW+KFZ/E9OF/IpXJPbyLO9vGzcmXB5FgSP2jfL3LGKJPdV7zUE8rWeKeeI/iueQggOeFa3onrCmhxXZTg==", - "dependencies": { - "oauth": "0.10.x", - "passport-strategy": "1.x.x" - }, - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" - } - }, - "node_modules/passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/patch-package": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", - "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", - "dependencies": { - "@yarnpkg/lockfile": "^1.1.0", - "chalk": "^4.1.2", - "ci-info": "^3.7.0", - "cross-spawn": "^7.0.3", - "find-yarn-workspace-root": "^2.0.0", - "fs-extra": "^9.0.0", - "json-stable-stringify": "^1.0.2", - "klaw-sync": "^6.0.0", - "minimist": "^1.2.6", - "open": "^7.4.2", - "rimraf": "^2.6.3", - "semver": "^7.5.3", - "slash": "^2.0.0", - "tmp": "^0.0.33", - "yaml": "^2.2.2" - }, - "bin": { - "patch-package": "index.js" - }, - "engines": { - "node": ">=14", - "npm": ">5" - } - }, - "node_modules/patch-package/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/patch-package/node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "engines": { - "node": ">=6" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5788,6 +5447,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -5804,11 +5464,6 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, - "node_modules/pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -5819,6 +5474,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -5957,14 +5613,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -6206,6 +5854,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6217,6 +5866,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -6655,17 +6305,6 @@ "node": ">=8" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -6685,6 +6324,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -6774,22 +6414,6 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uid2": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", - "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -6801,14 +6425,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -6925,6 +6541,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -7038,17 +6655,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/server/package.json b/server/package.json index 4eb536d..da602ee 100644 --- a/server/package.json +++ b/server/package.json @@ -7,8 +7,7 @@ "build": "webpack --config webpack.config.js", "start": "node app.js", "dev": "cross-env NODE_ENV=development nodemon app.js", - "test": "jest", - "postinstall": "patch-package" + "test": "jest --colors" }, "keywords": [], "author": "", @@ -18,15 +17,10 @@ "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", - "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.9", - "passport": "^0.7.0", - "passport-oauth2": "^1.8.0", - "passport-openidconnect": "^0.1.2", - "patch-package": "^8.0.0", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, diff --git a/server/patches/passport-openidconnect+0.1.2.patch b/server/patches/passport-openidconnect+0.1.2.patch deleted file mode 100644 index e386741..0000000 --- a/server/patches/passport-openidconnect+0.1.2.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/node_modules/passport-openidconnect/lib/profile.js b/node_modules/passport-openidconnect/lib/profile.js -index eeabf4e..8abe391 100644 ---- a/node_modules/passport-openidconnect/lib/profile.js -+++ b/node_modules/passport-openidconnect/lib/profile.js -@@ -17,6 +17,7 @@ exports.parse = function(json) { - if (json.middle_name) { profile.name.middleName = json.middle_name; } - } - if (json.email) { profile.emails = [ { value: json.email } ]; } -+ if (json.groups) { profile.groups = [ { value: json.groups } ]; } - - return profile; - }; diff --git a/server/routers/auth.js b/server/routers/auth.js deleted file mode 100644 index b1e57e2..0000000 --- a/server/routers/auth.js +++ /dev/null @@ -1,9 +0,0 @@ -const express = require('express'); -const router = express.Router(); - -const authController = require('../controllers/auth.js') - -router.get("/getActiveAuth",authController.getActive); -router.get("/getRoomsRequireAuth", authController.getRoomsRequireAuth); - -module.exports = router; \ No newline at end of file diff --git a/server/routers/users.js b/server/routers/users.js index f88436d..d1f81b7 100644 --- a/server/routers/users.js +++ b/server/routers/users.js @@ -3,12 +3,11 @@ const router = express.Router(); const users = require('../app.js').users; const jwt = require('../middleware/jwtToken.js'); const asyncHandler = require('./routerUtils.js'); -const usersController = require('../controllers/users.js') router.post("/register", asyncHandler(users.register)); router.post("/login", asyncHandler(users.login)); router.post("/reset-password", asyncHandler(users.resetPassword)); router.post("/change-password", jwt.authenticate, asyncHandler(users.changePassword)); -router.post("/delete-user", jwt.authenticate, usersController); +router.post("/delete-user", jwt.authenticate, asyncHandler(users.delete)); module.exports = router; diff --git a/server/utils.js b/server/utils.js deleted file mode 100644 index 91f5972..0000000 --- a/server/utils.js +++ /dev/null @@ -1,35 +0,0 @@ -function hasNestedValue(obj, path, delimiter = "_") { - const keys = path.split(delimiter); - let current = obj; - - for (const key of keys) { - while(Array.isArray(current) && current.length == 1 && current[0]){ - current = current[0] - } - while(current['value']){ - current = current.value - } - - if (current && typeof current === "object") { - if (Array.isArray(current)) { - const index = current.findIndex(x => x == key) - if (index != -1) { - current = current[index]; - } else { - return false; - } - } else if (key in current) { - current = current[key]; - } else { - return false; - } - } else { - return false; - } - } - - return true; -} - - -module.exports = { hasNestedValue}; \ No newline at end of file From 79c4965e8099a49f5979cf06e0486a44d0218da1 Mon Sep 17 00:00:00 2001 From: Edwin S Lopez <85202478+eddi3as@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:58:09 -0500 Subject: [PATCH 19/25] Update LICENSE ajout PFE-H25 --- LICENSE | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE b/LICENSE index cf016c7..406c5af 100644 --- a/LICENSE +++ b/LICENSE @@ -3,6 +3,7 @@ MIT License Copyright (c) 2023 ETS-PFE004-Plateforme-sondage-minitest Copyright (c) 2024 Louis-Antoine Caron, Mathieu Roy, Mélanie St-Hilaire, Samy Waddah Copyright (c) 2024 Gabriel Moisan-Matte, Mathieu Sévigny-Lavallée, Jerry Kwok Hiu Fung, Bruno Roesner, Florent Serres +Copyright (c) 2025 Nouhaïla Aâter, Kendrick Chan Hing Wah, Philippe Côté, Edwin Stanley Lopez Andino, Ana Lucia Munteanu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From a026f868d1b259fe3058cd88edd4d29d2fb64882 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Tue, 11 Feb 2025 19:07:15 -0500 Subject: [PATCH 20/25] =?UTF-8?q?Erreur=20lors=20de=20la=20cr=C3=A9ation?= =?UTF-8?q?=20d'un=20quiz=20en=20cours=20d'=C3=A9dition=20avec=20l'utilisa?= =?UTF-8?q?tion=20du=20\=20Fixes=20#242?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GiftTemplate/templates/TextTypeTemplate.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts index c86b25e..7e1f205 100644 --- a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts +++ b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts @@ -5,13 +5,21 @@ import { TextFormat } from 'gift-pegjs'; import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc. function formatLatex(text: string): string { - return text + let renderedText = ''; + + try { + renderedText = text .replace(/\$\$(.*?)\$\$/g, (_, inner) => katex.renderToString(inner, { displayMode: true })) .replace(/\$(.*?)\$/g, (_, inner) => katex.renderToString(inner, { displayMode: false })) .replace(/\\\[(.*?)\\\]/g, (_, inner) => katex.renderToString(inner, { displayMode: true })) .replace(/\\\((.*?)\\\)/g, (_, inner) => katex.renderToString(inner, { displayMode: false }) ); + } catch (error) { + renderedText = text; + } + + return renderedText; } /** From bd194a583e0ea5988146ebc28575bd9b8a8acb92 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:24:41 -0500 Subject: [PATCH 21/25] =?UTF-8?q?Erreur=20lors=20de=20la=20cr=C3=A9ation?= =?UTF-8?q?=20d'un=20quiz=20en=20cours=20d'=C3=A9dition=20avec=20l'utilisa?= =?UTF-8?q?tion=20du=20\=20Fixes=20#242?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/GiftTemplate/templates/TextTypeTemplate.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts index 7e1f205..4f89322 100644 --- a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts +++ b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts @@ -15,6 +15,7 @@ function formatLatex(text: string): string { .replace(/\\\((.*?)\\\)/g, (_, inner) => katex.renderToString(inner, { displayMode: false }) ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { renderedText = text; } From 01e96f7b37695eaea6584ade50c4f05485088b73 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:02:14 -0500 Subject: [PATCH 22/25] =?UTF-8?q?Ajuster=20les=20mod=C3=A8les=20de=20quest?= =?UTF-8?q?ions=20de=20l'=C3=A9diteur=20et=20corriger=20une=20possible=20e?= =?UTF-8?q?rreur=20Fixes=20#245?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/GIFTCheatSheet/GiftCheatSheet.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx index 98d2b96..47458ea 100644 --- a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx +++ b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx @@ -21,11 +21,11 @@ const GiftCheatSheet: React.FC = () => { }; - const QuestionVraiFaux = "2+2 \\= 4 ? {T}\n// Utilisez les valeurs {T}, {F}, {TRUE} \net {FALSE}."; - const QuestionChoixMul = "Quelle ville est la capitale du Canada? {\n~ Toronto\n~ Montréal\n= Ottawa #Bonne réponse!\n}\n// La bonne réponse est Ottawa"; - const QuestionChoixMulMany = "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#### La bonne réponse est Montréal, Ottawa et Vancouver \n}\n// Utilisez tilde (signe de vague) pour toutes les réponses.\n// On doit indiquer le pourcentage de chaque réponse."; - const QuestionCourte ="Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n}\n// Permet de fournir plusieurs bonnes réponses.\n// Note: La casse n'est pas prise en compte."; - const QuestionNum ="// Question de plage mathématique. \n Quel 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 Quel est un nombre de 1 à 5 ? {\n#1..5\n} \n\n// Réponses numériques multiples avec crédit partiel et commentaires.\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 QuestionVraiFaux = "::Titre:: \n 2+2 \\= 4 ? {T} //Utilisez les valeurs {T}, {F}, {TRUE} et {FALSE}."; + const QuestionChoixMul = "::Titre:: \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 = "::Titre:: \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 ="::Titre:: \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 de plage mathématique. \n ::Titre:: \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 ::Titre:: \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::Titre::\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

From 9c9c17cd0f82c73145c85e3af839b507180a3529 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:01:36 -0500 Subject: [PATCH 23/25] Refactoriser la LiveResultTable en fonction de ses composants Fixes #236 --- .../LiveResultsTable.test.tsx | 2 +- .../LiveResultsTableBody.test.tsx | 95 ++++++++ .../LiveResultsTableFooter.test.tsx | 55 +++++ .../LiveResultsTableHeader.test.tsx | 51 +++++ .../components/LiveResults/LiveResults.tsx | 20 +- .../LiveResults/LiveResultsTable.tsx | 215 ------------------ .../LiveResultsTable/LiveResultsTable.tsx | 75 ++++++ .../TableComponents/LiveResultTableFooter.tsx | 79 +++++++ .../TableComponents/LiveResultsTableBody.tsx | 94 ++++++++ .../LiveResultsTableHeader.tsx | 50 ++++ 10 files changed, 509 insertions(+), 227 deletions(-) rename client/src/__tests__/components/GiftTemplate/LiveResults/{ => LiveResultsTable}/LiveResultsTable.test.tsx (99%) create mode 100644 client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx create mode 100644 client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx create mode 100644 client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.test.tsx delete mode 100644 client/src/components/LiveResults/LiveResultsTable.tsx create mode 100644 client/src/components/LiveResults/LiveResultsTable/LiveResultsTable.tsx create mode 100644 client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter.tsx create mode 100644 client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx create mode 100644 client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx similarity index 99% rename from client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx rename to client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx index 7f4c8a0..021b82d 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { StudentType } from 'src/Types/StudentType'; -import LiveResultsTable from 'src/components/LiveResults/LiveResultsTable'; +import LiveResultsTable from 'src/components/LiveResults/LiveResultsTable/LiveResultsTable'; import { QuestionType } from 'src/Types/QuestionType'; import { BaseQuestion, parse } from 'gift-pegjs'; diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx new file mode 100644 index 0000000..11e41f1 --- /dev/null +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { StudentType } from 'src/Types/StudentType'; +import LiveResultsTableBody from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody'; +import { QuestionType } from 'src/Types/QuestionType'; +import { BaseQuestion, parse } from 'gift-pegjs'; + + +const mockGiftQuestions = parse( + `::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2} + + ::Sample Question 2:: Sample Question 2 {T}`); + +const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { + if (question.type !== "Category") + question.id = (index + 1).toString(); + const newMockQuestion = question; + return {question : newMockQuestion as BaseQuestion}; +}); + +const mockStudents: StudentType[] = [ + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, +]; + +const mockGetStudentGrade = jest.fn((student: StudentType) => { + const correctAnswers = student.answers.filter(answer => answer.isCorrect).length; + return (correctAnswers / mockQuestions.length) * 100; +}); + +describe('LiveResultsTableBody', () => { + test('renders LiveResultsTableBody component', () => { + render( + + ); + + expect(screen.getByText('Student 1')).toBeInTheDocument(); + expect(screen.getByText('Student 2')).toBeInTheDocument(); + }); + + test('displays correct and incorrect answers', () => { + render( + + ); + + expect(screen.getByText('Answer 1')).toBeInTheDocument(); + expect(screen.getByText('Answer 2')).toBeInTheDocument(); + }); + + test('displays icons for correct and incorrect answers when showCorrectAnswers is false', () => { + render( + + ); + + expect(screen.getByLabelText('correct')).toBeInTheDocument(); + expect(screen.getByLabelText('incorrect')).toBeInTheDocument(); + }); + + test('hides usernames when showUsernames is false', () => { + render( + + ); + + expect(screen.getAllByText('******').length).toBe(2); + }); +}); \ No newline at end of file diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx new file mode 100644 index 0000000..99a6dc3 --- /dev/null +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { StudentType } from 'src/Types/StudentType'; +import LiveResultsTableFooter from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter'; + + +const mockStudents: StudentType[] = [ + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, +]; + +const mockGetStudentGrade = jest.fn((student: StudentType) => { + const correctAnswers = student.answers.filter(answer => answer.isCorrect).length; + return (correctAnswers / 2) * 100; // Assuming there are 2 questions +}); + +describe('LiveResultsTableFooter', () => { + test('renders LiveResultsTableFooter component', () => { + render( + + ); + + expect(screen.getByText('% réussite')).toBeInTheDocument(); + }); + + test('calculates and displays correct answers per question', () => { + render( + + ); + + expect(screen.getByText('50 %')).toBeInTheDocument(); + expect(screen.getByText('0 %')).toBeInTheDocument(); + }); + + test('calculates and displays class average', () => { + render( + + ); + + expect(screen.getByText('50 %')).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.test.tsx new file mode 100644 index 0000000..5dff41a --- /dev/null +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import LiveResultsTableHeader from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader'; + + +const mockShowSelectedQuestion = jest.fn(); + +describe('LiveResultsTableHeader', () => { + test('renders LiveResultsTableHeader component', () => { + render( + + ); + + expect(screen.getByText("Nom d'utilisateur")).toBeInTheDocument(); + for (let i = 1; i <= 5; i++) { + expect(screen.getByText(`Q${i}`)).toBeInTheDocument(); + } + expect(screen.getByText('% réussite')).toBeInTheDocument(); + }); + + test('calls showSelectedQuestion when a question header is clicked', () => { + render( + + ); + + const questionHeader = screen.getByText('Q1'); + fireEvent.click(questionHeader); + + expect(mockShowSelectedQuestion).toHaveBeenCalledWith(0); + }); + + test('renders the correct number of question headers', () => { + render( + + ); + + for (let i = 1; i <= 3; i++) { + expect(screen.getByText(`Q${i}`)).toBeInTheDocument(); + } + }); +}); \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 13611eb..44c998f 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import { Socket } from 'socket.io-client'; import { QuestionType } from '../../Types/QuestionType'; - import './liveResult.css'; import { FormControlLabel, @@ -10,7 +9,7 @@ import { Switch, } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; -import LiveResultsTable from './LiveResultsTable'; +import LiveResultsTable from './LiveResultsTable/LiveResultsTable'; interface LiveResultsProps { @@ -21,12 +20,11 @@ interface LiveResultsProps { students: StudentType[] } - const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - + return (
@@ -58,13 +56,13 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti
- +
); diff --git a/client/src/components/LiveResults/LiveResultsTable.tsx b/client/src/components/LiveResults/LiveResultsTable.tsx deleted file mode 100644 index bb81b80..0000000 --- a/client/src/components/LiveResults/LiveResultsTable.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import React, { useMemo } from 'react'; -import { Paper, Table, TableBody, TableCell, TableContainer, TableFooter, TableHead, TableRow } from '@mui/material'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; -import { StudentType } from 'src/Types/StudentType'; -import { QuestionType } from '../../Types/QuestionType'; -import { FormattedTextTemplate } from '../GiftTemplate/templates/TextTypeTemplate'; - -interface LiveResultsTableProps { - students: StudentType[]; - questions: QuestionType[]; - showCorrectAnswers: boolean; - showSelectedQuestion: (index: number) => void; - showUsernames: boolean; -} - -const LiveResultsTable: React.FC = ({ - questions, - students, - showCorrectAnswers, - showSelectedQuestion, - showUsernames -}) => { - - const maxQuestions = questions.length; - - const getStudentGrade = (student: StudentType): number => { - if (student.answers.length === 0) { - return 0; - } - - const uniqueQuestions = new Set(); - let correctAnswers = 0; - - for (const answer of student.answers) { - const { idQuestion, isCorrect } = answer; - - if (!uniqueQuestions.has(idQuestion)) { - uniqueQuestions.add(idQuestion); - - if (isCorrect) { - correctAnswers++; - } - } - } - - return (correctAnswers / questions.length) * 100; - }; - - const classAverage: number = useMemo(() => { - let classTotal = 0; - - students.forEach((student) => { - classTotal += getStudentGrade(student); - }); - - return classTotal / students.length; - }, [students]); - - const getCorrectAnswersPerQuestion = (index: number): number => { - return ( - (students.filter((student) => - student.answers.some( - (answer) => - parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect - ) - ).length / students.length) * 100 - ); - }; - - return ( - - - - - -
Nom d'utilisateur
-
- {Array.from({ length: maxQuestions }, (_, index) => ( - showSelectedQuestion(index)} - > -
{`Q${index + 1}`}
-
- ))} - -
% réussite
-
-
-
- - {students.map((student) => ( - - -
- {showUsernames ? student.name : '******'} -
-
- {Array.from({ length: maxQuestions }, (_, index) => { - const answer = student.answers.find( - (answer) => parseInt(answer.idQuestion.toString()) === index + 1 - ); - const answerText = answer ? answer.answer.toString() : ''; - const isCorrect = answer ? answer.isCorrect : false; - - return ( - - {showCorrectAnswers ? ( -
- ) : isCorrect ? ( - - ) : ( - answerText !== '' && ( - - ) - )} -
- ); - })} - - {getStudentGrade(student).toFixed()} % - -
- ))} -
- - - -
% réussite
-
- {Array.from({ length: maxQuestions }, (_, index) => ( - - {students.length > 0 - ? `${getCorrectAnswersPerQuestion(index).toFixed()} %` - : '-'} - - ))} - - {students.length > 0 ? `${classAverage.toFixed()} %` : '-'} - -
-
-
-
- ); -}; - -export default LiveResultsTable; \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResultsTable/LiveResultsTable.tsx b/client/src/components/LiveResults/LiveResultsTable/LiveResultsTable.tsx new file mode 100644 index 0000000..e23c4a6 --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable/LiveResultsTable.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { Paper, Table, TableContainer } from '@mui/material'; +import { StudentType } from 'src/Types/StudentType'; +import { QuestionType } from '../../../Types/QuestionType'; +import LiveResultsTableFooter from './TableComponents/LiveResultTableFooter'; +import LiveResultsTableHeader from './TableComponents/LiveResultsTableHeader'; +import LiveResultsTableBody from './TableComponents/LiveResultsTableBody'; + +interface LiveResultsTableProps { + students: StudentType[]; + questions: QuestionType[]; + showCorrectAnswers: boolean; + showSelectedQuestion: (index: number) => void; + showUsernames: boolean; +} + +const LiveResultsTable: React.FC = ({ + questions, + students, + showSelectedQuestion, + showUsernames, + showCorrectAnswers +}) => { + + const maxQuestions = questions.length; + + const getStudentGrade = (student: StudentType): number => { + if (student.answers.length === 0) { + return 0; + } + + const uniqueQuestions = new Set(); + let correctAnswers = 0; + + for (const answer of student.answers) { + const { idQuestion, isCorrect } = answer; + + if (!uniqueQuestions.has(idQuestion)) { + uniqueQuestions.add(idQuestion); + + if (isCorrect) { + correctAnswers++; + } + } + } + + return (correctAnswers / questions.length) * 100; + }; + + + return ( + + + + + +
+
+ ); +}; + +export default LiveResultsTable; \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter.tsx b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter.tsx new file mode 100644 index 0000000..a24694e --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter.tsx @@ -0,0 +1,79 @@ +import { TableCell, TableFooter, TableRow } from "@mui/material"; +import React, { useMemo } from "react"; +import { StudentType } from "src/Types/StudentType"; + +interface LiveResultsFooterProps { + students: StudentType[]; + maxQuestions: number; + getStudentGrade: (student: StudentType) => number; +} + +const LiveResultsTableFooter: React.FC = ({ + maxQuestions, + students, + getStudentGrade + +}) => { + + const getCorrectAnswersPerQuestion = (index: number): number => { + return ( + (students.filter((student) => + student.answers.some( + (answer) => + parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect + ) + ).length / students.length) * 100 + ); + }; + + const classAverage: number = useMemo(() => { + let classTotal = 0; + + students.forEach((student) => { + classTotal += getStudentGrade(student); + }); + + return classTotal / students.length; + }, [students]); + + return ( + + + +
% réussite
+
+ {Array.from({ length: maxQuestions }, (_, index) => ( + + {students.length > 0 + ? `${getCorrectAnswersPerQuestion(index).toFixed()} %` + : '-'} + + ))} + + {students.length > 0 ? `${classAverage.toFixed()} %` : '-'} + +
+
+ ); +}; +export default LiveResultsTableFooter; \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx new file mode 100644 index 0000000..a0c67f7 --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx @@ -0,0 +1,94 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { TableBody, TableCell, TableRow } from "@mui/material"; +import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; +import { FormattedTextTemplate } from '../../../GiftTemplate/templates/TextTypeTemplate'; +import React from "react"; +import { StudentType } from "src/Types/StudentType"; + +interface LiveResultsFooterProps { + maxQuestions: number; + students: StudentType[]; + showUsernames: boolean; + showCorrectAnswers: boolean; + getStudentGrade: (student: StudentType) => number; + +} + +const LiveResultsTableFooter: React.FC = ({ + maxQuestions, + students, + showUsernames, + showCorrectAnswers, + getStudentGrade +}) => { + + return ( + + {students.map((student) => ( + + +
+ {showUsernames ? student.name : '******'} +
+
+ {Array.from({ length: maxQuestions }, (_, index) => { + const answer = student.answers.find( + (answer) => parseInt(answer.idQuestion.toString()) === index + 1 + ); + const answerText = answer ? answer.answer.toString() : ''; + const isCorrect = answer ? answer.isCorrect : false; + + return ( + + {showCorrectAnswers ? ( +
+ ) : isCorrect ? ( + + ) : ( + answerText !== '' && ( + + ) + )} +
+ ); + })} + + {getStudentGrade(student).toFixed()} % + +
+ ))} +
+ ); +}; +export default LiveResultsTableFooter; \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx new file mode 100644 index 0000000..fb4219b --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx @@ -0,0 +1,50 @@ +import { TableCell, TableHead, TableRow } from "@mui/material"; +import React from "react"; + +interface LiveResultsFooterProps { + maxQuestions: number; + showSelectedQuestion: (index: number) => void; +} + +const LiveResultsTableFooter: React.FC = ({ + maxQuestions, + showSelectedQuestion, +}) => { + + return ( + + + +
Nom d'utilisateur
+
+ {Array.from({ length: maxQuestions }, (_, index) => ( + showSelectedQuestion(index)} + > +
{`Q${index + 1}`}
+
+ ))} + +
% réussite
+
+
+
+ ); +}; +export default LiveResultsTableFooter; \ No newline at end of file From 69fd13e327abadd476aa151850c1974aec58a1b8 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Thu, 13 Feb 2025 08:50:53 -0500 Subject: [PATCH 24/25] =?UTF-8?q?Title=20r=C3=A9aliste?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/GIFTCheatSheet/GiftCheatSheet.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx index 47458ea..036f2d0 100644 --- a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx +++ b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx @@ -21,11 +21,11 @@ const GiftCheatSheet: React.FC = () => { }; - const QuestionVraiFaux = "::Titre:: \n 2+2 \\= 4 ? {T} //Utilisez les valeurs {T}, {F}, {TRUE} et {FALSE}."; - const QuestionChoixMul = "::Titre:: \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 = "::Titre:: \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 ="::Titre:: \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 de plage mathématique. \n ::Titre:: \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 ::Titre:: \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::Titre::\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 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}"; return (

Informations pratiques sur l'éditeur

@@ -79,7 +79,7 @@ const GiftCheatSheet: React.FC = () => {
-

5. Question numérique

+

5. Questions numériques

                     
                         {

From dff5b3c85fd7b91ab939a3c03dbdf8ee9496f6ea Mon Sep 17 00:00:00 2001
From: "Christopher (Cris) Fuhrman" 
Date: Fri, 14 Feb 2025 14:29:32 -0500
Subject: [PATCH 25/25] =?UTF-8?q?Update=20issue=20templates=20(en=20fran?=
 =?UTF-8?q?=C3=A7ais=20ou=20anglais)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Pour améliorer la qualité et la constance
---
 .github/ISSUE_TEMPLATE/bug_report.md      | 38 +++++++++++++++++++++++
 .github/ISSUE_TEMPLATE/feature-request.md | 20 ++++++++++++
 2 files changed, 58 insertions(+)
 create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
 create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md

diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..22bc52d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Créez un rapport pour nous aider à améliorer / Create a report to help us improve
+title: "[BUG] "
+labels: bug
+assignees: ''
+
+---
+
+**Décrivez le bug / Describe the bug**
+Une description claire et concise du bug. / A clear and concise description of what the bug is.
+
+**Pour reproduire / To Reproduce**
+Étapes pour reproduire le comportement : / Steps to reproduce the behavior:
+1. Aller à '...' / Go to '...'
+2. Cliquer sur '...' / Click on '...'
+3. Faites défiler jusqu'à '...' / Scroll down to '...'
+4. Voir l'erreur / See error
+
+**Comportement attendu / Expected behavior**
+Une description claire et concise de ce que vous attendiez. / A clear and concise description of what you expected to happen.
+
+**Captures d'écran / Screenshots**
+Si applicable, ajoutez des captures d'écran pour aider à expliquer votre problème. / If applicable, add screenshots to help explain your problem.
+
+**Ordinateur (veuillez compléter les informations suivantes) / Desktop (please complete the following information):**
+ - Système d'exploitation : [par exemple, Windows, macOS, Linux] / OS: [e.g. Windows, macOS, Linux]
+ - Navigateur : [par exemple, Chrome, Firefox, Safari] / Browser [e.g. Chrome, Firefox, Safari]
+ - Version : [par exemple, 22] / Version [e.g. 22]
+
+**Smartphone (veuillez compléter les informations suivantes) / Smartphone (please complete the following information):**
+ - Appareil : [par exemple, iPhone6, Samsung Galaxy S10] / Device: [e.g. iPhone6, Samsung Galaxy S10]
+ - Système d'exploitation : [par exemple, iOS 14.4, Android 11] / OS: [e.g. iOS 14.4, Android 11]
+ - Navigateur : [par exemple, navigateur par défaut, Safari] / Browser [e.g. stock browser, Safari]
+ - Version : [par exemple, 22] / Version [e.g. 22]
+
+**Contexte supplémentaire / Additional context**
+Ajoutez tout autre contexte concernant le problème ici. / Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100644
index 0000000..9ac24c3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggérez une idée pour ce projet / Suggest an idea for this project
+title: "[FEATURE] "
+labels: enhancement
+assignees: ''
+
+---
+
+**Votre demande de fonctionnalité est-elle liée à un problème ? Veuillez décrire. / Is your feature request related to a problem? Please describe.**
+Une description claire et concise du problème. Par exemple, je suis toujours frustré lorsque [...] / A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Décrivez la solution que vous souhaitez / Describe the solution you'd like**
+Une description claire et concise de ce que vous voulez qu'il se passe. / A clear and concise description of what you want to happen.
+
+**Décrivez les alternatives que vous avez envisagées / Describe alternatives you've considered**
+Une description claire et concise de toute autre solution ou fonctionnalité que vous avez envisagée. / A clear and concise description of any alternative solutions or features you've considered.
+
+**Contexte supplémentaire / Additional context**
+Ajoutez tout autre contexte ou capture d'écran concernant la demande de fonctionnalité ici. / Add any other context or screenshots about the feature request here.