diff --git a/middlewareNode/chatHistory.json b/middlewareNode/chatHistory.json new file mode 100644 index 00000000..f8291bc4 --- /dev/null +++ b/middlewareNode/chatHistory.json @@ -0,0 +1,66 @@ +[ + { + "role": "user", + "parts": [ + { + "text": "my name is Mario, what are you?" + } + ] + }, + { + "role": "model", + "parts": [ + { + "text": "Hi Mario! I'm a helpful chatbot here to assist you with information about Y STEM and Chess Inc. (YSC). We're a non-profit organization dedicated to introducing kids in K-12 to the exciting world of STEM through chess and the Mastery Learning method. We focus on helping underserved and at-risk children develop STEM skills and pursue STEM careers. How can I help you today?\n" + } + ] + }, + { + "role": "user", + "parts": [ + { + "text": "I have son named Luigi, he wants become an engineer, what advice do you have for him?" + } + ] + }, + { + "role": "model", + "parts": [ + { + "text": "That's wonderful that Luigi wants to become an engineer! It's a challenging but rewarding career path. Here's some advice for him, broken down by age group (assuming he's K-12 since that's our focus):\n\n**For younger Luigi (K-5):**\n\n* **Focus on fundamentals:** Encourage a love of learning and problem-solving. Chess is a great tool for this – it teaches critical thinking, planning, and strategic thinking, all essential for engineering. Make learning fun! Explore age-appropriate STEM kits, books, and activities.\n* **Develop strong math and science skills:** These are the cornerstones of engineering. Make sure he's comfortable with basic arithmetic, geometry, and science concepts.\n* **Curiosity is key:** Encourage Luigi to ask \"why?\" and \"how?\". Foster his natural curiosity about how things work. Take things apart (safely!) and see how they're put together.\n\n**For older Luigi (6-12):**\n\n* **Explore different engineering disciplines:** There are many types of engineering (civil, mechanical, electrical, chemical, software, etc.). Expose him to different fields through books, videos, or even visits to engineering companies or museums.\n* **Get involved in STEM activities:** Look for robotics clubs, coding classes, science fairs, and other opportunities to apply his skills. YSC programs might be a great fit! (Check out our website for more information on programs and locations.)\n* **Develop strong problem-solving skills:** Engineering is all about solving problems. Encourage him to tackle challenges creatively and think critically. Chess is excellent for this!\n* **Start thinking about college and career prep:** He should begin to explore STEM-related courses in school. Consider summer camps or programs focusing on engineering.\n\n**Regardless of age:**\n\n* **Mentorship:** Finding a mentor who's an engineer can be invaluable. They can provide guidance, support, and real-world insights.\n* **Persistence is crucial:** Engineering can be challenging. Encourage him to persevere through difficulties and celebrate his successes along the way.\n* **Embrace teamwork:** Many engineering projects are collaborative efforts. Encourage participation in group projects and activities.\n\n\nLet me know if you have any other questions, Mario. We at YSC are here to support Luigi's journey into the world of engineering!\n" + } + ] + }, + { + "role": "user", + "parts": [ + { + "text": "he wants to become a doctor" + } + ] + }, + { + "role": "model", + "parts": [ + { + "text": "That's fantastic that Luigi is aiming to be a doctor! It's a noble and challenging profession requiring dedication and hard work. Here’s some advice tailored for his age group (assuming he's K-12):\n\n**For younger Luigi (K-5):**\n\n* **Cultivate a love of learning:** A strong foundation in science and math is essential. Make learning fun through games, books, and age-appropriate experiments.\n* **Develop strong communication skills:** Doctors need to communicate effectively with patients and colleagues. Encourage him to read, write, and express himself clearly.\n* **Build empathy and compassion:** A caring demeanor is crucial for a doctor. Encourage him to be kind, understanding, and show concern for others.\n\n\n**For older Luigi (6-12):**\n\n* **Focus on science and math:** Biology, chemistry, and physics are particularly important. Encourage participation in science clubs, fairs, and extracurricular activities related to these subjects.\n* **Shadowing or volunteering:** If possible, arrange for Luigi to shadow a doctor or volunteer at a hospital (with appropriate parental supervision and clearances). This will give him a realistic view of the profession.\n* **Research different medical fields:** Medicine is a broad field, encompassing many specialties. Encourage him to explore various options, such as pediatrics, cardiology, or surgery.\n* **Prepare for higher education:** A strong academic record in high school is essential for medical school. Encourage him to focus on his studies and maintain a good GPA.\n* **Explore relevant extracurricular activities:** Joining clubs related to science, debate, or community service can showcase his commitment and skills.\n\n\n**Regardless of age:**\n\n* **Develop strong problem-solving skills:** Doctors constantly face challenges and need to think critically to diagnose and treat patients. Encourage logical reasoning and creative problem-solving.\n* **Learn about medical ethics:** Understanding ethical principles is vital for responsible medical practice. Discuss scenarios involving ethical dilemmas and different perspectives.\n* **Maintain good health habits:** Doctors need to be healthy role models. Encourage him to prioritize physical activity, healthy eating, and sufficient rest.\n\n\nWhile YSC focuses on STEM through chess, the problem-solving and critical thinking skills developed through chess will still be beneficial for Luigi's aspirations. The key is to build a strong foundation in science and math, cultivate empathy, and work hard to achieve his goals. Good luck to Luigi! Let me know if you have other questions.\n" + } + ] + }, + { + "role": "user", + "parts": [ + { + "text": "what was my name again?" + } + ] + }, + { + "role": "model", + "parts": [ + { + "text": "Your name is Mario.\n" + } + ] + } +] \ No newline at end of file diff --git a/middlewareNode/package-lock.json b/middlewareNode/package-lock.json index d375dff0..713e61b8 100644 --- a/middlewareNode/package-lock.json +++ b/middlewareNode/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@google/genai": "^1.5.1", "aws-sdk": "^2.889.0", "axios": "^0.21.1", "config": "^3.3.12", @@ -1091,6 +1092,168 @@ "tslib": "^2.3.1" } }, + "node_modules/@google/genai": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.5.1.tgz", + "integrity": "sha512-9SKpNo5iqvB622lN3tSCbeuiLGTcStRd+3muOrI9pZMpzfLDc/xC7dWIJd5kK+4AZuY28nsvQmCZe0fPj3JUew==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.14.2", + "ws": "^8.18.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.11.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@google/genai/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@google/genai/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@google/genai/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google/genai/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google/genai/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google/genai/node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google/genai/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@google/genai/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@google/genai/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@google/genai/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@google/genai/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -2122,6 +2285,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/google-p12-pem": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", @@ -3769,6 +3941,27 @@ "typedarray-to-buffer": "^3.1.5" } }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", @@ -3798,6 +3991,24 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/middlewareNode/package.json b/middlewareNode/package.json index a05f9cde..07c6a3a0 100644 --- a/middlewareNode/package.json +++ b/middlewareNode/package.json @@ -4,6 +4,7 @@ "description": "API for Y STEM And Chess Inc", "main": "server.js", "dependencies": { + "@google/genai": "^1.5.1", "aws-sdk": "^2.889.0", "axios": "^0.21.1", "config": "^3.3.12", diff --git a/middlewareNode/routes/auth.js b/middlewareNode/routes/auth.js index c66587e3..18537ea2 100644 --- a/middlewareNode/routes/auth.js +++ b/middlewareNode/routes/auth.js @@ -73,7 +73,7 @@ router.post( function (err, token) { if (err) throw err; res.json({ token }); - }, + } ); return jwt; //Return the encrypted jwt @@ -81,7 +81,7 @@ router.post( console.error(error.message); res.status(500).send("Server error"); } - }, + } ); module.exports = router; diff --git a/middlewareNode/routes/geminiAPI.js b/middlewareNode/routes/geminiAPI.js new file mode 100644 index 00000000..5855e684 --- /dev/null +++ b/middlewareNode/routes/geminiAPI.js @@ -0,0 +1,50 @@ +const express = require("express"); +const router = express.Router(); +const config = require("config"); + +router.post("/", async (req, res) => { + const { GoogleGenAI } = await import("@google/genai"); + const GEMINI_API_KEY = config.get("geminiApiKey"); + const { prompt, chatHistory } = req.body; + + if (!prompt) return res.status(400).json({ error: "Prompt is required" }); + + try { + const ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY }); + + const MAX_HISTORY_LENGTH = 12; + if (Array.isArray(chatHistory) && chatHistory.length > MAX_HISTORY_LENGTH) { + chatHistory = chatHistory.slice(-MAX_HISTORY_LENGTH); + } + else if (!Array.isArray(chatHistory)) { + chatHistory = []; + } + const systemInstruction = { + role: "user", + parts: [ + { + text: `API (admin) Instructions: + You are a helpfull chatbot on a non-profit org called yStem and chess. + Y STEM and Chess Inc. (YSC) seeks to open the hearts and minds of kids (K-12) to the world of STEM through chess and the Mastery Learning approach to ensure learning and mastery of STEM-related principles. YSC strives to empower underserved and at-risk children through mentoring and STEM skills development to enable them to pursue STEM careers and change their life trajectories. You give advice to users on this site. + + user prompt:`, + }, + ], + }; + const contents = [systemInstruction, ...chatHistory]; + + const result = await ai.models.generateContent({ + model: "gemini-1.5-flash", + contents: contents, + }); + const modelReply = result.candidates[0].content.parts[0].text; + return res.json({ response: modelReply }); + } catch (error) { + console.error("Gemini API Error:", error.response?.data || error.message); + return res + .status(500) + .json({ error: "Failed to get response from Gemini API" }); + } +}); + +module.exports = router; diff --git a/middlewareNode/routes/users.js b/middlewareNode/routes/users.js index 91bea764..b13556b8 100644 --- a/middlewareNode/routes/users.js +++ b/middlewareNode/routes/users.js @@ -2,6 +2,7 @@ const express = require("express"); const passport = require("passport"); const router = express.Router(); const crypto = require("crypto"); +const jwt = require("jsonwebtoken"); const { check, validationResult } = require("express-validator"); const users = require("../models/users"); const { @@ -9,14 +10,15 @@ const { } = require("../template/changePasswordTemplate"); const { sendMail } = require("../utils/nodemailer"); const { validator } = require("../utils/middleware"); -const { MongoClient } = require('mongodb'); +const { MongoClient } = require("mongodb"); const config = require("config"); let cachedClient = null; // cache db client to prevent repeated connections // get db client async function getDb() { - if (!cachedClient) { // if not cached, connect + if (!cachedClient) { + // if not cached, connect cachedClient = new MongoClient(config.get("mongoURI")); await cachedClient.connect(); } @@ -84,7 +86,6 @@ router.post( //Set the account created date for the new user const currDate = new Date(); - //Switch statement for functionality depending on role if (role === "parent") { let studentsArray = JSON.parse(students); @@ -118,7 +119,7 @@ router.post( timePlayed: 0, }); await newStudent.save(); - }), + }) ); } } @@ -138,7 +139,7 @@ router.post( console.error(error.message); res.status(500).json("Server error"); } - }, + } ); // @route POST /user/children @@ -194,7 +195,7 @@ router.post( console.error(error.message); res.status(500).json("Server error"); } - }, + } ); // @route POST /user/sendMail // @desc sending the mail based on username and email @@ -247,7 +248,7 @@ const updatePassword = async (body) => { const result = await users.findOneAndUpdate( { username: body.username, email: body.email }, { password: body.password }, - { new: true }, + { new: true } ); return result; }; @@ -255,17 +256,19 @@ const updatePassword = async (body) => { // @route GET /user/mentorless/:keyword // @desc for getting the mentorless students whose username matches keyword. router.get("/mentorless", async (req, res) => { - const keyword = req.query.keyword || ''; // get the keyword + const keyword = req.query.keyword || ""; // get the keyword try { const db = await getDb(); const users = db.collection("users"); - const userList = await users.find({ - role: 'student',// get student - username: { $regex: keyword, $options: 'i' }, // case-insensitive match for username - mentorshipUsername: { $ne: "" } - }).toArray();; - res.json(userList.map(user => user.username)); // return a list of the usernames + const userList = await users + .find({ + role: "student", // get student + username: { $regex: keyword, $options: "i" }, // case-insensitive match for username + mentorshipUsername: { $ne: "" }, + }) + .toArray(); + res.json(userList.map((user) => user.username)); // return a list of the usernames } catch (err) { res.status(500).json({ error: err.message }); // error } @@ -276,21 +279,28 @@ router.get("/mentorless", async (req, res) => { // @desc if user is mentor, update its student username to the mentorship= query // @access Public with jwt Authentication router.put( - "/updateMentorship", + "/updateMentorship", async (req, res, next) => { - passport.authenticate("jwt", { session: false }, async (err, user, info) => { - if (!user) { - return res.status(401).json({ message: "Unauthorized" }); + passport.authenticate( + "jwt", + { session: false }, + async (err, user, info) => { + if (!user) { + return res.status(401).json({ message: "Unauthorized" }); + } + req.user = user; + next(); } - req.user = user; - next(); - })(req, res, next) // authenticate jwt - }, + )(req, res, next); // authenticate jwt + }, async (req, res) => { // get the student/mentor username const mentorship = req.query.mentorship; - if (!mentorship) { // mentorship query is required - return res.status(400).json({ message: "Missing mentorship username in query" }); + if (!mentorship) { + // mentorship query is required + return res + .status(400) + .json({ message: "Missing mentorship username in query" }); } try { @@ -305,30 +315,37 @@ router.put( // if mentorship field is not modified if (result.modifiedCount === 0) { - return res.status(404).json({ message: "User not found or already has that mentorshipUsername" }); + return res.status(404).json({ + message: "User not found or already has that mentorshipUsername", + }); } res.json({ message: "Mentorship updated successfully" }); } catch (err) { res.status(500).json({ error: err.message }); // error } - } + } ); // @route GET /user/getMentorship // @desc if user is a student, responds with its mentor's object {username, firstName, lastName} // @desc if user is a mentor, responds with its student's object {username, firstName, lastName} // @access Public with jwt Authentication -router.get("/getMentorship", +router.get( + "/getMentorship", async (req, res, next) => { - passport.authenticate("jwt", { session: false }, async (err, user, info) => { - if (!user) { - return res.status(401).json({ message: "Unauthorized" }); + passport.authenticate( + "jwt", + { session: false }, + async (err, user, info) => { + if (!user) { + return res.status(401).json({ message: "Unauthorized" }); + } + req.user = user; + next(); } - req.user = user; - next(); - })(req, res, next) // authenticate jwt - }, + )(req, res, next); // authenticate jwt + }, async (req, res) => { try { const db = await getDb(); @@ -356,4 +373,63 @@ router.get("/getMentorship", } ); +router.get("/getStudent", async (req, res) => { + const keyword = req.query.keyword || ""; + + try { + const db = await getDb(); + const users = db.collection("users"); + + const userList = await users + .find({ + role: "student", // get student + username: { $regex: keyword, $options: "i" }, // case-insensitive match for username + }) + .toArray(); + + res.json(userList.map((user) => user.username)); // return a list of the usernames + } catch (err) { + res.status(500).json({ error: err.message }); // error + } +}); + +// verify role + +router.post("/verifyRole", async (req, res) => { + const { token } = req.body; + + if (!token.login) { + return res.status(400).json({ error: "Missing token" }); + } + + const decoded = jwt.verify(token.login, config.get("indexKey")); + + const user = await users + .findOne({ username: decoded.username }) + .select("role"); + + if (!user) { + return res.status(404).json({ error: "User not found" }); + } + console.log(decoded.role, user.role); + if (decoded.role === user.role) { + return res.json({ verified: true }); + } else { + return res.status(403).json({ error: "Role mismatch", verified: false }); + } +}); + +router.get("/getUser", async (req, res) => { + const username = req.query.username || ""; + try { + const user = await users.findOne({ username }).select("-password"); + if (!user) { + return res.status(404).json({ error: "User not found" }); + } + return res.status(200).json(user); + } catch (err) { + return res.status(401).json({ error: err }); + } +}); + module.exports = router; diff --git a/middlewareNode/server.js b/middlewareNode/server.js index 07b718fa..0d2fc8de 100644 --- a/middlewareNode/server.js +++ b/middlewareNode/server.js @@ -19,9 +19,9 @@ app.use(express.json({ extended: false })); // Configure express-session app.use( session({ - secret: 'your_secret_key_here', // Use a long and random string for better security + secret: "your_secret_key_here", // Use a long and random string for better security resave: false, - saveUninitialized: false + saveUninitialized: false, }) ); @@ -38,6 +38,7 @@ app.use("/auth", require("./routes/auth")); app.use("/timeTracking", require("./routes/timeTracking")); app.use("/puzzles", require("./routes/puzzles")); app.use("/lessons", require("./routes/lessons")); +app.use("/geminiApi", require("./routes/geminiAPI.js")); const PORT = process.env.PORT || 8000; diff --git a/react-ystemandchess/jest.config.js b/react-ystemandchess/jest.config.js index 6f7c0b27..60413e65 100644 --- a/react-ystemandchess/jest.config.js +++ b/react-ystemandchess/jest.config.js @@ -1,14 +1,14 @@ module.exports = { transform: { - '^.+\\.(ts|tsx|js|jsx)$': 'babel-jest', + "^.+\\.(ts|tsx|js|jsx)$": "babel-jest", }, moduleNameMapper: { - '\\.(css|scss|sass)$': 'identity-obj-proxy', - '\\.(jpg|jpeg|png|gif)$': '/__mocks__/fileMock.js', - '\\.svg$': '/__mocks__/svgrMock.js', + "\\.(css|scss|sass)$": "identity-obj-proxy", + "\\.(jpg|jpeg|png|gif)$": "/__mocks__/fileMock.js", + "\\.svg$": "/__mocks__/svgrMock.js", }, - testEnvironment: 'jsdom', - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], - setupFilesAfterEnv: ['@testing-library/jest-dom', '/jest.setup.js'], - moduleDirectories: ['node_modules', 'src'], + testEnvironment: "jsdom", + moduleFileExtensions: ["ts", "tsx", "js", "jsx"], + setupFilesAfterEnv: ["@testing-library/jest-dom", "/jest.setup.js"], + moduleDirectories: ["node_modules", "src"], }; diff --git a/react-ystemandchess/jest.setup.js b/react-ystemandchess/jest.setup.js index d3d5221a..7bdb5002 100644 --- a/react-ystemandchess/jest.setup.js +++ b/react-ystemandchess/jest.setup.js @@ -1,4 +1,4 @@ -import { TextEncoder, TextDecoder } from 'util'; +import { TextEncoder, TextDecoder } from "util"; global.TextEncoder = TextEncoder; global.TextDecoder = TextDecoder; diff --git a/react-ystemandchess/package-lock.json b/react-ystemandchess/package-lock.json index abc5c77e..747c1395 100644 --- a/react-ystemandchess/package-lock.json +++ b/react-ystemandchess/package-lock.json @@ -53,6 +53,7 @@ "@types/testing-library__react": "^10.0.1", "autoprefixer": "^10.4.20", "babel-jest": "^30.0.0-beta.3", + "jest-fixed-jsdom": "^0.0.9", "postcss": "^8.4.49", "sass": "^1.83.1", "tailwindcss": "^3.4.17", @@ -92,6 +93,29 @@ "node": ">=6.0.0" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -2051,6 +2075,126 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "license": "MIT" }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@csstools/normalize.css": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", @@ -3028,6 +3172,169 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.0.0.tgz", + "integrity": "sha512-Fcn1eZbH1JK+bqwUVkUVprlQL3xWUrhvOe/4L0PfDkaJOiAz3HUI1m4s0bgmXBYyCyTVogBuUFZkRpAKMox5Dw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "30.0.0", + "@jest/fake-timers": "30.0.0", + "@jest/types": "30.0.0", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jest-mock": "30.0.0", + "jest-util": "30.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/environment": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.0.tgz", + "integrity": "sha512-09sFbMMgS5JxYnvgmmtwIHhvoyzvR5fUPrVl8nOCrC5KdzmmErTcAxfWyAhJ2bv3rvHNQaKiS+COSG+O7oNbXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/fake-timers": "30.0.0", + "@jest/types": "30.0.0", + "@types/node": "*", + "jest-mock": "30.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/fake-timers": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.0.tgz", + "integrity": "sha512-yzBmJcrMHAMcAEbV2w1kbxmx8WFpEz8Cth3wjLMSkq+LO8VeGKRhpr5+BUp7PPK+x4njq/b6mVnDR8e/tPL5ng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "30.0.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.0.0", + "jest-mock": "30.0.0", + "jest-util": "30.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-message-util": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.0.tgz", + "integrity": "sha512-pV3qcrb4utEsa/U7UI2VayNzSDQcmCllBZLSoIucrESRu0geKThFZOjjh0kACDJFJRAQwsK7GVsmS6SpEceD8w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-mock": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.0.tgz", + "integrity": "sha512-W2sRA4ALXILrEetEOh2ooZG6fZ01iwVs0OWMKSSWRcUlaLr4ESHuiKXDNTg+ZVgOq8Ei5445i/Yxrv59VT+XkA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "30.0.0", + "@types/node": "*", + "jest-util": "30.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/pretty-format": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", + "integrity": "sha512-18NAOUr4ZOQiIR+BgI5NhQE7uREdx4ZyV0dyay5izh4yfQ+1T7BSvggxvRGoXocrRyevqW5OhScUjbi9GB8R8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "30.0.0", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@jest/environment/node_modules/@jest/types": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", @@ -5152,6 +5459,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5521,6 +5841,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -6075,15 +6403,14 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, "license": "MIT", - "dependencies": { - "debug": "4" - }, + "peer": true, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -8135,23 +8462,20 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.4.0.tgz", + "integrity": "sha512-W0Y2HOXlPkb2yaKrCVRjinYKciu/qSLEmK0K9mcfDei3zwlnHFEHAs/Du3cIRwPqY+J4JsiBzUjoHyc8RsJ03A==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "cssom": "~0.3.6" + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "license": "MIT" - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -8165,17 +8489,18 @@ "license": "BSD-2-Clause" }, "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/data-view-buffer": { @@ -10856,15 +11181,17 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "whatwg-encoding": "^1.0.5" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/html-entities": { @@ -11013,17 +11340,18 @@ } }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/http-proxy-middleware": { @@ -11051,16 +11379,18 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -12379,6 +12709,18 @@ "@types/yargs-parser": "*" } }, + "node_modules/jest-config/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/jest-config/node_modules/babel-jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", @@ -12469,6 +12811,92 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "license": "MIT" }, + "node_modules/jest-config/node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "license": "MIT" + }, + "node_modules/jest-config/node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-config/node_modules/form-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.3.tgz", + "integrity": "sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-config/node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-config/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-config/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/jest-config/node_modules/istanbul-lib-instrument": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", @@ -12485,6 +12913,24 @@ "node": ">=8" } }, + "node_modules/jest-config/node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/jest-config/node_modules/jest-get-type": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", @@ -12560,6 +13006,70 @@ "node": ">= 10.13.0" } }, + "node_modules/jest-config/node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" + }, + "node_modules/jest-config/node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest-config/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12584,39 +13094,152 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-config/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "license": "ISC", + "node_modules/jest-config/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" } }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, + "node_modules/jest-config/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "punycode": "^2.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-diff/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, + "node_modules/jest-config/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/jest-config/node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-config/node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/jest-config/node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/jest-config/node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "license": "MIT" + }, + "node_modules/jest-config/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "license": "MIT", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-config/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/jest-config/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "license": "Apache-2.0" + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -12762,80 +13385,165 @@ } }, "node_modules/jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.0.0.tgz", + "integrity": "sha512-IjDRABkSx+HpO7+WGVKPZL5XZajWRsMo2iQIudyiG4BhCi9Uah9HrFluqLUXdjPkIOoox+utUEUl8TDR2kc/Og==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/environment": "30.0.0", + "@jest/environment-jsdom-abstract": "30.0.0", + "@types/jsdom": "^21.1.7", "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" + "jsdom": "^26.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/jest-environment-jsdom/node_modules/@jest/environment": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.0.tgz", + "integrity": "sha512-09sFbMMgS5JxYnvgmmtwIHhvoyzvR5fUPrVl8nOCrC5KdzmmErTcAxfWyAhJ2bv3rvHNQaKiS+COSG+O7oNbXw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "30.0.0", + "@jest/types": "30.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "jest-mock": "30.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "16.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", - "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.0.tgz", + "integrity": "sha512-yzBmJcrMHAMcAEbV2w1kbxmx8WFpEz8Cth3wjLMSkq+LO8VeGKRhpr5+BUp7PPK+x4njq/b6mVnDR8e/tPL5ng==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@types/yargs-parser": "*" + "@jest/types": "30.0.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.0.0", + "jest-mock": "30.0.0", + "jest-util": "30.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-environment-jsdom/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "node_modules/jest-environment-jsdom/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-environment-jsdom/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "node_modules/jest-environment-jsdom/node_modules/jest-message-util": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.0.tgz", + "integrity": "sha512-pV3qcrb4utEsa/U7UI2VayNzSDQcmCllBZLSoIucrESRu0geKThFZOjjh0kACDJFJRAQwsK7GVsmS6SpEceD8w==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@jest/types": "^27.5.1", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-mock": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.0.tgz", + "integrity": "sha512-W2sRA4ALXILrEetEOh2ooZG6fZ01iwVs0OWMKSSWRcUlaLr4ESHuiKXDNTg+ZVgOq8Ei5445i/Yxrv59VT+XkA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "30.0.0", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-util": "30.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-environment-jsdom/node_modules/pretty-format": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", + "integrity": "sha512-18NAOUr4ZOQiIR+BgI5NhQE7uREdx4ZyV0dyay5izh4yfQ+1T7BSvggxvRGoXocrRyevqW5OhScUjbi9GB8R8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "30.0.0", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/jest-environment-node": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", @@ -12910,6 +13618,19 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/jest-fixed-jsdom": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/jest-fixed-jsdom/-/jest-fixed-jsdom-0.0.9.tgz", + "integrity": "sha512-KPfqh2+sn5q2B+7LZktwDcwhCpOpUSue8a1I+BcixWLOQoEVyAjAGfH+IYZGoxZsziNojoHGRTC8xRbB1wDD4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "jest-environment-jsdom": ">=28.0.0" + } + }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -13638,6 +14359,18 @@ "@types/yargs-parser": "*" } }, + "node_modules/jest-runner/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/jest-runner/node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -13675,6 +14408,92 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "license": "MIT" }, + "node_modules/jest-runner/node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "license": "MIT" + }, + "node_modules/jest-runner/node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-runner/node_modules/form-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.3.tgz", + "integrity": "sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-runner/node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-runner/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-runner/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/jest-runner/node_modules/istanbul-lib-instrument": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", @@ -13691,6 +14510,24 @@ "node": ">=8" } }, + "node_modules/jest-runner/node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/jest-runner/node_modules/jest-haste-map": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", @@ -13777,6 +14614,70 @@ "node": ">= 10.13.0" } }, + "node_modules/jest-runner/node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-runner/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" + }, + "node_modules/jest-runner/node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest-runner/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -13792,13 +14693,99 @@ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-runner/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/jest-runner/node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-runner/node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/jest-runner/node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/jest-runner/node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "license": "MIT" + }, + "node_modules/jest-runner/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "license": "MIT", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/jest-runner/node_modules/write-file-atomic": { @@ -13813,6 +14800,33 @@ "typedarray-to-buffer": "^3.1.5" } }, + "node_modules/jest-runner/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/jest-runner/node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "license": "Apache-2.0" + }, "node_modules/jest-runtime": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", @@ -14976,44 +15990,39 @@ } }, "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { "canvas": { @@ -15021,21 +16030,6 @@ } } }, - "node_modules/jsdom/node_modules/form-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.3.tgz", - "integrity": "sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -16235,10 +17229,32 @@ } }, "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "license": "MIT" + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/parseurl": { "version": "1.3.3", @@ -19533,6 +20549,14 @@ "randombytes": "^2.1.0" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -19736,15 +20760,17 @@ "license": "ISC" }, "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, "license": "ISC", + "peer": true, "dependencies": { "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=v12.22.7" } }, "node_modules/scheduler": { @@ -21262,6 +22288,28 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "license": "MIT" }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -21290,39 +22338,31 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "node": ">=16" } }, "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/tryer": { @@ -21822,15 +22862,17 @@ } }, "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/walker": { @@ -21871,12 +22913,14 @@ "license": "Apache-2.0" }, "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { - "node": ">=10.4" + "node": ">=12" } }, "node_modules/webpack": { @@ -22017,27 +23061,6 @@ "node": ">= 10" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-manifest-plugin": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", @@ -22131,12 +23154,31 @@ } }, "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "iconv-lite": "0.4.24" + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/whatwg-fetch": { @@ -22146,23 +23188,29 @@ "license": "MIT" }, "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "license": "MIT" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } }, "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/which": { @@ -22666,16 +23714,16 @@ } }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -22687,10 +23735,15 @@ } }, "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "license": "Apache-2.0" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18" + } }, "node_modules/xmlchars": { "version": "2.2.0", diff --git a/react-ystemandchess/package.json b/react-ystemandchess/package.json index ac729a49..aaadcb80 100644 --- a/react-ystemandchess/package.json +++ b/react-ystemandchess/package.json @@ -72,6 +72,7 @@ "@types/testing-library__react": "^10.0.1", "autoprefixer": "^10.4.20", "babel-jest": "^30.0.0-beta.3", + "jest-fixed-jsdom": "^0.0.9", "postcss": "^8.4.49", "sass": "^1.83.1", "tailwindcss": "^3.4.17", diff --git a/react-ystemandchess/src/App.tsx b/react-ystemandchess/src/App.tsx index 4299327d..f584361e 100644 --- a/react-ystemandchess/src/App.tsx +++ b/react-ystemandchess/src/App.tsx @@ -1,15 +1,15 @@ import "./App.css"; import React, { useEffect } from "react"; import { BrowserRouter as Router } from "react-router-dom"; -import { environment } from './environments/environment'; -import { useCookies } from 'react-cookie'; -import { SetPermissionLevel } from './globals'; +import { environment } from "./environments/environment"; +import { useCookies } from "react-cookie"; +import { SetPermissionLevel } from "./globals"; import NavBar from "./NavBar/NavBar"; import Footer from "./Footer/Footer"; import AppRoutes from "./AppRoutes"; function App() { - const [cookies] = useCookies(['login']); // get login info from cookie + const [cookies] = useCookies(["login"]); // get login info from cookie let username = null; let eventID = null; let startTime = null; @@ -19,18 +19,18 @@ function App() { const uInfo = await SetPermissionLevel(cookies); // get logged-in user info // do nothing if the user is not logged in - if(uInfo.error) return; + if (uInfo.error) return; username = uInfo.username; // else record username // start recording user's time spent browsing the website const response = await fetch( - `${environment.urls.middlewareURL}/timeTracking/start?username=${username}&eventType=website`, + `${environment.urls.middlewareURL}/timeTracking/start?username=${username}&eventType=website`, { - method: 'POST', - headers: { 'Authorization': `Bearer ${cookies.login}` } + method: "POST", + headers: { Authorization: `Bearer ${cookies.login}` }, } ); - if(response.status != 200) console.log(response) // error handling + if (response.status != 200) console.log(response); // error handling // if data is fetched, record for later updates const data = await response.json(); @@ -41,22 +41,22 @@ function App() { // handler called when user exist the website, complete recording time const handleUnload = async () => { try { - const startDate = new Date(startTime) + const startDate = new Date(startTime); const endDate = new Date(); const diffInMs = endDate.getTime() - startDate.getTime(); // time elapsed in milliseconds const diffInSeconds = Math.floor(diffInMs / 1000); // time elapsed in seconds // update the time users spent browsing website const response = await fetch( - `${environment.urls.middlewareURL}/timeTracking/update?username=${username}&eventType=website&eventId=${eventID}&totalTime=${diffInSeconds}`, + `${environment.urls.middlewareURL}/timeTracking/update?username=${username}&eventType=website&eventId=${eventID}&totalTime=${diffInSeconds}`, { - method: 'PUT', - headers: { 'Authorization': `Bearer ${cookies.login}` } + method: "PUT", + headers: { Authorization: `Bearer ${cookies.login}` }, } ); - if(response.status != 200) console.log(response) // error handling + if (response.status != 200) console.log(response); // error handling } catch (err) { - console.log(err) + console.log(err); } }; @@ -64,12 +64,12 @@ function App() { try { startRecording(); // start recording } catch (err) { - console.log(err) + console.log(err); } - window.addEventListener('beforeunload', handleUnload); // end recording when unloading + window.addEventListener("beforeunload", handleUnload); // end recording when unloading return () => { - window.removeEventListener('beforeunload', handleUnload); + window.removeEventListener("beforeunload", handleUnload); }; }, []); diff --git a/react-ystemandchess/src/AppRoutes.tsx b/react-ystemandchess/src/AppRoutes.tsx index af2b4af9..da1f2047 100644 --- a/react-ystemandchess/src/AppRoutes.tsx +++ b/react-ystemandchess/src/AppRoutes.tsx @@ -1,61 +1,63 @@ // Imports for React Components/Pages -import React from 'react'; -import { Route, Router, Routes } from 'react-router-dom'; -import Home from './Pages/Home/Home'; -import Programs from './Pages/Programs/Programs'; -import CSBenefitPage from './Pages/About-Us/Benefit-of-CS/CSBenefitPage'; -import ChessBenefitPage from './Pages/About-Us/Benefit-of-Chess/ChessBenefitPage'; -import MathTutBenefitPage from './Pages/About-Us/Benefit-of-Math-tut/MathTutBenefitPage'; -import MentoringBenefitPage from './Pages/About-Us/Benefit-of-Mentoring/MentoringBenefitPage'; -import Lessons from './Pages/Lessons/Lessons'; -import Login from './Pages/Login/Login'; -import SignUp from './Pages/SignUp/SignUp'; -import Mission from './Pages/About-Us/Mission/Mission'; -import SponsorsPartners from './Pages/About-Us/SponsorsPartners/SponsorsPartners'; -import Board from './Pages/About-Us/Board/Board'; -import Mentor from './Pages/Mentor/Mentor'; -import Financial from './Pages/About-Us/Financial/Financial'; -import StudentInventory from './Pages/Student-Inventory/StudentInventory'; -import ResetPassword from './Pages/Reset-Password/reset-password'; -import SetPassword from './Pages/Set-Password/set-password'; -import Student from './Pages/Student/Student'; +import React from "react"; +import { Route, Router, Routes } from "react-router-dom"; +import Home from "./Pages/Home/Home"; +import Programs from "./Pages/Programs/Programs"; +import CSBenefitPage from "./Pages/About-Us/Benefit-of-CS/CSBenefitPage"; +import ChessBenefitPage from "./Pages/About-Us/Benefit-of-Chess/ChessBenefitPage"; +import MathTutBenefitPage from "./Pages/About-Us/Benefit-of-Math-tut/MathTutBenefitPage"; +import MentoringBenefitPage from "./Pages/About-Us/Benefit-of-Mentoring/MentoringBenefitPage"; +import Lessons from "./Pages/Lessons/Lessons"; +import Login from "./Pages/Login/Login"; +import SignUp from "./Pages/SignUp/SignUp"; +import Mission from "./Pages/About-Us/Mission/Mission"; +import SponsorsPartners from "./Pages/About-Us/SponsorsPartners/SponsorsPartners"; +import Board from "./Pages/About-Us/Board/Board"; +import Mentor from "./Pages/Mentor/Mentor"; +import Financial from "./Pages/About-Us/Financial/Financial"; +import StudentInventory from "./Pages/Student-Inventory/StudentInventory"; +import ResetPassword from "./Pages/Reset-Password/reset-password"; +import SetPassword from "./Pages/Set-Password/set-password"; +import Student from "./Pages/Student/Student"; // import MentorProfile from './Pages/Mentor-Profile/MentorProfile'; -import NewMentorProfile from './Pages/NewMentorProfile/NewMentorProfile'; -import NewStudentProfile from './Pages/NewStudentProfile/NewStudentProfile'; -import AboutUs from './Pages/About-Us/AboutUs/AboutUs'; -import LessonSelection from "./Pages/LessonsSelection/LessonsSelection" +import NewMentorProfile from "./Pages/NewMentorProfile/NewMentorProfile"; +import NewStudentProfile from "./Pages/NewStudentProfile/NewStudentProfile"; +import AboutUs from "./Pages/About-Us/AboutUs/AboutUs"; +import LessonSelection from "./Pages/LessonsSelection/LessonsSelection"; import LessonOverlay from "./Pages/piece-lessons/lesson-overlay/lesson-overlay"; // Variables and Mutable Data -import userPortraitImg from './images/user-portrait-placeholder.svg'; -const userName = 'Nimesh Patel'; +import userPortraitImg from "./images/user-portrait-placeholder.svg"; +import Admin from "./Pages/Admin/Admin"; +const userName = "Nimesh Patel"; const AppRoutes = () => { // All components must be wrapped with the '' tag return ( - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> { } /> - } /> - } + } /> + } + /> + } /> - } /> - } /> + } /> } /> diff --git a/react-ystemandchess/src/NavBar/NavBar.tsx b/react-ystemandchess/src/NavBar/NavBar.tsx index 9a65cd04..12722fed 100644 --- a/react-ystemandchess/src/NavBar/NavBar.tsx +++ b/react-ystemandchess/src/NavBar/NavBar.tsx @@ -37,7 +37,11 @@ const navbarVariants = { }; const NavBar = () => { - const [cookies, setCookie, removeCookie] = useCookies(["login", "eventId", "timerStatus"]); + const [cookies, setCookie, removeCookie] = useCookies([ + "login", + "eventId", + "timerStatus", + ]); const [dropdown, setDropdown] = useState(false); const dropdownRef = useRef(null); const [link, setLink] = useState(""); @@ -58,13 +62,13 @@ const NavBar = () => { // close dropdown menu when user clicks outside of dropdown useEffect(() => { - const closeDropdown = (event: { target: any; }) => { + const closeDropdown = (event: { target: any }) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { setDropdown(false); } }; - const closeProfileDropdown = (event: { target: any; }) => { + const closeProfileDropdown = (event: { target: any }) => { if ( profileDropdownRef.current && !profileDropdownRef.current.contains(event.target) @@ -123,7 +127,7 @@ const NavBar = () => { Programs -
+

About Us {dropdown ? ( @@ -279,4 +283,4 @@ const NavBar = () => { ); }; -export default NavBar; \ No newline at end of file +export default NavBar; diff --git a/react-ystemandchess/src/Pages/Admin/Admin.tsx b/react-ystemandchess/src/Pages/Admin/Admin.tsx new file mode 100644 index 00000000..7ba570a5 --- /dev/null +++ b/react-ystemandchess/src/Pages/Admin/Admin.tsx @@ -0,0 +1,188 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router"; +import { useCookies } from "react-cookie"; +import { environment } from "../../environments/environment"; +const Admin = () => { + const navigate = useNavigate(); + // verify admin status + const [cookies, setCookie] = useCookies(["login"]); + + // search input + const [searchInput, setSearchInput] = useState(); + + const [error, setError] = useState(); + + // students + const [studentNames, setStudentNames] = useState<[string] | undefined>(); + + // can't find the User type in global.d.ts + const [students, setStudents] = useState([]); + // temporary + const [verified, setVerified] = useState(false); + + //. + //. + useEffect(() => { + // server url + let url = `${environment.urls.middlewareURL}/user/verifyRole`; + const verifyAdmin = async () => { + try { + const token = cookies; // Read JWT from cookie + if (!token) throw new Error("No token"); + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + token, + claimedRole: "admin", + }), + }); + const data = await response.json(); + if (response.ok) { + setVerified(data.verified); + } else { + throw new Error(data.error || "Role verification failed"); + } + + // User is verified as admin + } catch (err) { + console.error("Access denied:", err.message); + navigate("/"); + } + }; + verifyAdmin(); + }, []); + + // search + const handleStudentSearch = async () => { + if (!verified) { + navigate("/"); + return; + } + setStudentNames(undefined); + setStudents([]); + let url = `${environment.urls.middlewareURL}/user/getStudent?keyword=${searchInput}`; + try { + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + // Throws an error if the response status is failure. + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log(data, "--->"); + setStudentNames(data); + } catch { + console.error("Search error:", error); + // Sets a general error message for signup failure. + setError("Search failed"); + } + }; + + // get basic info about found students + useEffect(() => { + if (!studentNames) { + return; + } + for (let i = 0; i < studentNames.length; i++) { + let url = `${environment.urls.middlewareURL}/user/getUser?username=${studentNames[i]}`; + try { + const getUser = async () => { + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + setStudents((prevStudent) => [...prevStudent, data]); + }; + getUser(); + } catch (err) { + console.log(err); + } + } + }, [studentNames]); + return ( +

+
{ + e.preventDefault(); + handleStudentSearch(); + }} + > + { + setSearchInput(e.target.value); + }} + placeholder="Type student username to search" + /> + +
+ {/* students searched */} +
+
+
+
+ {students.map((student) => ( +
{ + // to the student's profile + }} + > +
+

+ {student.firstName} {student.lastName}{" "} + + ({student.username}) + +

+ + Role: {student.role} + +
+

+ Email: {student.email} +

+

+ Account Created: {student.accountCreatedAt} +

+

+ Lessons Completed: {student.lessonsCompleted.length} +

+

+ Time Played: {student.timePlayed} mins +

+
+ ))} +
+
+
+
+
+ ); +}; + +export default Admin; diff --git a/react-ystemandchess/src/Pages/Admin/admin.test.tsx b/react-ystemandchess/src/Pages/Admin/admin.test.tsx new file mode 100644 index 00000000..41facd9e --- /dev/null +++ b/react-ystemandchess/src/Pages/Admin/admin.test.tsx @@ -0,0 +1,139 @@ +import React from "react"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { MemoryRouter } from "react-router"; +import { CookiesProvider } from "react-cookie"; +import Admin from "./Admin"; + +// Mock environment +jest.mock("../../environments/environment", () => ({ + environment: { + urls: { + middlewareURL: "http://mock-api.com", + }, + }, +})); + +// Mock navigate +let mockedNavigate = jest.fn(); + +jest.mock("react-router", () => { + const actual = jest.requireActual("react-router"); + return { + ...actual, + useNavigate: () => mockedNavigate, + }; +}); +// Mock useCookies +jest.mock("react-cookie", () => { + const actual = jest.requireActual("react-cookie"); + return { + ...actual, + useCookies: () => [{ login: "mock-jwt-token" }, jest.fn()], + }; +}); + +// Mock student with 30 completed lessons +const mockStudent = { + _id: "123", + firstName: "Test", + lastName: "Student", + username: "student1", + role: "student", + email: "test@example.com", + accountCreatedAt: "2023-01-01", + lessonsCompleted: Array.from({ length: 30 }, (_, i) => ({ + _id: `lesson-${i}`, + piece: `Lesson ${i}`, + lessonNumber: i, + })), + timePlayed: 120, +}; + +describe("Admin Component", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("renders search input and button", () => { + render( + + + + + + ); + + const input = screen.getByPlaceholderText( + "Type student username to search" + ); + const button = screen.getByRole("button", { name: /search/i }); + + expect(input).toBeInTheDocument(); + expect(button).toBeInTheDocument(); + }); + + it("calls student search API and shows student with 30 completed lessons", async () => { + global.fetch = jest + .fn() + // verifyRole API + .mockResolvedValueOnce({ + ok: true, + json: async () => ({ verified: true }), + } as Response) + // getStudent API + .mockResolvedValueOnce({ + ok: true, + json: async () => ["student1"], + } as Response) + // getUser API + .mockResolvedValueOnce({ + ok: true, + json: async () => mockStudent, + } as Response); + + render( + + + + + + ); + + await waitFor(() => expect(global.fetch).toHaveBeenCalled()); + + const input = screen.getByPlaceholderText( + "Type student username to search" + ); + fireEvent.change(input, { target: { value: "student1" } }); + + const button = screen.getByRole("button", { name: /search/i }); + fireEvent.click(button); + + await waitFor(() => + expect(screen.getByText("Test Student")).toBeInTheDocument() + ); + + expect(screen.getByText("(student1)")).toBeInTheDocument(); + expect(screen.getByText("Lessons Completed: 30")).toBeInTheDocument(); + expect(screen.getByText("Time Played: 120 mins")).toBeInTheDocument(); + }); + + it("redirects to homepage if verification fails", async () => { + global.fetch = jest.fn().mockResolvedValueOnce({ + ok: false, + json: async () => ({ error: "Not authorized" }), + } as Response); + + render( + + + + + + ); + + await waitFor(() => { + expect(mockedNavigate).toHaveBeenCalledWith("/"); + }); + }); +}); diff --git a/react-ystemandchess/src/Pages/Home/Home.css b/react-ystemandchess/src/Pages/Home/Home.css index 4f23708b..e4c39ef3 100644 --- a/react-ystemandchess/src/Pages/Home/Home.css +++ b/react-ystemandchess/src/Pages/Home/Home.css @@ -17,20 +17,23 @@ display: flex; gap: 2rem; margin-bottom: 50px; + background-color: blue; } .info { - width: 50%; - height: 100%; + width: 10%; + height: 460px; display: flex; - justify-content: center; - align-items: center; + /* flex: 1; */ + /* justify-content: center; */ + /* align-items: center; */ flex-direction: column; + background-color: red; } .donateButton { margin-top: 2rem; - background-color: #1A1A1A; + background-color: #1a1a1a; border: solid 0.1rem white; color: white; border-radius: 1rem; @@ -240,7 +243,7 @@ padding-top: 50px; } -.home-content4-box button{ +.home-content4-box button { margin-top: 0.6rem; } diff --git a/react-ystemandchess/src/Pages/Home/Home.test.tsx b/react-ystemandchess/src/Pages/Home/Home.test.tsx index 6d1ec150..96907b41 100644 --- a/react-ystemandchess/src/Pages/Home/Home.test.tsx +++ b/react-ystemandchess/src/Pages/Home/Home.test.tsx @@ -1,58 +1,62 @@ -import React, { act } from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { MemoryRouter } from 'react-router'; -import Home from './Home'; +import React, { act } from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MemoryRouter } from "react-router"; +import Home from "./Home"; // mock navigation function const mockedNavigate = jest.fn(); -jest.mock('react-router', () => ({ - ...jest.requireActual('react-router'), +jest.mock("react-router", () => ({ + ...jest.requireActual("react-router"), useNavigate: () => mockedNavigate, })); // check if the heading is rendered it("renders the heading", () => { - render( - - - - ); - expect(screen.getByText(/Helping your child develop/i)).toBeInTheDocument(); + render( + + + + ); + expect(screen.getByText(/Helping your child develop/i)).toBeInTheDocument(); }); // check if join & subscribe subsections are rendered it("renders join & subscribe", () => { - render( - - - - ); - expect(screen.getByText(/Everyone is included. Everyone is welcome./i)).toBeInTheDocument(); - expect(screen.getByText(/Start now and sign up later!/i)).toBeInTheDocument(); + render( + + + + ); + expect( + screen.getByText(/Everyone is included. Everyone is welcome./i) + ).toBeInTheDocument(); + expect(screen.getByText(/Start now and sign up later!/i)).toBeInTheDocument(); }); // checks correct navigation it("navigates to the lessons page when Get Started is clicked", () => { - render( - - - - ); - // click on lesson start buttin - fireEvent.click(screen.getByText("Get Started!")); - // check if the page goes to /lessons - expect(mockedNavigate).toHaveBeenCalledWith("./lessons"); + render( + + + + ); + // click on lesson start buttin + fireEvent.click(screen.getByText("Get Started!")); + // check if the page goes to /lessons + expect(mockedNavigate).toHaveBeenCalledWith("./lessons"); }); // check if books are being rendered it("renders both book titles", () => { - render( - - - - ); - expect(screen.getByText(/Books by Devin Nakano/i)).toBeInTheDocument(); - expect(screen.getByText(/How to Start a Tech-Based Nonprofit/i)).toBeInTheDocument(); - expect(screen.getByText(/The Zero Dollar Workforce/i)).toBeInTheDocument(); + render( + + + + ); + expect(screen.getByText(/Books by Devin Nakano/i)).toBeInTheDocument(); + expect( + screen.getByText(/How to Start a Tech-Based Nonprofit/i) + ).toBeInTheDocument(); + expect(screen.getByText(/The Zero Dollar Workforce/i)).toBeInTheDocument(); }); diff --git a/react-ystemandchess/src/Pages/Home/Home.tsx b/react-ystemandchess/src/Pages/Home/Home.tsx index ceaeeecb..f3e09933 100644 --- a/react-ystemandchess/src/Pages/Home/Home.tsx +++ b/react-ystemandchess/src/Pages/Home/Home.tsx @@ -3,6 +3,7 @@ import "./Home.css"; import Images from "../../images/imageImporter"; import { ButtonsCard } from "../../components/ui/tailwindcss-buttons"; import { useNavigate } from "react-router"; +import GeminiChat from "../../components/ui/geminiChatBot/geminiChatBot"; const books = [ { @@ -22,9 +23,8 @@ const books = [ }, ]; - const Home = () => { - const navigate = useNavigate() + const navigate = useNavigate(); // Sends the user to donat // e page, when donate button is clicked const handleDonateButton = () => { @@ -36,117 +36,180 @@ const Home = () => { // Handle the Buy Now button click event // alert("Buy Now button clicked!"); if (title === "How to Start a Tech-Based Nonprofit") { - window.open("https://www.amazon.com/How-Start-Tech-based-Nonprofit-Opportunity/dp/B0C4MML5WG", "_blank"); + window.open( + "https://www.amazon.com/How-Start-Tech-based-Nonprofit-Opportunity/dp/B0C4MML5WG", + "_blank" + ); } if (title === "The Zero Dollar Workforce") { - window.open("https://www.amazon.com/Zero-Dollar-Workforce-Company-Spend/dp/B09NGVLQSS", "_blank"); + window.open( + "https://www.amazon.com/Zero-Dollar-Workforce-Company-Spend/dp/B09NGVLQSS", + "_blank" + ); } - - }; return (
-
-
-

+
+ {/* Left Side */} +
+

Helping your child develop
critical thinking skills!

- -

- We are a nonprofit organization empowering

children to find - their own success in STEM through

Chess, Math and Computer - Science. +

+ We are a nonprofit organization empowering
+ children to find their own success in STEM through
+ Chess, Math and Computer Science.

- - {/* */} - -
-
+ + {/* Right Side */} +
+
ystemandchess mascot + className="relative z-10 w-[340px] h-auto object-contain" + />
- line break - -

Everyone is included. Everyone is welcome.

- -
-
- heart -

Free

-

- For students who qualify for

free and reduced lunch. -

- Our lessons are free. -

- -
-
- gem -

Premium

-

- For students who don't qualify

for free and reduced lunch.{" "} -

- $25 / Week

First lesson is FREE.

Cancel anytime. -

- +
+ + {/* Premium Card */} +
+ gem +

+ Premium +

+

+ For students who don't qualify
+ for free and reduced lunch.
+ $25 / Week
+ First lesson is FREE.
+ Cancel anytime. +

+ + +
-
- mission statement -
+ {/* Three Action Cards */} +
+
+ {/* Play */} +
+
+ 🎮 +
+
+

Play

+

+ We strive to empower underserved and at-risk children through + mentoring and STEM skills-development to enable them to pursue + STEM careers and change their life trajectory. +

+
+
-
- + {/* Learn */} +
+
+ 📘 +
+
+

Learn

+

+ We strive to empower underserved and at-risk children through + mentoring and STEM skills-development to enable them to pursue + STEM careers and change their life trajectory. +

+
+
+ + {/* Donate */} +
+
+ 💰 +
+
+

Donate

+

+ The tax deductible donation will be used to scale our program to + underserved communities and students. Y STEM and Chess Inc. is a + registered tax organization. +

+
+
+
-
-
- ChessGroup -
- Start now and sign up later! + {/* YouTube + Box Section */} +
+
+ {/* YouTube */} +
+
+ +
+
+ + {/* Box Content */} +
+ ChessGroup +
+ Start now and sign up later! +
+ +
-
@@ -155,7 +218,6 @@ const Home = () => { className="logo-break" alt="line break" > -

Books by Devin Nakano

{books.map((book, index) => ( @@ -166,7 +228,10 @@ const Home = () => { alt={`${book.title} cover`} className="book-image" /> -
@@ -186,5 +251,3 @@ const Home = () => { }; export default Home; - - diff --git a/react-ystemandchess/src/Pages/LessonsSelection/LessonsSelection.test.tsx b/react-ystemandchess/src/Pages/LessonsSelection/LessonsSelection.test.tsx index 0998fce0..a5616a79 100644 --- a/react-ystemandchess/src/Pages/LessonsSelection/LessonsSelection.test.tsx +++ b/react-ystemandchess/src/Pages/LessonsSelection/LessonsSelection.test.tsx @@ -9,7 +9,7 @@ test("renders lesson selection page", () => { ); - + // Check if the lesson selection title is present const lessonTitle = screen.getByTestId("title"); expect(lessonTitle).toBeInTheDocument(); @@ -25,4 +25,4 @@ test("renders lesson selection page", () => { expect(goButton).toBeInTheDocument(); }); -// MAYBE: Add more tests to check if later lessons are blocked if you haven't completed the previous ones \ No newline at end of file +// MAYBE: Add more tests to check if later lessons are blocked if you haven't completed the previous ones diff --git a/react-ystemandchess/src/Pages/Login/Login.tsx b/react-ystemandchess/src/Pages/Login/Login.tsx index fd713488..814bca07 100644 --- a/react-ystemandchess/src/Pages/Login/Login.tsx +++ b/react-ystemandchess/src/Pages/Login/Login.tsx @@ -1,15 +1,15 @@ -import React from 'react'; +import React from "react"; // import { Link } from 'react-router-dom'; -import './Login.scss'; -import { useState } from 'react'; -import { environment } from '../../environments/environment'; -import { useCookies } from 'react-cookie'; +import "./Login.scss"; +import { useState } from "react"; +import { environment } from "../../environments/environment"; +import { useCookies } from "react-cookie"; const Login = () => { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [cookies, setCookie] = useCookies(['login']); - const [loginError, setLoginError] = useState(''); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [cookies, setCookie] = useCookies(["login"]); + const [loginError, setLoginError] = useState(""); const [usernameFlag, setUsernameFlag] = useState(false); const [passwordFlag, setPasswordFlag] = useState(false); @@ -25,16 +25,16 @@ const Login = () => { if (password.length < 8) { setPasswordFlag(false); } else { - console.log('verifying pw') + console.log("verifying pw"); setPasswordFlag(true); } }; const errorMessages = () => { if (passwordFlag === false || usernameFlag === false) { - setLoginError('Invalid username or password'); + setLoginError("Invalid username or password"); } else { - setLoginError(''); + setLoginError(""); } }; @@ -42,10 +42,10 @@ const Login = () => { e.preventDefault(); // errorMessages(); if (password.length < 8 || username.length <= 2) { - setLoginError('Invalid username or password'); + setLoginError("Invalid username or password"); return; } else { - setLoginError(''); + setLoginError(""); } let url = `${environment.urls.middlewareURL}/auth/login?username=${username}&password=${password}`; @@ -63,56 +63,60 @@ const Login = () => { } } }; - xmlHttp.open('POST', theUrl, true); + xmlHttp.open("POST", theUrl, true); xmlHttp.send(null); }; - httpGetAsync(url, (response: any) => { - if (response === 'The username or password is incorrect.') { - setLoginError('The username or password is incorrect.'); - } else { - const expires = new Date(); - expires.setDate(expires.getDate() + 1); - setCookie('login', JSON.parse(response).token, { expires }); + httpGetAsync( + url, + (response: any) => { + if (response === "The username or password is incorrect.") { + setLoginError("The username or password is incorrect."); + } else { + const expires = new Date(); + expires.setDate(expires.getDate() + 1); + setCookie("login", JSON.parse(response).token, { expires }); - let payload = JSON.parse(atob(response.split('.')[1])); - console.log(payload) + let payload = JSON.parse(atob(response.split(".")[1])); + console.log(payload); - switch (payload['role']) { - case 'student': - window.location.pathname = '/student-profile'; - console.log(payload['role']) - break; - case 'parent': - window.location.pathname = '/parent'; - break; - case 'mentor': - window.location.pathname = '/mentor-profile'; - break; - case 'admin': - window.location.pathname = '/admin'; - break; - default: - window.location.pathname = ''; + switch (payload["role"]) { + case "student": + window.location.pathname = "/student-profile"; + console.log(payload["role"]); + break; + case "parent": + window.location.pathname = "/parent"; + break; + case "mentor": + window.location.pathname = "/mentor-profile"; + break; + case "admin": + window.location.pathname = "/admin"; + break; + default: + window.location.pathname = ""; + } } + }, + () => { + setLoginError("The username or password is incorrect."); } - }, () => { - setLoginError('The username or password is incorrect.'); - }); + ); }; return ( <> -

Login

- {loginError &&

{loginError}

} +

Login

+ {loginError &&

{loginError}

} -
-
+ +
{ setUsername(e.target.value); @@ -121,12 +125,12 @@ const Login = () => { required />
-
+
{ setPassword(e.target.value); @@ -135,16 +139,16 @@ const Login = () => { required />
- -
- + diff --git a/react-ystemandchess/src/Pages/Mentor-Profile/MentorProfile.tsx b/react-ystemandchess/src/Pages/Mentor-Profile/MentorProfile.tsx index feda8468..39260e53 100644 --- a/react-ystemandchess/src/Pages/Mentor-Profile/MentorProfile.tsx +++ b/react-ystemandchess/src/Pages/Mentor-Profile/MentorProfile.tsx @@ -1,9 +1,10 @@ -import React, { useState, useEffect } from 'react'; -import { useCookies } from 'react-cookie'; -import './MentorProfile.scss'; // Import the SCSS file for styles -import { environment } from '../../environments/environment'; -import ViewSDKClient from '../../services/view-sdk.service'; // Adjust the path as necessary -import { SetPermissionLevel } from '../../globals'; // Adjust the path as necessary +import React, { useState, useEffect } from "react"; +import { useCookies } from "react-cookie"; +import "./MentorProfile.scss"; // Import the SCSS file for styles +import { environment } from "../../environments/environment"; +import ViewSDKClient from "../../services/view-sdk.service"; // Adjust the path as necessary +import { SetPermissionLevel } from "../../globals"; // Adjust the path as necessary +import GeminiChat from "../../components/ui/geminiChatBot/geminiChatBot"; type PdfItem = { id: string; @@ -11,541 +12,973 @@ type PdfItem = { FilePath: string; }; -type Record = { +type Record = { sid: string; meetingId: string; meetingStartTime: string; [key: string]: any; // This allows any other fields to be added with any type -} - +}; const MentorProfile = () => { - const [username, setUsername] = useState(''); - const [firstName, setFirstName] = useState(''); - const [lastName, setLastName] = useState(''); - const [accountCreatedAt, setAccountCreatedAt] = useState(''); - const [role, setRole] = useState(''); - const [logged, setLogged] = useState(false); - const [recordingList, setRecordingList] = useState([]); - const [signedURL, setSignedURL] = useState(''); - const [showData, setShowData] = useState(false); - const [categoryList, setCategoryList] = useState([ - { id: '1', name: 'Mantra' }, - { id: '2', name: 'Exercise' }, - { id: '3', name: 'One Personal Development Lesson' }, - { id: '4', name: 'Chess Lesson' }, - { id: '5', name: 'Game' }, - { id: '6', name: 'Puzzles' }, - ]); - const [sharedPdfList, setSharedPdfList] = useState([]); - const [categoryName, setCategoryName] = useState(''); - const [showPdfListView, setShowPdfListView] = useState(false); - const [cookies] = useCookies(['login']); - - useEffect(() => { - ViewSDKClient.ready().then(() => { - ViewSDKClient.previewFile( - '../../../assets/pdf/mentor/Y_STEM_Chess_Training_Lessons.pdf', - 'pdf-div', - { - embedMode: 'SIZED_CONTAINER', - dockPageControls: false, - } - ); - }); - - const fetchUserInfo = async () => { - const uInfo = await SetPermissionLevel(cookies); - setUsername(uInfo.username); - setFirstName(uInfo.firstName); - setLastName(uInfo.lastName); - setAccountCreatedAt(uInfo.accountCreatedAt); - setRole(uInfo.role); - - // Wait for the DOM to be fully updated before accessing elements - setTimeout(() => { - const defaultOpen = document.getElementById('defaultOpen'); - const student3 = document.getElementById('student3'); - const defaultOpen2 = document.getElementById('defaultOpen2'); - const defaultOpenStudent = document.getElementById('defaultOpenStudent'); - - if (defaultOpen) defaultOpen.click(); - if (student3) student3.click(); - if (defaultOpen2) defaultOpen2.click(); - if (defaultOpenStudent) defaultOpenStudent.click(); - }, 0); - - if (!uInfo.error) { - setLogged(true); - } - - if (uInfo.role === 'mentor') { - fetchRecordings(); + const [username, setUsername] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [accountCreatedAt, setAccountCreatedAt] = useState(""); + const [role, setRole] = useState(""); + const [logged, setLogged] = useState(false); + const [recordingList, setRecordingList] = useState([]); + const [signedURL, setSignedURL] = useState(""); + const [showData, setShowData] = useState(false); + const [categoryList, setCategoryList] = useState([ + { id: "1", name: "Mantra" }, + { id: "2", name: "Exercise" }, + { id: "3", name: "One Personal Development Lesson" }, + { id: "4", name: "Chess Lesson" }, + { id: "5", name: "Game" }, + { id: "6", name: "Puzzles" }, + ]); + const [sharedPdfList, setSharedPdfList] = useState([]); + const [categoryName, setCategoryName] = useState(""); + const [showPdfListView, setShowPdfListView] = useState(false); + const [cookies] = useCookies(["login"]); + + useEffect(() => { + ViewSDKClient.ready().then(() => { + ViewSDKClient.previewFile( + "../../../assets/pdf/mentor/Y_STEM_Chess_Training_Lessons.pdf", + "pdf-div", + { + embedMode: "SIZED_CONTAINER", + dockPageControls: false, } - }; - - fetchUserInfo(); - }, []); - - const fetchRecordings = () => { - const url = `${environment.urls.middlewareURL}/meetings/usersRecordings`; - fetch(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${cookies.login}`, - }, - }) - .then((response) => response.json()) - .then((data) => { - setRecordingList(data); - }); - }; - - const openTab = (evt: React.MouseEvent, tabName: string, className: string) => { - const tabcontent = document.getElementsByClassName(className); - for (let i = 0; i < tabcontent.length; i++) { - const element = tabcontent[i] as HTMLElement; - element.style.display = 'none'; - } - const tablinks = document.getElementsByClassName('tablinks'); - for (let i = 0; i < tablinks.length; i++) { - tablinks[i].className = tablinks[i].className.replace(' active', ''); + ); + }); + + const fetchUserInfo = async () => { + const uInfo = await SetPermissionLevel(cookies); + setUsername(uInfo.username); + setFirstName(uInfo.firstName); + setLastName(uInfo.lastName); + setAccountCreatedAt(uInfo.accountCreatedAt); + setRole(uInfo.role); + + // Wait for the DOM to be fully updated before accessing elements + setTimeout(() => { + const defaultOpen = document.getElementById("defaultOpen"); + const student3 = document.getElementById("student3"); + const defaultOpen2 = document.getElementById("defaultOpen2"); + const defaultOpenStudent = + document.getElementById("defaultOpenStudent"); + + if (defaultOpen) defaultOpen.click(); + if (student3) student3.click(); + if (defaultOpen2) defaultOpen2.click(); + if (defaultOpenStudent) defaultOpenStudent.click(); + }, 0); + + if (!uInfo.error) { + setLogged(true); } - document.getElementById(tabName)!.style.display = 'block'; - evt.currentTarget.className += ' active'; - }; - - const showSharedSlideShowPdfList = (catId: any, catName: React.SetStateAction) => { - setShowPdfListView(true); - setCategoryName(catName); - - let list = []; - switch (catId) { - case '1': - list = [ - { id: '1', FileName: 'Mantra 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - // Add more files as necessary - ]; - break; - case '2': - list = [ - { id: '1', FileName: 'Exercise 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - // Add more files as necessary - ]; - break; - case '3': - list = [ - { id: '1', FileName: 'One Personal Development Lesson 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - // Add more files as necessary - ]; - break; - case '4': - list = [ - { id: '1', FileName: 'Chess Lesson 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - // Add more files as necessary - ]; - break; - case '5': - list = [ - { id: '1', FileName: 'Game 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - // Add more files as necessary - ]; - break; - case '6': - list = [ - { id: '1', FileName: 'Puzzles 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - // Add more files as necessary - ]; - break; - default: - list = [ - { id: '1', FileName: 'Demo 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - // Add more files as necessary - ]; - break; + + if (uInfo.role === "mentor") { + fetchRecordings(); } - - setSharedPdfList(list); - }; - - const getPresignURL = (sid: any, meetingId: any) => { - const filename = `${sid}_${meetingId}_0.mp4`; - const url = `${environment.urls.middlewareURL}/meetings/singleRecording?filename=${filename}`; - fetch(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${cookies.login}`, - }, - }) - .then((response) => response.json()) - .then((data) => { - setSignedURL(data); - if (signedURL !== '') { - window.open(signedURL); - } - }); }; - return ( -
-
- - -
-
-
- User -

Hello, {firstName} {lastName}!

-
+ fetchUserInfo(); + }, []); + + const fetchRecordings = () => { + const url = `${environment.urls.middlewareURL}/meetings/usersRecordings`; + fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${cookies.login}`, + }, + }) + .then((response) => response.json()) + .then((data) => { + setRecordingList(data); + }); + }; + + const openTab = ( + evt: React.MouseEvent, + tabName: string, + className: string + ) => { + const tabcontent = document.getElementsByClassName(className); + for (let i = 0; i < tabcontent.length; i++) { + const element = tabcontent[i] as HTMLElement; + element.style.display = "none"; + } + const tablinks = document.getElementsByClassName("tablinks"); + for (let i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + document.getElementById(tabName)!.style.display = "block"; + evt.currentTarget.className += " active"; + }; + + const showSharedSlideShowPdfList = ( + catId: any, + catName: React.SetStateAction + ) => { + setShowPdfListView(true); + setCategoryName(catName); + + let list = []; + switch (catId) { + case "1": + list = [ + { + id: "1", + FileName: "Mantra 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + // Add more files as necessary + ]; + break; + case "2": + list = [ + { + id: "1", + FileName: "Exercise 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + // Add more files as necessary + ]; + break; + case "3": + list = [ + { + id: "1", + FileName: "One Personal Development Lesson 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + // Add more files as necessary + ]; + break; + case "4": + list = [ + { + id: "1", + FileName: "Chess Lesson 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + // Add more files as necessary + ]; + break; + case "5": + list = [ + { + id: "1", + FileName: "Game 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + // Add more files as necessary + ]; + break; + case "6": + list = [ + { + id: "1", + FileName: "Puzzles 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + // Add more files as necessary + ]; + break; + default: + list = [ + { + id: "1", + FileName: "Demo 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + // Add more files as necessary + ]; + break; + } + + setSharedPdfList(list); + }; + + const getPresignURL = (sid: any, meetingId: any) => { + const filename = `${sid}_${meetingId}_0.mp4`; + const url = `${environment.urls.middlewareURL}/meetings/singleRecording?filename=${filename}`; + fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${cookies.login}`, + }, + }) + .then((response) => response.json()) + .then((data) => { + setSignedURL(data); + if (signedURL !== "") { + window.open(signedURL); + } + }); + }; + + return ( +
+
+ + +
+
+
+ User +

+ Hello, {firstName} {lastName}! +

+
+
+ +
+
+
+ + +
- -
-
-
- - - -
- -
-
-
-
- Student Graph -
-
-
-
-
Time Spent:
-

  Website: 45 minutes

-

  Lesson: 10 minutes

-

  Puzzle: 5 minutes

-

  Playing: 15 minutes

-

  Mentoring: 15 minutes

-
-
-
- -
- - - - - - - - -
- -
-
-
-
    -
  • -
    23rd July 2022
    7:00 PM
    -
    Solved 2 tactical puzzles
    -
  • -
  • -
    19th July 2022
    3:00 PM
    -
    Practiced 7 positions on Piece Checkmates I
    -
  • -
  • -
    17st July 2022
    7:00 PM
    -
    Signed up to ystemandchess.com
    -
  • -
-
-
+ +
+
+
+
+ Student Graph
- -
-

Mentor Session

-

The project started in 2018 when someone needed something.

- - +
+
+
+
+ Time Spent: +
+

+   Website:{" "} + 45 minutes +

+

+   Lesson:{" "} + 10 minutes +

+

+   Puzzle:{" "} + 5 minutes +

+

+   Playing:{" "} + 15 minutes +

+

+   Mentoring:{" "} + 15 minutes +

- -
-

Professional Development

+
+
+ +
+ + + + + + + + +
+ +
+
+
+
    +
  • +
    + 23rd July 2022 +
    + 7:00 PM +
    +
    + Solved 2 tactical puzzles +
    +
  • +
  • +
    + 19th July 2022 +
    + 3:00 PM +
    +
    + Practiced 7 positions on{" "} + Piece Checkmates I +
    +
  • +
  • +
    + 17st July 2022 +
    + 7:00 PM +
    +
    + Signed up to{" "} + + ystemandchess.com + +
    +
  • +
- -
-

Chess Lession

+
+
+ +
+

Mentor Session

+

The project started in 2018 when someone needed something.

+ + +
+ +
+

Professional Development

+ +
+ +
+

Chess Lession

+
+ +
+

Games

+
+ +
+

Puzzle

+
+ +
+

Computer

+
+ +
+

Recording

+ {recordingList.map((record: Record, index) => ( +

+ Play Icon +   + + + getPresignURL(record.sid, record.meetingId) + } + > + {record.sid}_{record.meetingId}_0.mp4 + + +   - {record.meetingStartTime} +

+ ))} +
+
+ + {/* Repeat similar structure for Studentname2 and Studentname3 */} +
+
+
+
+ Student Graph
- -
-

Games

+
+
+
+
+ Time Spent: +
+

+   Website:{" "} + 60 minutes +

+

+   Lesson:{" "} + 13 minutes +

+

+   Puzzle:{" "} + 35 minutes +

+

+   Playing:{" "} + 15 minutes +

+

+   Mentoring:{" "} + 5 minutes +

- -
-

Puzzle

+
+
+
+ + + + + + + + +
+ +
+
+
+
    +
  • +
    + 23rd July 2022 +
    + 7:00 PM +
    +
    + Solved 2 tactical puzzles +
    +
  • +
  • +
    + 19th July 2022 +
    + 3:00 PM +
    +
    + Practiced 7 positions on{" "} + Piece Checkmates I +
    +
  • +
  • +
    + 17st July 2022 +
    + 7:00 PM +
    +
    + Signed up to{" "} + + ystemandchess.com + +
    +
  • +
- -
-

Computer

+
+
+ +
+

Mentor Session

+

The project started in 2018 when someone needed something.

+
+ +
+

Professional Development

+
+ +
+

Chess Lession

+
+ +
+

Games

+
+ +
+

Puzzle

+
+ +
+

Computer

+
+ +
+

Recording

+ {recordingList.map((record: Record, index) => ( +

+ Play Icon +   + + + getPresignURL(record.sid, record.meetingId) + } + > + {record.sid}_{record.meetingId}_0.mp4 + + +   - {record.meetingStartTime} +

+ ))} +
+
+ +
+
+
+
+ Student Graph
- -
-

Recording

- {recordingList.map((record: Record, index) => ( -

- Play Icon  - - getPresignURL(record.sid, record.meetingId)}> - {record.sid}_{record.meetingId}_0.mp4 - -   - {record.meetingStartTime} -

- ))} +
+
+
+
+ Time Spent: +
+

+   Website:{" "} + 50 minutes +

+

+   Lesson:{" "} + 25 minutes +

+

+   Puzzle:{" "} + 30 minutes +

+

+   Playing:{" "} + 5 minutes +

+

+   Mentoring:{" "} + 20 minutes +

- - {/* Repeat similar structure for Studentname2 and Studentname3 */} -
-
-
-
- Student Graph -
-
-
-
-
Time Spent:
-

  Website: 60 minutes

-

  Lesson: 13 minutes

-

  Puzzle: 35 minutes

-

  Playing: 15 minutes

-

  Mentoring: 5 minutes

+
+ +
+ + + + + + + + +
+ +
+
+
+
    +
  • +
    + 23rd July 2022 +
    + 7:00 PM +
    +
    + Solved 2 tactical puzzles +
    +
  • +
  • +
    + 19th July 2022 +
    + 3:00 PM +
    +
    + Practiced 7 positions on{" "} + Piece Checkmates I
    -
+ +
  • +
    + 17st July 2022 +
    + 7:00 PM +
    +
    + Signed up to{" "} + + ystemandchess.com + +
    +
  • +
    -
    - - - - - - - - -
    - -
    -
    -
    -
      -
    • -
      23rd July 2022
      7:00 PM
      -
      Solved 2 tactical puzzles
      -
    • -
    • -
      19th July 2022
      3:00 PM
      -
      Practiced 7 positions on Piece Checkmates I
      -
    • -
    • -
      17st July 2022
      7:00 PM
      -
      Signed up to ystemandchess.com
      -
    • -
    -
    -
    -
    - -
    -

    Mentor Session

    -

    The project started in 2018 when someone needed something.

    -
    - -
    -

    Professional Development

    -
    - -
    -

    Chess Lession

    -
    - -
    -

    Games

    -
    - -
    -

    Puzzle

    -
    - -
    -

    Computer

    -
    - -
    -

    Recording

    - {recordingList.map((record: Record, index) => ( -

    - Play Icon  - - getPresignURL(record.sid, record.meetingId)}> - {record.sid}_{record.meetingId}_0.mp4 - -   - {record.meetingStartTime} -

    - ))} -
    -
    - -
    -
    -
    -
    - Student Graph -
    -
    -
    -
    -
    Time Spent:
    -

      Website: 50 minutes

    -

      Lesson: 25 minutes

    -

      Puzzle: 30 minutes

    -

      Playing: 5 minutes

    -

      Mentoring: 20 minutes

    -
    -
    -
    - -
    - - - - - - - - -
    - -
    -
    -
    -
      -
    • -
      23rd July 2022
      7:00 PM
      -
      Solved 2 tactical puzzles
      -
    • -
    • -
      19th July 2022
      3:00 PM
      -
      Practiced 7 positions on Piece Checkmates I
      -
    • -
    • -
      17st July 2022
      7:00 PM
      -
      Signed up to ystemandchess.com
      -
    • -
    -
    -
    -
    - -
    -

    Mentor Session

    -

    The project started in 2018 when someone needed something.

    -
    - -
    -

    Professional Development

    -
    - -
    -

    Chess Lession

    -
    - -
    -

    Games

    -
    - -
    -

    Puzzle

    -
    - -
    -

    Computer

    -
    - -
    -

    Recording

    - {recordingList.map((record: Record, index) => ( -

    - Play Icon  - - getPresignURL(record.sid, record.meetingId)}> - {record.sid}_{record.meetingId}_0.mp4 - -   - {record.meetingStartTime} -

    - ))} -
    -
    -
    - -
    -
    -
    -
    - -
    - {/* */} -
    +
    +
    + +
    +

    Mentor Session

    +

    The project started in 2018 when someone needed something.

    +
    + +
    +

    Professional Development

    +
    + +
    +

    Chess Lession

    +
    + +
    +

    Games

    +
    + +
    +

    Puzzle

    +
    + +
    +

    Computer

    +
    + +
    +

    Recording

    + {recordingList.map((record: Record, index) => ( +

    + Play Icon +   + + + getPresignURL(record.sid, record.meetingId) + } + > + {record.sid}_{record.meetingId}_0.mp4 + + +   - {record.meetingStartTime} +

    + ))} +
    +
    -)}; -export default MentorProfile; \ No newline at end of file + +
    +
    +
    +
    + +
    + {/* */} +
    +
    + ); +}; +export default MentorProfile; diff --git a/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.tsx b/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.tsx index 3a5a9baf..2c9607c4 100644 --- a/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.tsx +++ b/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.tsx @@ -1,10 +1,11 @@ import React, { useState, useEffect, useRef } from "react"; import "./NewMentorProfile.scss"; import Images from "../../images/imageImporter"; -import { SetPermissionLevel } from '../../globals'; -import { useCookies } from 'react-cookie'; -import { environment } from '../../environments/environment'; +import { SetPermissionLevel } from "../../globals"; +import { useCookies } from "react-cookie"; +import { environment } from "../../environments/environment"; import { useNavigate } from "react-router"; +import GeminiChat from "../../components/ui/geminiChatBot/geminiChatBot"; interface NewMentorProfileProps { userPortraitSrc: string; @@ -17,10 +18,11 @@ interface Student { lastName: string; } -const NewMentorProfile: React.FC = ({ userPortraitSrc }) => { - +const NewMentorProfile: React.FC = ({ + userPortraitSrc, +}) => { const [activeTab, setActiveTab] = useState("activity"); - const [cookies] = useCookies(['login']); + const [cookies] = useCookies(["login"]); const navigate = useNavigate(); // mentor info @@ -49,17 +51,18 @@ const NewMentorProfile: React.FC = ({ userPortraitSrc }) const containerRef = useRef(null); // current date for display - const [date, setDate] = useState(() => new Date().toLocaleDateString('en-US', { - month: 'long', - day: 'numeric', - }) -); + const [date, setDate] = useState(() => + new Date().toLocaleDateString("en-US", { + month: "long", + day: "numeric", + }) + ); - useEffect(()=>{ - fetchUserData().catch(err => console.log(err)); // fetch user data when the component mounts + useEffect(() => { + fetchUserData().catch((err) => console.log(err)); // fetch user data when the component mounts setStubStudent("joeyman43"); // set a stub student for testing purposes, setting students should happen outside of this component - fetchStudentData().catch(err => console.log(err)); // fetch student data when the component mounts - }, []) + fetchStudentData().catch((err) => console.log(err)); // fetch student data when the component mounts + }, []); useEffect(() => { const el = containerRef.current; @@ -75,65 +78,73 @@ const NewMentorProfile: React.FC = ({ userPortraitSrc }) return () => el.removeEventListener("scroll", handleScroll); }, [events, loading]); - const fetchUserData = async () => { - const uInfo = await SetPermissionLevel(cookies); // get logged-in user info - if (uInfo.error) { - console.log("Error: user not logged in.") // error if the user is not logged in - navigate("/login"); // redirect to login page - } else { - // record user info - setUsername(uInfo.username); - setFirstName(uInfo.firstName); - setLastName(uInfo.lastName) - } + const uInfo = await SetPermissionLevel(cookies); // get logged-in user info + if (uInfo.error) { + console.log("Error: user not logged in."); // error if the user is not logged in + navigate("/login"); // redirect to login page + } else { + // record user info + setUsername(uInfo.username); + setFirstName(uInfo.firstName); + setLastName(uInfo.lastName); + } - // fetch usage time stats - const responseStats = await fetch( - // fetches student's stats if studentUsername is provided, otherwise fetches mentor's stats - `${environment.urls.middlewareURL}/timeTracking/statistics?username=${studentUsername ? studentUsername : uInfo.username}`, - { - method: 'GET', - headers: { 'Authorization': `Bearer ${cookies.login}` } - } - ); - const dataStats = await responseStats.json(); - // update time usage for different events - setWebTime(dataStats.website); - setLessonTime(dataStats.lesson); - setPlayTime(dataStats.play); - setMentorTime(dataStats.mentor); - setPuzzleTime(dataStats.puzzle); + // fetch usage time stats + const responseStats = await fetch( + // fetches student's stats if studentUsername is provided, otherwise fetches mentor's stats + `${environment.urls.middlewareURL}/timeTracking/statistics?username=${ + studentUsername ? studentUsername : uInfo.username + }`, + { + method: "GET", + headers: { Authorization: `Bearer ${cookies.login}` }, + } + ); + const dataStats = await responseStats.json(); + // update time usage for different events + setWebTime(dataStats.website); + setLessonTime(dataStats.lesson); + setPlayTime(dataStats.play); + setMentorTime(dataStats.mentor); + setPuzzleTime(dataStats.puzzle); - // fetch latest usage history - fetchEvents(studentUsername ? studentUsername : uInfo.username) - } + // fetch latest usage history + fetchEvents(studentUsername ? studentUsername : uInfo.username); + }; const fetchStudentData = async () => { console.log("Fetching student data..."); fetch(`${environment.urls.middlewareURL}/user/getMentorship`, { - method: 'GET', - headers: { 'Authorization': `Bearer ${cookies.login}` } - }).then(data => data.json()) - .then(data => { + method: "GET", + headers: { Authorization: `Bearer ${cookies.login}` }, + }) + .then((data) => data.json()) + .then((data) => { setStudentFirstName(data.firstName); setStudentLastName(data.lastName); setStudentUsername(data.username); - setHasStudent(true) + setHasStudent(true); }); - } + }; const setStubStudent = async (stubStudentUsername) => { console.log("Setting stub student:", stubStudentUsername); - fetch(`${environment.urls.middlewareURL}/user/updateMentorship?mentorship=${stubStudentUsername}`, { - method: 'PUT', - headers: { 'Authorization': `Bearer ${cookies.login}`, - 'Content-Type': 'application/json'} - }).then(data => data.json()) - .then(data => { + fetch( + `${environment.urls.middlewareURL}/user/updateMentorship?mentorship=${stubStudentUsername}`, + { + method: "PUT", + headers: { + Authorization: `Bearer ${cookies.login}`, + "Content-Type": "application/json", + }, + } + ) + .then((data) => data.json()) + .then((data) => { console.log("Set student response:", data); }); - } + }; // fetch latest usage history const fetchEvents = async (username) => { @@ -147,32 +158,32 @@ const NewMentorProfile: React.FC = ({ userPortraitSrc }) try { // fetch latest usage history const responseLatest = await fetch( - `${environment.urls.middlewareURL}/timeTracking/latest?username=${username}&limit=${limit}&skip=${skip}`, + `${environment.urls.middlewareURL}/timeTracking/latest?username=${username}&limit=${limit}&skip=${skip}`, { - method: 'GET', - headers: { 'Authorization': `Bearer ${cookies.login}` } + method: "GET", + headers: { Authorization: `Bearer ${cookies.login}` }, } ); const dataLatest = await responseLatest.json(); - setEvents(prev => [...prev, ...dataLatest]); // append more events to old list - setPage(prev => prev + 1); // update pagination number + setEvents((prev) => [...prev, ...dataLatest]); // append more events to old list + setPage((prev) => prev + 1); // update pagination number setHasMore(dataLatest.length === limit && dataLatest.length > 0); } catch (err) { console.error("Failed to fetch events", err); } finally { setLoading(false); // stop fetching } - } + }; const handleTabClick = (tab: any) => { setActiveTab(tab); }; const handleNavigateEvent = (type: string, name: string) => { - if(type == "lesson") { + if (type == "lesson") { navigate("/lessons", { state: { piece: name } }); } - } + }; const renderTabContent = () => { switch (activeTab) { @@ -188,33 +199,48 @@ const NewMentorProfile: React.FC = ({ userPortraitSrc })
    - {events && events.map((event, index) => { // render list of usage history - const dateObj = new Date(event.startTime); - const dateStr = dateObj.toLocaleDateString('en-US', { // date of each history - year: 'numeric', - month: 'short', - day: 'numeric' - }); - const timeStr = dateObj.toLocaleTimeString('en-US', { // time of each history - hour: 'numeric', - minute: '2-digit' - }); + {events && + events.map((event, index) => { + // render list of usage history + const dateObj = new Date(event.startTime); + const dateStr = dateObj.toLocaleDateString("en-US", { + // date of each history + year: "numeric", + month: "short", + day: "numeric", + }); + const timeStr = dateObj.toLocaleTimeString("en-US", { + // time of each history + hour: "numeric", + minute: "2-digit", + }); - return ( -
    -
    -
    -

    {dateStr} {timeStr}

    -
    -
    -

    - Working on {event.eventType}:{' '} - handleNavigateEvent(event.eventType, event.eventName)}>{event.eventName} -

    -
    -
    - ); - })} + return ( +
    +
    +
    +

    + {dateStr} {timeStr} +

    +
    +
    +

    + Working on {event.eventType}:{" "} + + handleNavigateEvent( + event.eventType, + event.eventName + ) + } + > + {event.eventName} + +

    +
    +
    + ); + })} {loading &&

    Loading more...

    } {!hasMore &&

    No more activity left!

    }
    @@ -222,56 +248,90 @@ const NewMentorProfile: React.FC = ({ userPortraitSrc }) ); case "mentor": return ( -
    +

    Mentor

    This is the content for the Mentor tab.

    ); case "learning": return ( -
    +

    Learning

    This is the content for the Learning tab.

    ); case "chessLessons": return ( -
    +

    Chess Lessons

    This is the content for the Chess Lessons tab.

    ); case "games": return ( -
    +

    Games

    This is the content for the Games tab.

    ); case "puzzles": return ( -
    +

    Puzzles

    This is the content for the Puzzles tab.

    ); + case "professionalDevelopment": + return ( +
    +

    Professional Development

    + +
    + ); case "playComputer": return ( -
    +

    Play with Computer

    This is the content for the Play with Computer tab.

    ); case "recordings": return ( -
    +

    Recordings

    This is the content for the Recordings tab.

    ); case "backpack": return ( -
    +

    Backpack

    This is the content for the Backpack tab.

    @@ -301,64 +361,101 @@ const NewMentorProfile: React.FC = ({ userPortraitSrc }) >
    -

    Hello, {firstName} {lastName}!

    +

    + Hello, {firstName} {lastName}! +

    - { hasStudent ? ( -
    -
    -

    Check in on {studentFirstName} {studentLastName}'s progress!

    -
    -
    -
    - + {hasStudent ? ( +
    +
    +

    + Check in on{" "} + + {studentFirstName} {studentLastName}'s + {" "} + progress!{" "} +

    -
    -

    Time Spent:

    -
      -
    • Website: {webTime} minutes
    • -
    • Playing: {playTime} minutes
    • -
    • Lessons: {lessonTime} minutes
    • -
    • Puzzle: {puzzleTime} minutes
    • -
    • Mentoring: {mentorTime} minutes
    • -
    +
    +
    + +
    +
    +

    Time Spent:

    +
      +
    • + Website: {webTime} minutes +
    • +
    • + Playing: {playTime} minutes +
    • +
    • + Lessons: {lessonTime} minutes +
    • +
    • + Puzzle: {puzzleTime} minutes +
    • +
    • + Mentoring: {mentorTime} minutes +
    • +
    +
    -
    -
    - -
    {renderTabContent()}
    -
    -
    ) : ( -
    -

    No Student Selected

    -

    Please select a student to view their progress.

    -
    +
    + {renderTabContent()} +
    +
    +
    + ) : ( +
    +

    No Student Selected

    +

    Please select a student to view their progress.

    +
    )} ); diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.scss b/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.scss index a3ab64d4..7ed5e9e8 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.scss +++ b/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.scss @@ -169,14 +169,13 @@ $inventory-tab-active: linear-gradient( filter: grayscale(1); transition: filter 0.3s, transform 0.3s; } - + &.active-tab { img { filter: none; transform: scale(1.1); } } - } .inv-inventory-content-content { @@ -274,4 +273,3 @@ $inventory-tab-active: linear-gradient( cursor: pointer; } } - diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.tsx b/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.tsx index 66563604..a7d453ca 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.tsx +++ b/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.tsx @@ -1,16 +1,16 @@ import React, { useState, useEffect, useRef } from "react"; import "./NewStudentProfile.scss"; import Images from "../../images/imageImporter"; -import { SetPermissionLevel } from '../../globals'; -import { useCookies } from 'react-cookie'; -import { environment } from '../../environments/environment'; +import { SetPermissionLevel } from "../../globals"; +import { useCookies } from "react-cookie"; +import { environment } from "../../environments/environment"; import { useNavigate } from "react-router"; import { StatsChart } from "./StatsChart"; +import GeminiChat from "../../components/ui/geminiChatBot/geminiChatBot"; const NewStudentProfile = ({ userPortraitSrc }: any) => { - const [activeTab, setActiveTab] = useState("activity"); - const [cookies] = useCookies(['login']); + const [cookies] = useCookies(["login"]); const navigate = useNavigate(); // user info @@ -26,8 +26,15 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { const [mentorTime, setMentorTime] = useState(0); // data for chart plotting - const [displayMonths, setDisplayMonths] = useState(6); // display data from 6 many months back - const [monthAxis, setMonthAxis] = useState(["Jan", "Feb", "Mar", "Apr", "May", "Jun"]); + const [displayMonths, setDisplayMonths] = useState(6); // display data from 6 many months back + const [monthAxis, setMonthAxis] = useState([ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + ]); const [dataAxis, setDataAxis] = useState([0, 0, 0, 0, 0, 0]); // time spent on events each month // event tracking for pagination @@ -38,27 +45,29 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { const containerRef = useRef(null); // current date for display - const [date, setDate] = useState(() => new Date().toLocaleDateString('en-US', { - month: 'long', - day: 'numeric', - })); + const [date, setDate] = useState(() => + new Date().toLocaleDateString("en-US", { + month: "long", + day: "numeric", + }) + ); - useEffect(()=>{ + useEffect(() => { try { fetchUserData(); // asynchronously fetch user data upon mounting } catch (err) { - console.log(err) + console.log(err); } - }, []) + }, []); // loading changes, update listener to update handler closures useEffect(() => { - const el = containerRef.current; // get the activity element + const el = containerRef.current; // get the activity element if (!el) return; const handleScroll = () => { // if scrolling within 50px of displayed height - if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) { + if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) { fetchActivity(username); // fetch new acitivty } }; @@ -68,42 +77,42 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { }, [loading]); const fetchUserData = async () => { - const uInfo = await SetPermissionLevel(cookies); // get logged-in user info - if (uInfo.error) { - navigate("/login") // if user is not logged in, go to login page - } else { - // record user info - setUsername(uInfo.username); - setFirstName(uInfo.firstName); - setLastName(uInfo.lastName) - } + const uInfo = await SetPermissionLevel(cookies); // get logged-in user info + if (uInfo.error) { + navigate("/login"); // if user is not logged in, go to login page + } else { + // record user info + setUsername(uInfo.username); + setFirstName(uInfo.firstName); + setLastName(uInfo.lastName); + } - // fetch usage time stats to disaply in header - fetchUsageTime(uInfo.username) - // fetch data for graph plotting - fetchGraphData(uInfo.username) - // fetch latest usage history to show in Activity tab - fetchActivity(uInfo.username) - } + // fetch usage time stats to disaply in header + fetchUsageTime(uInfo.username); + // fetch data for graph plotting + fetchGraphData(uInfo.username); + // fetch latest usage history to show in Activity tab + fetchActivity(uInfo.username); + }; // fetch usage time stats to display in header const fetchUsageTime = async (username) => { - // fetch from back end - const responseStats = await fetch( - `${environment.urls.middlewareURL}/timeTracking/statistics?username=${username}`, - { - method: 'GET', - headers: { 'Authorization': `Bearer ${cookies.login}` } - } - ); - const dataStats = await responseStats.json(); - // update time usage for different events in header display - setWebTime(dataStats.website); - setLessonTime(dataStats.lesson); - setPlayTime(dataStats.play); - setMentorTime(dataStats.mentor); - setPuzzleTime(dataStats.puzzle); - } + // fetch from back end + const responseStats = await fetch( + `${environment.urls.middlewareURL}/timeTracking/statistics?username=${username}`, + { + method: "GET", + headers: { Authorization: `Bearer ${cookies.login}` }, + } + ); + const dataStats = await responseStats.json(); + // update time usage for different events in header display + setWebTime(dataStats.website); + setLessonTime(dataStats.lesson); + setPlayTime(dataStats.play); + setMentorTime(dataStats.mentor); + setPuzzleTime(dataStats.puzzle); + }; // fetch latest usage history (Activity Tab) const fetchActivity = async (username) => { @@ -116,45 +125,45 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { try { // fetch another batch of usage history const responseLatest = await fetch( - `${environment.urls.middlewareURL}/timeTracking/latest?username=${username}&limit=${limit}&skip=${skip}`, + `${environment.urls.middlewareURL}/timeTracking/latest?username=${username}&limit=${limit}&skip=${skip}`, { - method: 'GET', - headers: { 'Authorization': `Bearer ${cookies.login}` } + method: "GET", + headers: { Authorization: `Bearer ${cookies.login}` }, } ); const dataLatest = await responseLatest.json(); - setEvents(prev => [...prev, ...dataLatest]); // append more events to old list - setPage(prev => prev + 1); // update pagination number + setEvents((prev) => [...prev, ...dataLatest]); // append more events to old list + setPage((prev) => prev + 1); // update pagination number setHasMore(dataLatest.length === limit && dataLatest.length > 0); // if there are more activities setLoading(false); } catch (err) { console.error("Failed to fetch events", err); } - } + }; // fetch data needed for updating graph plot const fetchGraphData = async (username) => { try { // fetch the time spent on the website in the past few months const response = await fetch( - `${environment.urls.middlewareURL}/timeTracking/graph-data?username=${username}&eventType=website&months=${displayMonths}`, + `${environment.urls.middlewareURL}/timeTracking/graph-data?username=${username}&eventType=website&months=${displayMonths}`, { - method: 'GET', - headers: { 'Authorization': `Bearer ${cookies.login}` } + method: "GET", + headers: { Authorization: `Bearer ${cookies.login}` }, } ); - const data = await response.json(); - const months = data.map(item => item.monthText); // month list for ploting - const times = data.map(item => item.timeSpent); // timeSpent for plotting + const data = await response.json(); + const months = data.map((item) => item.monthText); // month list for ploting + const times = data.map((item) => item.timeSpent); // timeSpent for plotting // update for graph plotting setMonthAxis(months); - setDataAxis(times); + setDataAxis(times); } catch (err) { console.error("Failed to fetch events", err); } - } + }; const handleTabClick = (tab: any) => { setActiveTab(tab); @@ -162,10 +171,11 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { // navigate to previous activities const handleNavigateEvent = (type: string, name: string) => { - if(type == "lesson") { // if event is a lesson + if (type == "lesson") { + // if event is a lesson navigate("/lessons", { state: { piece: name } }); } - } + }; const renderTabContent = () => { switch (activeTab) { @@ -181,33 +191,48 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => {
    - {events && events.map((event, index) => { // render list of usage history - const dateObj = new Date(event.startTime); - const dateStr = dateObj.toLocaleDateString('en-US', { // date of each history - year: 'numeric', - month: 'short', - day: 'numeric' - }); - const timeStr = dateObj.toLocaleTimeString('en-US', { // time of each history - hour: 'numeric', - minute: '2-digit' - }); + {events && + events.map((event, index) => { + // render list of usage history + const dateObj = new Date(event.startTime); + const dateStr = dateObj.toLocaleDateString("en-US", { + // date of each history + year: "numeric", + month: "short", + day: "numeric", + }); + const timeStr = dateObj.toLocaleTimeString("en-US", { + // time of each history + hour: "numeric", + minute: "2-digit", + }); - return ( -
    -
    -
    -

    {dateStr} {timeStr}

    -
    -
    -

    - Working on {event.eventType}:{' '} - handleNavigateEvent(event.eventType, event.eventName)}>{event.eventName} -

    -
    -
    - ); - })} + return ( +
    +
    +
    +

    + {dateStr} {timeStr} +

    +
    +
    +

    + Working on {event.eventType}:{" "} + + handleNavigateEvent( + event.eventType, + event.eventName + ) + } + > + {event.eventName} + +

    +
    +
    + ); + })} {loading &&

    Loading more...

    } {!hasMore &&

    No more activity left!

    }
    @@ -215,56 +240,90 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { ); case "mentor": return ( -
    +

    Mentor

    This is the content for the Mentor tab.

    ); case "learning": return ( -
    +

    Learning

    This is the content for the Learning tab.

    ); case "chessLessons": return ( -
    +

    Chess Lessons

    This is the content for the Chess Lessons tab.

    ); case "games": return ( -
    +

    Games

    This is the content for the Games tab.

    ); case "puzzles": return ( -
    +

    Puzzles

    This is the content for the Puzzles tab.

    ); + case "professionalDevelopment": + return ( +
    +

    Professional Development

    + +
    + ); case "playComputer": return ( -
    +

    Play with Computer

    This is the content for the Play with Computer tab.

    ); case "recordings": return ( -
    +

    Recordings

    This is the content for the Recordings tab.

    ); case "backpack": return ( -
    +

    Backpack

    This is the content for the Backpack tab.

    @@ -294,7 +353,9 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { >
    -

    Hello, {firstName} {lastName}!

    +

    + Hello, {firstName} {lastName}! +

    @@ -302,46 +363,79 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => {
    - +

    Time Spent:

      -
    • Website: {webTime} minutes
    • -
    • Playing: {playTime} minutes
    • -
    • Lessons: {lessonTime} minutes
    • -
    • Puzzle: {puzzleTime} minutes
    • -
    • Mentoring: {mentorTime} minutes
    • +
    • + Website: {webTime} minutes +
    • +
    • + Playing: {playTime} minutes +
    • +
    • + Lessons: {lessonTime} minutes +
    • +
    • + Puzzle: {puzzleTime} minutes +
    • +
    • + Mentoring: {mentorTime} minutes +
    -
    {renderTabContent()}
    +
    + {renderTabContent()} +
    diff --git a/react-ystemandchess/src/Pages/Reset-Password/resetPasswordService.ts b/react-ystemandchess/src/Pages/Reset-Password/resetPasswordService.ts index 62cf2173..1a6a15a4 100644 --- a/react-ystemandchess/src/Pages/Reset-Password/resetPasswordService.ts +++ b/react-ystemandchess/src/Pages/Reset-Password/resetPasswordService.ts @@ -1,8 +1,8 @@ -import { environment } from '../../environments/environment'; -const nodemailer = require('nodemailer'); +import { environment } from "../../environments/environment"; +const nodemailer = require("nodemailer"); const transporter = nodemailer.createTransport({ - service: 'Gmail', // Or any other email service + service: "Gmail", // Or any other email service auth: { user: environment.email!.user, pass: environment.email!.pass, @@ -13,18 +13,21 @@ const sendResetPasswordEmail = (email: any, resetLink: any) => { const mailOptions = { from: environment.email!.user, to: email, - subject: 'Password Reset', + subject: "Password Reset", text: `Click the link to reset your password: ${resetLink}`, }; return new Promise((resolve, reject) => { - transporter.sendMail(mailOptions, (error: any, info: { response: unknown; }) => { - if (error) { - reject(error); - } else { - resolve(info.response); + transporter.sendMail( + mailOptions, + (error: any, info: { response: unknown }) => { + if (error) { + reject(error); + } else { + resolve(info.response); + } } - }); + ); }); }; diff --git a/react-ystemandchess/src/Pages/SignUp/SignUp.tsx b/react-ystemandchess/src/Pages/SignUp/SignUp.tsx index 64a0b6de..1ef19334 100644 --- a/react-ystemandchess/src/Pages/SignUp/SignUp.tsx +++ b/react-ystemandchess/src/Pages/SignUp/SignUp.tsx @@ -1,20 +1,20 @@ -import React, { useState } from 'react'; -import './SignUp.scss'; // Imports the stylesheet for this component. -import { environment } from '../../environments/environment'; // Imports environment variables, likely containing API URLs. +import React, { useState } from "react"; +import "./SignUp.scss"; // Imports the stylesheet for this component. +import { environment } from "../../environments/environment"; // Imports environment variables, likely containing API URLs. // Test comment - This is a manual test comment. -console.log('Environment URL:', environment.urls.middlewareURL); // Logs the middleware URL from the environment. +console.log("Environment URL:", environment.urls.middlewareURL); // Logs the middleware URL from the environment. const Signup = () => { // State to manage the form data for the user. const [formData, setFormData] = useState({ - firstName: '', - lastName: '', - email: '', - username: '', - password: '', - retypedPassword: '', - accountType: 'mentor', // Default account type is set to 'mentor'. + firstName: "", + lastName: "", + email: "", + username: "", + password: "", + retypedPassword: "", + accountType: "mentor", // Default account type is set to 'mentor'. }); // State flags to track the validity of individual input fields. @@ -27,12 +27,12 @@ const Signup = () => { // State to store any validation errors for the form fields. const [errors, setErrors] = useState({ - firstName: '', - lastName: '', - email: '', - username: '', - password: '', - retypePassword: '', + firstName: "", + lastName: "", + email: "", + username: "", + password: "", + retypePassword: "", }); // State to manage the parent account specific UI and data. @@ -51,22 +51,22 @@ const Signup = () => { // Performs specific validation based on the input field that changed. switch (name) { - case 'firstName': + case "firstName": firstNameVerification(value); break; - case 'lastName': + case "lastName": lastNameVerification(value); break; - case 'email': + case "email": emailVerification(value); break; - case 'username': + case "username": usernameVerification(value); break; - case 'password': + case "password": passwordVerification(value); break; - case 'retypedPassword': + case "retypedPassword": retypePasswordVerification(value, formData.password); break; default: @@ -80,7 +80,7 @@ const Signup = () => { setFirstNameFlag(isValid); setErrors((prev) => ({ ...prev, - firstName: isValid ? '' : 'Invalid First Name', + firstName: isValid ? "" : "Invalid First Name", })); return isValid; }; @@ -91,7 +91,7 @@ const Signup = () => { setLastNameFlag(isValid); setErrors((prev) => ({ ...prev, - lastName: isValid ? '' : 'Invalid Last Name', + lastName: isValid ? "" : "Invalid Last Name", })); return isValid; }; @@ -102,7 +102,7 @@ const Signup = () => { setEmailFlag(isValid); setErrors((prev) => ({ ...prev, - email: isValid ? '' : 'Invalid Email', + email: isValid ? "" : "Invalid Email", })); return isValid; }; @@ -113,7 +113,7 @@ const Signup = () => { setUserNameFlag(isValid); setErrors((prev) => ({ ...prev, - username: isValid ? '' : 'Invalid Username', + username: isValid ? "" : "Invalid Username", })); return isValid; }; @@ -124,25 +124,28 @@ const Signup = () => { setPasswordFlag(isValid); setErrors((prev) => ({ ...prev, - password: isValid ? '' : 'Password must be at least 8 characters', + password: isValid ? "" : "Password must be at least 8 characters", })); return isValid; }; // Verifies if the retyped password matches the original password. - const retypePasswordVerification = (retypedPassword: string, password: string) => { + const retypePasswordVerification = ( + retypedPassword: string, + password: string + ) => { const isValid = retypedPassword === password; setRetypeFlag(isValid); setErrors((prev) => ({ ...prev, - retypePassword: isValid ? '' : 'Passwords do not match', + retypePassword: isValid ? "" : "Passwords do not match", })); return isValid; }; // Handles changes to the account type dropdown. const handleAccountTypeChange = (e: any) => { - const isParent = e.target.value === 'parent'; + const isParent = e.target.value === "parent"; setParentAccountFlag(isParent); // Updates the accountType in the form data. setFormData((prev) => ({ @@ -155,12 +158,12 @@ const Signup = () => { const handleAddStudent = () => { const newStudent = { id: Date.now(), - firstName: '', - lastName: '', - username: '', - email: '', - password: '', - retypedPassword: '', + firstName: "", + lastName: "", + username: "", + email: "", + password: "", + retypedPassword: "", errors: {}, }; setStudents((prev: any) => [...prev, newStudent]); @@ -168,7 +171,11 @@ const Signup = () => { }; // Handles changes to input fields within a student's form. - const handleStudentInputChange = (studentId: any, field: string, value: string) => { + const handleStudentInputChange = ( + studentId: any, + field: string, + value: string + ) => { // Updates the specific student's data in the students array. setStudents((prev: any) => prev.map((student: any) => @@ -180,7 +187,9 @@ const Signup = () => { // Removes a student form from the UI. const handleRemoveStudent = (studentId: any) => { // Filters out the student with the given ID. - setStudents((prev: any) => prev.filter((student: any) => student.id !== studentId)); + setStudents((prev: any) => + prev.filter((student: any) => student.id !== studentId) + ); // Hides the student form if no students are present. if (students.length === 1) { setShowStudentForm(false); @@ -189,7 +198,7 @@ const Signup = () => { // Handles the submission of the signup form. const handleSubmit = async () => { - console.log('Submit clicked', formData); + console.log("Submit clicked", formData); // Checks if all main form fields are valid based on their flags. const isValid = @@ -200,7 +209,7 @@ const Signup = () => { passwordFlag && retypeFlag; - console.log('Validation status:', { + console.log("Validation status:", { firstNameFlag, lastNameFlag, emailFlag, @@ -211,11 +220,11 @@ const Signup = () => { // If the main form is not valid, prevents submission. if (!isValid) { - console.log('Form validation failed'); + console.log("Form validation failed"); return; } - let url = ''; + let url = ""; // Constructs the API URL based on whether it's a parent account with students. if (parentAccountFlag && students.length > 0) { // Maps student data into the required format for the API. @@ -240,18 +249,18 @@ const Signup = () => { url = `${environment.urls.middlewareURL}/user/?first=${formData.firstName}&last=${formData.lastName}&email=${formData.email}&password=${formData.password}&username=${formData.username}&role=${formData.accountType}`; } - console.log('Request URL:', url); + console.log("Request URL:", url); try { // Sends a POST request to the signup API. const response = await fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, }); - console.log('Response status:', response.status); + console.log("Response status:", response.status); // Throws an error if the response status indicates failure. if (!response.ok) { @@ -260,100 +269,100 @@ const Signup = () => { // Parses the JSON response. const data = await response.json(); - console.log('Response data:', data); + console.log("Response data:", data); // Handles the case where the chosen username is already taken. - if (data === 'This username has been taken. Please choose another.') { + if (data === "This username has been taken. Please choose another.") { setErrors((prev) => ({ ...prev, - username: 'Username already taken', + username: "Username already taken", })); } else { // Redirects to the homepage upon successful signup. - window.location.href = '/'; + window.location.href = "/"; } } catch (error) { - console.error('Signup error:', error); + console.error("Signup error:", error); // Sets a general error message for signup failure. setErrors((prev) => ({ ...prev, - general: 'Signup failed. Please try again.', + general: "Signup failed. Please try again.", })); } }; return ( -
    e.preventDefault()}> + e.preventDefault()}>

    Sign up

    -
    +
    {/* Maps through the errors object and displays any error messages. */} {Object.values(errors).map((error, index) => error ?

    {error}

    : null )}
    -
    +
    -
    +

    Select Account Type

    {/* Conditional rendering of the student section for parent accounts. */} {parentAccountFlag && ( -
    +
    {/* Button to add a new student form if the student form is not currently shown. */} {!showStudentForm && ( handleStudentInputChange( student.id, - 'firstName', + "firstName", e.target.value ) } /> handleStudentInputChange( student.id, - 'lastName', + "lastName", e.target.value ) } /> handleStudentInputChange( student.id, - 'username', + "username", e.target.value ) } /> - handleStudentInputChange(student.id, 'email', e.target.value) + handleStudentInputChange(student.id, "email", e.target.value) } /> handleStudentInputChange( student.id, - 'password', + "password", e.target.value ) } /> handleStudentInputChange( student.id, - 'retypedPassword', + "retypedPassword", e.target.value ) } @@ -445,17 +454,17 @@ const Signup = () => {
    )} -
    - - +
    + +
    {/* Submit button for the signup form. */} - ); }; -export default Signup; \ No newline at end of file +export default Signup; diff --git a/react-ystemandchess/src/Pages/Student-Profile/UserProfile.tsx b/react-ystemandchess/src/Pages/Student-Profile/UserProfile.tsx index a393d7db..e31fcb68 100644 --- a/react-ystemandchess/src/Pages/Student-Profile/UserProfile.tsx +++ b/react-ystemandchess/src/Pages/Student-Profile/UserProfile.tsx @@ -1,8 +1,9 @@ -import React, { useEffect, useState } from 'react'; -import { SetPermissionLevel } from '../../globals'; -import { useCookies } from 'react-cookie'; -import { environment } from '../../environments/environment'; -import './UserProfile.scss'; +import React, { useEffect, useState } from "react"; +import { SetPermissionLevel } from "../../globals"; +import { useCookies } from "react-cookie"; +import { environment } from "../../environments/environment"; +import "./UserProfile.scss"; +import GeminiChat from "../../components/ui/geminiChatBot/geminiChatBot"; type PdfItem = { id: string; @@ -10,28 +11,26 @@ type PdfItem = { FilePath: string; }; -type Record = { +type Record = { sid: string; meetingId: string; meetingStartTime: string; [key: string]: any; // This allows any other fields to be added with any type -} - - +}; const UserProfile = () => { - const [cookies] = useCookies(['login']); - const [username, setUsername] = useState(''); - const [firstName, setFirstName] = useState(''); - const [lastName, setLastName] = useState(''); - const [accountCreatedAt, setAccountCreatedAt] = useState(''); - const [role, setRole] = useState(''); + const [cookies] = useCookies(["login"]); + const [username, setUsername] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [accountCreatedAt, setAccountCreatedAt] = useState(""); + const [role, setRole] = useState(""); const [recordingList, setRecordingList] = useState([]); const [sharedPdfList, setSharedPdfList] = useState([]); - const [categoryName, setCategoryName] = useState(''); + const [categoryName, setCategoryName] = useState(""); const [showPdfListView, setShowPdfListView] = useState(false); - const [signedURL, setSignedURL] = useState(''); - const [playLink, setPlayLink] = useState('play-nolog'); + const [signedURL, setSignedURL] = useState(""); + const [playLink, setPlayLink] = useState("play-nolog"); useEffect(() => { async function fetchUserInfo() { @@ -44,10 +43,11 @@ const UserProfile = () => { // Wait for the DOM to be fully updated before accessing elements setTimeout(() => { - const defaultOpen = document.getElementById('defaultOpen'); - const student3 = document.getElementById('student3'); - const defaultOpen2 = document.getElementById('defaultOpen2'); - const defaultOpenStudent = document.getElementById('defaultOpenStudent'); + const defaultOpen = document.getElementById("defaultOpen"); + const student3 = document.getElementById("student3"); + const defaultOpen2 = document.getElementById("defaultOpen2"); + const defaultOpenStudent = + document.getElementById("defaultOpenStudent"); if (defaultOpen) defaultOpen.click(); if (student3) student3.click(); @@ -55,7 +55,10 @@ const UserProfile = () => { if (defaultOpenStudent) defaultOpenStudent.click(); }, 0); - if (!uInfo.error && (uInfo.role === 'mentor' || uInfo.role === 'student')) { + if ( + !uInfo.error && + (uInfo.role === "mentor" || uInfo.role === "student") + ) { fetchRecordings(); } } @@ -66,9 +69,9 @@ const UserProfile = () => { const fetchRecordings = () => { const url = `${environment.urls.middlewareURL}/meetings/usersRecordings`; fetch(url, { - method: 'GET', + method: "GET", headers: { - 'Authorization': `Bearer ${cookies.login}`, + Authorization: `Bearer ${cookies.login}`, }, }) .then((response) => response.json()) @@ -77,80 +80,196 @@ const UserProfile = () => { }); }; - const openTab = (evt: React.MouseEvent, tabName: string) => { + const openTab = ( + evt: React.MouseEvent, + tabName: string + ) => { var i, tabcontent, tablinks; - tabcontent = document.getElementsByClassName('tabcontent'); + tabcontent = document.getElementsByClassName("tabcontent"); for (i = 0; i < tabcontent.length; i++) { - const element = tabcontent[i] as HTMLElement - element.style.display = 'none'; + const element = tabcontent[i] as HTMLElement; + element.style.display = "none"; } - tablinks = document.getElementsByClassName('tablinks'); + tablinks = document.getElementsByClassName("tablinks"); for (i = 0; i < tablinks.length; i++) { - tablinks[i].className = tablinks[i].className.replace(' active', ''); + tablinks[i].className = tablinks[i].className.replace(" active", ""); } const element = document.getElementById(tabName); if (element) { - element.style.display = 'block'; + element.style.display = "block"; } else { console.error(`Element with id ${tabName} not found`); } - - evt.currentTarget.className += ' active'; + + evt.currentTarget.className += " active"; }; - const showSharedSlideShowPdfList = (catId: string, catName: React.SetStateAction) => { + const showSharedSlideShowPdfList = ( + catId: string, + catName: React.SetStateAction + ) => { setShowPdfListView(true); setCategoryName(catName); let pdfList: PdfItem[] = []; switch (catId) { - case '1': + case "1": pdfList = [ - { id: '1', FileName: 'Mantra 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '2', FileName: 'Mantra 2', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '3', FileName: 'Mantra 3', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '4', FileName: 'Mantra 4', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '5', FileName: 'Mantra 5', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '6', FileName: 'Mantra 6', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, + { + id: "1", + FileName: "Mantra 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "2", + FileName: "Mantra 2", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "3", + FileName: "Mantra 3", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "4", + FileName: "Mantra 4", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "5", + FileName: "Mantra 5", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "6", + FileName: "Mantra 6", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, ]; break; - case '2': + case "2": pdfList = [ - { id: '1', FileName: 'Exercise 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '2', FileName: 'Exercise 2', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '3', FileName: 'Exercise 3', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '4', FileName: 'Exercise 4', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '5', FileName: 'Exercise 5', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, + { + id: "1", + FileName: "Exercise 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "2", + FileName: "Exercise 2", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "3", + FileName: "Exercise 3", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "4", + FileName: "Exercise 4", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "5", + FileName: "Exercise 5", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, ]; break; - case '3': + case "3": pdfList = [ - { id: '1', FileName: 'One Personal Development Lesson 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '2', FileName: 'One Personal Development Lesson 2', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '3', FileName: 'One Personal Development Lesson 3', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '4', FileName: 'One Personal Development Lesson 4', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, + { + id: "1", + FileName: "One Personal Development Lesson 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "2", + FileName: "One Personal Development Lesson 2", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "3", + FileName: "One Personal Development Lesson 3", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "4", + FileName: "One Personal Development Lesson 4", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, ]; break; - case '4': + case "4": pdfList = [ - { id: '1', FileName: 'Chess Lesson 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '2', FileName: 'Chess Lesson 2', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '3', FileName: 'Chess Lesson 3', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, + { + id: "1", + FileName: "Chess Lesson 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "2", + FileName: "Chess Lesson 2", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "3", + FileName: "Chess Lesson 3", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, ]; break; - case '5': + case "5": pdfList = [ - { id: '1', FileName: 'Game 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, - { id: '2', FileName: 'Game 2', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, + { + id: "1", + FileName: "Game 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, + { + id: "2", + FileName: "Game 2", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, ]; break; - case '6': + case "6": pdfList = [ - { id: '1', FileName: 'Puzzles 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, + { + id: "1", + FileName: "Puzzles 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, ]; break; default: pdfList = [ - { id: '1', FileName: 'demo 1', FilePath: 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' }, + { + id: "1", + FileName: "demo 1", + FilePath: + "https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf", + }, ]; } setSharedPdfList(pdfList); @@ -161,7 +280,7 @@ const UserProfile = () => { // console.error("AdobeDC is not defined"); // return; // } - + // const adobeDCView = new window.AdobeDC.View({ clientId: "YOUR_CLIENT_ID" }); // adobeDCView.previewFile({ // content: { location: { url: filePath } }, @@ -178,12 +297,12 @@ const UserProfile = () => { try { const response = await fetch(url, { - method: 'GET', + method: "GET", headers: { - 'Authorization': `Bearer ${document.cookie}`, // Adjust the way you retrieve the token + Authorization: `Bearer ${document.cookie}`, // Adjust the way you retrieve the token }, }); - + if (response.ok) { const data = await response.json(); setSignedURL(data); @@ -191,26 +310,33 @@ const UserProfile = () => { window.open(data); } } else { - console.error('Failed to fetch pre-signed URL'); + console.error("Failed to fetch pre-signed URL"); } } catch (error) { - console.error('Error fetching pre-signed URL:', error); + console.error("Error fetching pre-signed URL:", error); } -}; - + }; -return ( + return (
    - - + +
    -

    {firstName} {lastName}

    +

    + {firstName} {lastName} +

    - +
    @@ -218,74 +344,173 @@ return (
    -

    Time Spent :

    -
    +

    + Time Spent : +

    +
    -

      Website: 45 minutes

    +

    + {" "} +   Website:{" "} + 45 minutes +

    -

      Lesson: 10 minutes

    +

    +   Lesson:{" "} + 10 minutes +

    -

      Puzzle: 5 minutes

    +

    +   Puzzle:{" "} + 5 minutes +

    -

      Playing: 15 minutes

    +

    +   Playing:{" "} + 15 minutes +

    -

      Mentoring: 15 minutes

    +

    +   Mentoring:{" "} + 15 minutes +

    - +
    - - - - - - - -
    - +
    • -
      23rd July 2022
      7:00 PM
      +
      + 23rd July 2022 +
      + 7:00 PM +
      Solved 2 tactical puzzles
    • -
      19th July 2022
      3:00 PM
      -
      Practiced 7 positions on Piece Checkmates I
      +
      + 19th July 2022 +
      + 3:00 PM +
      +
      + Practiced 7 positions on{" "} + Piece Checkmates I +
    • -
      17st July 2022
      7:00 PM
      -
      Signed up to ystemandchess.com
      +
      + 17st July 2022 +
      + 7:00 PM +
      +
      + Signed up to{" "} + + ystemandchess.com + +
    - +

    Mentor Session

    - +

    Professional Development

    +
    - +

    Chess Lession

    - +

    Games

    - +

    Puzzles

    - +

    Computer

    - +

    Recording

    {recordingList.map((record: Record, index) => (

    - Play Icon  + Play Icon +   - getPresignURL(record.sid, record.meetingId)}> + getPresignURL(record.sid, record.meetingId)} + > {record.sid}_{record.meetingId}_0.mp4 -   - {record.meetingStartTime} + +   - {record.meetingStartTime}

    ))}
    - -
    + +
    - -
    + +
    {/* */}
    - )}; - - export default UserProfile; \ No newline at end of file + ); +}; + +export default UserProfile; diff --git a/react-ystemandchess/src/Pages/Student/Student.tsx b/react-ystemandchess/src/Pages/Student/Student.tsx index 889540c4..381c80d4 100644 --- a/react-ystemandchess/src/Pages/Student/Student.tsx +++ b/react-ystemandchess/src/Pages/Student/Student.tsx @@ -1,32 +1,32 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import "./Student.scss"; -import {environment} from "../../environments/environment"; +import { environment } from "../../environments/environment"; const Student = () => { - const chessSrc = environment.urls.chessClientURL; - const [movesAhead, setMovesAhead] = useState(5); + const chessSrc = environment.urls.chessClientURL; + const [movesAhead, setMovesAhead] = useState(5); - return ( -
    -
    -
    -
    -