diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ff37386..5fad6fd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,13 +70,17 @@ jobs: - name: Create environment file run: | - mkdir -p src/environments - cat < src/environments/environment.js + mkdir -p src/core/environments + cat < src/core/environments/environment.ts export const environment = { production: false, agora: { appId: '${{ secrets.APP_ID }}', }, + email: { + user: '${{ secrets.EMAIL_USER || '' }}', + pass: '${{ secrets.EMAIL_PASS || '' }}' + }, urls: { middlewareURL: '${{ secrets.MIDDLEWARE_URL }}', chessClientURL: '${{ secrets.CHESS_CLIENT_URL }}', diff --git a/.gitignore b/.gitignore index 1ae98ad2..07e1fe56 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,8 @@ react-ystemandchess/environment.prod.ts react-ystemandchess/environment.ts environment.php .env -environment.* +environment.*.local +environment.secret.* create_envs.sh create_env.sh create_dev_envs.sh diff --git a/README.md b/README.md index 5885bc6d..0bbabced 100644 --- a/README.md +++ b/README.md @@ -128,15 +128,16 @@ Each service runs independently and requires its own terminal window. Start the Handles user authentication, database operations, and coordinates other services. - ```bash cd middlewareNode npm install # Install backend dependencies -npm start # Start the API server +npm start # Start the API server (runs from src/server.js) ``` The server typically runs on port 8000. You should see "MongoDB Connected..." when it starts successfully. +> **Note**: After modularization, the entry point is now `src/server.js` + **If you intend to test the mentor and student login pages, you may use the following usernames and passwords respectively:** mentor 123123123 @@ -166,11 +167,13 @@ Manages chess game logic, validates moves, and handles real-time gameplay: ```bash cd chessServer npm install -npm start +npm start # Starts from src/index.js ``` Defaults to port 3000 (or the next available port if taken). +> **Note**: After modularization, the entry point is now `src/index.js` + --- #### Chess Engine Server @@ -180,11 +183,13 @@ Integrates Stockfish for AI opponents and move analysis: ```bash cd stockfishServer npm install -npm start +npm start # Starts from src/index.js ``` This service usually runs on port 8080. +> **Note**: After modularization, the entry point is now `src/index.js` + --- #### Chess Client (Testing Interface) @@ -198,8 +203,9 @@ Run the http server on port 80 using: `npx http-server -p 80` to ensure that the You can use the **Live Server extension in VS Code** to open the HTML files for local testing of the chess board: * **Board only:** Right-click `index.html` β†’ "Open with Live Server" -* **Board with controls:** Right-click `parent.html` β†’ "Open with Live Server" -* **Mentor/Student interaction:** Right-click `both.html` β†’ "Open with Live Server" +* **Archived files:** `archive/parent.html` and `archive/both.html` contain legacy testing interfaces + +> **Note**: Old HTML test files have been moved to `archive/` directory after modularization --- @@ -231,4 +237,30 @@ git checkout -b my-branch-name --- -You’re all set! Happy coding and thank you for contributing to educational equity! πŸŽ―β™ŸοΈ \ No newline at end of file +## Project Structure + +This project has been recently modularized for better organization and maintainability. Key structural changes: + +- **All Node.js services** now have their code in a `src/` directory +- **React app** uses a feature-based architecture with: + - `components/` - Reusable UI components + - `features/` - Feature modules (auth, lessons, student, mentor, etc.) + - `core/` - Core infrastructure (services, types, utils) + - `assets/` - Static assets and images +- **Configuration files** are organized in the `config/` directory +- **Kubernetes deployments** are in the `yaml/` directory + +--- + +## Docker Deployment + +To run all services using Docker: + +```bash +cd config +docker-compose up +``` + +--- + +You're all set! Happy coding and thank you for contributing to educational equity! πŸŽ―β™ŸοΈ \ No newline at end of file diff --git a/chessClient/both.html b/chessClient/archive/both.html similarity index 100% rename from chessClient/both.html rename to chessClient/archive/both.html diff --git a/chessClient/parent.html b/chessClient/archive/parent.html similarity index 100% rename from chessClient/parent.html rename to chessClient/archive/parent.html diff --git a/chessClient/rapid_render.json b/chessClient/archive/rapid_render.json similarity index 100% rename from chessClient/rapid_render.json rename to chessClient/archive/rapid_render.json diff --git a/chessClient/CHANGELOG.md b/chessClient/docs/CHANGELOG.md similarity index 100% rename from chessClient/CHANGELOG.md rename to chessClient/docs/CHANGELOG.md diff --git a/chessClient/LICENSE.md b/chessClient/docs/LICENSE.md similarity index 100% rename from chessClient/LICENSE.md rename to chessClient/docs/LICENSE.md diff --git a/chessClient/README_MOBILE_RESPONSIVE.md b/chessClient/docs/README_MOBILE_RESPONSIVE.md similarity index 100% rename from chessClient/README_MOBILE_RESPONSIVE.md rename to chessClient/docs/README_MOBILE_RESPONSIVE.md diff --git a/chessServer/package.json b/chessServer/package.json index 05f6e79c..86ebbe29 100644 --- a/chessServer/package.json +++ b/chessServer/package.json @@ -2,9 +2,9 @@ "name": "chess-server", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "src/index.js", "scripts": { - "start": "nodemon index.js", + "start": "nodemon src/index.js", "test": "jest --detectOpenHandles --forceExit" }, "keywords": [], diff --git a/chessServer/index old 2.js b/chessServer/src/archive/index old 2.js similarity index 100% rename from chessServer/index old 2.js rename to chessServer/src/archive/index old 2.js diff --git a/chessServer/index old.js b/chessServer/src/archive/index old.js similarity index 100% rename from chessServer/index old.js rename to chessServer/src/archive/index old.js diff --git a/chessServer/index.js b/chessServer/src/index.js similarity index 100% rename from chessServer/index.js rename to chessServer/src/index.js diff --git a/chessServer/managers/EventHandlers.js b/chessServer/src/managers/EventHandlers.js similarity index 100% rename from chessServer/managers/EventHandlers.js rename to chessServer/src/managers/EventHandlers.js diff --git a/chessServer/managers/GameManager.js b/chessServer/src/managers/GameManager.js similarity index 100% rename from chessServer/managers/GameManager.js rename to chessServer/src/managers/GameManager.js diff --git a/chessServer/__tests__/GameManager.test.js b/chessServer/src/tests/GameManager.test.js similarity index 100% rename from chessServer/__tests__/GameManager.test.js rename to chessServer/src/tests/GameManager.test.js diff --git a/chessServer/__tests__/index.test.js b/chessServer/src/tests/index.test.js similarity index 100% rename from chessServer/__tests__/index.test.js rename to chessServer/src/tests/index.test.js diff --git a/chessServer/server.test.js b/chessServer/src/tests/server.test.js similarity index 100% rename from chessServer/server.test.js rename to chessServer/src/tests/server.test.js diff --git a/docker-compose.yml b/config/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to config/docker-compose.yml diff --git a/create_travis_envs.sh b/create_travis_envs.sh index 5594fef8..4ebf9864 100644 --- a/create_travis_envs.sh +++ b/create_travis_envs.sh @@ -4,8 +4,8 @@ printf "Creating environment files and variables\n\n" #Creating environment files and variables for react-ystemandchess printf "Creating environment files for react-ystemandchess\n" -cd react-ystemandchess/src && mkdir environments -cd environments +cd react-ystemandchess/src && mkdir -p core/environments +cd core/environments #Creating and adding environment.js file and variables touch environment.js @@ -14,6 +14,10 @@ printf " production: false,\n" >> environment.js printf " agora: {\n" >> environment.js printf " appId: ' ',\n" >> environment.js printf " },\n" >> environment.js +printf " email: {\n" >> environment.js +printf " user: '',\n" >> environment.js +printf " pass: ''\n" >> environment.js +printf " },\n" >> environment.js printf " urls: {\n" >> environment.js printf " middlewareURL: 'http://127.0.0.1:8000',\n" >> environment.js printf " chessClientURL: 'http://localhost',\n" >> environment.js @@ -29,6 +33,10 @@ printf " production: false,\n" >> environment.prod.js printf " agora: {\n" >> environment.prod.js printf " appId: ' ',\n" >> environment.prod.js printf " },\n" >> environment.prod.js +printf " email: {\n" >> environment.prod.js +printf " user: '',\n" >> environment.prod.js +printf " pass: ''\n" >> environment.prod.js +printf " },\n" >> environment.prod.js printf " urls: {\n" >> environment.prod.js printf " middlewareURL: 'http://127.0.0.1:8000',\n" >> environment.prod.js printf " chessClientURL: 'http://localhost',\n" >> environment.prod.js diff --git a/mission-image.png b/documentation/mission-image.png similarity index 100% rename from mission-image.png rename to documentation/mission-image.png diff --git a/middleware/php.ini b/middleware/src/config/php.ini similarity index 100% rename from middleware/php.ini rename to middleware/src/config/php.ini diff --git a/middleware/awsGen.php b/middleware/src/endpoints/awsGen.php similarity index 100% rename from middleware/awsGen.php rename to middleware/src/endpoints/awsGen.php diff --git a/middleware/delete.php b/middleware/src/endpoints/delete.php similarity index 100% rename from middleware/delete.php rename to middleware/src/endpoints/delete.php diff --git a/middleware/endMeeting.php b/middleware/src/endpoints/endMeeting.php similarity index 100% rename from middleware/endMeeting.php rename to middleware/src/endpoints/endMeeting.php diff --git a/middleware/endSearch.php b/middleware/src/endpoints/endSearch.php similarity index 100% rename from middleware/endSearch.php rename to middleware/src/endpoints/endSearch.php diff --git a/middleware/genLink.php b/middleware/src/endpoints/genLink.php similarity index 100% rename from middleware/genLink.php rename to middleware/src/endpoints/genLink.php diff --git a/middleware/getCompletedLesson.php b/middleware/src/endpoints/getCompletedLesson.php similarity index 100% rename from middleware/getCompletedLesson.php rename to middleware/src/endpoints/getCompletedLesson.php diff --git a/middleware/getInfo.php b/middleware/src/endpoints/getInfo.php similarity index 100% rename from middleware/getInfo.php rename to middleware/src/endpoints/getInfo.php diff --git a/middleware/getLesson.php b/middleware/src/endpoints/getLesson.php similarity index 100% rename from middleware/getLesson.php rename to middleware/src/endpoints/getLesson.php diff --git a/middleware/getRecordings.php b/middleware/src/endpoints/getRecordings.php similarity index 100% rename from middleware/getRecordings.php rename to middleware/src/endpoints/getRecordings.php diff --git a/middleware/getTotalPieceLesson.php b/middleware/src/endpoints/getTotalPieceLesson.php similarity index 100% rename from middleware/getTotalPieceLesson.php rename to middleware/src/endpoints/getTotalPieceLesson.php diff --git a/middleware/index.php b/middleware/src/endpoints/index.php similarity index 100% rename from middleware/index.php rename to middleware/src/endpoints/index.php diff --git a/middleware/isInMeeting.php b/middleware/src/endpoints/isInMeeting.php similarity index 100% rename from middleware/isInMeeting.php rename to middleware/src/endpoints/isInMeeting.php diff --git a/middleware/newGame.php b/middleware/src/endpoints/newGame.php similarity index 100% rename from middleware/newGame.php rename to middleware/src/endpoints/newGame.php diff --git a/middleware/pairUp.php b/middleware/src/endpoints/pairUp.php similarity index 100% rename from middleware/pairUp.php rename to middleware/src/endpoints/pairUp.php diff --git a/middleware/record.php b/middleware/src/endpoints/record.php similarity index 100% rename from middleware/record.php rename to middleware/src/endpoints/record.php diff --git a/middleware/studentInfo.php b/middleware/src/endpoints/studentInfo.php similarity index 100% rename from middleware/studentInfo.php rename to middleware/src/endpoints/studentInfo.php diff --git a/middleware/updateLessonCompletion.php b/middleware/src/endpoints/updateLessonCompletion.php similarity index 100% rename from middleware/updateLessonCompletion.php rename to middleware/src/endpoints/updateLessonCompletion.php diff --git a/middleware/verify.php b/middleware/src/endpoints/verify.php similarity index 100% rename from middleware/verify.php rename to middleware/src/endpoints/verify.php diff --git a/middleware/verifyNoEcho.php b/middleware/src/endpoints/verifyNoEcho.php similarity index 100% rename from middleware/verifyNoEcho.php rename to middleware/src/endpoints/verifyNoEcho.php diff --git a/middleware/testQueries.txt b/middleware/src/testQueries.txt similarity index 100% rename from middleware/testQueries.txt rename to middleware/src/testQueries.txt diff --git a/middlewareNode/package-lock.json b/middlewareNode/package-lock.json index adc30e6b..ffa0f23e 100644 --- a/middlewareNode/package-lock.json +++ b/middlewareNode/package-lock.json @@ -3171,6 +3171,7 @@ "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", "dev": true, + "license": "MIT", "dependencies": { "cron-parser": "^4.2.0", "long-timeout": "0.1.1", diff --git a/middlewareNode/package.json b/middlewareNode/package.json index c05159ee..236c407e 100644 --- a/middlewareNode/package.json +++ b/middlewareNode/package.json @@ -2,7 +2,7 @@ "name": "middleware", "version": "1.0.0", "description": "API for Y STEM And Chess Inc", - "main": "server.js", + "main": "src/server.js", "dependencies": { "aws-sdk": "^2.889.0", "axios": "^0.21.1", @@ -22,7 +22,7 @@ "uuid": "^8.3.2" }, "scripts": { - "start": "nodemon server.js" + "start": "nodemon src/server.js" }, "author": "", "license": "ISC", diff --git a/middlewareNode/badges/catalog.js b/middlewareNode/src/badges/catalog.js similarity index 100% rename from middlewareNode/badges/catalog.js rename to middlewareNode/src/badges/catalog.js diff --git a/middlewareNode/badges/service.js b/middlewareNode/src/badges/service.js similarity index 100% rename from middlewareNode/badges/service.js rename to middlewareNode/src/badges/service.js diff --git a/middlewareNode/src/config/custom-environment-variables.json b/middlewareNode/src/config/custom-environment-variables.json new file mode 100644 index 00000000..9589aa2f --- /dev/null +++ b/middlewareNode/src/config/custom-environment-variables.json @@ -0,0 +1,32 @@ +{ + "mongoURI": "MONGO_URI", + "jwtSecret": "JWT_SECRET", + "indexKey": "INDEX_KEY", + + "corsOptions": { "origin": "CORS_ORIGIN" }, + + "email": { + "user": "EMAIL_USER", + "pass": "EMAIL_PASS" + }, + + "user": "EMAIL_USER", + "senderEmail": "SENDER_EMAIL", + + "clientId": "GOOGLE_CLIENT_ID", + "clientSecret": "GOOGLE_CLIENT_SECRET", + "redirectUri": "GOOGLE_REDIRECT_URI", + "refreshToken": "GOOGLE_REFRESH_TOKEN", + + "basepath": "BASEPATH", + + "awsAccessKey": "AWS_ACCESS_KEY_ID", + "awsSecretKey": "AWS_SECRET_ACCESS_KEY", + + "appID": "AGORA_APP_ID", + "uid": "AGORA_UID", + "customerId": "AGORA_CUSTOMER_ID", + "customerCertificate": "AGORA_CUSTOMER_CERT", + + "server": { "port": "PORT" } +} diff --git a/middlewareNode/config/db.js b/middlewareNode/src/config/db.js similarity index 100% rename from middlewareNode/config/db.js rename to middlewareNode/src/config/db.js diff --git a/middlewareNode/config/passport.js b/middlewareNode/src/config/passport.js similarity index 100% rename from middlewareNode/config/passport.js rename to middlewareNode/src/config/passport.js diff --git a/middlewareNode/controllers/meetings.js b/middlewareNode/src/controllers/meetings.js similarity index 100% rename from middlewareNode/controllers/meetings.js rename to middlewareNode/src/controllers/meetings.js diff --git a/middlewareNode/models/UserBadges.js b/middlewareNode/src/models/UserBadges.js similarity index 100% rename from middlewareNode/models/UserBadges.js rename to middlewareNode/src/models/UserBadges.js diff --git a/middlewareNode/models/activities.js b/middlewareNode/src/models/activities.js similarity index 100% rename from middlewareNode/models/activities.js rename to middlewareNode/src/models/activities.js diff --git a/middlewareNode/models/categorys.js b/middlewareNode/src/models/categorys.js similarity index 100% rename from middlewareNode/models/categorys.js rename to middlewareNode/src/models/categorys.js diff --git a/middlewareNode/models/guest.js b/middlewareNode/src/models/guest.js similarity index 100% rename from middlewareNode/models/guest.js rename to middlewareNode/src/models/guest.js diff --git a/middlewareNode/models/meetings.js b/middlewareNode/src/models/meetings.js similarity index 100% rename from middlewareNode/models/meetings.js rename to middlewareNode/src/models/meetings.js diff --git a/middlewareNode/models/moves.js b/middlewareNode/src/models/moves.js similarity index 100% rename from middlewareNode/models/moves.js rename to middlewareNode/src/models/moves.js diff --git a/middlewareNode/models/puzzles.js b/middlewareNode/src/models/puzzles.js similarity index 100% rename from middlewareNode/models/puzzles.js rename to middlewareNode/src/models/puzzles.js diff --git a/middlewareNode/models/timeTracking.js b/middlewareNode/src/models/timeTracking.js similarity index 100% rename from middlewareNode/models/timeTracking.js rename to middlewareNode/src/models/timeTracking.js diff --git a/middlewareNode/models/undoPermission.js b/middlewareNode/src/models/undoPermission.js similarity index 100% rename from middlewareNode/models/undoPermission.js rename to middlewareNode/src/models/undoPermission.js diff --git a/middlewareNode/models/users.js b/middlewareNode/src/models/users.js similarity index 100% rename from middlewareNode/models/users.js rename to middlewareNode/src/models/users.js diff --git a/middlewareNode/models/waiting.js b/middlewareNode/src/models/waiting.js similarity index 100% rename from middlewareNode/models/waiting.js rename to middlewareNode/src/models/waiting.js diff --git a/middlewareNode/routes/activities.js b/middlewareNode/src/routes/activities.js similarity index 100% rename from middlewareNode/routes/activities.js rename to middlewareNode/src/routes/activities.js diff --git a/middlewareNode/routes/auth.js b/middlewareNode/src/routes/auth.js similarity index 100% rename from middlewareNode/routes/auth.js rename to middlewareNode/src/routes/auth.js diff --git a/middlewareNode/routes/badges.js b/middlewareNode/src/routes/badges.js similarity index 100% rename from middlewareNode/routes/badges.js rename to middlewareNode/src/routes/badges.js diff --git a/middlewareNode/routes/categorys.js b/middlewareNode/src/routes/categorys.js similarity index 100% rename from middlewareNode/routes/categorys.js rename to middlewareNode/src/routes/categorys.js diff --git a/middlewareNode/routes/lessons.js b/middlewareNode/src/routes/lessons.js similarity index 57% rename from middlewareNode/routes/lessons.js rename to middlewareNode/src/routes/lessons.js index d72df93c..c64c2188 100644 --- a/middlewareNode/routes/lessons.js +++ b/middlewareNode/src/routes/lessons.js @@ -40,9 +40,9 @@ async function getDb() { */ router.get( "/stick", - async(req, res) => { + async (req, res) => { const db = await getDb(); - const lessons= db.collection("newLessons"); + const lessons = db.collection("newLessons"); const doc = await lessons.findOne({ piece: "Undermining Remove the defending piece" }); res.json(doc); @@ -88,21 +88,19 @@ router.get( router.get( "/getCompletedLessonCount", async (req, res, next) => { - passport.authenticate("jwt", { session: false }, async (err, user, info) => { + passport.authenticate("jwt", { session: false }, async (err, user) => { req.user = user || null; next(); })(req, res, next) // authenticate jwt }, async (req, res) => { - const piece = decodeURIComponent(req.query.piece); - - if (!piece) { - return res.status(400).json("Error: 400. Please provide a piece."); - } try { + const piece = req.query.piece ? decodeURIComponent(req.query.piece) : null; + if (!piece) { + return res.status(400).json("Error: 400. Please provide a piece."); + } const db = await getDb(); const users = db.collection("users"); // get users collection - const guests = db.collection("guest"); // get guests collection if (req.user) { const { username } = req.user; // get username from jwt @@ -115,125 +113,13 @@ router.get( if (!userDoc.lessonsCompleted) throw new Error("User does not have lesson record"); // the number of lessons completed for the piece - let lessonNum = 0; - for (const chessPiece of userDoc.lessonsCompleted) { - if (chessPiece.piece === piece) { // find the piece - lessonNum = chessPiece.lessonNumber; - break; - } - } + const entry = userDoc.lessonsCompleted.find(e => e.piece === piece); + const lessonNum = entry ? (Number(entry.lessonNumber) || 0) : 0; + return res.json(lessonNum); - res.json(lessonNum); } else { - const clientIp = getClientIp(req); // get client ip - const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // new expiration date - - await guests.updateOne( - { ip: clientIp }, - { - $set: { - ip: clientIp, - updatedAt: new Date(), // update date - expiresAt, // update expiration date - }, - $setOnInsert: { // if this ip is not in db, add lessonsCompelted field - lessonsCompleted: [ - { - piece: 'Piece Checkmate 1 Basic checkmates', - lessonNumber: 0 - }, - { - piece: 'Checkmate Pattern 1 Recognize the patterns', - lessonNumber: 0 - }, - { - piece: 'Checkmate Pattern 2 Recognize the patterns', - lessonNumber: 0 - }, - { - piece: 'Checkmate Pattern 3 Recognize the patterns', - lessonNumber: 0 - }, - { - piece: 'Checkmate Pattern 4 Recognize the patterns', - lessonNumber: 0 - }, - { - piece: 'Piece checkmates 2 Challenging checkmates', - lessonNumber: 0 - }, - { - piece: 'Knight and Bishop Mate interactive lesson', - lessonNumber: 0 - }, - { piece: 'The Pin Pin it to win it', lessonNumber: 0 }, - { piece: 'The Skewer Yum - Skewers!', lessonNumber: 0 }, - { piece: 'The Fork Use the fork, Luke', lessonNumber: 0 }, - { - piece: 'Discovered Attacks Including discovered checks', - lessonNumber: 0 - }, - { piece: 'Double Check A very powerfull tactic', lessonNumber: 0 }, - { - piece: 'Overloaded Pieces They have too much work', - lessonNumber: 0 - }, - { piece: 'Zwischenzug In-between moves', lessonNumber: 0 }, - { piece: 'X-Ray Attacking through an enemy piece', lessonNumber: 0 }, - { piece: 'Zugzwang Being forced to move', lessonNumber: 0 }, - { - piece: 'Interference Interpose a piece to great effect', - lessonNumber: 0 - }, - { - piece: 'Greek Gift Study the greek gift scrifice', - lessonNumber: 0 - }, - { piece: 'Deflection Distracting a defender', lessonNumber: 0 }, - { piece: 'Attraction Lure a piece to bad square', lessonNumber: 0 }, - { - piece: 'Underpromotion Promote - but not to a queen!', - lessonNumber: 0 - }, - { - piece: 'Desperado A piece is lost, but it can still help', - lessonNumber: 0 - }, - { - piece: 'Counter Check Respond to a check with a check', - lessonNumber: 0 - }, - { piece: 'Undermining Remove the defending piece', lessonNumber: 0 }, - { piece: 'Clearance Get out of the way!', lessonNumber: 0 }, - { piece: 'Key Squares Reach the key square', lessonNumber: 0 }, - { piece: 'Opposition take the opposition', lessonNumber: 0 }, - { piece: '7th-Rank Rook Pawn Versus a Queen', lessonNumber: 0 }, - { - piece: '7th-Rank Rook Pawn And Passive Rook vs Rook', - lessonNumber: 0 - }, - { piece: 'Basic Rook Endgames Lucena and Philidor', lessonNumber: 0 } - ], - }, - }, - { upsert: true } - ); - - const guestDoc = await guests.findOne( // get guestDoc by ip - { ip: clientIp } - ); - if (!guestDoc) throw new Error("Guest does not exist"); - - let lessonNum = -1; - for (const chessPiece of guestDoc.lessonsCompleted) { - if (chessPiece.piece === piece) { - lessonNum = chessPiece.lessonNumber; // the number of lessons completed for the piece - break; - } - } - if(lessonNum == -1) return res.status(400).json("Error: 400. Invalid piece."); - - else res.json(lessonNum); + // guest: always return 0 (frontend will show all lessons) + return res.json(0); } } catch (err) { console.error(err); @@ -269,7 +155,7 @@ router.get( try { const db = await getDb(); - const lessons= db.collection("newLessons"); // get lessons collection + const lessons = db.collection("newLessons"); // get lessons collection const lessonDoc = await lessons.findOne({ piece: piece }); // all lessons for that piece res.json(lessonDoc.lessons.length); // respond with length of lessons @@ -362,7 +248,7 @@ router.get( const users = db.collection("users"); // get users collection const guests = db.collection("guest"); // get users collection - if (req.user){ + if (req.user) { const { username } = req.user; // get username const userDoc = await users.findOne( // get userDoc by username @@ -383,7 +269,7 @@ router.get( return res.status(404).json("Piece not found in lessonsCompleted"); } - const updateResult = await users.updateOne( + const updateResult = await users.updateOne( { username, $or: [ @@ -408,7 +294,7 @@ router.get( res.status(304).json("No changes made"); } } else { - const clientIp = getClientIp(req); + const clientIp = getClientIp(req); const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); await guests.updateOne( @@ -517,7 +403,7 @@ router.get( return res.status(404).json("Piece not found in lessonsCompleted"); } - const updateResult = await guests.updateOne( + const updateResult = await guests.updateOne( { ip: clientIp, $or: [ @@ -574,7 +460,7 @@ router.get( const users = db.collection("users"); // get users collection const guests = db.collection("guest"); // get users collection - if (req.user){ + if (req.user) { const { username } = req.user; const userDoc = await users.findOne( // get userDoc by username @@ -723,249 +609,249 @@ module.exports = router; * Contains lesson completion states for various chess tactics and pieces */ list = [ - { - name: "Piece Checkmate 1 Basic checkmates", - list: ["8/8/7R/3k4/3Q4/4K3/8/8 b - - 5 3", - "8/8/R7/1R6/8/1k1K4/8/8 b - - 7 4", - "8/5Q2/5k2/8/2B2K2/8/8/8 b - - 9 5", - "8/kQ6/8/2N5/8/3K4/8/8 b - - 9 5", - "8/8/8/8/2K5/kQ6/8/8 b - - 11 6", - "R7/8/k1K5/8/8/8/8/8 b - - 21 11" - ] - }, - { - name: "The Pin Pin it to win it", - list: ["8/6k1/8/4B3/4P3/8/8/7K b - - 0 2", - "4k3/p1p2pp1/7p/2B5/8/1P3P2/P1P3PP/1K6 b - - 0 2", - "1k6/ppp3q1/8/4r3/8/2B5/5PPP/R4QK1 b - - 1 1", - "8/4k1p1/5p1p/4P3/8/7P/6P1/4R1K1 b - - 0 2", - "r4rk1/pp1p1ppp/1qp2B2/8/4P3/1P1P2Q1/P1P2PPP/R4RK1 b - - 0 1", - "4r1r1/2p5/1p1kn3/p1p1R1N1/P6p/7P/1PP1R1PK/8 b - - 0 1", - "1r1n1rk1/ppq2pQ1/2b5/2pB3p/2P4P/4P3/PB3PP1/1R3RK1 b - - 0 2", - "q5k1/5pp1/8/1pb1P3/2p5/2P3p1/1P3P2/1N3RK1 w - - 0 4" - ] - }, - { - name: "The Skewer Yum - Skewers!", - list: ["8/1Bq2k2/4ppp1/8/5P2/4P3/4QK2/5R2 b - - 0 2", - "r2r2k1/2p2ppp/5n2/4p3/p3P3/P6P/3B1PP1/2RQ2K1 b - - 0 2", - "5r1k/ppq3p1/3b4/2p2n2/2Pp1P1P/PP1Q2P1/3BN3/R3K2b w Q - 0 25", - "8/3qk3/8/8/5B2/4Kb2/8/8 w - - 0 3", - "8/1p4Q1/p2k4/6p1/P3b3/7P/5PP1/6K1 b - - 0 53", - "3k4/5R2/3b1P2/3p4/3P1p2/2p4P/2P3P1/7K b - - 0 3", - "8/pp1b4/5pp1/3k4/2p5/Q1P2P2/5KP1/8 b - - 0 42", - "B7/6p1/7p/2k5/p7/8/2P3P1/2K5 b - - 0 3" - ] - }, - { - name: "The Fork Use the fork, Luke", - list: ["2N5/5k2/8/8/6P1/7K/8/8 b - - 0 2", - "7k/5N1p/p7/nppP2q1/2P5/1P2N3/P6P/7K b - - 0 2", - "7k/5n2/8/4P3/8/8/6PP/5R1K b - - 0 2", - "r1bqkb1r/ppp2ppp/2n5/4p3/4p3/3B1N2/PPPP1PPP/R1BQK2R w KQkq - 0 4", - "8/8/R7/5k2/8/8/1K6/8 b - - 0 2", - "8/5k2/8/8/8/B6P/8/6K1 b - - 0 2", - "7Q/2nk1p1p/6p1/3n4/8/8/5PPP/6K1 b - - 0 2", - "N7/3k3p/6p1/5p2/r7/3P4/PPP2PPP/R5K1 b - - 0 3", - "3r1k2/pp1n2pb/2p2p1N/2P2r2/2QPp2p/P1P1B2P/6P1/1R1R2K1 b - - 0 2", - "8/5pk1/8/4p3/pp2P3/5P2/PP3K2/2n5 w - - 0 4", - "R7/5bN1/4k3/p3P3/8/5K2/8/8 b - - 0 4", - "r5k1/ppp2p1p/6pB/8/3bP2q/2NB2RP/PPP2nP1/R2Q3K w - - 1 5", - "6k1/1q6/p3p3/2P5/P3P2P/2P5/4Q1PK/8 b - - 0 3", - "5Bk1/1q1r2p1/5p1p/8/2P5/6PP/P4P2/4R1K1 w - - 0 34", - "r1bq1rk1/4p1bp/p2p1p2/1PpPn1B1/4PQ2/2N5/PP1N2PP/R3KB1R w KQ - 1 4", - "4b2r/5kP1/4p2n/pp1p3P/2pP1R2/P1P2B1N/2PK1P2/8 b - - 0 3" - ] - }, - { - name: "Discovered Attacks Including discovered checks", - list: [ - "4kR2/6pp/8/4N3/8/8/6PP/6K1 b - - 0 2", - "8/3Nk1pp/8/8/8/8/6PP/5RK1 b - - 0 2", - "B4k2/3r1pp1/3P3p/8/8/5N2/6PP/5RK1 b - - 0 2", - "r2q1bnN/pp3kpp/3p1p2/1Bp3B1/8/2Pp4/PP3PPP/RN1bR1K1 b - - 0 13", - "r1b2rk1/pp1n1p1p/6p1/3p4/3bP3/1PqB3P/P2N2PN/R2Q1R1K w - - 2 17", - "2k5/8/p2P4/2p1b3/8/2P4r/P4R2/1K2R3 b - - 0 43", - "8/2N5/4np2/4pk1p/R6P/P3KP2/1P6/8 b - - 0 2", - "r1bq2rk/1p1p1N2/p1n1pP1p/3n4/8/1N1Bb3/PPP3PP/R4R1K b - - 3 3", - "8/5ppk/p3p2p/1p1b4/3Pp3/1P2b1P1/P6P/2R2K2 w - - 0 4", - "4r3/1R2qk1p/1Q4p1/1Pp5/2P5/6P1/6KP/8 b - - 0 53" - ] - }, - { - name: "Double Check A very powerfull tactic", - list: [ - "", - "r3k3/ppp2pp1/2np4/2B1p3/2B1P1N1/3P2n1/PPP2PP1/RN1Q1RKr w q - 3 14", - "", - "", - "3r2k1/pp6/6p1/2P1q2p/4p3/4BK2/PP2P2r/R1Q1R2B w - - 0 30", - "B6r/2qk1P1p/p5p1/1p2p3/8/2P1B3/P1PR2PP/5RK1 b - - 0 25" - ] - }, - { - name: "Overloaded Pieces They have too much work", - list: [ - "6k1/5pp1/4R1n1/8/8/3B4/5PPP/6K1 b - - 0 1", - "2r1rbk1/3P1pp1/5n2/8/8/3Q3P/2B2PPK/8 b - - 0 1", - "6b1/4k2p/7P/1p1pKBP1/1P1P4/8/5P2/8 b - - 2 4", - "3q3k/pp2R1pp/8/2P5/1P3BQ1/2P5/P4rPp/7K b - - 0 29", - "8/6Q1/p3p1k1/3nB1r1/8/3q3P/PP6/K1R5 b - - 0 36", - "4R3/pb1R1p1k/1pn2qpp/8/4B3/P4NP1/1P3PP1/6K1 b - - 0 26", - "5R2/1p5k/p2pp3/8/7p/8/PP4P1/6K1 b - - 0 4", - "1r3k1r/p4p2/7p/3pP3/8/4BPN1/6P1/2R3K1 b - - 0 3", - "5k2/1pp4p/p1n3p1/8/1P2q3/P3P1Pb/3N3P/6K1 w - - 0 4", - "rk2Q3/5p1p/p1n2p2/P1b5/2p5/2P3P1/5P1P/6K1 b - - 0 32" - ] - }, - { - name: "Zwischenzug In-between moves", - list: [ - "1rbqk2r/pp3ppp/8/3n4/1bpP4/8/PP2BKPP/RN1Q2NR w k - 0 11", - "5rk1/pp1b1ppp/4p3/3pP3/1q3Pn1/3B1N2/P2K2PP/RN3Q2 w - - 0 19", - "B3k3/3b1q2/p2bp3/6Q1/8/4B1P1/PPP4P/6K1 b - - 0 31", - "2r3k1/B5pp/4p3/1Q1p1p2/1p3P2/1P2P3/P1r3PP/6K1 b - - 0 29", - "2B2r1k/1pN1Qpbp/p4pp1/q7/8/8/PP3PPP/3R2K1 b - - 0 25" - ] - }, - { - name: "Interference Interpose a piece to great effect", - list: [ - "4r1k1/p1p1qppp/3b1n2/1r1p4/P2P4/2P5/1P2BPPP/R1B1KN1R w KQ - 0 16", - "1k6/1pp2pp1/p1p5/8/8/2Pp1Pp1/PP2q1P1/R1BK1N2 w - - 4 27", - "1R4k1/p3bppp/4p3/8/3r4/6P1/P3PP1P/5RK1 b - - 0 19", - "3r2k1/pp3p1p/6p1/8/4P3/5P2/PP2nKPP/2B5 w - - 0 26", - "3B2k1/6p1/3b4/1p1p3q/3P4/2P1pNPb/1P3P1P/R5K1 w - - 0 32", - "2r4k/5pR1/7p/5q2/4p2P/1Qn1P3/5P1K/8 w - - 0 38", - "5q2/1b4pk/1p2p1n1/1P1pPp2/P2P1P1p/1N3R1P/1Q4PK/8 b - - 0 47" - ] - }, - { - name: "Greek Gift Study the greek gift scrifice", - list: [ - "rnb2rk1/pppn1pp1/4p3/3pP1B1/1b1P4/2NQ4/PPP2PPP/R3K2R b KQ - 0 4", - "rnb2rk1/pp3p1Q/4p3/3pn1N1/3p4/2NB4/PPP2PP1/R3K2R b KQ - 5 16", - "r3r2k/1b3Qp1/p7/1p1P1R2/1P6/P3q3/6PP/3R3K b - - 4 26", - "r3rbk1/6p1/pn4p1/np2p1B1/3p4/5Q2/PP3PP1/2R1R1K1 b - - 1 25", - "3r1B1Q/bpq2k2/p1b1pp2/2P5/1P6/P7/5PPP/2R2RK1 b - - 0 22", - "r1b2rk1/ppq2pp1/4pn2/2p3NQ/1p1P4/8/PP1N1PPP/R4RK1 w - - 0 16" - ] - }, - { - name: "Deflection Distracting a defender", - list: [ - "5R2/5ppk/4b3/8/8/7P/5PP1/6K1 b - - 0 2", - "8/8/8/1k4Q1/8/8/5K2/8 b - - 0 3", - "4r1k1/1p1Q1p1p/p2p1Pp1/1n1P4/1P6/6P1/P4PKP/4R3 b - - 0 4", - "rnbQ1b1r/pp2pkpp/5n2/8/4P3/2N5/PPP2PPP/R1B1K2R b - - 0 2", - "2b2qk1/7Q/3r2p1/8/8/3r3R/6PP/6K1 b - - 0 2", - "6k1/5p2/3q2pp/8/6P1/5QNP/5P2/4r1K1 w - - 0 3", - "r3r1k1/p4pp1/1p1Q3p/8/P7/7P/1P3PP1/R5K1 b - - 0 2", - "r5rk/6qQ/p1p2B1p/PpP5/1P6/2PB2P1/5PKP/8 b - - 1 3", - "r1bk3r/pppp2p1/1n3p1p/7n/2N4Q/5P2/PPP3PP/R3R1K1 b - - 0 2", - "6k1/2r2pp1/5q1p/Np6/8/1P5Q/3pbPPR/1R4K1 w - - 0 6" - ] - }, - { - name: "Attraction Lure a piece to bad square", - list: [ - "3R1k2/5p1p/8/5N2/8/5p1q/8/6K1 b - - 0 3", - "rnbB1b1r/ppk2ppp/2p5/4q3/4n3/8/PPP2PPP/2KR1BNR b - - 3 11", - "r2B3r/3n2pp/p2bR3/1b1k1P2/1qpPB3/4P1PP/5P1K/8 b - - 1 2", - "", - "", - "8/5pk1/4p3/1p6/6P1/4P3/PR2KP2/7q w - - 0 4", - "", - "6k1/pp4bp/2n2np1/5p2/2P2q2/4KBN1/PP5P/RQ6 w - - 0 24", - "", - "7r/6kp/5Rp1/8/8/8/6PP/2B3K1 b - - 0 1" - ] - }, - { - name: "Underpromotion Promote - but not to a queen!", - list: [ - "5N1n/r6k/5b2/5N1p/8/1q6/PP6/K5R1 b - - 0 1", - "5R2/7k/8/7K/8/8/8/8 b - - 0 1", - "1R1B4/8/k7/8/8/5K2/8/8 b - - 0 1", - "5R2/7k/5K2/8/8/8/8/8 b - - 0 1", - "8/7p/7B/5k1p/7P/5K2/8/8 b - - 0 2", - "8/8/8/8/8/k7/1b1n4/KB6 w - - 4 4", - "B3Q3/1q4pk/5p1p/5P1K/6PP/8/8/8 b - - 0 1", - "5N2/3q3k/8/8/6p1/6Pp/7P/7K b - - 0 3", - "6K1/8/5k2/8/8/8/8/8 b - - 0 2", - "3N4/k7/P7/1K6/8/8/8/8 b - - 0 1" - ] - }, - { - name: "Desperado A piece is lost, but it can still help", - list: [ - "r3kb1r/pp3ppp/2N1pn2/8/3P4/2P5/P4PPP/R1Bb1RK1 w - - 0 3", - "r2B2rk/2p2p2/p2p3p/1p1N4/3bP1n1/PB3P2/1P3P2/R3RK2 b - - 0 2", - "r5k1/ppp1p1rp/8/3P3b/4P3/2N5/PP1nN1P1/2K4R w - - 0 4", - "r5k1/ppN2pPp/6p1/5R2/1p6/1P2n3/P3K1PP/3R4 b - - 2 4", - "4br2/6k1/4pn1p/1p1P4/p2BB3/6P1/PP3P2/6K1 w - - 0 4" - ] - }, - { - name: "Counter Check Respond to a check with a check", - list: [ - "", - "8/6P1/8/8/1q1QK3/8/8/k7 b - - 1 1", - "", - "8/8/8/8/8/1k6/2r5/K7 w - - 0 96", - "8/5Npp/p1P5/P1R3rk/1p4b1/3r2K1/4pP1P/7R w - - 0 34" - ] - }, - { - name: "Undermining Remove the defending piece", - list: [ - "4rrk1/R5bp/3qp1p1/3p3Q/2pB1P2/6PP/1P3PB1/4R1K1 b - - 0 24", - "1b4k1/1p5p/8/1N1p4/P1nBn3/4P3/5q1N/5K1Q w - - 0 33", - "3qRrk1/1p3ppp/8/p4N2/8/P5QP/1P3PP1/6K1 b - - 0 1", - "1q3r1k/r4ppp/5Q2/8/5N2/p6R/PPP5/1K5R b - - 0 1", - "2q1rk2/1p3ppp/p7/2N5/1P6/5RP1/P1Q3KP/8 b - - 2 2" - ] - }, - { - name: "Clearance Get out of the way!", - list: [ - "8/2k5/5Np1/1p4K1/5P2/8/1R6/3r4 b - - 0 58", - "8/k7/p1p5/2P1p3/1P6/P3PB2/3K4/6q1 w - - 0 50", - "1k1r3r/pp1b4/5Ppp/3p4/P2R4/3B2q1/2PB1PPP/R5K1 b - - 0 24", - "4k2r/1p6/2b1p1pp/2N1P2N/5P1Q/pPp5/Pn4PK/4R3 b - - 0 41", - "" - ] - }, - { - name: "7th-Rank Rook Pawn And Passive Rook vs Rook", - list: [ - "8/6k1/8/4K2p/4P2p/5P2/6P1/8 b - - 0 58", - "R7/P6k/r7/8/8/8/1K6/8 w - - 27 15", - "R7/P5k1/6P1/8/8/1K6/8/r7 b - - 22 14", - "8/R7/5k2/8/8/8/6K1/8 b - - 0 6", - "", - "", - "R7/P5k1/5P2/8/8/8/7K/r7 b - - 0 80", - "8/6k1/8/4K2p/4P2p/5P2/6P1/8 b - - 0 58", - "R7/P4k2/6p1/r3P2p/4K2P/6P1/8/8 b - - 1 62" - ] - }, - { - name: "Basic Rook Endgames Lucena and Philidor", - list: [ - "", - "", - "", - "", - "8/8/8/3k4/8/7r/2K5/R7 b - - 9 11", - "", - "", - "8/8/8/8/R5K1/4k3/8/1r6 w - - 5 21", - "8/8/4K3/8/2k5/1R6/1p6/8 w - - 4 8", - "" - ] + { + name: "Piece Checkmate 1 Basic checkmates", + list: ["8/8/7R/3k4/3Q4/4K3/8/8 b - - 5 3", + "8/8/R7/1R6/8/1k1K4/8/8 b - - 7 4", + "8/5Q2/5k2/8/2B2K2/8/8/8 b - - 9 5", + "8/kQ6/8/2N5/8/3K4/8/8 b - - 9 5", + "8/8/8/8/2K5/kQ6/8/8 b - - 11 6", + "R7/8/k1K5/8/8/8/8/8 b - - 21 11" + ] + }, + { + name: "The Pin Pin it to win it", + list: ["8/6k1/8/4B3/4P3/8/8/7K b - - 0 2", + "4k3/p1p2pp1/7p/2B5/8/1P3P2/P1P3PP/1K6 b - - 0 2", + "1k6/ppp3q1/8/4r3/8/2B5/5PPP/R4QK1 b - - 1 1", + "8/4k1p1/5p1p/4P3/8/7P/6P1/4R1K1 b - - 0 2", + "r4rk1/pp1p1ppp/1qp2B2/8/4P3/1P1P2Q1/P1P2PPP/R4RK1 b - - 0 1", + "4r1r1/2p5/1p1kn3/p1p1R1N1/P6p/7P/1PP1R1PK/8 b - - 0 1", + "1r1n1rk1/ppq2pQ1/2b5/2pB3p/2P4P/4P3/PB3PP1/1R3RK1 b - - 0 2", + "q5k1/5pp1/8/1pb1P3/2p5/2P3p1/1P3P2/1N3RK1 w - - 0 4" + ] + }, + { + name: "The Skewer Yum - Skewers!", + list: ["8/1Bq2k2/4ppp1/8/5P2/4P3/4QK2/5R2 b - - 0 2", + "r2r2k1/2p2ppp/5n2/4p3/p3P3/P6P/3B1PP1/2RQ2K1 b - - 0 2", + "5r1k/ppq3p1/3b4/2p2n2/2Pp1P1P/PP1Q2P1/3BN3/R3K2b w Q - 0 25", + "8/3qk3/8/8/5B2/4Kb2/8/8 w - - 0 3", + "8/1p4Q1/p2k4/6p1/P3b3/7P/5PP1/6K1 b - - 0 53", + "3k4/5R2/3b1P2/3p4/3P1p2/2p4P/2P3P1/7K b - - 0 3", + "8/pp1b4/5pp1/3k4/2p5/Q1P2P2/5KP1/8 b - - 0 42", + "B7/6p1/7p/2k5/p7/8/2P3P1/2K5 b - - 0 3" + ] + }, + { + name: "The Fork Use the fork, Luke", + list: ["2N5/5k2/8/8/6P1/7K/8/8 b - - 0 2", + "7k/5N1p/p7/nppP2q1/2P5/1P2N3/P6P/7K b - - 0 2", + "7k/5n2/8/4P3/8/8/6PP/5R1K b - - 0 2", + "r1bqkb1r/ppp2ppp/2n5/4p3/4p3/3B1N2/PPPP1PPP/R1BQK2R w KQkq - 0 4", + "8/8/R7/5k2/8/8/1K6/8 b - - 0 2", + "8/5k2/8/8/8/B6P/8/6K1 b - - 0 2", + "7Q/2nk1p1p/6p1/3n4/8/8/5PPP/6K1 b - - 0 2", + "N7/3k3p/6p1/5p2/r7/3P4/PPP2PPP/R5K1 b - - 0 3", + "3r1k2/pp1n2pb/2p2p1N/2P2r2/2QPp2p/P1P1B2P/6P1/1R1R2K1 b - - 0 2", + "8/5pk1/8/4p3/pp2P3/5P2/PP3K2/2n5 w - - 0 4", + "R7/5bN1/4k3/p3P3/8/5K2/8/8 b - - 0 4", + "r5k1/ppp2p1p/6pB/8/3bP2q/2NB2RP/PPP2nP1/R2Q3K w - - 1 5", + "6k1/1q6/p3p3/2P5/P3P2P/2P5/4Q1PK/8 b - - 0 3", + "5Bk1/1q1r2p1/5p1p/8/2P5/6PP/P4P2/4R1K1 w - - 0 34", + "r1bq1rk1/4p1bp/p2p1p2/1PpPn1B1/4PQ2/2N5/PP1N2PP/R3KB1R w KQ - 1 4", + "4b2r/5kP1/4p2n/pp1p3P/2pP1R2/P1P2B1N/2PK1P2/8 b - - 0 3" + ] + }, + { + name: "Discovered Attacks Including discovered checks", + list: [ + "4kR2/6pp/8/4N3/8/8/6PP/6K1 b - - 0 2", + "8/3Nk1pp/8/8/8/8/6PP/5RK1 b - - 0 2", + "B4k2/3r1pp1/3P3p/8/8/5N2/6PP/5RK1 b - - 0 2", + "r2q1bnN/pp3kpp/3p1p2/1Bp3B1/8/2Pp4/PP3PPP/RN1bR1K1 b - - 0 13", + "r1b2rk1/pp1n1p1p/6p1/3p4/3bP3/1PqB3P/P2N2PN/R2Q1R1K w - - 2 17", + "2k5/8/p2P4/2p1b3/8/2P4r/P4R2/1K2R3 b - - 0 43", + "8/2N5/4np2/4pk1p/R6P/P3KP2/1P6/8 b - - 0 2", + "r1bq2rk/1p1p1N2/p1n1pP1p/3n4/8/1N1Bb3/PPP3PP/R4R1K b - - 3 3", + "8/5ppk/p3p2p/1p1b4/3Pp3/1P2b1P1/P6P/2R2K2 w - - 0 4", + "4r3/1R2qk1p/1Q4p1/1Pp5/2P5/6P1/6KP/8 b - - 0 53" + ] + }, + { + name: "Double Check A very powerfull tactic", + list: [ + "", + "r3k3/ppp2pp1/2np4/2B1p3/2B1P1N1/3P2n1/PPP2PP1/RN1Q1RKr w q - 3 14", + "", + "", + "3r2k1/pp6/6p1/2P1q2p/4p3/4BK2/PP2P2r/R1Q1R2B w - - 0 30", + "B6r/2qk1P1p/p5p1/1p2p3/8/2P1B3/P1PR2PP/5RK1 b - - 0 25" + ] + }, + { + name: "Overloaded Pieces They have too much work", + list: [ + "6k1/5pp1/4R1n1/8/8/3B4/5PPP/6K1 b - - 0 1", + "2r1rbk1/3P1pp1/5n2/8/8/3Q3P/2B2PPK/8 b - - 0 1", + "6b1/4k2p/7P/1p1pKBP1/1P1P4/8/5P2/8 b - - 2 4", + "3q3k/pp2R1pp/8/2P5/1P3BQ1/2P5/P4rPp/7K b - - 0 29", + "8/6Q1/p3p1k1/3nB1r1/8/3q3P/PP6/K1R5 b - - 0 36", + "4R3/pb1R1p1k/1pn2qpp/8/4B3/P4NP1/1P3PP1/6K1 b - - 0 26", + "5R2/1p5k/p2pp3/8/7p/8/PP4P1/6K1 b - - 0 4", + "1r3k1r/p4p2/7p/3pP3/8/4BPN1/6P1/2R3K1 b - - 0 3", + "5k2/1pp4p/p1n3p1/8/1P2q3/P3P1Pb/3N3P/6K1 w - - 0 4", + "rk2Q3/5p1p/p1n2p2/P1b5/2p5/2P3P1/5P1P/6K1 b - - 0 32" + ] + }, + { + name: "Zwischenzug In-between moves", + list: [ + "1rbqk2r/pp3ppp/8/3n4/1bpP4/8/PP2BKPP/RN1Q2NR w k - 0 11", + "5rk1/pp1b1ppp/4p3/3pP3/1q3Pn1/3B1N2/P2K2PP/RN3Q2 w - - 0 19", + "B3k3/3b1q2/p2bp3/6Q1/8/4B1P1/PPP4P/6K1 b - - 0 31", + "2r3k1/B5pp/4p3/1Q1p1p2/1p3P2/1P2P3/P1r3PP/6K1 b - - 0 29", + "2B2r1k/1pN1Qpbp/p4pp1/q7/8/8/PP3PPP/3R2K1 b - - 0 25" + ] + }, + { + name: "Interference Interpose a piece to great effect", + list: [ + "4r1k1/p1p1qppp/3b1n2/1r1p4/P2P4/2P5/1P2BPPP/R1B1KN1R w KQ - 0 16", + "1k6/1pp2pp1/p1p5/8/8/2Pp1Pp1/PP2q1P1/R1BK1N2 w - - 4 27", + "1R4k1/p3bppp/4p3/8/3r4/6P1/P3PP1P/5RK1 b - - 0 19", + "3r2k1/pp3p1p/6p1/8/4P3/5P2/PP2nKPP/2B5 w - - 0 26", + "3B2k1/6p1/3b4/1p1p3q/3P4/2P1pNPb/1P3P1P/R5K1 w - - 0 32", + "2r4k/5pR1/7p/5q2/4p2P/1Qn1P3/5P1K/8 w - - 0 38", + "5q2/1b4pk/1p2p1n1/1P1pPp2/P2P1P1p/1N3R1P/1Q4PK/8 b - - 0 47" + ] + }, + { + name: "Greek Gift Study the greek gift scrifice", + list: [ + "rnb2rk1/pppn1pp1/4p3/3pP1B1/1b1P4/2NQ4/PPP2PPP/R3K2R b KQ - 0 4", + "rnb2rk1/pp3p1Q/4p3/3pn1N1/3p4/2NB4/PPP2PP1/R3K2R b KQ - 5 16", + "r3r2k/1b3Qp1/p7/1p1P1R2/1P6/P3q3/6PP/3R3K b - - 4 26", + "r3rbk1/6p1/pn4p1/np2p1B1/3p4/5Q2/PP3PP1/2R1R1K1 b - - 1 25", + "3r1B1Q/bpq2k2/p1b1pp2/2P5/1P6/P7/5PPP/2R2RK1 b - - 0 22", + "r1b2rk1/ppq2pp1/4pn2/2p3NQ/1p1P4/8/PP1N1PPP/R4RK1 w - - 0 16" + ] + }, + { + name: "Deflection Distracting a defender", + list: [ + "5R2/5ppk/4b3/8/8/7P/5PP1/6K1 b - - 0 2", + "8/8/8/1k4Q1/8/8/5K2/8 b - - 0 3", + "4r1k1/1p1Q1p1p/p2p1Pp1/1n1P4/1P6/6P1/P4PKP/4R3 b - - 0 4", + "rnbQ1b1r/pp2pkpp/5n2/8/4P3/2N5/PPP2PPP/R1B1K2R b - - 0 2", + "2b2qk1/7Q/3r2p1/8/8/3r3R/6PP/6K1 b - - 0 2", + "6k1/5p2/3q2pp/8/6P1/5QNP/5P2/4r1K1 w - - 0 3", + "r3r1k1/p4pp1/1p1Q3p/8/P7/7P/1P3PP1/R5K1 b - - 0 2", + "r5rk/6qQ/p1p2B1p/PpP5/1P6/2PB2P1/5PKP/8 b - - 1 3", + "r1bk3r/pppp2p1/1n3p1p/7n/2N4Q/5P2/PPP3PP/R3R1K1 b - - 0 2", + "6k1/2r2pp1/5q1p/Np6/8/1P5Q/3pbPPR/1R4K1 w - - 0 6" + ] + }, + { + name: "Attraction Lure a piece to bad square", + list: [ + "3R1k2/5p1p/8/5N2/8/5p1q/8/6K1 b - - 0 3", + "rnbB1b1r/ppk2ppp/2p5/4q3/4n3/8/PPP2PPP/2KR1BNR b - - 3 11", + "r2B3r/3n2pp/p2bR3/1b1k1P2/1qpPB3/4P1PP/5P1K/8 b - - 1 2", + "", + "", + "8/5pk1/4p3/1p6/6P1/4P3/PR2KP2/7q w - - 0 4", + "", + "6k1/pp4bp/2n2np1/5p2/2P2q2/4KBN1/PP5P/RQ6 w - - 0 24", + "", + "7r/6kp/5Rp1/8/8/8/6PP/2B3K1 b - - 0 1" + ] + }, + { + name: "Underpromotion Promote - but not to a queen!", + list: [ + "5N1n/r6k/5b2/5N1p/8/1q6/PP6/K5R1 b - - 0 1", + "5R2/7k/8/7K/8/8/8/8 b - - 0 1", + "1R1B4/8/k7/8/8/5K2/8/8 b - - 0 1", + "5R2/7k/5K2/8/8/8/8/8 b - - 0 1", + "8/7p/7B/5k1p/7P/5K2/8/8 b - - 0 2", + "8/8/8/8/8/k7/1b1n4/KB6 w - - 4 4", + "B3Q3/1q4pk/5p1p/5P1K/6PP/8/8/8 b - - 0 1", + "5N2/3q3k/8/8/6p1/6Pp/7P/7K b - - 0 3", + "6K1/8/5k2/8/8/8/8/8 b - - 0 2", + "3N4/k7/P7/1K6/8/8/8/8 b - - 0 1" + ] + }, + { + name: "Desperado A piece is lost, but it can still help", + list: [ + "r3kb1r/pp3ppp/2N1pn2/8/3P4/2P5/P4PPP/R1Bb1RK1 w - - 0 3", + "r2B2rk/2p2p2/p2p3p/1p1N4/3bP1n1/PB3P2/1P3P2/R3RK2 b - - 0 2", + "r5k1/ppp1p1rp/8/3P3b/4P3/2N5/PP1nN1P1/2K4R w - - 0 4", + "r5k1/ppN2pPp/6p1/5R2/1p6/1P2n3/P3K1PP/3R4 b - - 2 4", + "4br2/6k1/4pn1p/1p1P4/p2BB3/6P1/PP3P2/6K1 w - - 0 4" + ] + }, + { + name: "Counter Check Respond to a check with a check", + list: [ + "", + "8/6P1/8/8/1q1QK3/8/8/k7 b - - 1 1", + "", + "8/8/8/8/8/1k6/2r5/K7 w - - 0 96", + "8/5Npp/p1P5/P1R3rk/1p4b1/3r2K1/4pP1P/7R w - - 0 34" + ] + }, + { + name: "Undermining Remove the defending piece", + list: [ + "4rrk1/R5bp/3qp1p1/3p3Q/2pB1P2/6PP/1P3PB1/4R1K1 b - - 0 24", + "1b4k1/1p5p/8/1N1p4/P1nBn3/4P3/5q1N/5K1Q w - - 0 33", + "3qRrk1/1p3ppp/8/p4N2/8/P5QP/1P3PP1/6K1 b - - 0 1", + "1q3r1k/r4ppp/5Q2/8/5N2/p6R/PPP5/1K5R b - - 0 1", + "2q1rk2/1p3ppp/p7/2N5/1P6/5RP1/P1Q3KP/8 b - - 2 2" + ] + }, + { + name: "Clearance Get out of the way!", + list: [ + "8/2k5/5Np1/1p4K1/5P2/8/1R6/3r4 b - - 0 58", + "8/k7/p1p5/2P1p3/1P6/P3PB2/3K4/6q1 w - - 0 50", + "1k1r3r/pp1b4/5Ppp/3p4/P2R4/3B2q1/2PB1PPP/R5K1 b - - 0 24", + "4k2r/1p6/2b1p1pp/2N1P2N/5P1Q/pPp5/Pn4PK/4R3 b - - 0 41", + "" + ] + }, + { + name: "7th-Rank Rook Pawn And Passive Rook vs Rook", + list: [ + "8/6k1/8/4K2p/4P2p/5P2/6P1/8 b - - 0 58", + "R7/P6k/r7/8/8/8/1K6/8 w - - 27 15", + "R7/P5k1/6P1/8/8/1K6/8/r7 b - - 22 14", + "8/R7/5k2/8/8/8/6K1/8 b - - 0 6", + "", + "", + "R7/P5k1/5P2/8/8/8/7K/r7 b - - 0 80", + "8/6k1/8/4K2p/4P2p/5P2/6P1/8 b - - 0 58", + "R7/P4k2/6p1/r3P2p/4K2P/6P1/8/8 b - - 1 62" + ] + }, + { + name: "Basic Rook Endgames Lucena and Philidor", + list: [ + "", + "", + "", + "", + "8/8/8/3k4/8/7r/2K5/R7 b - - 9 11", + "", + "", + "8/8/8/8/R5K1/4k3/8/1r6 w - - 5 21", + "8/8/4K3/8/2k5/1R6/1p6/8 w - - 4 8", + "" + ] - } + } ] module.exports = router; \ No newline at end of file diff --git a/middlewareNode/routes/meetings.js b/middlewareNode/src/routes/meetings.js similarity index 100% rename from middlewareNode/routes/meetings.js rename to middlewareNode/src/routes/meetings.js diff --git a/middlewareNode/routes/puzzles.js b/middlewareNode/src/routes/puzzles.js similarity index 100% rename from middlewareNode/routes/puzzles.js rename to middlewareNode/src/routes/puzzles.js diff --git a/middlewareNode/routes/streak.js b/middlewareNode/src/routes/streak.js similarity index 100% rename from middlewareNode/routes/streak.js rename to middlewareNode/src/routes/streak.js diff --git a/middlewareNode/routes/timeTracking.js b/middlewareNode/src/routes/timeTracking.js similarity index 100% rename from middlewareNode/routes/timeTracking.js rename to middlewareNode/src/routes/timeTracking.js diff --git a/middlewareNode/routes/users.js b/middlewareNode/src/routes/users.js similarity index 100% rename from middlewareNode/routes/users.js rename to middlewareNode/src/routes/users.js diff --git a/middlewareNode/scheduler/activitiesScheduler.js b/middlewareNode/src/scheduler/activitiesScheduler.js similarity index 100% rename from middlewareNode/scheduler/activitiesScheduler.js rename to middlewareNode/src/scheduler/activitiesScheduler.js diff --git a/middlewareNode/server.js b/middlewareNode/src/server.js similarity index 100% rename from middlewareNode/server.js rename to middlewareNode/src/server.js diff --git a/middlewareNode/template/changePasswordTemplate.js b/middlewareNode/src/template/changePasswordTemplate.js similarity index 100% rename from middlewareNode/template/changePasswordTemplate.js rename to middlewareNode/src/template/changePasswordTemplate.js diff --git a/middlewareNode/utils/activities.js b/middlewareNode/src/utils/activities.js similarity index 100% rename from middlewareNode/utils/activities.js rename to middlewareNode/src/utils/activities.js diff --git a/middlewareNode/utils/middleware.js b/middlewareNode/src/utils/middleware.js similarity index 100% rename from middlewareNode/utils/middleware.js rename to middlewareNode/src/utils/middleware.js diff --git a/middlewareNode/utils/nodemailer.js b/middlewareNode/src/utils/nodemailer.js similarity index 100% rename from middlewareNode/utils/nodemailer.js rename to middlewareNode/src/utils/nodemailer.js diff --git a/middlewareNode/utils/recordings.js b/middlewareNode/src/utils/recordings.js similarity index 100% rename from middlewareNode/utils/recordings.js rename to middlewareNode/src/utils/recordings.js diff --git a/react-ystemandchess/README.md b/react-ystemandchess/README.md index 58beeacc..6007de33 100644 --- a/react-ystemandchess/README.md +++ b/react-ystemandchess/README.md @@ -1,7 +1,41 @@ -# Getting Started with Create React App +# Y STEM and Chess React Application + +This is the main frontend application for the Y STEM and Chess educational platform. It provides an interactive interface for students and mentors to engage with chess lessons, puzzles, and educational content. This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +## Project Structure + +After recent modularization, the project follows a feature-based architecture: + +``` +src/ +β”œβ”€β”€ components/ # Reusable UI components +β”‚ β”œβ”€β”€ navbar/ # Navigation bar component +β”‚ β”œβ”€β”€ footer/ # Footer component +β”‚ β”œβ”€β”€ chessboard/ # Chess board component +β”‚ └── ui/ # Generic UI components +β”œβ”€β”€ features/ # Feature-based modules +β”‚ β”œβ”€β”€ about-us/ # About Us pages +β”‚ β”œβ”€β”€ auth/ # Authentication (login, signup, password) +β”‚ β”œβ”€β”€ home/ # Landing page +β”‚ β”œβ”€β”€ lessons/ # Chess lessons +β”‚ β”œβ”€β”€ mentor/ # Mentor features +β”‚ β”œβ”€β”€ programs/ # Programs information +β”‚ β”œβ”€β”€ puzzles/ # Chess puzzles +β”‚ └── student/ # Student features +β”œβ”€β”€ core/ # Core infrastructure +β”‚ β”œβ”€β”€ environments/ # Environment configuration +β”‚ β”œβ”€β”€ services/ # API services +β”‚ β”œβ”€β”€ types/ # TypeScript type definitions +β”‚ └── utils/ # Utility functions +β”œβ”€β”€ assets/ # Static assets +β”‚ └── images/ # Image files +β”œβ”€β”€ App.tsx # Main application component +β”œβ”€β”€ AppRoutes.tsx # Route definitions +└── index.tsx # Application entry point +``` + ## Available Scripts In the project directory, you can run: @@ -68,3 +102,67 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/d ### `npm run build` fails to minify This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) + +## Development Guidelines + +### Adding New Features + +When adding new features to the application: + +1. **Create a new feature directory** under `src/features/` with appropriate naming +2. **Use the existing structure** as a template (components, hooks, services) +3. **Update routes** in `src/AppRoutes.tsx` if adding new pages +4. **Follow the established patterns** for imports and file organization + +### Component Organization + +- **Reusable components** β†’ Place in `src/components/` +- **Feature-specific components** β†’ Place within the relevant feature directory +- **Shared utilities** β†’ Place in `src/core/utils/` +- **API services** β†’ Place in `src/core/services/` + +### Import Path Recommendations + +Consider using path aliases in `tsconfig.json` for cleaner imports: + +```json +{ + "compilerOptions": { + "paths": { + "@components/*": ["src/components/*"], + "@features/*": ["src/features/*"], + "@core/*": ["src/core/*"], + "@assets/*": ["src/assets/*"] + } + } +} +``` + +## Technology Stack + +- **React** 18.3 +- **TypeScript** 4.9 +- **React Router** 7 +- **Tailwind CSS** 3 +- **Socket.IO Client** 4 +- **Axios** for API calls +- **Chess.js** for chess logic +- **Framer Motion** for animations + +## Backend Services + +This frontend connects to several backend services: + +- **Middleware Node** - Main API backend (port 8000) +- **Chess Server** - Real-time game server (port 3000) +- **Stockfish Server** - Chess AI engine (port 8080) + +Make sure these services are running for full functionality. + +## Contributing + +1. Follow the established directory structure +2. Write TypeScript with proper typing +3. Include tests for new components +4. Update documentation as needed +5. Follow existing code style and conventions diff --git a/react-ystemandchess/src/App.tsx b/react-ystemandchess/src/App.tsx index 240e44e8..aac50eaa 100644 --- a/react-ystemandchess/src/App.tsx +++ b/react-ystemandchess/src/App.tsx @@ -12,8 +12,8 @@ import { BrowserRouter as Router } from "react-router-dom"; 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 NavBar from "./components/navbar/NavBar"; +import Footer from "./components/footer/Footer"; import AppRoutes from "./AppRoutes"; /** diff --git a/react-ystemandchess/src/AppRoutes.tsx b/react-ystemandchess/src/AppRoutes.tsx index 6c8b6feb..b566c568 100644 --- a/react-ystemandchess/src/AppRoutes.tsx +++ b/react-ystemandchess/src/AppRoutes.tsx @@ -18,41 +18,41 @@ import { Route, Router, Routes } from "react-router-dom"; // Page component imports - organized by category // Home and main pages -import Home from "./Pages/Home/Home"; -import Programs from "./Pages/Programs/Programs"; +import Home from "./features/home/Home"; +import Programs from "./features/programs/Programs"; // About Us section pages -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 Mission from "./Pages/About-Us/Mission/Mission"; -import SponsorsPartners from "./Pages/About-Us/SponsorsPartners/SponsorsPartners"; -import Board from "./Pages/About-Us/Board/Board"; -import Financial from "./Pages/About-Us/Financial/Financial"; -import AboutUs from "./Pages/About-Us/AboutUs/AboutUs"; +import CSBenefitPage from "./features/about-us/benefit-of-cs/CSBenefitPage"; +import ChessBenefitPage from "./features/about-us/benefit-of-chess/ChessBenefitPage"; +import MathTutBenefitPage from "./features/about-us/benefit-of-math-tut/MathTutBenefitPage"; +import MentoringBenefitPage from "./features/about-us/benefit-of-mentoring/MentoringBenefitPage"; +import Mission from "./features/about-us/mission/Mission"; +import SponsorsPartners from "./features/about-us/sponsors-partners/SponsorsPartners"; +import Board from "./features/about-us/board/Board"; +import Financial from "./features/about-us/financial/Financial"; +import AboutUs from "./features/about-us/aboutus/AboutUs"; // Educational content pages -import Lessons from "./Pages/Lessons/Lessons"; -import Puzzles from './Pages/Puzzles/Puzzles'; -import LessonSelection from "./Pages/LessonsSelection/LessonsSelection"; -import LessonOverlay from "./Pages/piece-lessons/lesson-overlay/Lesson-overlay"; +import Lessons from "./features/lessons/lessons-main/Lessons"; +import Puzzles from './features/puzzles/puzzles-page/Puzzles'; +import LessonSelection from "./features/lessons/lessons-selection/LessonsSelection"; +import LessonOverlay from "./features/lessons/piece-lessons/lesson-overlay/Lesson-overlay"; // Authentication and user management pages -import Login from "./Pages/Login/Login"; -import SignUp from "./Pages/SignUp/SignUp"; -import ResetPassword from "./Pages/Reset-Password/reset-password"; -import SetPassword from "./Pages/Set-Password/set-password"; +import Login from "./features/auth/login/Login"; +import SignUp from "./features/auth/signup/SignUp"; +import ResetPassword from "./features/auth/reset-password/Reset-Password/reset-password"; +import SetPassword from "./features/auth/set-password/Set-Password/set-password"; // User profile and dashboard pages -import Student from "./Pages/Student/Student"; -import Mentor from "./Pages/Mentor/Mentor"; -import StudentInventory from "./Pages/Student-Inventory/StudentInventory"; -import NewMentorProfile from "./Pages/NewMentorProfile/NewMentorProfile"; -import NewStudentProfile from "./Pages/NewStudentProfile/NewStudentProfile"; +import Student from "./features/student/student-page/Student"; +import Mentor from "./features/mentor/mentor-page/Mentor"; +import StudentInventory from "./features/student/student-inventory/StudentInventory"; +import NewMentorProfile from "./features/mentor/mentor-profile/NewMentorProfile"; +import NewStudentProfile from "./features/student/student-profile/NewStudentProfile"; // Static assets and default data -import userPortraitImg from "./images/user-portrait-placeholder.svg"; +import userPortraitImg from "./assets/images/user-portrait-placeholder.svg"; /** * Default username for components that require user data diff --git a/react-ystemandchess/src/Pages/LessonsSelection/LessonsStyle.module.scss b/react-ystemandchess/src/Pages/LessonsSelection/LessonsStyle.module.scss deleted file mode 100644 index 87b5d729..00000000 --- a/react-ystemandchess/src/Pages/LessonsSelection/LessonsStyle.module.scss +++ /dev/null @@ -1,207 +0,0 @@ -.wholePage { - height: 100vh; - width: 98vw; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.title { - font-size: 3rem; - font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-weight: 1000; -} - -.header { - font-size: 2.3rem; -} - -.list { - list-style-type: none; - font-size: 1.5rem; -} - -.list li { - margin-top: 0.5rem; -} - - -.itemTemplate { - display: flex; - background-color: whitesmoke; - color: black; - width: 100%; - height: 5vh; - text-align: left; - padding-left: 0.1rem; - align-items: center; - justify-content: left; - padding-top: 1.3rem; - padding-bottom: 1.3rem; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-weight: 900; -} - -.itemTemplate:not(:last-child) { - border-bottom: 0.1rem solid black; -} - -.itemTemplate:hover { - cursor: pointer; - background-color: lightgray; -} - -.subsections-list { - display: flex; - justify-content: left; - flex-direction: column; -} - -.subsection-item { - display: flex; - width: 0vw; - height: 8vh; -} - -.container { - display: flex; - flex-direction: column; - max-height: 50vh; - width: 67vw; - overflow-y: auto; -} - -.enterInfo { - margin-top: 1.5rem; - width: 10vw; - height: 10vh; - border-radius: 1rem; - font-size: clamp(0.5rem, 3vw, 2.5rem); - background-color: green; -} - -.selector { - display: flex; - height: 10vh; - width: 70vw; - row-gap: 1rem; - border-radius: 1rem; - border: 0.3rem solid black; - background-color: #FAF9F6; - align-items: center; - padding-left: 0.3rem; - font-size: clamp(0.5rem, 3vw, 2rem); - justify-content: space-between; - margin-top: 1rem; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-weight: 900; -} - -.selector:hover { - cursor: pointer; -} - -.errorBackground { - position: fixed; - display: flex; - z-index: 2; - justify-content: center; - align-items: center; - height: 130vh; - width: 130vw; - background-color: rgba(0,0,0,0.7); -} - -.errorBox { - display: flex; - position: fixed; - flex-direction: column; - row-gap: 0.3rem; - justify-content: center; - align-items: center; - height: 30vh; - width: 30vw; - background-color: white; - opacity: 1; - border-radius: 1rem; -} - -.errorText { - display: flex; - font-size: clamp(0.8rem, 10vw, 1.5rem); -} - -.errorBox button { - background-color: green; -} - -/* Mobile Responsive Styles */ -@media screen and (max-width: 768px) { - .wholePage { - width: 100vw; - padding: 1rem; - } - - .title { - font-size: 2rem; - text-align: center; - } - - .selector { - width: 90vw; - height: auto; - min-height: 8vh; - padding: 1rem; - font-size: 1rem; - } - - .container { - width: 90vw; - max-height: 40vh; - } - - .enterInfo { - width: 30vw; - height: 8vh; - font-size: 1.2rem; - margin-top: 2rem; - } - - .errorBox { - width: 80vw; - height: auto; - padding: 2rem 1rem; - min-height: 25vh; - } - - .errorText { - font-size: 1.2rem; - text-align: center; - } - - .itemTemplate { - font-size: 0.9rem; - padding: 1rem 0.5rem; - } -} - -@media screen and (max-width: 480px) { - .title { - font-size: 1.5rem; - } - - .selector { - font-size: 0.9rem; - padding: 0.8rem; - } - - .enterInfo { - width: 40vw; - font-size: 1rem; - } - - .errorBox { - width: 90vw; - } -} diff --git a/react-ystemandchess/src/Pages/Login/Login.scss b/react-ystemandchess/src/Pages/Login/Login.scss deleted file mode 100644 index 7d62bcd0..00000000 --- a/react-ystemandchess/src/Pages/Login/Login.scss +++ /dev/null @@ -1,159 +0,0 @@ -#login-h1 { - margin-top: 5rem; -} - -// #loginError-h3 { -// display: none; // Hide the element by default - -// &.show { -// display: block; // Show the element when it has content -// } -// } - -#loginError-h3 { - color: red; -} - - -footer { - margin-top: 5% ; -} -.login-input-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 1.5rem; - - .login-input-field { - width: 100%; - height: 40px; - margin-top: 20px; - position: relative; - width: 20rem; - - label { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - } - - input { - // width: 100%; - // height: 140%; - // border: none; - // border-bottom: 2px solid silver; - // font-size: 1.1rem; - // outline: none; - - outline-style: none; - margin-top: 20px; - padding: 15px 32px; - font-size: 22px; - font-family: Roboto !important; - font-weight: 500; - font-family: normal; - background-color: #fff; - color: #000; - border: 3px solid #d64309; - border-radius: 8px; - width: 75%; - } - - input:focus ~ label, - input:valid ~ label { - transform: translateY(-15px); - border: none; - color: black; - } - - .underline { - position: absolute; - bottom: 0px; - height: 2px; - width: 100%; - } - .underline:before { - position: absolute; - content: ""; - height: 80%; - width: 80%; - background: black; - transform: scale(0); - bottom: -12px; - left: 32px; - } - - input:focus ~ .underline:before, - input:valid ~ .underline:before { - transform: scale(1.01); - transition: transform 0.4s ease; - } - - label { - position: absolute; - bottom: 0px; - left: 35px; - color: grey; - pointer-events: none; - transition: all 0.3s ease; - } - } - #button-login { - border: 2px solid; // Sets the border width and style - border-color: #3a7cca; - width: 15%; - outline-style: none; - padding-left: 2%; - padding-right: 2%; - font-family: Roboto; - font-size: 22px; - font-style: normal; - font-weight: 700; - background-color: #fff; - color: #1a63ab; - border-radius: 33px; - margin-top: 35px; - } - - button:hover { - background-color: #3a7cca; - color: #fff; - } -} - -.additional-options { - display: flex; - gap: 3rem; - justify-content: center; - margin-top: 1.5rem; - - font-weight: bold; - font-size: 1rem; - - a { - position: relative; - text-decoration: none; - color: black; - letter-spacing: 0.4px; - } - a::after { - content: ""; - position: absolute; - background-color: black; - height: 3px; - width: 0; - left: 0; - bottom: -5px; - transition: 0.4s; - } - a:hover::after { - width: 100%; - } -} \ No newline at end of file diff --git a/react-ystemandchess/src/Pages/Mentor/Mentor.scss b/react-ystemandchess/src/Pages/Mentor/Mentor.scss deleted file mode 100644 index 713d1a04..00000000 --- a/react-ystemandchess/src/Pages/Mentor/Mentor.scss +++ /dev/null @@ -1,145 +0,0 @@ -.board-container { - text-align: center; - padding: 20px; - - .flex-container { - display: flex; - align-items: center; - justify-content: space-between; - - .text-content { - flex: 1; - text-align: left; - - h1 { - font-size: 2em; - color: #333; - margin-bottom: 10px; - } - - p { - font-size: 1.2em; - color: #666; - margin-bottom: 20px; - } - - .apply-button { - @extend .apply-button; // Reuse the same button styling - } - } - - .image-content { - flex: 1; - text-align: right; - - img { - max-width: 100%; - height: auto; - } - } - } - - .line-break { - margin-top: 20px; - margin-bottom: 20px; - } - - .mentor-details img { - width: 100%; - height: auto; - margin-bottom: 20px; - } - - .mentor-roles { - display: flex; - justify-content: center; - margin-bottom: 20px; - - img { - width: 45%; - height: auto; - margin: 0 5px; - } - } -} - -.apply-button { - background-color: #ead94c; /* Same color as view-button */ - border: none; - border-radius: 5px; - padding: 10px 20px; - font-size: 1em; - cursor: pointer; - font-weight: bold; - transition: background-color 0.3s, transform 0.2s; - - &:hover { - background-color: #ffe066; /* Brighter yellow on hover */ - transform: translateY(-1px); /* Slight push up on hover */ - } - - &:active { - transform: translateY(1px); /* Slight push down on click */ - } -} -@media screen and (max-width: 768px) { - .board-container { - padding: 10px; - - .flex-container { - flex-direction: column; - align-items: center; - gap: 1.5rem; - - .text-content { - text-align: center; - - h1 { - font-size: 1.5em; - } - - p { - font-size: 1em; - } - - .apply-button { - width: 100%; - } - } - - .image-content { - text-align: center; - - img { - width: 100%; - max-width: 300px; - } - } - } - - .mentor-details img { - margin-bottom: 10px; - } - - .mentor-roles { - flex-direction: column; - align-items: center; - gap: 10px; - - img { - width: 80%; - margin: 0; - } - } - - .line-break { - margin-top: 10px; - margin-bottom: 10px; - } - } - - .apply-button { - font-size: 1.1em; - padding: 12px; - } -} diff --git a/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.scss b/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.scss deleted file mode 100644 index 349d5699..00000000 --- a/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.scss +++ /dev/null @@ -1,29 +0,0 @@ -.topbar-greeting { - color: black; - font-size: 20px; -} - -.no-student-message { - color: black; - font-size: 20px; - margin-top: 20px; - border: 1px solid rgb(209, 86, 86); - width: 60%; - margin: 20px auto; - padding: 20px; - background-color: rgba(248, 46, 46, 0.2); - border-radius: 20px; -} - -.inventory-tab-img { - max-width: 50px; - height: auto; - display: inline-block; - vertical-align: middle; -} - -.no-student-message h1 { - font-size: 24px; - margin-bottom: 10px; - font-weight: bold; -} \ No newline at end of file diff --git a/react-ystemandchess/src/Pages/Programs/Programs.scss b/react-ystemandchess/src/Pages/Programs/Programs.scss deleted file mode 100644 index b59a0ec6..00000000 --- a/react-ystemandchess/src/Pages/Programs/Programs.scss +++ /dev/null @@ -1,158 +0,0 @@ -.hero-section { - display: flex; - justify-content: center; - align-items: center; - gap: 8rem; - margin: 5rem 7rem; - border-radius: 1rem; - background-color: #3f8cdf; - padding: 2rem; - - img { - object-fit: contain; - border-radius: 1rem; - box-shadow: 0.1rem 0.1rem 0.1rem black; - } - - .programs-text { - text-align: left; - color: rgb(255, 250, 250); - max-width: 65%; - display: flex; - flex-direction: column; - gap: 0.4rem; - - p { - font-size: 1.2rem; - } - ul { - margin: 0; - li { - font-size: 1.2rem; - } - } - - h2 { - font-size: 2.5rem; - margin: 0; - } - h4 { - margin: 0; - font-size: 1.5rem; - } - } - - a { - margin-top: 0.4rem; - button { - border: none; - border-radius: 0.5rem; - background: transparent; - width: 12rem; - height: 3rem; - cursor: pointer; - font-size: 1.2rem; - background-color: #eef36a; - } - button:hover { - transition: 0.3s; - transform: scale(1.1); - } - } -} - -.sub-terms { - display: flex; - margin: 5rem 7rem; - gap: 10rem; - - .sub-terms-left { - height: 80%; - } - - .sub-terms-left, - .sub-terms-right { - text-align: left; - background-color: #e2aa03; - border-radius: 1rem; - padding: 2rem; - font-size: 1.4rem; - flex: 1; - color: white; - display: flex; - flex-direction: column; - gap: 0.8rem; - - p { - margin: 0; - } - } - - h3 { - font-size: 2.4rem; - margin: 0; - } - h5 { - font-size: 1.7rem; - margin: 0; - } -} - -/* ----------- Responsive styles ----------- */ -@media (max-width: 768px) { - .hero-section { - flex-direction: column; - margin: 2rem 1rem; - gap: 2rem; - padding: 1.5rem; - - img { - width: 100%; - height: auto; - } - - .programs-text { - max-width: 100%; - text-align: center; - - h2 { - font-size: 2rem; - } - h4 { - font-size: 1.2rem; - } - p { - font-size: 1rem; - } - - ul li { - font-size: 1rem; - text-align: left; - } - - a button { - width: 100%; - } - } - } - - .sub-terms { - flex-direction: column; - margin: 2rem 1rem; - gap: 2rem; - - .sub-terms-left, - .sub-terms-right { - font-size: 1.1rem; - padding: 1.5rem; - } - - h3 { - font-size: 1.8rem; - } - - h5 { - font-size: 1.3rem; - } - } -} diff --git a/react-ystemandchess/src/Pages/Reset-Password/reset-password.component.scss b/react-ystemandchess/src/Pages/Reset-Password/reset-password.component.scss deleted file mode 100644 index c7e03184..00000000 --- a/react-ystemandchess/src/Pages/Reset-Password/reset-password.component.scss +++ /dev/null @@ -1,99 +0,0 @@ -body { - margin: 0; - text-align: center; - padding-top: 75px; - font-family: "Roboto", sans-serif; -} - -.input-container-rp { - display: inline-block; - text-align: center; - width: 100%; // Center container within body - max-width: 500px; // Set a max width for better alignment - margin: 0 auto; -} - -.input-container-rp li { - list-style: none; - margin-bottom: 15px; // Spacing between input fields -} -#email-rp { - outline-style: none; - margin-top: 20px; - padding: 15px 32px; - font-size: 22px; - font-family: Roboto !important; - font-weight: 500; - font-family: normal; - background-color: #fff; - color: #000; - border: 3px solid #d64309; - border-radius: 8px; - width: 75%; -} - - -#username-rp { - outline-style: none; - margin-top: 20px; - padding: 15px 32px; - font-size: 22px; - font-family: Roboto !important; - font-weight: 500; - font-family: normal; - background-color: #fff; - color: #000; - border: 3px solid #d64309; - border-radius: 8px; - width: 75%; -} - -input { - outline: none; - padding: 10px 20px; // Reduced padding to better fit fields - font-size: 18px; - font-weight: 500; - background-color: white; - color: black; - border: 2px solid #d64309; - border-radius: 8px; - width: 100%; // Ensuring input takes full width of container - box-sizing: border-box; // Prevents padding overflow -} - -#button-rp { - border: 2px solid; // Sets the border width and style - border-color: #3a7cca; - width: 35%; - outline-style: none; - padding-left: 2%; - padding-right: 2%; - font-family: Roboto; - font-size: 22px; - font-style: normal; - font-weight: 700; - background-color: #fff; - color: #1a63ab; - border-radius: 33px; -} - -button:hover { - background-color: #3a7cca; - color: #fff; -} - -h4 { - color: #333; - font-size: 20px; - margin-bottom: 20px; // Adjust spacing for header -} - -#err-reset, #h6-reset { - color: #d64309; - font-size: 14px; - margin-top: 5px; -} - -body a { - padding-left: 10px; -} diff --git a/react-ystemandchess/src/Pages/SignUp/SignUp.scss b/react-ystemandchess/src/Pages/SignUp/SignUp.scss deleted file mode 100644 index ba1bec43..00000000 --- a/react-ystemandchess/src/Pages/SignUp/SignUp.scss +++ /dev/null @@ -1,254 +0,0 @@ -h2 { - margin-top: 5rem; -} - -.dropdown-users { - height: 40vh; - width: 37vw; - background-color: white; - overflow-y: auto; - border: 0.1rem solid black; - margin-top: -1rem; -} - - - -.signupForm { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 100vh; /* Centers form vertically */ - width: 100%; - row-gap: 1rem; - - .sign-up-title { - font-size: 3rem; - font-weight: bold; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - } - - .submit-btn { - border: 1px solid black; - color: lightgreen; - width: 30%; - background-color: whitesmoke; - } - - .submit-btn:hover { - background-color: lightgreen; - color: whitesmoke; - } -} - -div.student-form input{ - display: flex; - width: 20rem; - border: 2px solid black; - border-radius: 1rem; - row-gap: 1rem; -} - -.student-form { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - .remove-student { - margin-top: 0.5rem; - border: 1px solid black; - color: white; - font-size: 1rem; - width: 11rem; - background-color: lightcoral; - } - - .remove-student:hover { - color: lightcoral; - background-color: white; - } -} - -input[name="setMentee"] { - width: 41vw; - height: 7vh; - border-radius: 1rem; - border: 0.1rem solid black; -} - -input[name="setMentee"]::placeholder { - opacity: 0.5; - font-size: 1.5rem; - -} - -.student-section { - display: flex; - align-items: center; - justify-content: center; - - .add-student-btn { - border: 1px solid black; - color: lightgreen; - height: 3rem; - font-size: 1rem; - width: 11rem; - background-color: whitesmoke; - margin-top: 0.5rem; - } - - .add-student-btn:hover { - background-color: lightgreen; - color: whitesmoke; - } - -} - -div.form-fields input { - display: flex; - width: 20rem; - height: 3rem; - border: 2px solid black; - border-radius: 1rem; - margin-top: 1rem; -} - -.termsCheckbox { - display: flex; // Use flexbox for alignment - align-items: center; // Center items vertically - justify-content: center; // Center items horizontally (optional) - margin: 15px 0; // Space above and below the terms checkbox - - input[type="checkbox"] { - margin-right: 10px; // Space between checkbox and label - } -} - -.errorMessages { - color: red; - text-align: center; - margin-bottom: 1rem; -} - -.errorMessages { - display: none; // Default to not displaying any error messages - - &.visible { - display: block; // Only show when the .visible class is added - } - - h3 { - color: red; // Styling for error messages - text-align: center; // Center the error message text - } -} - -.input-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2rem; - - .email-detail { - min-width: 41.5rem; - - #email { - min-width: 41.5rem; - } - } - - .terms-container { - font-size: 1.2rem; - display: flex; - gap: 0.5rem; - display: flex; - align-items: center; - - input { - width: 1.2rem; - height: 1.2rem; - } - } - .account-details-container { - display: flex; - gap: 1.5rem; - align-items: center; - - .account-type-container { - display: flex; - gap: 0.5rem; - font-size: 1.2rem; - align-items: center; - position: relative; - - select { - border-radius: 0.5rem; - font-size: 1rem; - padding: 0.5rem; - - option { - padding: 0.5rem; - } - } - } - } - .account-detail { - width: 100%; - height: 40px; - position: relative; - width: 20rem; - display: flex; - justify-content: center; - - input { - width: 100%; - height: 100%; - border: none; - border-bottom: 2px solid silver; - font-size: 1.1rem; - outline: none; - } - - input:focus ~ label, - input:valid ~ label { - transform: translateY(-25px); - border: none; - color: black; - } - - .underline { - position: absolute; - bottom: -2px; - height: 3px; - width: 100%; - } - .underline:before { - position: absolute; - content: ""; - height: 100%; - width: 100%; - background: black; - transform: scale(0); - bottom: -3px; - left: 2px; - } - - input:focus ~ .underline:before, - input:valid ~ .underline:before { - width: 100%; - transform: scale(1.01); - transition: transform 0.4s ease; - } - - label { - position: absolute; - bottom: 5px; - left: 1px; - color: grey; - pointer-events: none; - transition: all 0.3s ease; - } - } -} diff --git a/react-ystemandchess/src/Pages/SignUp/SignUp.tsx b/react-ystemandchess/src/Pages/SignUp/SignUp.tsx deleted file mode 100644 index 1070f963..00000000 --- a/react-ystemandchess/src/Pages/SignUp/SignUp.tsx +++ /dev/null @@ -1,730 +0,0 @@ -import React, { useState } from 'react'; -// import { Cookie } from 'react-router'; // 'react-router' does not export 'Cookie'. This line should probably be removed or corrected. -import './SignUp.scss'; // Imports the stylesheet for this component. -import { environment } from '../../environments/environment'; // Imports environment variables, likely containing API URLs. -// import StudentProfile from '../StudentProfile/Student-Profile'; // Only import if actively used in this component - -// Define the interface for the props of the StudentTemplate component -interface StudentTemplateProps { - studentUsername: string; // The student prop is a string (username) - onClick: (username: string) => void; // onClick takes the username as an argument -} - -// Renamed to start with a capital letter and typed its props -const StudentTemplate: React.FC = ({ studentUsername, onClick }) => { - return ( -
onClick(studentUsername)} role="option"> -
{studentUsername}
-
- ); -}; - -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'. - }); - - // State flags to track the validity of individual input fields. - const [firstNameFlag, setFirstNameFlag] = useState(false); - const [lastNameFlag, setLastNameFlag] = useState(false); - const [emailFlag, setEmailFlag] = useState(false); - const [userNameFlag, setUserNameFlag] = useState(false); - const [passwordFlag, setPasswordFlag] = useState(false); - const [retypeFlag, setRetypeFlag] = useState(false); - const [termsFlag, setTermsFlag] = useState(false); - - // States for the student search dropdown - const [matchingStudents, setMatchingStudents] = useState([]); // Array of strings (usernames) - const [usernameToSearch, setUserToSearch] = useState(''); // Text in the "Find a student" input - const [activeDropdown, setActiveDropdown] = useState(false); // Controls dropdown visibility - const [dropdownLoading, setDropdownLoading] = useState(false); // Loading state for dropdown search - - // State to store any validation errors for the form fields. - const [errors, setErrors] = useState({ - firstName: '', - lastName: '', - email: '', - username: '', - password: '', - retypePassword: '', - terms: '', - general: '', // Added for general signup errors - }); - - // State to manage the parent account specific UI and data. - const [parentAccountFlag, setParentAccountFlag] = useState(false); // Flag to indicate if the selected account type is 'parent'. - const [showStudentForm, setShowStudentForm] = useState(false); // Flag to control the visibility of the student creation form. - const [students, setStudents] = useState([]); // State to store data for student accounts under a parent. - const [assignedMenteeUsername, setAssignedMenteeUsername] = useState(null); // To store the selected mentee's username - - // Handles changes to input fields in the main form. - const handleInputChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - // Updates the formData state with the new value for the changed field. - setFormData((prev) => ({ - ...prev, - [name]: value, - })); - - // Performs specific validation based on the input field that changed. - switch (name) { - case 'firstName': - firstNameVerification(value); - break; - case 'lastName': - lastNameVerification(value); - break; - case 'email': - emailVerification(value); - break; - case 'username': - usernameVerification(value); - break; - case 'password': - passwordVerification(value); - break; - case 'retypedPassword': - retypePasswordVerification(value, formData.password); - break; - default: - break; - } - }; - - // Verifies the format of the first name. - const firstNameVerification = (firstName: string) => { - const isValid = /^[A-Za-z ]{2,15}$/.test(firstName); - setFirstNameFlag(isValid); - setErrors((prev) => ({ - ...prev, - firstName: isValid ? '' : 'Invalid First Name', - })); - return isValid; - }; - - // Verifies the format of the last name. - const lastNameVerification = (lastName: string) => { - const isValid = /^[A-Za-z]{2,15}$/.test(lastName); - setLastNameFlag(isValid); - setErrors((prev) => ({ - ...prev, - lastName: isValid ? '' : 'Invalid Last Name', - })); - return isValid; - }; - - // Verifies the format of the email address. - const emailVerification = (email: string) => { - const isValid = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}/.test(email); - setEmailFlag(isValid); - setErrors((prev) => ({ - ...prev, - email: isValid ? '' : 'Invalid Email', - })); - return isValid; - }; - - // Verifies the format of the username. - const usernameVerification = (username: string) => { - const isValid = /^[a-zA-Z](\S){1,14}$/.test(username); - setUserNameFlag(isValid); - setErrors((prev) => ({ - ...prev, - username: isValid ? '' : 'Invalid Username', - })); - return isValid; - }; - - // Verifies the length of the password. - const passwordVerification = (password: string) => { - const isValid = password.length >= 8; - setPasswordFlag(isValid); - setErrors((prev) => ({ - ...prev, - 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 isValid = retypedPassword === password; - setRetypeFlag(isValid); - setErrors((prev) => ({ - ...prev, - retypePassword: isValid ? '' : 'Passwords do not match', - })); - return isValid; - }; - - // Verifies if the retyped password matches the original password. - const termsCheckChange = (e: React.ChangeEvent) => { - setTermsFlag(e.target.checked) - }; - - // Handles changes to the account type dropdown. - const handleAccountTypeChange = (e: React.ChangeEvent) => { - const isParent = e.target.value === 'parent'; - setParentAccountFlag(isParent); - // Updates the accountType in the form data. - setFormData((prev) => ({ - ...prev, - accountType: e.target.value, - })); - // Reset mentee search/selection when switching account type - setUserToSearch(''); - setAssignedMenteeUsername(null); - setActiveDropdown(false); - setMatchingStudents([]); - }; - - - // Adds a new student form to the UI for parent accounts. - const handleAddStudent = () => { - const newStudent = { - id: Date.now(), - firstName: '', - lastName: '', - username: '', - email: '', - password: '', - retypedPassword: '', - errors: {}, - }; - setStudents((prev: any) => [...prev, newStudent]); - setShowStudentForm(true); // Makes the student form visible. - }; - - // Handles changes to input fields within a student's form. - const handleStudentInputChange = (studentId: any, field: string, value: string) => { - // Performs specific validation based on the input field that changed. - switch (field) { - case 'firstName': - firstNameVerification(value); - break; - case 'lastName': - lastNameVerification(value); - break; - case 'email': - emailVerification(value); - break; - case 'username': - usernameVerification(value); - break; - case 'password': - passwordVerification(value); - break; - case 'retypedPassword': - const student = students.find((s) => s.id === studentId); // ge the student from id - const password = student?.password; // get its password - retypePasswordVerification(value, password); // verify if the retype is the same - break; - default: - break; - } - - // Updates the specific student's data in the students array. - setStudents((prev: any) => - prev.map((student: any) => - student.id === studentId ? { ...student, [field]: value } : student - ) - ); - }; - - // 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)); - // Hides the student form if no students are present. - if (students.length === 1) { // If only one student left, and it's removed, hide the form - setShowStudentForm(false); - } - }; - - // Handles changes in the "Find a student" input and triggers API call - const handleMenteeSearchChange = async (searchText: string) => { - setUserToSearch(searchText); // Update the controlled input's value - setAssignedMenteeUsername(null); // Clear any previously assigned mentee - - if (searchText.trim() === "") { - setActiveDropdown(false); - setMatchingStudents([]); - setDropdownLoading(false); - return; - } - - setActiveDropdown(true); - setDropdownLoading(true); - try { - // Using query parameter for keyword - const response = await fetch( - `${environment.urls.middlewareURL}/user/mentorless?keyword=${searchText}`, - { - method: 'GET', - } - ); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const usernames = await response.json(); - - // Slice the array to limit results to top 10 - const top10Usernames = usernames.slice(0, 10); - setMatchingStudents(top10Usernames); - - setDropdownLoading(false); - } catch (error) { - console.error('Error fetching mentorless students:', error); - setDropdownLoading(false); - setMatchingStudents([]); // Clear matches on error - setErrors((prev) => ({ - ...prev, - general: 'Failed to fetch student list.', - })); - } - }; - - // Handles selecting a mentee from the dropdown - const handleSelectMentee = (selectedUsername: string) => { - setAssignedMenteeUsername(selectedUsername); - setUserToSearch(selectedUsername); // Set the input field to the selected username - setActiveDropdown(false); // Hide the dropdown - setMatchingStudents([]); // Clear the matching students - }; - - - // Handles the submission of the signup form. - const handleSubmit = async () => { - console.log('Submit clicked', formData); - - // check if the terms and conditions are checked - if (!termsFlag) { - setErrors((prev) => ({ ...prev, general: 'Please accept the terms and conditions.' })); - return; - } - - // Checks if all main form fields are valid based on their flags. - const isValid = - firstNameFlag && - lastNameFlag && - emailFlag && - userNameFlag && - passwordFlag && - retypeFlag; - - // If the main form is not valid, prevents submission. - if (!isValid) { - console.log('Form validation failed'); - setErrors((prev) => ({ ...prev, general: 'Please correct the form errors.' })); - return; - } - - // if user is a mentor but has not selected mentee - if (formData.accountType === 'mentor' && !assignedMenteeUsername) { - setErrors((prev) => ({ ...prev, general: 'Please select your mentee' })); - return; - } - - let signupUrl = `${environment.urls.middlewareURL}/user/`; - let signupParams: URLSearchParams; - - if (parentAccountFlag) { - // Maps student data into the required format for the API. - const studentsData = students.map((student: any) => ({ - first: student.firstName, - last: student.lastName, - email: student.email, - username: student.username, - password: student.password, - })); - - // Prepare params for parent signup, stringifying the students array - signupParams = new URLSearchParams({ - first: formData.firstName, - last: formData.lastName, - email: formData.email, - password: formData.password, - username: formData.username, - role: formData.accountType, - students: JSON.stringify(studentsData), // Students array sent as a stringified JSON in query - }); - } else { - // Prepare params for non-parent accounts. - signupParams = new URLSearchParams({ - first: formData.firstName, - last: formData.lastName, - email: formData.email, - password: formData.password, - username: formData.username, - role: formData.accountType, - }); - } - - // Append query parameters to the URL for the signup request - signupUrl = `${signupUrl}?${signupParams.toString()}`; - - console.log('Signup Request URL:', signupUrl); // Log the full URL for debugging - - try { - // --- STEP 1: Perform User Signup (POST request with ALL data in query params) --- - const signupResponse = await fetch(signupUrl, { - method: 'POST', - // IMPORTANT: Removed headers and body here, as data is now in query params. - // This prevents sending an empty body with 'Content-Type: application/json' header. - }); - - console.log('Signup Response status:', signupResponse.status); - - if (!signupResponse.ok) { - let errorContent: any; - try { - errorContent = await signupResponse.json(); - } catch (jsonError) { - errorContent = await signupResponse.text(); - } - - if (typeof errorContent === 'string' && errorContent === 'This username has been taken. Please choose another.') { - setErrors((prev) => ({ - ...prev, - username: 'Username already taken', - general: '', - })); - } else { - const errorMessage = (typeof errorContent === 'object' && errorContent !== null && 'message' in errorContent && typeof errorContent.message === 'string') - ? errorContent.message - : (typeof errorContent === 'string' && errorContent.length > 0) - ? errorContent - : 'Unknown error during signup'; - throw new Error(`HTTP error! status: ${signupResponse.status}, message: ${errorMessage}`); - } - return; - } - - console.log('Signup successful, proceeding to login to get token...'); - - let jwtToken: string | null = null; - - // --- STEP 2: Perform Login to get JWT Token (POST request with query params) --- - try { - // Construct Login URL with username and password as query parameters - const loginUrl = `${environment.urls.middlewareURL}/auth/login?username=${encodeURIComponent(formData.username)}&password=${encodeURIComponent(formData.password)}`; - console.log('Login URL for token acquisition:', loginUrl); // Log the full URL for debugging - - const loginResponse = await fetch(loginUrl, { - method: 'POST', - // No 'Content-Type' header or body needed as per backend expecting query params - }); - - console.log('Login Response status:', loginResponse.status); - - if (!loginResponse.ok) { - const loginErrorData = await loginResponse.json(); - throw new Error(`Login failed after signup: ${loginErrorData.message || 'Unknown login error'}`); - } - - const loginData = await loginResponse.json(); - jwtToken = loginData.token; - console.log('Login successful, JWT token obtained.'); - - } catch (loginError: any) { - console.error('Error during login after signup:', loginError); - setErrors((prev) => ({ - ...prev, - general: `Signup successful, but failed to log in to assign mentee: ${loginError.message || 'Login network error'}`, - })); - window.location.href = '/'; - return; - } - - // --- STEP 3: Conditionally Perform Mentee Assignment (PUT request for mentors with query params) --- - if (formData.accountType === 'mentor' && assignedMenteeUsername && jwtToken) { - // Construct URL with 'mentorship' query parameter - const updateMentorshipUrl = `${environment.urls.middlewareURL}/user/updateMentorship?mentorship=${encodeURIComponent(assignedMenteeUsername)}`; - - try { - const updateMentorshipResponse = await fetch(updateMentorshipUrl, { - method: 'PUT', - headers: { - 'Authorization': `Bearer ${jwtToken}`, // Bearer token is still in headers - }, - // No body needed as per your backend's current PUT route definition expecting query param - }); - - console.log('Update Mentorship Response status:', updateMentorshipResponse.status); - - if (!updateMentorshipResponse.ok) { - const updateErrorData = await updateMentorshipResponse.json(); - console.error('Mentorship update failed:', updateErrorData); - setErrors((prev) => ({ - ...prev, - general: `Signup successful, but mentee assignment failed: ${updateErrorData.message || 'Unknown error'}`, - })); - } else { - const updateSuccessData = await updateMentorshipResponse.json(); - console.log('Mentorship update successful:', updateSuccessData); - } - } catch (updateError: any) { - console.error('Error during mentorship update:', updateError); - setErrors((prev) => ({ - ...prev, - general: `Signup successful, but mentee assignment network error: ${updateError.message || 'Network error'}`, - })); - } - } - - // --- Final Step: Redirect to homepage after all operations --- - window.location.href = '/'; - - } catch (error: any) { - console.error('Signup error:', error); - setErrors((prev) => ({ - ...prev, - general: error.message || 'Signup failed. Please try again.', - })); - } - }; - - return ( -
e.preventDefault()} aria-labelledby="signup-title"> -

Sign up

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

{error}

: null - )} -
- -
- - - - - - - - - - - - -
- -
-

Select Account Type

- - -
- - {!parentAccountFlag && ( - <> {/* Using a React Fragment to wrap without an extra div */} - - handleMenteeSearchChange(e.target.value)} - autoComplete="off" // Prevent browser's default autocomplete - /> - {activeDropdown && ( -
{/* Keep ID for click outside handler */} - {dropdownLoading ? ( -
Loading...
- ) : ( - matchingStudents.length > 0 ? ( - matchingStudents.map((username) => ( - - )) - ) : ( -
No matching students found.
- ) - )} -
- )} - - )} - - {/* 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 && ( - - )} - - {/* Maps through the students array and renders a form for each student. */} - {students.map((student: any) => ( -
- {/* Button to remove a student form. */} - - - - handleStudentInputChange( - student.id, - 'firstName', - e.target.value - ) - } - /> - - handleStudentInputChange( - student.id, - 'lastName', - e.target.value - ) - } - /> - - handleStudentInputChange( - student.id, - 'username', - e.target.value - ) - } - /> - - handleStudentInputChange(student.id, 'email', e.target.value) - } - /> - - handleStudentInputChange( - student.id, - 'password', - e.target.value - ) - } - /> - - handleStudentInputChange( - student.id, - 'retypedPassword', - e.target.value - ) - } - /> -
- ))} -
- )} - -
- - -
- - {/* Submit button for the signup form. */} - -
- ); -}; - -export default Signup; diff --git a/react-ystemandchess/src/images/ActivitiesAssets/bottom_vine.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/bottom_vine.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/bottom_vine.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/bottom_vine.svg diff --git a/react-ystemandchess/src/images/ActivitiesAssets/growth_box.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/growth_box.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/growth_box.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/growth_box.svg diff --git a/react-ystemandchess/src/images/ActivitiesAssets/hanging_vine.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/hanging_vine.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/hanging_vine.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/hanging_vine.svg diff --git a/react-ystemandchess/src/images/ActivitiesAssets/middle_vine.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/middle_vine.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/middle_vine.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/middle_vine.svg diff --git a/react-ystemandchess/src/images/ActivitiesAssets/short_bottom_vine.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/short_bottom_vine.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/short_bottom_vine.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/short_bottom_vine.svg diff --git a/react-ystemandchess/src/images/ActivitiesAssets/stemmy.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/stemmy.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/stemmy.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/stemmy.svg diff --git a/react-ystemandchess/src/images/ActivitiesAssets/top_vine.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/top_vine.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/top_vine.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/top_vine.svg diff --git a/react-ystemandchess/src/images/ActivitiesAssets/topic_bag.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/topic_bag.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/topic_bag.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/topic_bag.svg diff --git a/react-ystemandchess/src/images/ActivitiesAssets/vine_background.png b/react-ystemandchess/src/assets/images/ActivitiesAssets/vine_background.png similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/vine_background.png rename to react-ystemandchess/src/assets/images/ActivitiesAssets/vine_background.png diff --git a/react-ystemandchess/src/images/ActivitiesAssets/water_meter.svg b/react-ystemandchess/src/assets/images/ActivitiesAssets/water_meter.svg similarity index 100% rename from react-ystemandchess/src/images/ActivitiesAssets/water_meter.svg rename to react-ystemandchess/src/assets/images/ActivitiesAssets/water_meter.svg diff --git a/react-ystemandchess/src/images/LogoLineBreak.png b/react-ystemandchess/src/assets/images/LogoLineBreak.png similarity index 100% rename from react-ystemandchess/src/images/LogoLineBreak.png rename to react-ystemandchess/src/assets/images/LogoLineBreak.png diff --git a/react-ystemandchess/src/images/StreakProgressAssets/Calendar.png b/react-ystemandchess/src/assets/images/StreakProgressAssets/Calendar.png similarity index 100% rename from react-ystemandchess/src/images/StreakProgressAssets/Calendar.png rename to react-ystemandchess/src/assets/images/StreakProgressAssets/Calendar.png diff --git a/react-ystemandchess/src/images/StreakProgressAssets/polygon.svg b/react-ystemandchess/src/assets/images/StreakProgressAssets/polygon.svg similarity index 100% rename from react-ystemandchess/src/images/StreakProgressAssets/polygon.svg rename to react-ystemandchess/src/assets/images/StreakProgressAssets/polygon.svg diff --git a/react-ystemandchess/src/images/StreakProgressAssets/polygon_2.svg b/react-ystemandchess/src/assets/images/StreakProgressAssets/polygon_2.svg similarity index 100% rename from react-ystemandchess/src/images/StreakProgressAssets/polygon_2.svg rename to react-ystemandchess/src/assets/images/StreakProgressAssets/polygon_2.svg diff --git a/react-ystemandchess/src/images/StreakProgressAssets/stemette.svg b/react-ystemandchess/src/assets/images/StreakProgressAssets/stemette.svg similarity index 100% rename from react-ystemandchess/src/images/StreakProgressAssets/stemette.svg rename to react-ystemandchess/src/assets/images/StreakProgressAssets/stemette.svg diff --git a/react-ystemandchess/src/images/StreakProgressAssets/stemmy.svg b/react-ystemandchess/src/assets/images/StreakProgressAssets/stemmy.svg similarity index 100% rename from react-ystemandchess/src/images/StreakProgressAssets/stemmy.svg rename to react-ystemandchess/src/assets/images/StreakProgressAssets/stemmy.svg diff --git a/react-ystemandchess/src/images/StreakProgressAssets/streak_progress_clock.png b/react-ystemandchess/src/assets/images/StreakProgressAssets/streak_progress_clock.png similarity index 100% rename from react-ystemandchess/src/images/StreakProgressAssets/streak_progress_clock.png rename to react-ystemandchess/src/assets/images/StreakProgressAssets/streak_progress_clock.png diff --git a/react-ystemandchess/src/images/StreakProgressAssets/streak_progress_clock.svg b/react-ystemandchess/src/assets/images/StreakProgressAssets/streak_progress_clock.svg similarity index 100% rename from react-ystemandchess/src/images/StreakProgressAssets/streak_progress_clock.svg rename to react-ystemandchess/src/assets/images/StreakProgressAssets/streak_progress_clock.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/activity-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/activity-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/activity-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/activity-icon.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/backpack-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/backpack-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/backpack-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/backpack-icon.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/chess-lessons-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/chess-lessons-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/chess-lessons-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/chess-lessons-icon.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/games-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/games-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/games-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/games-icon.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/learning-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/learning-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/learning-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/learning-icon.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/mentor-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/mentor-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/mentor-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/mentor-icon.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/play-computer-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/play-computer-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/play-computer-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/play-computer-icon.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/puzzles-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/puzzles-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/puzzles-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/puzzles-icon.svg diff --git a/react-ystemandchess/src/images/StudentInventoryIcons/recordings-icon.svg b/react-ystemandchess/src/assets/images/StudentInventoryIcons/recordings-icon.svg similarity index 100% rename from react-ystemandchess/src/images/StudentInventoryIcons/recordings-icon.svg rename to react-ystemandchess/src/assets/images/StudentInventoryIcons/recordings-icon.svg diff --git a/react-ystemandchess/src/images/Trees-Group.png b/react-ystemandchess/src/assets/images/Trees-Group.png similarity index 100% rename from react-ystemandchess/src/images/Trees-Group.png rename to react-ystemandchess/src/assets/images/Trees-Group.png diff --git a/react-ystemandchess/src/images/aboutUs/02.png b/react-ystemandchess/src/assets/images/aboutUs/02.png similarity index 100% rename from react-ystemandchess/src/images/aboutUs/02.png rename to react-ystemandchess/src/assets/images/aboutUs/02.png diff --git a/react-ystemandchess/src/images/aboutUs/09.png b/react-ystemandchess/src/assets/images/aboutUs/09.png similarity index 100% rename from react-ystemandchess/src/images/aboutUs/09.png rename to react-ystemandchess/src/assets/images/aboutUs/09.png diff --git a/react-ystemandchess/src/images/aboutUs/40.png b/react-ystemandchess/src/assets/images/aboutUs/40.png similarity index 100% rename from react-ystemandchess/src/images/aboutUs/40.png rename to react-ystemandchess/src/assets/images/aboutUs/40.png diff --git a/react-ystemandchess/src/images/aboutUs/about-us.png b/react-ystemandchess/src/assets/images/aboutUs/about-us.png similarity index 100% rename from react-ystemandchess/src/images/aboutUs/about-us.png rename to react-ystemandchess/src/assets/images/aboutUs/about-us.png diff --git a/react-ystemandchess/src/images/aboutUs/divide_icon.png b/react-ystemandchess/src/assets/images/aboutUs/divide_icon.png similarity index 100% rename from react-ystemandchess/src/images/aboutUs/divide_icon.png rename to react-ystemandchess/src/assets/images/aboutUs/divide_icon.png diff --git a/react-ystemandchess/src/images/aboutUs/student.png b/react-ystemandchess/src/assets/images/aboutUs/student.png similarity index 100% rename from react-ystemandchess/src/images/aboutUs/student.png rename to react-ystemandchess/src/assets/images/aboutUs/student.png diff --git a/react-ystemandchess/src/images/book-howtostart.png b/react-ystemandchess/src/assets/images/book-howtostart.png similarity index 100% rename from react-ystemandchess/src/images/book-howtostart.png rename to react-ystemandchess/src/assets/images/book-howtostart.png diff --git a/react-ystemandchess/src/images/book-thezerodollar.png b/react-ystemandchess/src/assets/images/book-thezerodollar.png similarity index 100% rename from react-ystemandchess/src/images/book-thezerodollar.png rename to react-ystemandchess/src/assets/images/book-thezerodollar.png diff --git a/react-ystemandchess/src/images/buy-now.png b/react-ystemandchess/src/assets/images/buy-now.png similarity index 100% rename from react-ystemandchess/src/images/buy-now.png rename to react-ystemandchess/src/assets/images/buy-now.png diff --git a/react-ystemandchess/src/images/camera.svg b/react-ystemandchess/src/assets/images/camera.svg similarity index 100% rename from react-ystemandchess/src/images/camera.svg rename to react-ystemandchess/src/assets/images/camera.svg diff --git a/react-ystemandchess/src/images/chess-piece-pattern.png b/react-ystemandchess/src/assets/images/chess-piece-pattern.png similarity index 100% rename from react-ystemandchess/src/images/chess-piece-pattern.png rename to react-ystemandchess/src/assets/images/chess-piece-pattern.png diff --git a/react-ystemandchess/src/images/chess-piece-pattern.svg b/react-ystemandchess/src/assets/images/chess-piece-pattern.svg similarity index 100% rename from react-ystemandchess/src/images/chess-piece-pattern.svg rename to react-ystemandchess/src/assets/images/chess-piece-pattern.svg diff --git a/react-ystemandchess/src/images/chessGroup.png b/react-ystemandchess/src/assets/images/chessGroup.png similarity index 100% rename from react-ystemandchess/src/images/chessGroup.png rename to react-ystemandchess/src/assets/images/chessGroup.png diff --git a/react-ystemandchess/src/Pages/Lessons/chess_background.png b/react-ystemandchess/src/assets/images/chess_background.png similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/chess_background.png rename to react-ystemandchess/src/assets/images/chess_background.png diff --git a/react-ystemandchess/src/images/difference.png b/react-ystemandchess/src/assets/images/difference.png similarity index 100% rename from react-ystemandchess/src/images/difference.png rename to react-ystemandchess/src/assets/images/difference.png diff --git a/react-ystemandchess/src/images/donate.png b/react-ystemandchess/src/assets/images/donate.png similarity index 100% rename from react-ystemandchess/src/images/donate.png rename to react-ystemandchess/src/assets/images/donate.png diff --git a/react-ystemandchess/src/images/facebookIcon.svg b/react-ystemandchess/src/assets/images/facebookIcon.svg similarity index 100% rename from react-ystemandchess/src/images/facebookIcon.svg rename to react-ystemandchess/src/assets/images/facebookIcon.svg diff --git a/react-ystemandchess/src/images/founder-story.png b/react-ystemandchess/src/assets/images/founder-story.png similarity index 100% rename from react-ystemandchess/src/images/founder-story.png rename to react-ystemandchess/src/assets/images/founder-story.png diff --git a/react-ystemandchess/src/images/free-lunch.png b/react-ystemandchess/src/assets/images/free-lunch.png similarity index 100% rename from react-ystemandchess/src/images/free-lunch.png rename to react-ystemandchess/src/assets/images/free-lunch.png diff --git a/react-ystemandchess/src/images/full_logo.png b/react-ystemandchess/src/assets/images/full_logo.png similarity index 100% rename from react-ystemandchess/src/images/full_logo.png rename to react-ystemandchess/src/assets/images/full_logo.png diff --git a/react-ystemandchess/src/images/gem-regular.svg b/react-ystemandchess/src/assets/images/gem-regular.svg similarity index 100% rename from react-ystemandchess/src/images/gem-regular.svg rename to react-ystemandchess/src/assets/images/gem-regular.svg diff --git a/react-ystemandchess/src/images/googleIcon.svg b/react-ystemandchess/src/assets/images/googleIcon.svg similarity index 100% rename from react-ystemandchess/src/images/googleIcon.svg rename to react-ystemandchess/src/assets/images/googleIcon.svg diff --git a/react-ystemandchess/src/images/heart-regular.svg b/react-ystemandchess/src/assets/images/heart-regular.svg similarity index 100% rename from react-ystemandchess/src/images/heart-regular.svg rename to react-ystemandchess/src/assets/images/heart-regular.svg diff --git a/react-ystemandchess/src/Pages/Lessons/icon_back.svg b/react-ystemandchess/src/assets/images/icons/icon_back.svg similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/icon_back.svg rename to react-ystemandchess/src/assets/images/icons/icon_back.svg diff --git a/react-ystemandchess/src/Pages/Lessons/icon_back_inactive.svg b/react-ystemandchess/src/assets/images/icons/icon_back_inactive.svg similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/icon_back_inactive.svg rename to react-ystemandchess/src/assets/images/icons/icon_back_inactive.svg diff --git a/react-ystemandchess/src/Pages/Lessons/icon_next.svg b/react-ystemandchess/src/assets/images/icons/icon_next.svg similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/icon_next.svg rename to react-ystemandchess/src/assets/images/icons/icon_next.svg diff --git a/react-ystemandchess/src/Pages/Lessons/icon_next_inactive.svg b/react-ystemandchess/src/assets/images/icons/icon_next_inactive.svg similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/icon_next_inactive.svg rename to react-ystemandchess/src/assets/images/icons/icon_next_inactive.svg diff --git a/react-ystemandchess/src/Pages/Lessons/icon_redo.svg b/react-ystemandchess/src/assets/images/icons/icon_redo.svg similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/icon_redo.svg rename to react-ystemandchess/src/assets/images/icons/icon_redo.svg diff --git a/react-ystemandchess/src/images/imageImporter.tsx b/react-ystemandchess/src/assets/images/imageImporter.tsx similarity index 99% rename from react-ystemandchess/src/images/imageImporter.tsx rename to react-ystemandchess/src/assets/images/imageImporter.tsx index 907ce4d2..114accfb 100644 --- a/react-ystemandchess/src/images/imageImporter.tsx +++ b/react-ystemandchess/src/assets/images/imageImporter.tsx @@ -82,7 +82,7 @@ type ImageKey = * * Usage: * ```typescript - * import images from './images/imageImporter'; + * import images from "./assets/images/imageImporter'; * * // Type-safe image access * const logoSrc = images.LogoLineBr; diff --git a/react-ystemandchess/src/images/instagramIcon.svg b/react-ystemandchess/src/assets/images/instagramIcon.svg similarity index 100% rename from react-ystemandchess/src/images/instagramIcon.svg rename to react-ystemandchess/src/assets/images/instagramIcon.svg diff --git a/react-ystemandchess/src/images/kidsCoding.png b/react-ystemandchess/src/assets/images/kidsCoding.png similarity index 100% rename from react-ystemandchess/src/images/kidsCoding.png rename to react-ystemandchess/src/assets/images/kidsCoding.png diff --git a/react-ystemandchess/src/images/large_info.png b/react-ystemandchess/src/assets/images/large_info.png similarity index 100% rename from react-ystemandchess/src/images/large_info.png rename to react-ystemandchess/src/assets/images/large_info.png diff --git a/react-ystemandchess/src/images/line-graph-placeholder.png b/react-ystemandchess/src/assets/images/line-graph-placeholder.png similarity index 100% rename from react-ystemandchess/src/images/line-graph-placeholder.png rename to react-ystemandchess/src/assets/images/line-graph-placeholder.png diff --git a/react-ystemandchess/src/images/mathArticle/Footer.png b/react-ystemandchess/src/assets/images/mathArticle/Footer.png similarity index 100% rename from react-ystemandchess/src/images/mathArticle/Footer.png rename to react-ystemandchess/src/assets/images/mathArticle/Footer.png diff --git a/react-ystemandchess/src/images/mathArticle/Group 67.png b/react-ystemandchess/src/assets/images/mathArticle/Group 67.png similarity index 100% rename from react-ystemandchess/src/images/mathArticle/Group 67.png rename to react-ystemandchess/src/assets/images/mathArticle/Group 67.png diff --git a/react-ystemandchess/src/images/mathArticle/Junechamp 2.png b/react-ystemandchess/src/assets/images/mathArticle/Junechamp 2.png similarity index 100% rename from react-ystemandchess/src/images/mathArticle/Junechamp 2.png rename to react-ystemandchess/src/assets/images/mathArticle/Junechamp 2.png diff --git a/react-ystemandchess/src/Pages/Lessons/Rectangle 313.png b/react-ystemandchess/src/assets/images/mathArticle/Rectangle 313.png similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/Rectangle 313.png rename to react-ystemandchess/src/assets/images/mathArticle/Rectangle 313.png diff --git a/react-ystemandchess/src/images/mathArticle/Signup.png b/react-ystemandchess/src/assets/images/mathArticle/Signup.png similarity index 100% rename from react-ystemandchess/src/images/mathArticle/Signup.png rename to react-ystemandchess/src/assets/images/mathArticle/Signup.png diff --git a/react-ystemandchess/src/images/mathArticle/computer.png b/react-ystemandchess/src/assets/images/mathArticle/computer.png similarity index 100% rename from react-ystemandchess/src/images/mathArticle/computer.png rename to react-ystemandchess/src/assets/images/mathArticle/computer.png diff --git a/react-ystemandchess/src/images/mathArticle/logo.png b/react-ystemandchess/src/assets/images/mathArticle/logo.png similarity index 100% rename from react-ystemandchess/src/images/mathArticle/logo.png rename to react-ystemandchess/src/assets/images/mathArticle/logo.png diff --git a/react-ystemandchess/src/images/mission-image.png b/react-ystemandchess/src/assets/images/mission-image.png similarity index 100% rename from react-ystemandchess/src/images/mission-image.png rename to react-ystemandchess/src/assets/images/mission-image.png diff --git a/react-ystemandchess/src/images/partners/Rotary.png b/react-ystemandchess/src/assets/images/partners/Rotary.png similarity index 100% rename from react-ystemandchess/src/images/partners/Rotary.png rename to react-ystemandchess/src/assets/images/partners/Rotary.png diff --git a/react-ystemandchess/src/images/partners/boiseDistrict.png b/react-ystemandchess/src/assets/images/partners/boiseDistrict.png similarity index 100% rename from react-ystemandchess/src/images/partners/boiseDistrict.png rename to react-ystemandchess/src/assets/images/partners/boiseDistrict.png diff --git a/react-ystemandchess/src/images/partners/boiseRescue.png b/react-ystemandchess/src/assets/images/partners/boiseRescue.png similarity index 100% rename from react-ystemandchess/src/images/partners/boiseRescue.png rename to react-ystemandchess/src/assets/images/partners/boiseRescue.png diff --git a/react-ystemandchess/src/images/partners/boysAndGirls.png b/react-ystemandchess/src/assets/images/partners/boysAndGirls.png similarity index 100% rename from react-ystemandchess/src/images/partners/boysAndGirls.png rename to react-ystemandchess/src/assets/images/partners/boysAndGirls.png diff --git a/react-ystemandchess/src/images/partners/possible.png b/react-ystemandchess/src/assets/images/partners/possible.png similarity index 100% rename from react-ystemandchess/src/images/partners/possible.png rename to react-ystemandchess/src/assets/images/partners/possible.png diff --git a/react-ystemandchess/src/images/premium.png b/react-ystemandchess/src/assets/images/premium.png similarity index 100% rename from react-ystemandchess/src/images/premium.png rename to react-ystemandchess/src/assets/images/premium.png diff --git a/react-ystemandchess/src/images/sponsors/PH.svg b/react-ystemandchess/src/assets/images/sponsors/PH.svg similarity index 100% rename from react-ystemandchess/src/images/sponsors/PH.svg rename to react-ystemandchess/src/assets/images/sponsors/PH.svg diff --git a/react-ystemandchess/src/images/sponsors/idahoCentral.png b/react-ystemandchess/src/assets/images/sponsors/idahoCentral.png similarity index 100% rename from react-ystemandchess/src/images/sponsors/idahoCentral.png rename to react-ystemandchess/src/assets/images/sponsors/idahoCentral.png diff --git a/react-ystemandchess/src/images/sponsors/kount.png b/react-ystemandchess/src/assets/images/sponsors/kount.png similarity index 100% rename from react-ystemandchess/src/images/sponsors/kount.png rename to react-ystemandchess/src/assets/images/sponsors/kount.png diff --git a/react-ystemandchess/src/images/sponsors/ventive.png b/react-ystemandchess/src/assets/images/sponsors/ventive.png similarity index 100% rename from react-ystemandchess/src/images/sponsors/ventive.png rename to react-ystemandchess/src/assets/images/sponsors/ventive.png diff --git a/react-ystemandchess/src/images/student/Leaderboard_User_avatar_1.png b/react-ystemandchess/src/assets/images/student/Leaderboard_User_avatar_1.png similarity index 100% rename from react-ystemandchess/src/images/student/Leaderboard_User_avatar_1.png rename to react-ystemandchess/src/assets/images/student/Leaderboard_User_avatar_1.png diff --git a/react-ystemandchess/src/images/student/Leaderboard_User_avatar_2.png b/react-ystemandchess/src/assets/images/student/Leaderboard_User_avatar_2.png similarity index 100% rename from react-ystemandchess/src/images/student/Leaderboard_User_avatar_2.png rename to react-ystemandchess/src/assets/images/student/Leaderboard_User_avatar_2.png diff --git a/react-ystemandchess/src/images/student/Leaderboard_User_avatar_3.png b/react-ystemandchess/src/assets/images/student/Leaderboard_User_avatar_3.png similarity index 100% rename from react-ystemandchess/src/images/student/Leaderboard_User_avatar_3.png rename to react-ystemandchess/src/assets/images/student/Leaderboard_User_avatar_3.png diff --git a/react-ystemandchess/src/images/student/Leaderboard_rank_1.svg b/react-ystemandchess/src/assets/images/student/Leaderboard_rank_1.svg similarity index 100% rename from react-ystemandchess/src/images/student/Leaderboard_rank_1.svg rename to react-ystemandchess/src/assets/images/student/Leaderboard_rank_1.svg diff --git a/react-ystemandchess/src/images/student/Leaderboard_rank_2.svg b/react-ystemandchess/src/assets/images/student/Leaderboard_rank_2.svg similarity index 100% rename from react-ystemandchess/src/images/student/Leaderboard_rank_2.svg rename to react-ystemandchess/src/assets/images/student/Leaderboard_rank_2.svg diff --git a/react-ystemandchess/src/images/student/Leaderboard_rank_3.svg b/react-ystemandchess/src/assets/images/student/Leaderboard_rank_3.svg similarity index 100% rename from react-ystemandchess/src/images/student/Leaderboard_rank_3.svg rename to react-ystemandchess/src/assets/images/student/Leaderboard_rank_3.svg diff --git a/react-ystemandchess/src/images/student/STEMy_Mascot.png b/react-ystemandchess/src/assets/images/student/STEMy_Mascot.png similarity index 100% rename from react-ystemandchess/src/images/student/STEMy_Mascot.png rename to react-ystemandchess/src/assets/images/student/STEMy_Mascot.png diff --git a/react-ystemandchess/src/images/student/activities_button.svg b/react-ystemandchess/src/assets/images/student/activities_button.svg similarity index 100% rename from react-ystemandchess/src/images/student/activities_button.svg rename to react-ystemandchess/src/assets/images/student/activities_button.svg diff --git a/react-ystemandchess/src/images/student/activity_tab.png b/react-ystemandchess/src/assets/images/student/activity_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/activity_tab.png rename to react-ystemandchess/src/assets/images/student/activity_tab.png diff --git a/react-ystemandchess/src/images/student/badges_button.svg b/react-ystemandchess/src/assets/images/student/badges_button.svg similarity index 100% rename from react-ystemandchess/src/images/student/badges_button.svg rename to react-ystemandchess/src/assets/images/student/badges_button.svg diff --git a/react-ystemandchess/src/images/student/bg-image.png b/react-ystemandchess/src/assets/images/student/bg-image.png similarity index 100% rename from react-ystemandchess/src/images/student/bg-image.png rename to react-ystemandchess/src/assets/images/student/bg-image.png diff --git a/react-ystemandchess/src/images/student/chess_tab.png b/react-ystemandchess/src/assets/images/student/chess_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/chess_tab.png rename to react-ystemandchess/src/assets/images/student/chess_tab.png diff --git a/react-ystemandchess/src/images/student/chess_tab1.png b/react-ystemandchess/src/assets/images/student/chess_tab1.png similarity index 100% rename from react-ystemandchess/src/images/student/chess_tab1.png rename to react-ystemandchess/src/assets/images/student/chess_tab1.png diff --git a/react-ystemandchess/src/images/student/games_tab.png b/react-ystemandchess/src/assets/images/student/games_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/games_tab.png rename to react-ystemandchess/src/assets/images/student/games_tab.png diff --git a/react-ystemandchess/src/images/student/games_tab1.png b/react-ystemandchess/src/assets/images/student/games_tab1.png similarity index 100% rename from react-ystemandchess/src/images/student/games_tab1.png rename to react-ystemandchess/src/assets/images/student/games_tab1.png diff --git a/react-ystemandchess/src/images/student/inventory_tab.png b/react-ystemandchess/src/assets/images/student/inventory_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/inventory_tab.png rename to react-ystemandchess/src/assets/images/student/inventory_tab.png diff --git a/react-ystemandchess/src/images/student/leaderboard_button.svg b/react-ystemandchess/src/assets/images/student/leaderboard_button.svg similarity index 100% rename from react-ystemandchess/src/images/student/leaderboard_button.svg rename to react-ystemandchess/src/assets/images/student/leaderboard_button.svg diff --git a/react-ystemandchess/src/images/student/leaderboard_sidebar_icon.svg b/react-ystemandchess/src/assets/images/student/leaderboard_sidebar_icon.svg similarity index 100% rename from react-ystemandchess/src/images/student/leaderboard_sidebar_icon.svg rename to react-ystemandchess/src/assets/images/student/leaderboard_sidebar_icon.svg diff --git a/react-ystemandchess/src/images/student/math_tab.png b/react-ystemandchess/src/assets/images/student/math_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/math_tab.png rename to react-ystemandchess/src/assets/images/student/math_tab.png diff --git a/react-ystemandchess/src/images/student/mento_tab.png b/react-ystemandchess/src/assets/images/student/mento_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/mento_tab.png rename to react-ystemandchess/src/assets/images/student/mento_tab.png diff --git a/react-ystemandchess/src/images/student/mento_tab1.png b/react-ystemandchess/src/assets/images/student/mento_tab1.png similarity index 100% rename from react-ystemandchess/src/images/student/mento_tab1.png rename to react-ystemandchess/src/assets/images/student/mento_tab1.png diff --git a/react-ystemandchess/src/images/student/play_tab.png b/react-ystemandchess/src/assets/images/student/play_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/play_tab.png rename to react-ystemandchess/src/assets/images/student/play_tab.png diff --git a/react-ystemandchess/src/images/student/play_tab1.png b/react-ystemandchess/src/assets/images/student/play_tab1.png similarity index 100% rename from react-ystemandchess/src/images/student/play_tab1.png rename to react-ystemandchess/src/assets/images/student/play_tab1.png diff --git a/react-ystemandchess/src/images/student/prodev_tab.png b/react-ystemandchess/src/assets/images/student/prodev_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/prodev_tab.png rename to react-ystemandchess/src/assets/images/student/prodev_tab.png diff --git a/react-ystemandchess/src/images/student/prodev_tab1.png b/react-ystemandchess/src/assets/images/student/prodev_tab1.png similarity index 100% rename from react-ystemandchess/src/images/student/prodev_tab1.png rename to react-ystemandchess/src/assets/images/student/prodev_tab1.png diff --git a/react-ystemandchess/src/images/student/profilePic 2.png b/react-ystemandchess/src/assets/images/student/profilePic 2.png similarity index 100% rename from react-ystemandchess/src/images/student/profilePic 2.png rename to react-ystemandchess/src/assets/images/student/profilePic 2.png diff --git a/react-ystemandchess/src/images/student/profilePic.png b/react-ystemandchess/src/assets/images/student/profilePic.png similarity index 100% rename from react-ystemandchess/src/images/student/profilePic.png rename to react-ystemandchess/src/assets/images/student/profilePic.png diff --git a/react-ystemandchess/src/images/student/profile_button.svg b/react-ystemandchess/src/assets/images/student/profile_button.svg similarity index 100% rename from react-ystemandchess/src/images/student/profile_button.svg rename to react-ystemandchess/src/assets/images/student/profile_button.svg diff --git a/react-ystemandchess/src/images/student/pupil.svg b/react-ystemandchess/src/assets/images/student/pupil.svg similarity index 100% rename from react-ystemandchess/src/images/student/pupil.svg rename to react-ystemandchess/src/assets/images/student/pupil.svg diff --git a/react-ystemandchess/src/images/student/puzzles_tab.png b/react-ystemandchess/src/assets/images/student/puzzles_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/puzzles_tab.png rename to react-ystemandchess/src/assets/images/student/puzzles_tab.png diff --git a/react-ystemandchess/src/images/student/puzzles_tab1.png b/react-ystemandchess/src/assets/images/student/puzzles_tab1.png similarity index 100% rename from react-ystemandchess/src/images/student/puzzles_tab1.png rename to react-ystemandchess/src/assets/images/student/puzzles_tab1.png diff --git a/react-ystemandchess/src/images/student/recordings_tab.png b/react-ystemandchess/src/assets/images/student/recordings_tab.png similarity index 100% rename from react-ystemandchess/src/images/student/recordings_tab.png rename to react-ystemandchess/src/assets/images/student/recordings_tab.png diff --git a/react-ystemandchess/src/images/student/recordings_tab1.png b/react-ystemandchess/src/assets/images/student/recordings_tab1.png similarity index 100% rename from react-ystemandchess/src/images/student/recordings_tab1.png rename to react-ystemandchess/src/assets/images/student/recordings_tab1.png diff --git a/react-ystemandchess/src/images/student/sponsor_1.png b/react-ystemandchess/src/assets/images/student/sponsor_1.png similarity index 100% rename from react-ystemandchess/src/images/student/sponsor_1.png rename to react-ystemandchess/src/assets/images/student/sponsor_1.png diff --git a/react-ystemandchess/src/images/student/sponsor_2.png b/react-ystemandchess/src/assets/images/student/sponsor_2.png similarity index 100% rename from react-ystemandchess/src/images/student/sponsor_2.png rename to react-ystemandchess/src/assets/images/student/sponsor_2.png diff --git a/react-ystemandchess/src/images/student/sponsor_3.png b/react-ystemandchess/src/assets/images/student/sponsor_3.png similarity index 100% rename from react-ystemandchess/src/images/student/sponsor_3.png rename to react-ystemandchess/src/assets/images/student/sponsor_3.png diff --git a/react-ystemandchess/src/images/student/sponsor_4.png b/react-ystemandchess/src/assets/images/student/sponsor_4.png similarity index 100% rename from react-ystemandchess/src/images/student/sponsor_4.png rename to react-ystemandchess/src/assets/images/student/sponsor_4.png diff --git a/react-ystemandchess/src/images/student/sponsor_5.png b/react-ystemandchess/src/assets/images/student/sponsor_5.png similarity index 100% rename from react-ystemandchess/src/images/student/sponsor_5.png rename to react-ystemandchess/src/assets/images/student/sponsor_5.png diff --git a/react-ystemandchess/src/images/student/sponsor_6.png b/react-ystemandchess/src/assets/images/student/sponsor_6.png similarity index 100% rename from react-ystemandchess/src/images/student/sponsor_6.png rename to react-ystemandchess/src/assets/images/student/sponsor_6.png diff --git a/react-ystemandchess/src/images/student/sponsor_7.png b/react-ystemandchess/src/assets/images/student/sponsor_7.png similarity index 100% rename from react-ystemandchess/src/images/student/sponsor_7.png rename to react-ystemandchess/src/assets/images/student/sponsor_7.png diff --git a/react-ystemandchess/src/images/student/sponsor_8.png b/react-ystemandchess/src/assets/images/student/sponsor_8.png similarity index 100% rename from react-ystemandchess/src/images/student/sponsor_8.png rename to react-ystemandchess/src/assets/images/student/sponsor_8.png diff --git a/react-ystemandchess/src/images/student/streak_button.svg b/react-ystemandchess/src/assets/images/student/streak_button.svg similarity index 100% rename from react-ystemandchess/src/images/student/streak_button.svg rename to react-ystemandchess/src/assets/images/student/streak_button.svg diff --git a/react-ystemandchess/src/images/student/sunrise.png b/react-ystemandchess/src/assets/images/student/sunrise.png similarity index 100% rename from react-ystemandchess/src/images/student/sunrise.png rename to react-ystemandchess/src/assets/images/student/sunrise.png diff --git a/react-ystemandchess/src/images/teaching.png b/react-ystemandchess/src/assets/images/teaching.png similarity index 100% rename from react-ystemandchess/src/images/teaching.png rename to react-ystemandchess/src/assets/images/teaching.png diff --git a/react-ystemandchess/src/images/twitterIcon.svg b/react-ystemandchess/src/assets/images/twitterIcon.svg similarity index 100% rename from react-ystemandchess/src/images/twitterIcon.svg rename to react-ystemandchess/src/assets/images/twitterIcon.svg diff --git a/react-ystemandchess/src/images/user-portrait-placeholder.svg b/react-ystemandchess/src/assets/images/user-portrait-placeholder.svg similarity index 100% rename from react-ystemandchess/src/images/user-portrait-placeholder.svg rename to react-ystemandchess/src/assets/images/user-portrait-placeholder.svg diff --git a/react-ystemandchess/src/images/volunteer.png b/react-ystemandchess/src/assets/images/volunteer.png similarity index 100% rename from react-ystemandchess/src/images/volunteer.png rename to react-ystemandchess/src/assets/images/volunteer.png diff --git a/react-ystemandchess/src/components/ChessBoard/ChessBoard.css b/react-ystemandchess/src/components/ChessBoard/ChessBoard.css index 88607727..c2b3b04a 100644 --- a/react-ystemandchess/src/components/ChessBoard/ChessBoard.css +++ b/react-ystemandchess/src/components/ChessBoard/ChessBoard.css @@ -71,35 +71,18 @@ animation: shake 0.4s ease; } -/* Responsive Chessboard Container */ -.chessboard-container { +.chessboard-wrapper { width: 100%; max-width: 600px; - margin: 0 auto; - position: relative; - aspect-ratio: 1 / 1; - box-sizing: border-box; -} - -.chessboard-container > div { - max-width: 100% !important; - box-sizing: border-box; + height: auto; + display: flex; + justify-content: center; + align-items: center; } /* Tablet/Large Mobile Responsive */ @media screen and (max-width: 1024px) { - .chessboard-container { - max-width: 50vw; - width: 50vw !important; - max-height: 50vw !important; - height: auto !important; - padding: 0; - margin: 0 auto; - } - .board-b72b1 { - max-width: 100% !important; - width: 100% !important; box-sizing: border-box !important; border-width: 1px !important; } @@ -114,24 +97,12 @@ } @media screen and (max-width: 768px) { - .chessboard-container { - max-width: 55vw; - width: 55vw !important; - max-height: 55vw !important; - } - .notation-322f9 { font-size: 9px; } } @media screen and (max-width: 480px) { - .chessboard-container { - max-width: 60vw; - width: 60vw !important; - max-height: 60vw !important; - } - .notation-322f9 { font-size: 8px; } diff --git a/react-ystemandchess/src/components/ChessBoard/ChessBoard.tsx b/react-ystemandchess/src/components/ChessBoard/ChessBoard.tsx index 038c81f4..088dda9e 100644 --- a/react-ystemandchess/src/components/ChessBoard/ChessBoard.tsx +++ b/react-ystemandchess/src/components/ChessBoard/ChessBoard.tsx @@ -30,36 +30,26 @@ export interface ChessBoardRef { const ChessBoard = forwardRef( ({ lessonMoves = [], onMove, onPromote, onReset, fen: controlledFEN, onLessonComplete }, ref) => { const gameRef = useRef(new Chess()); - const canvasRef = useRef(null); const [fen, setFen] = useState(gameRef.current.fen()); const [highlightSquares, setHighlightSquares] = useState([]); const [lessonIndex, setLessonIndex] = useState(0); const [isShaking, setIsShaking] = useState(false); const [orientation, setOrientationState] = useState<"white" | "black">("white"); - const [boardWidth, setBoardWidth] = useState(600); - - // Calculate board width based on viewport - const calcWidth = () => { - if (typeof window !== 'undefined') { - const width = window.innerWidth; - // Use smaller widths to ensure entire 8x8 board fits with all rows visible - if (width <= 480) return Math.floor(width * 0.60); // 60% for small phones - if (width <= 768) return Math.floor(width * 0.55); // 55% for tablets - if (width <= 1024) return Math.floor(width * 0.50); // 50% for medium tablets - return 600; - } - return 600; - }; + const [boardWidth, setBoardWidth] = useState(0); + const boardRef = useRef(null); - // Update board width on mount and window resize + // Update width based on parent size useEffect(() => { - const updateWidth = () => { - setBoardWidth(calcWidth()); + const handleResize = () => { + if (boardRef.current) { + const containerWidth = boardRef.current.offsetWidth; + setBoardWidth(containerWidth); + } }; - - updateWidth(); // Set initial width - window.addEventListener('resize', updateWidth); - return () => window.removeEventListener('resize', updateWidth); + + handleResize(); // initial + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); }, []); // Sync controlled FEN to engine whenever it changes @@ -67,10 +57,10 @@ const ChessBoard = forwardRef( if (!controlledFEN) return; if (controlledFEN !== gameRef.current.fen()) { try { - gameRef.current.load(controlledFEN); - } catch (err) { - console.warn("Invalid FEN passed to ChessBoard:", controlledFEN, err); - } + gameRef.current.load(controlledFEN); + } catch (err) { + console.warn("Invalid FEN passed to ChessBoard:", controlledFEN, err); + } setFen(gameRef.current.fen()); setHighlightSquares([]); } @@ -161,7 +151,7 @@ const ChessBoard = forwardRef( }; return ( -
+
( return acc; }, {} as Record)} /> -
); } diff --git a/react-ystemandchess/src/components/animations/Confetti/Confetti.scss b/react-ystemandchess/src/components/animations/Confetti/Confetti.scss new file mode 100644 index 00000000..7abbfd1c --- /dev/null +++ b/react-ystemandchess/src/components/animations/Confetti/Confetti.scss @@ -0,0 +1,25 @@ +.confetti-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1000; + overflow: hidden; +} + +.confetti-piece { + position: absolute; + width: 10px; + height: 10px; + animation: confetti-fall 3s linear forwards; +} + +@keyframes confetti-fall { + to { + transform: translateY(100vh) rotate(360deg); + opacity: 0; + } +} + diff --git a/react-ystemandchess/src/components/animations/Confetti/Confetti.tsx b/react-ystemandchess/src/components/animations/Confetti/Confetti.tsx new file mode 100644 index 00000000..1c04340d --- /dev/null +++ b/react-ystemandchess/src/components/animations/Confetti/Confetti.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import './Confetti.scss'; + +interface ConfettiProps { + show: boolean; + pieceCount?: number; +} + +const Confetti: React.FC = ({ show, pieceCount = 50 }) => { + if (!show) return null; + + const colors = ['#3a7cca', '#d64309', '#ffd700', '#ff6b6b', '#4ecdc4']; + + return ( + ); }; diff --git a/react-ystemandchess/src/Pages/Login/loginController.ts b/react-ystemandchess/src/features/auth/login/loginController.ts similarity index 100% rename from react-ystemandchess/src/Pages/Login/loginController.ts rename to react-ystemandchess/src/features/auth/login/loginController.ts diff --git a/react-ystemandchess/src/Pages/Login/loginRouter.ts b/react-ystemandchess/src/features/auth/login/loginRouter.ts similarity index 100% rename from react-ystemandchess/src/Pages/Login/loginRouter.ts rename to react-ystemandchess/src/features/auth/login/loginRouter.ts diff --git a/react-ystemandchess/src/Pages/Login/loginService.ts b/react-ystemandchess/src/features/auth/login/loginService.ts similarity index 100% rename from react-ystemandchess/src/Pages/Login/loginService.ts rename to react-ystemandchess/src/features/auth/login/loginService.ts diff --git a/react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.component.scss b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.component.scss new file mode 100644 index 00000000..313bc09b --- /dev/null +++ b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.component.scss @@ -0,0 +1,108 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap"); + +.reset-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 1rem; + background: linear-gradient(to bottom right, #fef9c3, #bbf7d0); + font-family: 'Poppins', sans-serif; +} + +.reset-title { + font-size: 2.5rem; + color: #1e293b; + margin-bottom: 2rem; + text-align: center; +} + +.reset-error { + color: #ef4444; + font-weight: 600; + margin-bottom: 1rem; + text-align: center; +} + +.reset-form { + display: flex; + flex-direction: column; + gap: 1.5rem; + width: 100%; + max-width: 360px; + background: white; + padding: 2rem; + border-radius: 20px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); +} + +.input-wrapper { + display: flex; + flex-direction: column; + text-align: left; +} + +.input-wrapper label { + font-size: 1rem; + font-weight: 600; + color: #1e293b; + margin-bottom: 0.5rem; +} + +.reset-input { + padding: 0.75rem 1rem; + border: 2px solid #3a7cca; + border-radius: 8px; + font-size: 1rem; + font-family: 'Poppins', sans-serif; + transition: border-color 0.2s, box-shadow 0.2s; + + &:focus { + outline: none; + border-color: #3a7cca; + box-shadow: 0 0 0 3px rgba(58, 124, 202, 0.1); + } +} + +.reset-button { + padding: 0.875rem 1.5rem; + background: #3a7cca; + color: #fff; + border: none; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + font-family: 'Poppins', sans-serif; + cursor: pointer; + transition: background-color 0.2s, transform 0.1s; + box-shadow: 0 2px 4px rgba(58, 124, 202, 0.2); + + &:hover { + background: #2563eb; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(58, 124, 202, 0.3); + } + + &:active { + transform: translateY(0); + } +} + +.reset-links { + margin-top: 1.5rem; + text-align: center; +} + +.reset-links a { + color: #1e293b; + text-decoration: none; + font-weight: 600; + font-family: 'Poppins', sans-serif; + transition: color 0.2s; +} + +.reset-links a:hover { + color: #3a7cca; + text-decoration: underline; +} diff --git a/react-ystemandchess/src/Pages/Reset-Password/reset-password.test.tsx b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.test.tsx similarity index 72% rename from react-ystemandchess/src/Pages/Reset-Password/reset-password.test.tsx rename to react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.test.tsx index ac30d4ae..282c9386 100644 --- a/react-ystemandchess/src/Pages/Reset-Password/reset-password.test.tsx +++ b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.test.tsx @@ -35,10 +35,10 @@ test('failed verification', async () => { }) as jest.Mock // input username & email - const usernameInput = screen.getByPlaceholderText('UserName'); - const emailInput = screen.getByPlaceholderText('Email'); - fireEvent.blur(usernameInput, { target: { value: 'validusername' } }); - fireEvent.blur(emailInput, { target: { value: 'validemail@example.com' } }); + const usernameInput = screen.getByPlaceholderText('Enter your username'); + const emailInput = screen.getByPlaceholderText('Enter your email'); + fireEvent.change(usernameInput, { target: { value: 'validusername' } }); + fireEvent.change(emailInput, { target: { value: 'validemail@example.com' } }); // submit credentials const button = screen.getByTestId('reset-submit'); @@ -65,10 +65,10 @@ test('network error', async () => { }) as jest.Mock // input username & email - const usernameInput = screen.getByPlaceholderText('UserName'); - const emailInput = screen.getByPlaceholderText('Email'); - fireEvent.blur(usernameInput, { target: { value: 'validusername' } }); - fireEvent.blur(emailInput, { target: { value: 'validemail@example.com' } }); + const usernameInput = screen.getByPlaceholderText('Enter your username'); + const emailInput = screen.getByPlaceholderText('Enter your email'); + fireEvent.change(usernameInput, { target: { value: 'validusername' } }); + fireEvent.change(emailInput, { target: { value: 'validemail@example.com' } }); // submit credentials const button = screen.getByTestId('reset-submit'); @@ -98,10 +98,10 @@ test('successful verification', async () => { }) as jest.Mock // input username & email - const usernameInput = screen.getByPlaceholderText('UserName'); - const emailInput = screen.getByPlaceholderText('Email'); - fireEvent.blur(usernameInput, { target: { value: 'validusername' } }); - fireEvent.blur(emailInput, { target: { value: 'validemail@example.com' } }); + const usernameInput = screen.getByPlaceholderText('Enter your username'); + const emailInput = screen.getByPlaceholderText('Enter your email'); + fireEvent.change(usernameInput, { target: { value: 'validusername' } }); + fireEvent.change(emailInput, { target: { value: 'validemail@example.com' } }); // submit credentials const button = screen.getByTestId('reset-submit'); diff --git a/react-ystemandchess/src/Pages/Reset-Password/reset-password.tsx b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.tsx similarity index 63% rename from react-ystemandchess/src/Pages/Reset-Password/reset-password.tsx rename to react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.tsx index 10302d71..b9d6031b 100644 --- a/react-ystemandchess/src/Pages/Reset-Password/reset-password.tsx +++ b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/reset-password.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router'; +import './reset-password.component.scss'; const ResetPassword = () => { const [username, setUsername] = useState(''); @@ -42,64 +43,58 @@ const ResetPassword = () => { }; return ( -
-

Reset Password

+
+

Reset Password

- {error != '' && ( -
+ {error && ( +
{error}
)} -
-
- + +
+ setUsername(e.target.value)} - className='w-full p-2 border rounded' + className="reset-input" required disabled={isLoading} />
-
- +
+ setEmail(e.target.value)} - className='w-full p-2 border rounded' + className="reset-input" required disabled={isLoading} />
+ +
); }; diff --git a/react-ystemandchess/src/Pages/Reset-Password/resetPasswordController.ts b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/resetPasswordController.ts similarity index 100% rename from react-ystemandchess/src/Pages/Reset-Password/resetPasswordController.ts rename to react-ystemandchess/src/features/auth/reset-password/Reset-Password/resetPasswordController.ts diff --git a/react-ystemandchess/src/Pages/Reset-Password/resetPasswordRouter.ts b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/resetPasswordRouter.ts similarity index 100% rename from react-ystemandchess/src/Pages/Reset-Password/resetPasswordRouter.ts rename to react-ystemandchess/src/features/auth/reset-password/Reset-Password/resetPasswordRouter.ts diff --git a/react-ystemandchess/src/Pages/Reset-Password/resetPasswordService.ts b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/resetPasswordService.ts similarity index 95% rename from react-ystemandchess/src/Pages/Reset-Password/resetPasswordService.ts rename to react-ystemandchess/src/features/auth/reset-password/Reset-Password/resetPasswordService.ts index c7697932..cff77239 100644 --- a/react-ystemandchess/src/Pages/Reset-Password/resetPasswordService.ts +++ b/react-ystemandchess/src/features/auth/reset-password/Reset-Password/resetPasswordService.ts @@ -5,7 +5,7 @@ * Uses Nodemailer with Gmail to deliver reset links. */ -import { environment } from '../../environments/environment'; +import { environment } from "../../../../environments/environment"; const nodemailer = require('nodemailer'); // Configure email transporter with Gmail credentials diff --git a/react-ystemandchess/src/Pages/Set-Password/set-password.component.scss b/react-ystemandchess/src/features/auth/set-password/Set-Password/set-password.component.scss similarity index 100% rename from react-ystemandchess/src/Pages/Set-Password/set-password.component.scss rename to react-ystemandchess/src/features/auth/set-password/Set-Password/set-password.component.scss diff --git a/react-ystemandchess/src/Pages/Set-Password/set-password.test.ts b/react-ystemandchess/src/features/auth/set-password/Set-Password/set-password.test.ts similarity index 100% rename from react-ystemandchess/src/Pages/Set-Password/set-password.test.ts rename to react-ystemandchess/src/features/auth/set-password/Set-Password/set-password.test.ts diff --git a/react-ystemandchess/src/Pages/Set-Password/set-password.test.tsx b/react-ystemandchess/src/features/auth/set-password/Set-Password/set-password.test.tsx similarity index 100% rename from react-ystemandchess/src/Pages/Set-Password/set-password.test.tsx rename to react-ystemandchess/src/features/auth/set-password/Set-Password/set-password.test.tsx diff --git a/react-ystemandchess/src/Pages/Set-Password/set-password.tsx b/react-ystemandchess/src/features/auth/set-password/Set-Password/set-password.tsx similarity index 100% rename from react-ystemandchess/src/Pages/Set-Password/set-password.tsx rename to react-ystemandchess/src/features/auth/set-password/Set-Password/set-password.tsx diff --git a/react-ystemandchess/src/Pages/Set-Password/setPasswordController.ts b/react-ystemandchess/src/features/auth/set-password/Set-Password/setPasswordController.ts similarity index 100% rename from react-ystemandchess/src/Pages/Set-Password/setPasswordController.ts rename to react-ystemandchess/src/features/auth/set-password/Set-Password/setPasswordController.ts diff --git a/react-ystemandchess/src/Pages/Set-Password/setPasswordRouter.ts b/react-ystemandchess/src/features/auth/set-password/Set-Password/setPasswordRouter.ts similarity index 100% rename from react-ystemandchess/src/Pages/Set-Password/setPasswordRouter.ts rename to react-ystemandchess/src/features/auth/set-password/Set-Password/setPasswordRouter.ts diff --git a/react-ystemandchess/src/Pages/Set-Password/setPasswordService.ts b/react-ystemandchess/src/features/auth/set-password/Set-Password/setPasswordService.ts similarity index 100% rename from react-ystemandchess/src/Pages/Set-Password/setPasswordService.ts rename to react-ystemandchess/src/features/auth/set-password/Set-Password/setPasswordService.ts diff --git a/react-ystemandchess/src/features/auth/signup/SignUp.scss b/react-ystemandchess/src/features/auth/signup/SignUp.scss new file mode 100644 index 00000000..8b48861a --- /dev/null +++ b/react-ystemandchess/src/features/auth/signup/SignUp.scss @@ -0,0 +1,123 @@ +body { + margin: 0; + font-family: 'Poppins', sans-serif; + background: linear-gradient(to bottom right, #fef9c3, #bbf7d0); +} + +.signup-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 1rem; +} + +.signup-title { + font-size: 2.5rem; + color: #1e293b; + margin-bottom: 2rem; + text-align: center; +} + +.signup-error { + color: #ef4444; + font-weight: 600; + margin-bottom: 1rem; + text-align: center; +} + +.signup-form { + display: flex; + flex-direction: column; + gap: 1.5rem; + width: 100%; + max-width: 360px; + background: white; + padding: 2rem; + border-radius: 20px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); +} + +.input-wrapper { + display: flex; + flex-direction: column; +} + +.input-wrapper label { + font-size: 1rem; + font-weight: 600; + color: #1e293b; + margin-bottom: 0.4rem; +} + +.input-wrapper input, +.input-wrapper select { + padding: 12px 16px; + font-size: 1rem; + width: 100%; + border-radius: 8px; + border: 2px solid #cbd5e1; + transition: border 0.3s ease; + background-color: #fff; + font-family: 'Comic Sans MS', cursive; + color: #334155; + box-sizing: border-box; +} + +.input-wrapper input:focus, +.input-wrapper select:focus { + border-color: #3b82f6; + outline: none; +} + +.terms-wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.9rem; +} + +.button-wrapper { + display: flex; + justify-content: center; + margin-top: 1rem; +} + +.signup-form button { + padding: 12px 40px; + background-color: #3b82f6; + color: white; + font-weight: bold; + font-size: 1.1rem; + border: none; + border-radius: 25px; + box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15); + transition: 0.3s; + cursor: pointer; +} + +.signup-form button:hover { + background-color: #2563eb; + transform: scale(1.05); +} + +.signup-links { + margin-top: 1.5rem; + display: flex; + justify-content: center; + gap: 2rem; + font-size: 1rem; + font-weight: 600; +} + +.signup-links a { + color: #1e293b; + text-decoration: none; + border-bottom: 2px solid transparent; + transition: border-color 0.3s; +} + +.signup-links a:hover { + border-color: #1e293b; +} diff --git a/react-ystemandchess/src/Pages/SignUp/SignUp.test.tsx b/react-ystemandchess/src/features/auth/signup/SignUp.test.tsx similarity index 68% rename from react-ystemandchess/src/Pages/SignUp/SignUp.test.tsx rename to react-ystemandchess/src/features/auth/signup/SignUp.test.tsx index 62eae7ec..f5f98107 100644 --- a/react-ystemandchess/src/Pages/SignUp/SignUp.test.tsx +++ b/react-ystemandchess/src/features/auth/signup/SignUp.test.tsx @@ -2,7 +2,6 @@ import React from "react"; import { render, screen, fireEvent } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter } from "react-router"; -import { useNavigate } from 'react-router'; import SignUp from './SignUp' // mock fetching from database @@ -30,19 +29,22 @@ test("renders sign up page", () => { ); // Check if the sign up title is present - const title = screen.getByTestId("title"); + const title = screen.getByText("Create Account"); expect(title).toBeInTheDocument(); // Check if all the fields are present - const formFields = screen.getByTestId("form-fields"); - expect(formFields).toBeInTheDocument(); - const accountType = screen.getByTestId("account-type"); - expect(accountType).toBeInTheDocument(); - const terms = screen.getByTestId("terms"); - expect(terms).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Enter your first name")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Enter your last name")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("you@example.com")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Choose a username")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Create a password")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Re-type your password")).toBeInTheDocument(); + expect(screen.getByText("Account Type")).toBeInTheDocument(); + expect(screen.getByRole("combobox")).toBeInTheDocument(); + expect(screen.getByLabelText(/I accept the terms and conditions/)).toBeInTheDocument(); // Check if submission button is present - const submitBtn = screen.getByTestId("submit-btn"); + const submitBtn = screen.getByRole("button", { name: "Sign Up" }); expect(submitBtn).toBeInTheDocument(); }); @@ -54,49 +56,55 @@ test("field text validation", async () => { ); // check for first names that are too short - const firstName = screen.getByPlaceholderText('First name'); + const firstName = screen.getByPlaceholderText('Enter your first name'); await userEvent.type(firstName, 'f'); const firstNameError = await screen.findByText("Invalid First Name") expect(firstNameError).toBeInTheDocument(); + await userEvent.clear(firstName); await userEvent.type(firstName, 'firstname'); // check for last names that include numbers - const lastName = screen.getByPlaceholderText('Last name'); + const lastName = screen.getByPlaceholderText('Enter your last name'); await userEvent.type(lastName, '123'); const lastNameError = await screen.findByText("Invalid Last Name") expect(lastNameError).toBeInTheDocument(); + await userEvent.clear(lastName); await userEvent.type(lastName, 'lastname'); // check for invalid emails - const email = screen.getByPlaceholderText('Email'); + const email = screen.getByPlaceholderText('you@example.com'); await userEvent.type(email, 'invalid@email'); const emailError = await screen.findByText("Invalid Email") expect(emailError).toBeInTheDocument(); + await userEvent.clear(email); await userEvent.type(email, 'valid@email.com'); // check for usernames that are too long - const username = screen.getByPlaceholderText('Username'); + const username = screen.getByPlaceholderText('Choose a username'); await userEvent.type(username, '123456789012345'); const usernameError = await screen.findByText("Invalid Username") expect(usernameError).toBeInTheDocument(); + await userEvent.clear(username); await userEvent.type(username, 'username'); // check for passwords that are too short - const password = screen.getByPlaceholderText('Password'); + const password = screen.getByPlaceholderText('Create a password'); await userEvent.type(password, 'short'); const passwordError = await screen.findByText("Password must be at least 8 characters") expect(passwordError).toBeInTheDocument(); - await userEvent.type(password, 'password'); + await userEvent.clear(password); + await userEvent.type(password, 'password123'); - // check for passwords that are too short - const retype = screen.getByPlaceholderText('Re-type password'); + // check for passwords that don't match + const retype = screen.getByPlaceholderText('Re-type your password'); await userEvent.type(retype, 'different'); const retypeError = await screen.findByText("Passwords do not match") expect(retypeError).toBeInTheDocument(); - await userEvent.type(password, 'password'); + await userEvent.clear(retype); + await userEvent.type(retype, 'password123'); // when submitting without checking terms & conditions - const submitBtn = screen.getByTestId('submit-btn'); + const submitBtn = screen.getByRole("button", { name: "Sign Up" }); fireEvent.click(submitBtn); await screen.findByText("Please accept the terms and conditions."); }); @@ -129,7 +137,7 @@ test("adding children", async () => { ); // select the parent account - const selector = screen.getByTestId("account-selector"); + const selector = screen.getByRole("combobox"); fireEvent.change(selector, { target: { value: 'parent' } }); // create new student diff --git a/react-ystemandchess/src/features/auth/signup/SignUp.tsx b/react-ystemandchess/src/features/auth/signup/SignUp.tsx new file mode 100644 index 00000000..dfe33ed5 --- /dev/null +++ b/react-ystemandchess/src/features/auth/signup/SignUp.tsx @@ -0,0 +1,735 @@ +import React, { useState, useEffect, useRef } from 'react'; +import './SignUp.scss'; +import { environment } from '../../../environments/environment'; + +// Define the interface for the props of the StudentTemplate component +interface StudentTemplateProps { + studentUsername: string; + onClick: (username: string) => void; +} + +// Component for displaying student search results +const StudentTemplate: React.FC = ({ studentUsername, onClick }) => { + return ( +
onClick(studentUsername)}> +
{studentUsername}
+
+ ); +}; + +const Signup = () => { + // State to manage the form data for the user + const [formData, setFormData] = useState({ + firstName: '', + lastName: '', + email: '', + username: '', + password: '', + retypedPassword: '', + accountType: 'mentor', + }); + + // State flags to track the validity of individual input fields + const [firstNameFlag, setFirstNameFlag] = useState(false); + const [lastNameFlag, setLastNameFlag] = useState(false); + const [emailFlag, setEmailFlag] = useState(false); + const [userNameFlag, setUserNameFlag] = useState(false); + const [passwordFlag, setPasswordFlag] = useState(false); + const [retypeFlag, setRetypeFlag] = useState(false); + const [termsFlag, setTermsFlag] = useState(false); + + // States for the student search dropdown + const [matchingStudents, setMatchingStudents] = useState([]); + const [usernameToSearch, setUserToSearch] = useState(''); + const [activeDropdown, setActiveDropdown] = useState(false); + const [dropdownLoading, setDropdownLoading] = useState(false); + + // State to store any validation errors for the form fields + const [errors, setErrors] = useState({ + firstName: '', + lastName: '', + email: '', + username: '', + password: '', + retypePassword: '', + terms: '', + general: '', + }); + + // State to manage the parent account specific UI and data + const [parentAccountFlag, setParentAccountFlag] = useState(false); + const [showStudentForm, setShowStudentForm] = useState(false); + const [students, setStudents] = useState([]); + const [assignedMenteeUsername, setAssignedMenteeUsername] = useState(null); + + // Handle click outside dropdown to close it + const dropdownRef = useRef(null); + const inputRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + inputRef.current && + !inputRef.current.contains(event.target as Node) + ) { + setActiveDropdown(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + // Handles changes to input fields in the main form + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ + ...prev, + [name]: value, + })); + + // Performs specific validation based on the input field that changed + switch (name) { + case 'firstName': + firstNameVerification(value); + break; + case 'lastName': + lastNameVerification(value); + break; + case 'email': + emailVerification(value); + break; + case 'username': + usernameVerification(value); + break; + case 'password': + passwordVerification(value); + break; + case 'retypedPassword': + retypePasswordVerification(value, formData.password); + break; + default: + break; + } + }; + + // Verifies the format of the first name + const firstNameVerification = (firstName: string) => { + const isValid = /^[A-Za-z ]{2,15}$/.test(firstName); + setFirstNameFlag(isValid); + setErrors((prev) => ({ + ...prev, + firstName: isValid ? '' : 'Invalid First Name', + })); + return isValid; + }; + + // Verifies the format of the last name + const lastNameVerification = (lastName: string) => { + const isValid = /^[A-Za-z]{2,15}$/.test(lastName); + setLastNameFlag(isValid); + setErrors((prev) => ({ + ...prev, + lastName: isValid ? '' : 'Invalid Last Name', + })); + return isValid; + }; + + // Verifies the format of the email address + const emailVerification = (email: string) => { + const isValid = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}/.test(email); + setEmailFlag(isValid); + setErrors((prev) => ({ + ...prev, + email: isValid ? '' : 'Invalid Email', + })); + return isValid; + }; + + // Verifies the format of the username + const usernameVerification = (username: string) => { + const isValid = /^[a-zA-Z](\S){1,14}$/.test(username); + setUserNameFlag(isValid); + setErrors((prev) => ({ + ...prev, + username: isValid ? '' : 'Invalid Username', + })); + return isValid; + }; + + // Verifies the length of the password + const passwordVerification = (password: string) => { + const isValid = password.length >= 8; + setPasswordFlag(isValid); + setErrors((prev) => ({ + ...prev, + 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 isValid = retypedPassword === password; + setRetypeFlag(isValid); + setErrors((prev) => ({ + ...prev, + retypePassword: isValid ? '' : 'Passwords do not match', + })); + return isValid; + }; + + // Handles terms checkbox change + const termsCheckChange = (e: React.ChangeEvent) => { + setTermsFlag(e.target.checked); + }; + + // Handles changes to the account type dropdown + const handleAccountTypeChange = (e: React.ChangeEvent) => { + const isParent = e.target.value === 'parent'; + setParentAccountFlag(isParent); + setFormData((prev) => ({ + ...prev, + accountType: e.target.value, + })); + // Reset mentee search/selection when switching account type + setUserToSearch(''); + setAssignedMenteeUsername(null); + setActiveDropdown(false); + setMatchingStudents([]); + }; + + // Adds a new student form to the UI for parent accounts + const handleAddStudent = () => { + const newStudent = { + id: Date.now(), + firstName: '', + lastName: '', + username: '', + email: '', + password: '', + retypedPassword: '', + errors: {}, + }; + setStudents((prev: any) => [...prev, newStudent]); + setShowStudentForm(true); + }; + + // Handles changes to input fields within a student's form + const handleStudentInputChange = (studentId: any, field: string, value: string) => { + switch (field) { + case 'firstName': + firstNameVerification(value); + break; + case 'lastName': + lastNameVerification(value); + break; + case 'email': + emailVerification(value); + break; + case 'username': + usernameVerification(value); + break; + case 'password': + passwordVerification(value); + break; + case 'retypedPassword': + const student = students.find((s) => s.id === studentId); + const password = student?.password; + retypePasswordVerification(value, password); + break; + default: + break; + } + + setStudents((prev: any) => + prev.map((student: any) => + student.id === studentId ? { ...student, [field]: value } : student + ) + ); + }; + + // Removes a student form from the UI + const handleRemoveStudent = (studentId: any) => { + setStudents((prev: any) => prev.filter((student: any) => student.id !== studentId)); + if (students.length === 1) { + setShowStudentForm(false); + } + }; + + // Handles changes in the "Find a student" input and triggers API call + const handleMenteeSearchChange = async (searchText: string) => { + setUserToSearch(searchText); + setAssignedMenteeUsername(null); + + if (searchText.trim() === "") { + setActiveDropdown(false); + setMatchingStudents([]); + setDropdownLoading(false); + return; + } + + setActiveDropdown(true); + setDropdownLoading(true); + try { + const response = await fetch( + `${environment.urls.middlewareURL}/user/mentorless?keyword=${searchText}`, + { + method: 'GET', + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const usernames = await response.json(); + const top10Usernames = usernames.slice(0, 10); + setMatchingStudents(top10Usernames); + setDropdownLoading(false); + } catch (error) { + console.error('Error fetching mentorless students:', error); + setDropdownLoading(false); + setMatchingStudents([]); + setErrors((prev) => ({ + ...prev, + general: 'Failed to fetch student list.', + })); + } + }; + + // Handles selecting a mentee from the dropdown + const handleSelectMentee = (selectedUsername: string) => { + setAssignedMenteeUsername(selectedUsername); + setUserToSearch(selectedUsername); + setActiveDropdown(false); + setMatchingStudents([]); + }; + + // Handles the submission of the signup form + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + console.log('Submit clicked', formData); + + // Check if the terms and conditions are checked + if (!termsFlag) { + setErrors((prev) => ({ ...prev, general: 'Please accept the terms and conditions.' })); + return; + } + + // Checks if all main form fields are valid based on their flags + const isValid = + firstNameFlag && + lastNameFlag && + emailFlag && + userNameFlag && + passwordFlag && + retypeFlag; + + // If the main form is not valid, prevents submission + if (!isValid) { + console.log('Form validation failed'); + setErrors((prev) => ({ ...prev, general: 'Please correct the form errors.' })); + return; + } + + // If user is a mentor but has not selected mentee + if (formData.accountType === 'mentor' && !assignedMenteeUsername) { + setErrors((prev) => ({ ...prev, general: 'Please select your mentee' })); + return; + } + + let signupUrl = `${environment.urls.middlewareURL}/user/`; + let signupParams: URLSearchParams; + + if (parentAccountFlag) { + // Maps student data into the required format for the API + const studentsData = students.map((student: any) => ({ + first: student.firstName, + last: student.lastName, + email: student.email, + username: student.username, + password: student.password, + })); + + // Prepare params for parent signup, stringifying the students array + signupParams = new URLSearchParams({ + first: formData.firstName, + last: formData.lastName, + email: formData.email, + password: formData.password, + username: formData.username, + role: formData.accountType, + students: JSON.stringify(studentsData), + }); + } else { + // Prepare params for non-parent accounts + signupParams = new URLSearchParams({ + first: formData.firstName, + last: formData.lastName, + email: formData.email, + password: formData.password, + username: formData.username, + role: formData.accountType, + }); + } + + // Append query parameters to the URL for the signup request + signupUrl = `${signupUrl}?${signupParams.toString()}`; + console.log('Signup Request URL:', signupUrl); + + try { + // STEP 1: Perform User Signup + const signupResponse = await fetch(signupUrl, { + method: 'POST', + }); + + console.log('Signup Response status:', signupResponse.status); + + if (!signupResponse.ok) { + let errorContent: any; + try { + errorContent = await signupResponse.json(); + } catch (jsonError) { + errorContent = await signupResponse.text(); + } + + if (typeof errorContent === 'string' && errorContent === 'This username has been taken. Please choose another.') { + setErrors((prev) => ({ + ...prev, + username: 'Username already taken', + general: '', + })); + } else { + const errorMessage = (typeof errorContent === 'object' && errorContent !== null && 'message' in errorContent && typeof errorContent.message === 'string') + ? errorContent.message + : (typeof errorContent === 'string' && errorContent.length > 0) + ? errorContent + : 'Unknown error during signup'; + throw new Error(`HTTP error! status: ${signupResponse.status}, message: ${errorMessage}`); + } + return; + } + + console.log('Signup successful, proceeding to login to get token...'); + + let jwtToken: string | null = null; + + // STEP 2: Perform Login to get JWT Token + try { + const loginUrl = `${environment.urls.middlewareURL}/auth/login?username=${encodeURIComponent(formData.username)}&password=${encodeURIComponent(formData.password)}`; + console.log('Login URL for token acquisition:', loginUrl); + + const loginResponse = await fetch(loginUrl, { + method: 'POST', + }); + + console.log('Login Response status:', loginResponse.status); + + if (!loginResponse.ok) { + const loginErrorData = await loginResponse.json(); + throw new Error(`Login failed after signup: ${loginErrorData.message || 'Unknown login error'}`); + } + + const loginData = await loginResponse.json(); + jwtToken = loginData.token; + console.log('Login successful, JWT token obtained.'); + } catch (loginError: any) { + console.error('Error during login after signup:', loginError); + setErrors((prev) => ({ + ...prev, + general: `Signup successful, but failed to log in to assign mentee: ${loginError.message || 'Login network error'}`, + })); + window.location.href = '/'; + return; + } + + // STEP 3: Conditionally Perform Mentee Assignment + if (formData.accountType === 'mentor' && assignedMenteeUsername && jwtToken) { + const updateMentorshipUrl = `${environment.urls.middlewareURL}/user/updateMentorship?mentorship=${encodeURIComponent(assignedMenteeUsername)}`; + + try { + const updateMentorshipResponse = await fetch(updateMentorshipUrl, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${jwtToken}`, + }, + }); + + console.log('Update Mentorship Response status:', updateMentorshipResponse.status); + + if (!updateMentorshipResponse.ok) { + const updateErrorData = await updateMentorshipResponse.json(); + console.error('Mentorship update failed:', updateErrorData); + setErrors((prev) => ({ + ...prev, + general: `Signup successful, but mentee assignment failed: ${updateErrorData.message || 'Unknown error'}`, + })); + } else { + const updateSuccessData = await updateMentorshipResponse.json(); + console.log('Mentorship update successful:', updateSuccessData); + } + } catch (updateError: any) { + console.error('Error during mentorship update:', updateError); + setErrors((prev) => ({ + ...prev, + general: `Signup successful, but mentee assignment network error: ${updateError.message || 'Network error'}`, + })); + } + } + + // Final Step: Redirect to homepage after all operations + window.location.href = '/'; + } catch (error: any) { + console.error('Signup error:', error); + setErrors((prev) => ({ + ...prev, + general: error.message || 'Signup failed. Please try again.', + })); + } + }; + + return ( +
+

Create Account

+ + {errors.general &&

{errors.general}

} + +
+
+ + + {errors.firstName && {errors.firstName}} +
+ +
+ + + {errors.lastName && {errors.lastName}} +
+ +
+ + + {errors.email && {errors.email}} +
+ +
+ + + {errors.username && {errors.username}} +
+ +
+ + + {errors.password && {errors.password}} +
+ +
+ + + {errors.retypePassword && {errors.retypePassword}} +
+ +
+ + +
+ + {!parentAccountFlag && ( +
+ + handleMenteeSearchChange(e.target.value)} + autoComplete="off" + /> + {activeDropdown && ( +
+ {dropdownLoading ? ( +
Loading...
+ ) : ( + matchingStudents.length > 0 ? ( + matchingStudents.map((username) => ( + + )) + ) : ( +
No matching students found.
+ ) + )} +
+ )} +
+ )} + + {parentAccountFlag && ( +
+ {!showStudentForm && ( + + )} + + {students.map((student: any) => ( +
+ + + handleStudentInputChange( + student.id, + 'firstName', + e.target.value + ) + } + /> + + handleStudentInputChange( + student.id, + 'lastName', + e.target.value + ) + } + /> + + handleStudentInputChange( + student.id, + 'username', + e.target.value + ) + } + /> + + handleStudentInputChange(student.id, 'email', e.target.value) + } + /> + + handleStudentInputChange( + student.id, + 'password', + e.target.value + ) + } + /> + + handleStudentInputChange( + student.id, + 'retypedPassword', + e.target.value + ) + } + /> +
+ ))} +
+ )} + +
+ + + {errors.terms && {errors.terms}} +
+ +
+ +
+
+ + +
+ ); +}; + +export default Signup; diff --git a/react-ystemandchess/src/Pages/SignUp/signupController.ts b/react-ystemandchess/src/features/auth/signup/signupController.ts similarity index 100% rename from react-ystemandchess/src/Pages/SignUp/signupController.ts rename to react-ystemandchess/src/features/auth/signup/signupController.ts diff --git a/react-ystemandchess/src/Pages/SignUp/signupRouter.ts b/react-ystemandchess/src/features/auth/signup/signupRouter.ts similarity index 100% rename from react-ystemandchess/src/Pages/SignUp/signupRouter.ts rename to react-ystemandchess/src/features/auth/signup/signupRouter.ts diff --git a/react-ystemandchess/src/Pages/SignUp/signupService.ts b/react-ystemandchess/src/features/auth/signup/signupService.ts similarity index 100% rename from react-ystemandchess/src/Pages/SignUp/signupService.ts rename to react-ystemandchess/src/features/auth/signup/signupService.ts diff --git a/react-ystemandchess/src/Pages/Home/Home.css b/react-ystemandchess/src/features/home/Home.css similarity index 100% rename from react-ystemandchess/src/Pages/Home/Home.css rename to react-ystemandchess/src/features/home/Home.css diff --git a/react-ystemandchess/src/Pages/Home/Home.test.tsx b/react-ystemandchess/src/features/home/Home.test.tsx similarity index 100% rename from react-ystemandchess/src/Pages/Home/Home.test.tsx rename to react-ystemandchess/src/features/home/Home.test.tsx diff --git a/react-ystemandchess/src/Pages/Home/Home.tsx b/react-ystemandchess/src/features/home/Home.tsx similarity index 99% rename from react-ystemandchess/src/Pages/Home/Home.tsx rename to react-ystemandchess/src/features/home/Home.tsx index c1e55e7b..d59d6b97 100644 --- a/react-ystemandchess/src/Pages/Home/Home.tsx +++ b/react-ystemandchess/src/features/home/Home.tsx @@ -15,7 +15,7 @@ import React from "react"; import "./Home.css"; -import Images from "../../images/imageImporter"; +import Images from "../../assets/images/imageImporter"; import { ButtonsCard } from "../../components/ui/tailwindcss-buttons"; import { useNavigate } from "react-router"; diff --git a/react-ystemandchess/src/features/home/Home/Home.tsx b/react-ystemandchess/src/features/home/Home/Home.tsx new file mode 100644 index 00000000..eb0e1a3e --- /dev/null +++ b/react-ystemandchess/src/features/home/Home/Home.tsx @@ -0,0 +1,275 @@ +/** + * Home Page Component + * + * This is the main landing page component for the Y STEM and Chess application. + * It serves as the entry point for visitors and provides an overview of the + * organization's mission, featured content, and calls-to-action. + * + * Features: + * - Hero section with organization branding + * - Featured books and publications + * - Donation and purchase functionality + * - Navigation to other sections of the site + * - Responsive design for all device types + */ + +import React from "react"; +import "./Home.css"; +import Images from "../../../assets/images/imageImporter"; +import { ButtonsCard } from "../../../components/ui/tailwindcss-buttons"; +import { useNavigate } from "react-router"; + +/** + * Static data for featured books displayed on the home page + * + * This array contains information about the organization's published books, + * including cover images, titles, subtitles, and descriptions. The data + * is used to render book cards with purchase functionality. + * + * Each book object contains: + * - image: Reference to the book cover image + * - title: Main book title + * - subtitle: Descriptive subtitle + * - description: Detailed book description for marketing + */ +const books = [ + { + image: Images.Book1, + title: "How to Start a Tech-Based Nonprofit", + subtitle: + "Bridging the Opportunity Gap: Building a STEM Nonprofit to Change the Trajectory of Underserved Children's Lives", + description: + "How to start tech-based Nonprofit details the steps of Devin Nakano as he builds Y STEM and Chess (YSC) Inc. The first in its series covers the first 4 years of YSC. Each chapter brings unique perspective of an entrepreneur building a nonprofit that uses technology to fulfill the Company Mission.", + }, + { + image: Images.Book2, + title: "The Zero Dollar Workforce", + subtitle: "Hire a Team, Run Your Company, and Don't Spend Any Money", + description: + "It's easier to hire and manage 40 people than just 2... Someone can also hire and run this same team of 40 people completely for FREE... The above sounds like total nonsense. Like someone is crazy. Like it's some kind of miracle. But a lot of creations in our world don't make any sense until they're fully produced and studied...", + }, +]; + +/** + * Main Home page component + * + * This component renders the landing page with featured content, donation + * options, and book purchases. It serves as the main entry point for + * visitors to learn about the organization and take action. + * + * @returns JSX element representing the complete home page + */ +const Home = () => { + // Hook for programmatic navigation between pages + const navigate = useNavigate(); + + /** + * Handles donation button click events + * + * This function redirects users to the external donation platform + * (DonorBox) where they can make contributions to support the + * organization's mission and programs. + * + * Uses window.location.href for external navigation to ensure + * proper handling of the third-party donation platform. + */ + const handleDonateButton = () => { + window.location.href = + "https://donorbox.org/y-stem-and-chess-inc-learning-platform"; + }; + + /** + * Handles book purchase button click events + * + * This function opens the appropriate Amazon product page in a new tab + * based on the book title provided. It supports multiple books and + * can be easily extended for additional publications. + * + * @param title - The title of the book to purchase (optional) + * + * Supported books: + * - "How to Start a Tech-Based Nonprofit" + * - "The Zero Dollar Workforce" + */ + const handleBuyNow = (title = "") => { + // Handle purchase for "How to Start a Tech-Based Nonprofit" + if (title === "How to Start a Tech-Based Nonprofit") { + window.open( + "https://www.amazon.com/How-Start-Tech-based-Nonprofit-Opportunity/dp/B0C4MML5WG", + "_blank" // Open in new tab to preserve user's current session + ); + } + + // Handle purchase for "The Zero Dollar Workforce" + if (title === "The Zero Dollar Workforce") { + window.open( + "https://www.amazon.com/Zero-Dollar-Workforce-Company-Spend/dp/B09NGVLQSS", + "_blank" // Open in new tab to preserve user's current session + ); + } + }; + return ( +
+
+
+

+ 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. +

+ + {/* */} + + +
+
+ Group of Y STEM mascots playing chess +
+
+ + + +

Everyone is included. Everyone is welcome.

+ +
+
+ Heart icon +

Free

+

+ For students who qualify for

free and reduced lunch. +

+ Our lessons are free. +

+ +
+
+ Gem icon +

Premium

+

+ For students who don't qualify

for free and reduced lunch.{" "} +

+ $25 / Week

First lesson is FREE.

Cancel anytime. +

+ +
+
+ +
+ Y STEM mission statement emphasising Play, Learn and Donate +
+ +
+ +
+ +
+
+ Chess peices lined up next to eachother +
Start now and sign up later!
+ +
+
+ + Y STEM styled line break + +
+

Books by Devin Nakano

+ {books.map((book, index) => ( +
+
+ {`${book.title} + +
+
+

{book.title}

+

{book.subtitle}

+

{book.description}

+
+
+ ))} +
+

All proceeds will be donated to the organization

+
+
+
+ ); +}; + +export default Home; diff --git a/react-ystemandchess/src/Pages/Lessons/Lessons-profile.module.scss b/react-ystemandchess/src/features/lessons/lessons-main/Lessons-profile.module.scss similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/Lessons-profile.module.scss rename to react-ystemandchess/src/features/lessons/lessons-main/Lessons-profile.module.scss diff --git a/react-ystemandchess/src/Pages/Lessons/Lessons.module.scss b/react-ystemandchess/src/features/lessons/lessons-main/Lessons.module.scss similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/Lessons.module.scss rename to react-ystemandchess/src/features/lessons/lessons-main/Lessons.module.scss diff --git a/react-ystemandchess/src/Pages/Lessons/Lessons.test.tsx b/react-ystemandchess/src/features/lessons/lessons-main/Lessons.test.tsx similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/Lessons.test.tsx rename to react-ystemandchess/src/features/lessons/lessons-main/Lessons.test.tsx diff --git a/react-ystemandchess/src/Pages/Lessons/Lessons.tsx b/react-ystemandchess/src/features/lessons/lessons-main/Lessons.tsx similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/Lessons.tsx rename to react-ystemandchess/src/features/lessons/lessons-main/Lessons.tsx diff --git a/react-ystemandchess/src/Pages/Lessons/Placeholder.jsx b/react-ystemandchess/src/features/lessons/lessons-main/Placeholder.jsx similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/Placeholder.jsx rename to react-ystemandchess/src/features/lessons/lessons-main/Placeholder.jsx diff --git a/react-ystemandchess/src/Pages/Lessons/PromotionPopup.tsx b/react-ystemandchess/src/features/lessons/lessons-main/PromotionPopup.tsx similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/PromotionPopup.tsx rename to react-ystemandchess/src/features/lessons/lessons-main/PromotionPopup.tsx diff --git a/react-ystemandchess/src/images/mathArticle/Rectangle 313.png b/react-ystemandchess/src/features/lessons/lessons-main/Rectangle 313.png similarity index 100% rename from react-ystemandchess/src/images/mathArticle/Rectangle 313.png rename to react-ystemandchess/src/features/lessons/lessons-main/Rectangle 313.png diff --git a/react-ystemandchess/src/Pages/Lessons/Scenarios.js b/react-ystemandchess/src/features/lessons/lessons-main/Scenarios.js similarity index 93% rename from react-ystemandchess/src/Pages/Lessons/Scenarios.js rename to react-ystemandchess/src/features/lessons/lessons-main/Scenarios.js index 7f3a2418..4292e1f4 100644 --- a/react-ystemandchess/src/Pages/Lessons/Scenarios.js +++ b/react-ystemandchess/src/features/lessons/lessons-main/Scenarios.js @@ -1,197 +1,197 @@ export const scenariosArray = [ - // { - // name: 'Pawn - It moves forward only', - // subSections: [ - // { - // name: 'Basic', - // fen: '8/8/8/P7/8/5p2/8/8 w k - 0 1', - // info: `Pawns move one square only. - // But when they reach the other side of the board, they become a stronger piece!`, - // }, - // { - // name: 'Capture', - // fen: '8/3p4/2p5/3p4/8/4P3/8/8 w - - 0 1', - // info: `Pawns move forward, - // but capture diagonally!`, - // }, - // { - // name: 'Training 1', - // fen: '8/2p5/1ppp4/8/1pp5/1P6/8/8 w - - 0 1', - // info: 'Capture, then promote!', - // }, - // { - // name: 'Training 2', - // fen: '2p5/3p4/1p2p3/1p1p4/2p5/3P4/8/8 w - - 0 1', - // info: `Capture, then promote!`, - // }, - // { - // name: 'Traning 3', - // fen: '8/8/8/1pp1p3/3p2p1/P1PP3P/8/8 w - - 0 1', - // info: `Use all the pawns! - // No need to promote.`, - // }, - // { - // name: 'Special Move', - // fen: '8/8/3p4/8/8/8/4P3/8 w - - 0 1', - // info: `A pawn on the second rank can move 2 squares at once!`, - // }, - // ], - // }, - // { - // name: 'Bishop - It moves diagonally ', - // subSections: [ - // { - // name: 'The Basic', - // fen: '8/7p/8/8/4p3/8/6B1/8 w - - 0 1', - // info: 'Grab all the black pawns! ', - // }, - // { - // name: 'Training 1', - // fen: '8/8/8/1p6/8/1B1p4/p3p3/1p1p4 w - - 0 1', - // info: `The fewer moves you make, the better!`, - // }, - // { - // name: 'Training 2', - // fen: '8/8/8/8/p1B5/1p1p4/2p1p3/1p6 w - - 0 1', - // info: 'Grab all the black pawns!', - // }, - // { - // name: 'Training 3', - // fen: '8/8/8/3pp3/3pp3/3pp3/8/2B2B2 w - - 0 1', - // info: `One light-squared bishop, one dark-squared bishop. You need both!`, - // }, - // { - // name: 'Training 4', - // fen: '8/6p1/1p5p/8/3B4/4p3/8/p1p5 w - - 0 1', - // info: 'Grab all the black pawns!', - // }, - // { - // name: 'Final', - // fen: '6p1/3Bp2p/5p2/5p2/7p/p1B5/2p5/8 w - - 0 1', - // info: `One light-squared bishop, one dark-squared bishop. You need both!`, - // }, - // ], - // }, - // { - // name: 'Knight - It moves in an L shape ', - // subSections: [ - // { - // name: 'The Basic', - // fen: '8/3p4/8/2p5/4N3/8/8/8 w - - 0 1', - // info: `Knights have a fancy way of jumping around!`, - // }, - // { - // name: 'Training 1', - // fen: '7p/5p2/8/6p1/3p4/2p2p2/4p3/1N6 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Training 2', - // fen: '8/2Np4/1p2p3/3p4/5p2/8/8/8 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Training 3', - // fen: '8/8/8/8/4ppp1/4pNp1/4ppp1/8 w - - 0 1', - // info: `Knights can jump over obstacles!Escape and vanquish the pawns!`, - // }, - // { - // name: 'Training 4', - // fen: '8/8/6p1/8/4pp2/2pN4/4pp2/8 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Final', - // fen: '2p5/2N1p3/2p5/1p1p1p2/1p1p4/4p3/8/8 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // ], - // }, - // { - // name: 'Rook - It moves in straight lines ', - // subSections: [ - // { - // name: 'The Basic', - // fen: '8/8/4p3/8/8/8/4R3/8 w - - 0 1', - // info: `Click on the rook to bring it to the pawn!`, - // }, - // { - // name: 'Training 1', - // fen: '8/2R5/8/2p2p2/8/8/8/8 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Training 2', - // fen: '8/8/8/8/8/1p2R2p/7p/8 w - - 0 1', - // info: `The fewer moves you make, the better!`, - // }, - // { - // name: 'Training 3', - // fen: '5ppR/6pp/8/8/8/8/8/6p1 w - - 0 1', - // info: `The fewer moves you make, the better!`, - // }, - // { - // name: 'Training 4', - // fen: '8/2R3p1/8/8/p3R2p/6p1/8/8 w - - 0 1', - // info: `Use two rooks to speed things up!`, - // }, - // { - // name: 'Final', - // fen: '8/1p3pp1/8/3p4/6p1/5R2/5p2/R2p4 w - - 0 1', - // info: `Use two rooks to speed things up!`, - // }, - // ], - // }, - // { - // name: 'Queen - Queen = Rook + Bishop ', - // subSections: [ - // { - // name: 'The Basic', - // fen: '8/2p5/8/4p3/8/8/4Q3/8 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Training 1', - // fen: '5p2/8/8/8/3Q4/p6p/5p2/8 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Training 2', - // fen: '5p2/8/3p3p/8/2Q5/p5p1/8/5p2 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Training 3', - // fen: '6p1/6Q1/8/1p5p/8/3p4/p6p/6p1 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Final', - // fen: '6p1/8/p4pp1/8/7p/8/5p2/3pQ2p w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // ], - // }, - // { - // name: 'King - The most important piece ', - // subSections: [ - // { - // name: 'The Basic', - // fen: '8/8/3p4/8/8/8/3K4/8 w - - 0 1', - // info: 'The king is slow. ', - // }, - // { - // name: 'Training', - // fen: '8/8/8/8/8/3p4/2p1p3/4K3 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // { - // name: 'Final', - // fen: '8/8/8/2ppK3/2p3p1/4pp2/8/8 w - - 0 1', - // info: `Grab all the pawns!`, - // }, - // ], - // }, + { + name: 'Pawn - It moves forward only', + subSections: [ + { + name: 'Basic', + fen: '7k/8/8/P7/8/5p2/8/K7 w - - 0 1', + info: `Pawns move one square only. + But when they reach the other side of the board, they become a stronger piece!`, + }, + { + name: 'Capture', + fen: '8/3p4/2p5/3p4/8/4P3/8/K6k w - - 0 1', + info: `Pawns move forward, + but capture diagonally!`, + }, + { + name: 'Training 1', + fen: '8/2p5/1ppp4/8/1pp5/1P6/8/K6k w - - 0 1', + info: 'Capture, then promote!', + }, + { + name: 'Training 2', + fen: '2p5/3p4/1p2p3/1p1p4/2p5/3P4/8/K6k w - - 0 1', + info: `Capture, then promote!`, + }, + { + name: 'Traning 3', + fen: '8/8/8/1pp1p3/3p2p1/P1PP3P/8/K6k w - - 0 1', + info: `Use all the pawns! + No need to promote.`, + }, + { + name: 'Special Move', + fen: '8/8/3p4/8/8/8/4P3/K6k w - - 0 1', + info: `A pawn on the second rank can move 2 squares at once!`, + }, + ], + }, + { + name: 'Bishop - It moves diagonally', + subSections: [ + { + name: 'The Basic', + fen: '7k/7p/8/8/4p3/8/6B1/K7 w - - 0 1', + info: 'Grab all the black pawns! ', + }, + { + name: 'Training 1', + fen: '7k/8/8/1p6/8/1B1p4/p3p3/Kp1p4 w - - 0 1', + info: `The fewer moves you make, the better!`, + }, + { + name: 'Training 2', + fen: '7k/8/8/8/p1B5/1p1p4/2p1p3/Kp6 w - - 0 1', + info: 'Grab all the black pawns!', + }, + { + name: 'Training 3', + fen: '7k/8/8/3pp3/3pp3/3pp3/8/K2B2B1 w - - 0 1', + info: `One light-squared bishop, one dark-squared bishop. You need both!`, + }, + { + name: 'Training 4', + fen: '7k/6p1/1p5p/8/3B4/4p3/8/K1p1p3 w - - 0 1', + info: 'Grab all the black pawns!', + }, + { + name: 'Final', + fen: '6pk/3Bp2p/5p2/5p2/7p/p1B5/2p5/K7 w - - 0 1', + info: `One light-squared bishop, one dark-squared bishop. You need both!`, + }, + ], + }, + { + name: 'Knight - It moves in an L shape', + subSections: [ + { + name: 'The Basic', + fen: '7k/3p4/8/2p5/4N3/8/8/K7 w - - 0 1', + info: `Knights have a fancy way of jumping around!`, + }, + { + name: 'Training 1', + fen: 'k7/5p2/8/6p1/3p4/2p2p2/4p3/KN6 w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Training 2', + fen: '7k/2Np4/1p2p3/3p4/5p2/8/8/K7 w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Training 3', + fen: '7k/8/8/8/4ppp1/4pNp1/4ppp1/K7 w - - 0 1', + info: `Knights can jump over obstacles! Escape and vanquish the pawns!`, + }, + { + name: 'Training 4', + fen: '7k/8/6p1/8/4pp2/2pN4/4pp2/K7 w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Final', + fen: 'k1p5/2N1p3/2p5/1p1p1p2/1p1p4/4p3/8/K7 w - - 0 1', + info: `Grab all the pawns!`, + }, + ], + }, + { + name: 'Rook - It moves in straight lines', + subSections: [ + { + name: 'The Basic', + fen: 'k7/8/4p3/8/8/8/4R3/7K w - - 0 1', + info: `Click on the rook to bring it to the pawn!`, + }, + { + name: 'Training 1', + fen: 'k7/2R5/8/2p2p2/8/8/8/7K w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Training 2', + fen: 'k7/8/8/8/8/1p2R2p/7p/7K w - - 0 1', + info: `The fewer moves you make, the better!`, + }, + { + name: 'Training 3', + fen: 'k4ppR/6pp/8/8/8/8/8/6pK w - - 0 1', + info: `The fewer moves you make, the better!`, + }, + { + name: 'Training 4', + fen: 'k7/2R3p1/8/8/p3R2p/6p1/8/7K w - - 0 1', + info: `Use two rooks to speed things up!`, + }, + { + name: 'Final', + fen: 'k7/1p3pp1/8/3p4/6p1/5R2/5p2/R2p3K w - - 0 1', + info: `Use two rooks to speed things up!`, + }, + ], + }, + { + name: 'Queen - Queen = Rook + Bishop', + subSections: [ + { + name: 'The Basic', + fen: 'k7/2p5/8/4p3/8/8/4Q3/7K w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Training 1', + fen: 'k4p2/8/8/8/3Q4/p6p/5p2/7K w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Training 2', + fen: 'k4p2/8/3p3p/8/2Q5/p5p1/8/5p1K w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Training 3', + fen: 'k5p1/6Q1/8/1p5p/8/3p4/p6p/6pK w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Final', + fen: 'k5p1/8/p4pp1/8/7p/8/5p2/K2pQ2p w - - 0 1', + info: `Grab all the pawns!`, + }, + ], + }, + { + name: 'King - The most important piece', + subSections: [ + { + name: 'The Basic', + fen: '7k/8/3p4/8/8/8/3K4/8 w - - 0 1', + info: 'The king is slow. ', + }, + { + name: 'Training', + fen: '7k/8/8/8/8/3p4/2p1p3/4K3 w - - 0 1', + info: `Grab all the pawns!`, + }, + { + name: 'Final', + fen: '7k/8/8/2ppK3/2p3p1/4pp2/8/8 w - - 0 1', + info: `Grab all the pawns!`, + }, + ], + }, { name: 'Piece Checkmate 1 Basic checkmates', subSections: [ diff --git a/react-ystemandchess/src/images/chess_background.png b/react-ystemandchess/src/features/lessons/lessons-main/chess_background.png similarity index 100% rename from react-ystemandchess/src/images/chess_background.png rename to react-ystemandchess/src/features/lessons/lessons-main/chess_background.png diff --git a/react-ystemandchess/src/images/icons/icon_back.svg b/react-ystemandchess/src/features/lessons/lessons-main/icon_back.svg similarity index 100% rename from react-ystemandchess/src/images/icons/icon_back.svg rename to react-ystemandchess/src/features/lessons/lessons-main/icon_back.svg diff --git a/react-ystemandchess/src/images/icons/icon_back_inactive.svg b/react-ystemandchess/src/features/lessons/lessons-main/icon_back_inactive.svg similarity index 100% rename from react-ystemandchess/src/images/icons/icon_back_inactive.svg rename to react-ystemandchess/src/features/lessons/lessons-main/icon_back_inactive.svg diff --git a/react-ystemandchess/src/images/icons/icon_next.svg b/react-ystemandchess/src/features/lessons/lessons-main/icon_next.svg similarity index 100% rename from react-ystemandchess/src/images/icons/icon_next.svg rename to react-ystemandchess/src/features/lessons/lessons-main/icon_next.svg diff --git a/react-ystemandchess/src/images/icons/icon_next_inactive.svg b/react-ystemandchess/src/features/lessons/lessons-main/icon_next_inactive.svg similarity index 100% rename from react-ystemandchess/src/images/icons/icon_next_inactive.svg rename to react-ystemandchess/src/features/lessons/lessons-main/icon_next_inactive.svg diff --git a/react-ystemandchess/src/images/icons/icon_redo.svg b/react-ystemandchess/src/features/lessons/lessons-main/icon_redo.svg similarity index 100% rename from react-ystemandchess/src/images/icons/icon_redo.svg rename to react-ystemandchess/src/features/lessons/lessons-main/icon_redo.svg diff --git a/react-ystemandchess/src/Pages/Lessons/register_button.png b/react-ystemandchess/src/features/lessons/lessons-main/register_button.png similarity index 100% rename from react-ystemandchess/src/Pages/Lessons/register_button.png rename to react-ystemandchess/src/features/lessons/lessons-main/register_button.png diff --git a/react-ystemandchess/src/Pages/LessonsSelection/LessonTemplate/LessonTemplate.jsx b/react-ystemandchess/src/features/lessons/lessons-selection/LessonTemplate/LessonTemplate.jsx similarity index 100% rename from react-ystemandchess/src/Pages/LessonsSelection/LessonTemplate/LessonTemplate.jsx rename to react-ystemandchess/src/features/lessons/lessons-selection/LessonTemplate/LessonTemplate.jsx diff --git a/react-ystemandchess/src/Pages/LessonsSelection/LessonsSelection.jsx b/react-ystemandchess/src/features/lessons/lessons-selection/LessonsSelection.jsx similarity index 59% rename from react-ystemandchess/src/Pages/LessonsSelection/LessonsSelection.jsx rename to react-ystemandchess/src/features/lessons/lessons-selection/LessonsSelection.jsx index 7de17f6c..fa888646 100644 --- a/react-ystemandchess/src/Pages/LessonsSelection/LessonsSelection.jsx +++ b/react-ystemandchess/src/features/lessons/lessons-selection/LessonsSelection.jsx @@ -1,9 +1,9 @@ import profileStyles from "./ProfileStyle.module.scss"; import pageStyles from "./LessonsStyle.module.scss"; import { useNavigate } from "react-router"; -import React, { useState, useEffect, useCallback, useMemo } from 'react'; -import { environment } from "../../environments/environment"; -import { getAllScenarios } from "../Lessons/Scenarios"; +import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import { environment } from "../../../environments/environment"; +import { getAllScenarios } from "../lessons-main/Scenarios"; import { useCookies } from "react-cookie"; import ScenarioTemplate from "./ScenarioTemplate/ScenarioTemplate.jsx"; // Importing the ScenarioTemplate component import LessonTemplate from "./LessonTemplate/LessonTemplate.jsx"; // Importing the LessonTemplate component @@ -21,11 +21,11 @@ export default function LessonSelection({ onGo, styleType = "page" }) { // what const [selectedLesson, setSelectedLesson] = useState(null); const [isLessonsLoading, setLoadingLessons] = useState(false); const [unlockedLessonCount, setUnlockedLessonCount] = useState(0); // Renamed for clarity - - const [error, setError] = useState(null); // Combine error states - const [scenarios, setScenarios] = useState([]); const [lessons, setLessons] = useState([]); + const scenarioRef = useRef(null); + const lessonRef = useRef(null); + const [error, setError] = useState(""); // Effect to fetch the list of scenarios when the component mounts. useEffect(() => { @@ -60,7 +60,7 @@ export default function LessonSelection({ onGo, styleType = "page" }) { // what // Handles the submission (click on the "Go!" button) to navigate to the selected lesson. const handleSubmit = async () => { if (!selectedLesson || !selectedScenario) { - setError("Select a scenario & lesson."); + setError("Please select a scenario and a lesson."); return; } @@ -86,26 +86,6 @@ export default function LessonSelection({ onGo, styleType = "page" }) { // what return; } - let unlocked = 0; - try { - const response = await fetch( - `${environment.urls.middlewareURL}/lessons/getCompletedLessonCount?piece=${selectedScenario}`, - { - method: 'GET', - headers: { 'Authorization': `Bearer ${cookies.login}` } - } - ); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - unlocked = await response.json(); - } catch (error) { - console.error('Error fetching unlocked lesson count:', error); - // Optionally set an error state here if needed for display - unlocked = 0; // Default to 0 unlocked lessons on error - } - const currentScenario = scenarios.find(s => s.name === selectedScenario); if (!currentScenario) { setLessons([]); @@ -113,6 +93,34 @@ export default function LessonSelection({ onGo, styleType = "page" }) { // what return; } + setLoadingLessons(true); + + let unlocked = 0; + if (!cookies.login) { + // Non-logged users: expose all lessons + unlocked = currentScenario.subSections.length; + console.log("No login cookie detected. Granting full access. unlocked:", unlocked); + } else { + // Logged users: ask backend for how many lessons they have completed + try { + const response = await fetch( + `${environment.urls.middlewareURL}/lessons/getCompletedLessonCount?piece=${selectedScenario}`, + { + method: 'GET', + headers: { 'Authorization': `Bearer ${cookies.login}` } + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + unlocked = await response.json(); + } catch (error) { + console.error('Error fetching unlocked lesson count:', error); + unlocked = 0; // Default to 0 unlocked lessons on error + } + } + // Determine available lessons const maxIndex = Math.min(unlocked, currentScenario.subSections.length - 1); const availableLessons = currentScenario.subSections.slice(0, maxIndex + 1); @@ -129,6 +137,29 @@ export default function LessonSelection({ onGo, styleType = "page" }) { // what fetchLessonsForScenario(); }, [selectedScenario, cookies.login, scenarios]); + useEffect(() => { + const handleClickOutside = (event) => { + if ( + scenarioRef.current && + !scenarioRef.current.contains(event.target) + ) { + setShowScenarios(false); + } + + if ( + lessonRef.current && + !lessonRef.current.contains(event.target) + ) { + setShowLessons(false); + } + }; + + document.addEventListener("click", handleClickOutside); + return () => { + document.removeEventListener("click", handleClickOutside); + }; + }, []); + const renderedScenarios = useMemo(() => { return scenarios.map((scenarioItem) => ( @@ -154,53 +185,66 @@ export default function LessonSelection({ onGo, styleType = "page" }) { // what return (
- {/* Conditional rendering of the error message popup. */} - {error && ( -
-
-
{error}
- -
-
- )}
Lesson Selection
{/* Dropdown-like selector for choosing a scenario. */} -
setShowScenarios(!showScenarios)}> -
- {selectedScenario || "Select a scenario."} -
-
- {showScenarios ? "β–Ό" : "β–²"} +
+
setShowScenarios(!showScenarios)}> +
+ {selectedScenario || "Select a scenario"} +
+
+ {showScenarios ? "β–Ό" : "β–²"} +
-
- {/* Conditional rendering of the scenarios list. */} - {showScenarios && ( -
- {renderedScenarios} -
- )} + {/* Conditional rendering of the scenarios list. */} + {showScenarios && ( +
+ {renderedScenarios} +
+ )} +
{/* Dropdown-like selector for choosing a lesson within the selected scenario. */} -
setShowLessons(!showLessons)}> -
- {selectedLesson || "Select a lesson."} -
-
- {showLessons ? "β–Ό" : "β–²"} +
+
{ + if (!selectedScenario) { + setError("Please select a scenario first."); + setTimeout(() => setError(""), 2500); // auto-clear after 2.5s + return; + } + setShowLessons(!showLessons); + }}> +
+ {selectedLesson || "Select a lesson"} +
+
+ {showLessons ? "β–Ό" : "β–²"} +
+ + {/* Conditional rendering of the lessons list for the selected scenario. */} + {showLessons && ( +
+ {isLessonsLoading ? ( +
Loading...
+ ) : (renderedLessons) + } +
+ )}
- {/* Conditional rendering of the lessons list for the selected scenario. */} - {showLessons && ( -
- {isLessonsLoading ? ( -
Loading...
- ) : (renderedLessons) - } -
- )} + +
+ {error && ( +
+ {error} +
+ )} +
+ {/* Button to submit the selection and navigate to the lesson. */} - +
+ + +
+
{ + if (navigateFunc) navigateFunc(); + else navigate("/lessons-selection"); + }}>Switch Lesson +
@@ -361,9 +388,9 @@ const LessonOverlay: React.FC = ({ ) }
- {styleType != 'profile' && ()} + {styleType !== 'profile' && ()}
-
+
= ({ cy="60" r="54" fill="none" - stroke="#a3d0ff" + stroke="#7fcc26" strokeWidth="6" > @@ -492,15 +519,33 @@ const LessonOverlay: React.FC = ({ {/* have users read instructions first */} {showInstruction && ( -
+
-

Read this instruction:

+

Lesson Instructions

{info}

- + {/* Loading bar at the bottom */} +
+
+
+
+
+ )} + {allLessonsDone && ( +
+
+

πŸŽ‰ Congratulations!

+

+ You have completed all lessons for this scenario. +

+
)} - {/* */} {isPromoting ? : null /* Show promotion popup if needed */}
diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/answers.js b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/answers.js similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/answers.js rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/answers.js diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useChessGameLogic.test.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useChessGameLogic.test.ts similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useChessGameLogic.test.ts rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useChessGameLogic.test.ts diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useChessGameLogic.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useChessGameLogic.ts similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useChessGameLogic.ts rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useChessGameLogic.ts diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useLessonManager.test.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useLessonManager.test.ts similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useLessonManager.test.ts rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useLessonManager.test.ts diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useLessonManager.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useLessonManager.ts similarity index 98% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useLessonManager.ts rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useLessonManager.ts index 72c59c6f..71dd2194 100644 --- a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useLessonManager.ts +++ b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useLessonManager.ts @@ -1,5 +1,5 @@ import { useState, useCallback } from "react"; -import { environment } from "../../../../environments/environment"; +import { environment } from "../../../../../environments/environment"; export function useLessonManager(piece: string, cookies: any, initialLessonNum?: number) { const [lessonNum, setLessonNum] = useState(initialLessonNum ?? 0); diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.test.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.test.ts similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.test.ts rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.test.ts diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.ts similarity index 92% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.ts rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.ts index 3e37be23..b26aaf97 100644 --- a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.ts +++ b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useSocketChessEngine.ts @@ -1,4 +1,4 @@ -import { environment } from "../../../../environments/environment"; +import { environment } from "../../../../../environments/environment"; import { useEffect, useRef } from "react"; import io from "socket.io-client"; diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useTimeTracking.test.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useTimeTracking.test.ts similarity index 93% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useTimeTracking.test.ts rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useTimeTracking.test.ts index d5410c98..58c48dd0 100644 --- a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useTimeTracking.test.ts +++ b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useTimeTracking.test.ts @@ -1,15 +1,15 @@ import { renderHook, act } from "@testing-library/react"; import { useTimeTracking } from "./useTimeTracking"; -import { SetPermissionLevel } from "../../../../globals"; -import { environment } from "../../../../environments/environment"; +import { SetPermissionLevel } from "../../../../../globals"; +import { environment } from "../../../../../environments/environment"; // mock SetPermissionLevel -jest.mock("../../../../globals", () => ({ +jest.mock("../../../../../globals", () => ({ SetPermissionLevel: jest.fn(), })); // mock environment URL -jest.mock("../../../../environments/environment", () => ({ +jest.mock("../../../../../core/environments/environment", () => ({ environment: { urls: { middlewareURL: "http://mockurl.com" } }, })); diff --git a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useTimeTracking.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useTimeTracking.ts similarity index 93% rename from react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useTimeTracking.ts rename to react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useTimeTracking.ts index ff4965d2..f58b182d 100644 --- a/react-ystemandchess/src/Pages/piece-lessons/lesson-overlay/hooks/useTimeTracking.ts +++ b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useTimeTracking.ts @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; -import { environment } from "../../../../environments/environment"; -import { SetPermissionLevel } from "../../../../globals"; +import { environment } from "../../../../../environments/environment"; +import { SetPermissionLevel } from "../../../../../globals"; export function useTimeTracking(piece: string, cookies: any) { const [username, setUsername] = useState(null); diff --git a/react-ystemandchess/src/Pages/piece-lessons/move-tracker/MoveTracker.tsx b/react-ystemandchess/src/features/lessons/piece-lessons/move-tracker/MoveTracker.tsx similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/move-tracker/MoveTracker.tsx rename to react-ystemandchess/src/features/lessons/piece-lessons/move-tracker/MoveTracker.tsx diff --git a/react-ystemandchess/src/Pages/piece-lessons/play-lesson/PlayLesson.tsx b/react-ystemandchess/src/features/lessons/piece-lessons/play-lesson/PlayLesson.tsx similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/play-lesson/PlayLesson.tsx rename to react-ystemandchess/src/features/lessons/piece-lessons/play-lesson/PlayLesson.tsx diff --git a/react-ystemandchess/src/Pages/piece-lessons/play-lesson/lesson-content copy.scss b/react-ystemandchess/src/features/lessons/piece-lessons/play-lesson/lesson-content copy.scss similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/play-lesson/lesson-content copy.scss rename to react-ystemandchess/src/features/lessons/piece-lessons/play-lesson/lesson-content copy.scss diff --git a/react-ystemandchess/src/Pages/piece-lessons/play-lesson/lesson-content.scss b/react-ystemandchess/src/features/lessons/piece-lessons/play-lesson/lesson-content.scss similarity index 100% rename from react-ystemandchess/src/Pages/piece-lessons/play-lesson/lesson-content.scss rename to react-ystemandchess/src/features/lessons/piece-lessons/play-lesson/lesson-content.scss diff --git a/react-ystemandchess/src/features/mentor/mentor-page/Mentor.scss b/react-ystemandchess/src/features/mentor/mentor-page/Mentor.scss new file mode 100644 index 00000000..9a964547 --- /dev/null +++ b/react-ystemandchess/src/features/mentor/mentor-page/Mentor.scss @@ -0,0 +1,124 @@ +.mentor-roles { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 3rem; + margin-bottom: 3rem; +} + +.mentor-card { + flex: 1 1 300px; + max-width: 400px; + min-width: 280px; + padding: 2.5rem; + border-radius: 20px; + display: flex; + flex-direction: column; + align-items: center; + backdrop-filter: blur(10px); + transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); + text-align: center; +} + +.mentor-card:hover { + transform: translateY(-10px) scale(1.02); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); +} + +.icon-container { + width: 80px; + height: 80px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1.5rem; +} + +.icon-container span { + font-size: 2rem; + color: white; +} + +.transform-icon:hover span { + transform: rotate(360deg); + transition: transform 0.6s ease-in-out; + } + +.breathe-icon span { + animation: breathe 2.5s ease-in-out infinite; +} + +@keyframes breathe { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.15); + } + } + +.mentor-card h3 { + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 1rem; + } + +.mentor-card p { + font-size: 1.1rem; + line-height: 1.6; +} + +.green-card { + background: linear-gradient(145deg, #73b313 0%, #8bc34a 100%); + color: #ffffff; + } + +.white-card { + background: linear-gradient(145deg, #ffffff 0%, #f8f9ff 100%); + border: 2px solid #73b313; + color: #333; +} + +.apply-wrapper { + text-align: center; +} + +.apply-button { + background-color: #0f172a; + border: none; + border-radius: 9999px; + padding: 0.8rem 2rem; + font-size: 1.1rem; + color: #fff; + cursor: pointer; + font-weight: 600; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.apply-button:hover { + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); + } + +.apply-button:active { + transform: translateY(1px); + } + +@media (max-width: 768px) { + .hero-section { + flex-direction: column; + text-align: center; + } + + .hero-text { + text-align: center; + } + + .mentor-card { + width: 100%; + max-width: 350px; + } +} diff --git a/react-ystemandchess/src/Pages/Mentor/Mentor.test.tsx b/react-ystemandchess/src/features/mentor/mentor-page/Mentor.test.tsx similarity index 100% rename from react-ystemandchess/src/Pages/Mentor/Mentor.test.tsx rename to react-ystemandchess/src/features/mentor/mentor-page/Mentor.test.tsx diff --git a/react-ystemandchess/src/Pages/Mentor/Mentor.tsx b/react-ystemandchess/src/features/mentor/mentor-page/Mentor.tsx similarity index 79% rename from react-ystemandchess/src/Pages/Mentor/Mentor.tsx rename to react-ystemandchess/src/features/mentor/mentor-page/Mentor.tsx index 5db7a3f6..22e7d17c 100644 --- a/react-ystemandchess/src/Pages/Mentor/Mentor.tsx +++ b/react-ystemandchess/src/features/mentor/mentor-page/Mentor.tsx @@ -1,10 +1,10 @@ import React from "react"; import "./Mentor.scss"; -import LogoLineBr from "../../images/LogoLineBreak.png"; -import cabbageImg from "../../images/mission-image.png"; -import volunteerImg from "../../images/volunteer.png"; -import teacher from "../../images/teaching.png"; -import makeADifference from "../../images/difference.png"; +import LogoLineBr from "../../../assets/images/LogoLineBreak.png"; +import cabbageImg from "../../../assets/images/mission-image.png"; +import volunteerImg from "../../../assets/images/volunteer.png"; +import teacher from "../../../assets/images/teaching.png"; +import makeADifference from "../../../assets/images/difference.png"; const Mentor = () => { return ( diff --git a/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.scss b/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.scss new file mode 100644 index 00000000..e0c48df8 --- /dev/null +++ b/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.scss @@ -0,0 +1,472 @@ +// ===================================== +// Font Imports +// ===================================== + +@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Lato:wght@700&display=swap"); + +// ===================================== +// Color Variables +// ===================================== + +$brand-color-primary: #ffffff; +$brand-color-secondary: #eaf4f4; +$brand-color-tertiary: #3a7cca; +$brand-color-accent: #d64309; +$brand-color-background-grey: #f7f7f7; +$inventory-tab-color: #ffffff; +$inventory-tab-active: #3a7cca; + +// ===================================== +// Main Container +// ===================================== + +#main-inventory-content { + background-color: $brand-color-primary; + font-family: "Roboto", sans-serif; + min-height: 100vh; + position: relative; + + // =========================== + // Toolbar Section + // =========================== + + .inv-toolbar { + width: 100%; + height: 8.3rem; + background-color: $brand-color-tertiary; + display: flex; + align-items: center; + justify-content: center; + } + + .inv-toolbar-buttons { + width: clamp(1200px, 80%, 1700px); + display: flex; + justify-content: space-between; + } + + .toolbar-button { + position: relative; + width: 21.6675rem; + height: 6.75rem; + padding: 0; + margin: 0; + border: none; + background: none; + border-radius: 8px; + cursor: pointer; + overflow: hidden; + + .toolbar-icon { + width: 100%; + height: 100%; + transition: filter 0.2s ease; + display: block; + color: #FDD835; + object-fit: contain; + } + + &:hover .toolbar-icon, + &.active .toolbar-icon { + color: #FFFF00; + } + } + + // =========================== + // Intro Section + // =========================== + + .inv-intro { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 0; + + .inv-intro-portrait { + position: relative; + width: 120px; + height: 120px; + border-radius: 50%; + background: #ffffff; + border: 4px solid #3a7cca; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 12px rgba(58, 124, 202, 0.3); + margin-top: 2rem; + + .inv-intro-portrait-face { + width: 60%; + height: 60%; + object-fit: contain; + filter: brightness(0) saturate(100%) invert(29%) sepia(81%) saturate(746%) hue-rotate(189deg) brightness(95%) contrast(91%); + } + + .inv-intro-portrait-camera { + position: absolute; + bottom: 0; + right: 0; + transform: translate(30%, 30%); + width: 30px; + height: 30px; + background: #ffffff; + border: 2px solid #3a7cca; + border-radius: 50%; + padding: 4px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + } + } + + .inv-intro-welcome { + margin-top: 1.5rem; + + h1 { + font-size: 1.8rem; + font-weight: 700; + color: #333; + margin: 0 0 1rem 0; + padding: 0; + } + } + } + + // =========================== + // Inventory Section + // =========================== + + .inv-inventory { + background: $brand-color-background-grey; + width: clamp(300px, 80%, 1200px); + margin: 2rem auto; + border-radius: 1rem; + overflow: hidden; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + + .inv-inventory-topbar { + background: $brand-color-tertiary; + padding: 1rem 0; + margin: 0; + + .progress-heading { + font-family: "Lato", sans-serif; + font-weight: 700; + font-size: 2.25rem; + color: #ffffff; + margin: 0; + } + } + + .inv-inventory-analytics { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + padding: 2rem; + + .inv-inventory-analytics-graph { + height: 18rem; + width: 40rem; + padding: 0.8rem; + margin-right: 3rem; + } + + .inv-inventory-analytics-metrics { + text-align: left; + + h3 { + font-size: 1.2rem; + font-weight: bold; + margin: 0 0 1rem 0; + color: #333; + } + + ul { + list-style: none; + padding: 0; + + li { + margin-bottom: 0.5rem; + color: #333; + font-weight: 500; + + strong { + color: $brand-color-accent; + } + } + } + } + } + + // =========================== + // Inventory Tab Layout + // =========================== + + .inv-inventory-content-section { + display: grid; + grid-template-columns: 30% 70%; + background: $brand-color-primary; + + .inv-inventory-content-tabs { + position: relative; + + ul { + list-style: none; + padding: 1rem; + background: $brand-color-secondary; + border-right: 1px solid #ddd; + margin: 0; + + li { + &:first-child .inventory-tab { + border-top-left-radius: 0.5rem; + } + + &:last-child .inventory-tab { + border-bottom-left-radius: 0.5rem; + margin-bottom: 0; + } + } + } + } + + .inventory-tab { + all: unset; + position: relative; + display: flex; + align-items: center; + cursor: pointer; + padding: 1rem; + margin-bottom: 1rem; + border-radius: 0.5rem; + transition: background 0.3s; + + img { + width: 32px; + height: 32px; + max-width: 32px; + max-height: 32px; + object-fit: contain; + margin-right: 0.75rem; + flex-shrink: 0; + filter: grayscale(1); + transition: filter 0.3s, transform 0.3s; + } + + li { + list-style: none; + font-size: 1rem; + font-weight: 500; + color: #333; + } + + &:hover { + background: lighten($brand-color-tertiary, 40%); + } + + &.active-tab { + background: $brand-color-tertiary; + + img { + filter: none; + } + + li { + color: white; + } + } + } + + .inv-inventory-content-content { + padding: 2rem; + overflow-y: auto; + } + + .inventory-content { + position: relative; + display: none; + height: 100%; + width: 100%; + + &.active-content { + display: block; + } + + .inventory-content-headingbar { + display: flex; + justify-content: space-between; + align-items: baseline; + border-bottom: 1px solid #ccc; + margin-bottom: 1rem; + padding: 0; + + h2 { + font-size: 1.5rem; + font-weight: 700; + color: #333; + margin: 0; + padding: 0; + } + + h4 { + font-size: 0.9rem; + color: #666; + margin: 0; + padding: 0; + } + } + + .inventory-content-body { + position: relative; + display: grid; + max-height: 500px; + overflow-y: auto; + scrollbar-width: none; + } + + .inventory-content-line { + position: absolute; + height: 78%; + top: 20%; + left: 3.2%; + + &::after { + content: ""; + position: absolute; + z-index: 0; + top: 0; + bottom: 0; + left: 50%; + border-left: 3px dotted #222; + } + } + + .inventory-content-timecard { + display: flex; + justify-content: space-between; + align-items: center; + background: #fff; + border: 1px solid #ddd; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1rem; + + p { + margin: 0; + + strong { + text-decoration: underline; + cursor: pointer; + + &:hover { + color: $brand-color-tertiary; + } + } + } + } + + .inventory-date { + font-family: "Roboto", sans-serif; + font-weight: 400; + font-size: 1.0rem; + color: #000; + } + + .inventory-time { + font-family: "Roboto", sans-serif; + font-weight: 400; + font-size: 0.8125rem; + color: #000; + } + } + } + } + + // =========================== + // Animation States + // =========================== + + // Celebration pulse animation + .inv-inventory.celebrate { + animation: celebrate-pulse 1s ease-in-out; + } + + @keyframes celebrate-pulse { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.02); + } + } + + // Portrait click animation + .inv-intro-portrait { + cursor: pointer; + transition: transform 0.2s ease; + + &:hover { + transform: scale(1.05); + } + + &:active { + transform: scale(0.95); + } + } + + // Enhanced loading spinner + .loading-spinner { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid rgba(58, 124, 202, 0.3); + border-radius: 50%; + border-top-color: $brand-color-tertiary; + animation: spin 1s ease-in-out infinite; + } + + @keyframes spin { + to { + transform: rotate(360deg); + } + } +} + +// =========================== +// Mentor-specific styles +// =========================== + +.topbar-greeting { + color: black; + font-size: 20px; +} + +.no-student-message { + color: black; + font-size: 20px; + margin-top: 20px; + border: 1px solid rgb(209, 86, 86); + width: 60%; + margin: 20px auto; + padding: 20px; + background-color: rgba(248, 46, 46, 0.2); + border-radius: 20px; +} + +.inventory-tab-img { + max-width: 32px; + max-height: 32px; + width: 32px; + height: 32px; + object-fit: contain; + display: inline-block; + vertical-align: middle; +} + +.no-student-message h1 { + font-size: 24px; + margin-bottom: 10px; + font-weight: bold; +} diff --git a/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.test.tsx b/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.test.tsx similarity index 83% rename from react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.test.tsx rename to react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.test.tsx index f257666b..7824c77b 100644 --- a/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.test.tsx +++ b/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.test.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { render, screen, act } from '@testing-library/react'; import { MemoryRouter } from 'react-router'; -import { SetPermissionLevel } from '../../globals'; +import { SetPermissionLevel } from '../../../globals'; import NewMentorProfile from './NewMentorProfile'; // ------------- MOCKS ------------- // mock being logged in -jest.mock('../../globals', () => ({ +jest.mock('../../../globals', () => ({ __esModule: true, SetPermissionLevel: jest.fn(), })); @@ -17,6 +17,41 @@ jest.mock('react-chartjs-2', () => ({ Line: () =>
, })); +// mock SweetAlert2 to avoid CSS parsing errors in jsdom +jest.mock('sweetalert2', () => { + const mockSwal = { + fire: jest.fn(() => Promise.resolve({ isConfirmed: true, isDenied: false, isDismissed: false })), + mixin: jest.fn(), + bindClickHandler: jest.fn(), + stopTimer: jest.fn(), + resumeTimer: jest.fn(), + toggleTimer: jest.fn(), + isTimerRunning: jest.fn(), + incrementTimer: jest.fn(), + showLoading: jest.fn(), + hideLoading: jest.fn(), + clickConfirm: jest.fn(), + clickCancel: jest.fn(), + clickDeny: jest.fn(), + close: jest.fn(), + enableButtons: jest.fn(), + disableButtons: jest.fn(), + showValidationMessage: jest.fn(), + resetValidationMessage: jest.fn(), + getInput: jest.fn(), + }; + return { + __esModule: true, + default: mockSwal, + }; +}); + +// mock Puzzles component to avoid SweetAlert2 CSS parsing issues +jest.mock('../../puzzles/puzzles-page/Puzzles', () => ({ + __esModule: true, + default: () =>
, +})); + // ------------- HELPER FUNCTIONS ------------- // helper to for date formatting to match with component diff --git a/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.tsx b/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.tsx similarity index 96% rename from react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.tsx rename to react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.tsx index f4ca1c10..20379019 100644 --- a/react-ystemandchess/src/Pages/NewMentorProfile/NewMentorProfile.tsx +++ b/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile.tsx @@ -1,15 +1,15 @@ import React, { useState, useEffect, useRef, useMemo } from "react"; import "./NewMentorProfile.scss"; -import Images from "../../images/imageImporter"; -import { SetPermissionLevel } from '../../globals'; +import Images from "../../../assets/images/imageImporter"; +import { SetPermissionLevel } from '../../../globals'; import { useCookies } from 'react-cookie'; -import { environment } from '../../environments/environment'; +import { environment } from "../../../environments/environment"; import { useNavigate } from "react-router"; -import StatsChart from "../NewStudentProfile/StatsChart"; -import Lessons from "../Lessons/Lessons"; -import LessonSelection from "../LessonsSelection/LessonsSelection"; -import LessonOverlay from "../piece-lessons/lesson-overlay/Lesson-overlay"; -import Puzzles from "../Puzzles/Puzzles"; +import StatsChart from "../../student/student-profile/StatsChart"; +import Lessons from "../../lessons/lessons-main/Lessons"; +import LessonSelection from "../../lessons/lessons-selection/LessonsSelection"; +import LessonOverlay from "../../lessons/piece-lessons/lesson-overlay/Lesson-overlay"; +import Puzzles from "../../puzzles/puzzles-page/Puzzles"; interface NewMentorProfileProps { userPortraitSrc: string; diff --git a/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile/NewMentorProfile.tsx b/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile/NewMentorProfile.tsx new file mode 100644 index 00000000..1c77cde5 --- /dev/null +++ b/react-ystemandchess/src/features/mentor/mentor-profile/NewMentorProfile/NewMentorProfile.tsx @@ -0,0 +1,436 @@ +import React, { useState, useEffect, useRef, useMemo } from "react"; +import "./NewMentorProfile.scss"; +import Images from "../../../../assets/images/imageImporter"; +import { SetPermissionLevel } from '../../../../globals'; +import { useCookies } from 'react-cookie'; +import { environment } from "../../../../environments/environment"; +import { useNavigate } from "react-router"; +import StatsChart from "../../../student/student-profile/StatsChart"; +import Lessons from "../../../lessons/lessons-main/Lessons"; +import LessonSelection from "../../../lessons/lessons-selection/LessonsSelection"; +import LessonOverlay from "../../../lessons/piece-lessons/lesson-overlay/Lesson-overlay"; +import Puzzles from "../../../puzzles/puzzles-page/Puzzles"; + +interface NewMentorProfileProps { + userPortraitSrc: string; + student?: Student; // optional student prop +} + +interface Student { + username: string; + firstName: string; + lastName: string; +} + +const NewMentorProfile: React.FC = ({ userPortraitSrc }) => { + + const [activeTab, setActiveTab] = useState("activity"); + const [cookies] = useCookies(['login']); + const navigate = useNavigate(); + + // mentor info + const [username, setUsername] = useState(""); + const [firstName, setFirstName] = useState(" "); + const [lastName, setLastName] = useState(" "); + const [hasStudent, setHasStudent] = useState(false); // if the mentor has a student + + // student usage stats in different modules + const [webTime, setWebTime] = useState(0); + const [playTime, setPlayTime] = useState(0); + const [lessonTime, setLessonTime] = useState(0); + const [puzzleTime, setPuzzleTime] = useState(0); + const [mentorTime, setMentorTime] = useState(0); + + // data for chart plotting + const [displayMonths, setDisplayMonths] = useState(6); // display data from 6 many months back + const [displayEvents, setDisplayEvents] = useState(["website", "play", "lesson", "puzzle", "mentor"]) + const [monthAxis, setMonthAxis] = useState(["Jan", "Feb", "Mar", "Apr", "May"]); // display the time as X-axis + const [dataAxis, setDataAxis] = useState<{[key: string]: number[]}>({website: [0, 0, 0, 0, 0],}); // time spent on events each month + + // student info + const [studentFirstName, setStudentFirstName] = useState(""); + const [studentLastName, setStudentLastName] = useState(""); + const [studentUsername, setStudentUsername] = useState(""); + + // event tracking for pagination + const [events, setEvents] = useState([]); + const [page, setPage] = useState(0); // page number + const [loading, setLoading] = useState(false); // if loading for more events + const [hasMore, setHasMore] = useState(true); + const containerRef = useRef(null); + + // current date for display + const [date, setDate] = useState(() => new Date().toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + }) + ); + + // states for lessons tab + const [lessonSelected, setLessonSelected] = useState(false); // whether user has navigated into a lesson + const [piece, setPiece] = useState(""); // lesson name for props + const [lessonNum, setLessonNum] = useState(0); // lesson number for props + + // Runs once upon first render + useEffect(()=>{ + fetchStudentData().catch(err => console.log(err)); // fetch student data when the component mounts + fetchUserData().catch(err => console.log(err)); // fetch user data when the component mounts + }, []) + + // Loads student data only after hasStudent has been updated + useEffect(() => { + if (hasStudent && studentUsername) { + // fetch student usage time stats to disaply in header + fetchUsageTime(studentUsername) + // fetch student data for graph plotting + fetchGraphData(studentUsername) + // fetch latest usage history to show in Activity tab + fetchActivity(studentUsername) + } + }, [hasStudent, studentUsername]) + + useEffect(() => { + const el = containerRef.current; + if (!el) return; + + const handleScroll = () => { + if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) { + fetchActivity(studentUsername); + } + }; + + el.addEventListener("scroll", handleScroll); + return () => el.removeEventListener("scroll", handleScroll); + }, [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) + } + } + + // 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); + } + + const fetchStudentData = async () => { + fetch(`${environment.urls.middlewareURL}/user/getMentorship`, { + method: 'GET', + headers: { 'Authorization': `Bearer ${cookies.login}` } + }).then(data => data.json()) + .then(data => { + if (data) { + setStudentFirstName(data.firstName); + setStudentLastName(data.lastName); + setStudentUsername(data.username); + 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 => { + console.log("Set student response:", data); + }); + } + +// fetch latest usage history (Activity Tab) + const fetchActivity = async (username) => { + if (loading || !hasMore) return; // return if already fetching or no more activity left + + setLoading(true); + const limit = 6; // fetch at most 6 activities at a time + const skip = page * limit; // skip over previously fetched data + + try { + // fetch another batch of usage history + const responseLatest = await fetch( + `${environment.urls.middlewareURL}/timeTracking/latest?username=${username}&limit=${limit}&skip=${skip}`, + { + 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 + 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}&events=${displayEvents.join(",")}&months=${displayMonths}`, + { + method: 'GET', + headers: { 'Authorization': `Bearer ${cookies.login}` } + } + ); + const data = await response.json(); + + const newDataAxis = {} as {[key: string]: number[]}; + for(let i = 0; i < displayEvents.length; i++) + { + // get time spent for each event for plotting + let event = displayEvents[i]; + let value = data[event]; + let months = value.map(item => item.monthText); // month list for ploting + let times = value.map(item => item.timeSpent); // timeSpent for plotting + + setMonthAxis(months); + newDataAxis[event] = times; + } + + // update for graph plotting + setDataAxis(newDataAxis); + } catch (err) { + console.error("Failed to fetch events", err); + } + } + + const handleTabClick = (tab: any) => { + setActiveTab(tab); + }; + + const handleNavigateEvent = (type: string, name: string) => { + if(type == "lesson") { + navigate("/lessons", { state: { piece: name } }); + } + } + + const renderTabContent = () => { + switch (activeTab) { + case "activity": + return ( +
+
+

Activity

+

{date}

+
+
+
+ {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} +

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

Loading more...

} + {!hasMore &&

No more activity left!

} +
+
+ ); + case "mentor": + return ( +
+

Mentor

+

This is the content for the Mentor tab.

+
+ ); + case "learning": + return ( +
+ +
+ ); + case "chessLessons": + return ( +
+ {lessonSelected ? ( + { + setLessonSelected(false); + }} styleType="profile"/> + ) : ( + { + setLessonSelected(true); + setPiece(selectedScenario); + setLessonNum(lessonNum); + }}/> + )} +
+ ); + case "games": + return ( +
+

Games

+

This is the content for the Games tab.

+
+ ); + case "puzzles": + return ( +
+ +
+ ); + 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.

+
+ ); + default: + return ( +
+

Select a tab to view its content.

+
+ ); + } + }; + + const tabContent = useMemo(() => renderTabContent(), [activeTab, events, loading, hasMore]); + + return ( +
+
+
+ user portrait + user portrait camera icon +
+
+

Hello, {firstName} {lastName}!

+
+
+ + { 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
  • +
+
+
+
+ + +
{tabContent}
+
+
) : ( +
+

No Student Selected

+

Please select a student to view their progress.

+
+ )} +
+ ); +}; + +export default NewMentorProfile; diff --git a/react-ystemandchess/src/features/programs/Programs.scss b/react-ystemandchess/src/features/programs/Programs.scss new file mode 100644 index 00000000..735b0340 --- /dev/null +++ b/react-ystemandchess/src/features/programs/Programs.scss @@ -0,0 +1,252 @@ +.programs-container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem 1rem; +} + +.hero-wrapper { + background: linear-gradient(145deg, #f0f8ec 0%, #ffffff 100%); + border-radius: 20px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.07); + padding: 4rem 2rem; + margin-bottom: 5rem; + position: relative; +} + +.hero-wrapper::after { + content: ""; + position: absolute; + bottom: -20px; + left: 50%; + transform: translateX(-50%); + width: 60px; + height: 4px; + border-radius: 2px; + background: #73b313; +} + +.hero-section { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 4rem; + position: relative; +} + +.hero-section img { + flex: 1 1 400px; + min-width: 300px; + max-width: 500px; + width: 100%; + border-radius: 20px; + object-fit: cover; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); +} + +.programs-text { + flex: 1 1 400px; + min-width: 300px; + max-width: 600px; + display: flex; + flex-direction: column; + gap: 1.2rem; + text-align: left; + color: #0f172a; + line-height: 1.7; +} + +.programs-text h2 { + font-size: 2.5rem; + font-weight: 700; + margin: 0; +} + +.programs-text h4 { + font-size: 1.2rem; + font-weight: 500; + margin: 0; +} + +.programs-text p { + font-size: 1.1rem; + margin: 0; +} + +.programs-text ul { + margin: 0; + padding-left: 1.5rem; +} + +.programs-text li { + font-size: 1.1rem; + margin-bottom: 0.4rem; +} + +.programs-text a button { + margin-top: 1rem; + background: #0f172a; + color: #fff; + border: none; + border-radius: 9999px; + padding: 0.8rem 2rem; + font-size: 1rem; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.programs-text a button:hover { + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); +} + +.sub-terms { + display: flex; + flex-wrap: wrap; + gap: 3rem; + justify-content: center; +} + +.sub-terms-left, +.sub-terms-right { + position: relative; + display: flex; + flex-direction: column; + flex: 1 1 350px; + max-width: 500px; + min-width: 300px; + padding: 2.5rem; + border-radius: 20px; + backdrop-filter: blur(10px); + transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); + transform-origin: center; + will-change: transform; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); +} + +.sub-terms-left:hover, +.sub-terms-right:hover { + transform: translateY(-10px) scale(1.02); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); +} + +.sub-terms-left { + background: linear-gradient(145deg, #73b313 0%, #8bc34a 100%); + color: #ffffff; +} + +.sub-terms-right { + background: linear-gradient(145deg, #ffffff 0%, #f8f9ff 100%); + color: #333; + border: 2px solid #73b313; +} + +.sub-terms-left h3, +.sub-terms-right h3 { + font-size: 2rem; + font-weight: 700; + text-align: center; + margin: 0 0 1rem 0; +} + +.sub-terms-left h5, +.sub-terms-right h5 { + font-size: 1.3rem; + font-weight: 600; + text-align: center; + margin: 0 0 1rem 0; +} + +.sub-terms-left p, +.sub-terms-right p { + font-size: 1.1rem; + line-height: 1.6; + margin: 0 0 1rem 0; +} + +@media (max-width: 768px) { + .hero-section { + flex-direction: column; + text-align: center; + } + + .programs-text { + text-align: center; + align-items: center; + } + + .programs-text ul { + text-align: left; + } + + .sub-terms { + flex-direction: column; + } + + .sub-terms-left, + .sub-terms-right { + width: 100%; + max-width: 500px; + margin: 0 auto; + } +} + +/* ----------- Responsive styles ----------- */ +@media (max-width: 768px) { + .hero-section { + flex-direction: column; + margin: 2rem 1rem; + gap: 2rem; + padding: 1.5rem; + + img { + width: 100%; + height: auto; + } + + .programs-text { + max-width: 100%; + text-align: center; + + h2 { + font-size: 2rem; + } + h4 { + font-size: 1.2rem; + } + p { + font-size: 1rem; + } + + ul li { + font-size: 1rem; + text-align: left; + } + + a button { + width: 100%; + } + } + } + + .sub-terms { + flex-direction: column; + margin: 2rem 1rem; + gap: 2rem; + + .sub-terms-left, + .sub-terms-right { + font-size: 1.1rem; + padding: 1.5rem; + } + + h3 { + font-size: 1.8rem; + } + + h5 { + font-size: 1.3rem; + } + } +} diff --git a/react-ystemandchess/src/Pages/Programs/Programs.test.tsx b/react-ystemandchess/src/features/programs/Programs.test.tsx similarity index 100% rename from react-ystemandchess/src/Pages/Programs/Programs.test.tsx rename to react-ystemandchess/src/features/programs/Programs.test.tsx diff --git a/react-ystemandchess/src/Pages/Programs/Programs.tsx b/react-ystemandchess/src/features/programs/Programs.tsx similarity index 98% rename from react-ystemandchess/src/Pages/Programs/Programs.tsx rename to react-ystemandchess/src/features/programs/Programs.tsx index 301d74c1..09958cdf 100644 --- a/react-ystemandchess/src/Pages/Programs/Programs.tsx +++ b/react-ystemandchess/src/features/programs/Programs.tsx @@ -1,5 +1,5 @@ import React from "react"; -import kidsCoding from "../../images/kidsCoding.png"; +import kidsCoding from "../../assets/images/kidsCoding.png"; import "./Programs.scss"; const Programs = () => { return ( diff --git a/react-ystemandchess/src/Pages/Puzzles/Puzzles-profile.module.scss b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles-profile.module.scss similarity index 100% rename from react-ystemandchess/src/Pages/Puzzles/Puzzles-profile.module.scss rename to react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles-profile.module.scss diff --git a/react-ystemandchess/src/Pages/Puzzles/Puzzles.module.scss b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.module.scss similarity index 94% rename from react-ystemandchess/src/Pages/Puzzles/Puzzles.module.scss rename to react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.module.scss index bffbb49b..be5e522c 100644 --- a/react-ystemandchess/src/Pages/Puzzles/Puzzles.module.scss +++ b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.module.scss @@ -1,6 +1,6 @@ body { background-color: #e5f3d2; - background-image: url("../../images/chess-piece-pattern.png"); + background-image: url("../../../assets/images/chess-piece-pattern.png"); } .mainElements { diff --git a/react-ystemandchess/src/Pages/Puzzles/Puzzles.test.tsx b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.test.tsx similarity index 99% rename from react-ystemandchess/src/Pages/Puzzles/Puzzles.test.tsx rename to react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.test.tsx index 8a6d110b..1029e8d0 100644 --- a/react-ystemandchess/src/Pages/Puzzles/Puzzles.test.tsx +++ b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.test.tsx @@ -5,7 +5,7 @@ import { MemoryRouter } from "react-router"; import Swal from "sweetalert2"; // Mock environment and chessClientURL for iframe src -jest.mock("../../environments/environment", () => ({ +jest.mock("../../../core/environments/environment", () => ({ environment: { urls: { chessClientURL: "http://localhost:3000", diff --git a/react-ystemandchess/src/Pages/Puzzles/Puzzles.tsx b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.tsx similarity index 99% rename from react-ystemandchess/src/Pages/Puzzles/Puzzles.tsx rename to react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.tsx index 80974fe8..36518693 100644 --- a/react-ystemandchess/src/Pages/Puzzles/Puzzles.tsx +++ b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.tsx @@ -2,11 +2,11 @@ import React, { useRef, useState, useEffect } from "react"; import pageStyles from "./Puzzles.module.scss"; import profileStyles from "./Puzzles-profile.module.scss"; import { Chess } from "chess.js"; -import { themesName, themesDescription } from '../../services/themesService'; +import { themesName, themesDescription } from "../../../core/services/themesService"; import Swal from 'sweetalert2'; -import { environment } from "../../environments/environment"; +import { environment } from "../../../environments/environment"; import { v4 as uuidv4 } from "uuid"; -import { SetPermissionLevel } from "../../globals"; +import { SetPermissionLevel } from "../../../globals"; import { useCookies } from 'react-cookie'; const chessClientURL = environment.urls.chessClientURL; diff --git a/react-ystemandchess/src/features/student/student-inventory/Student-Inventory/StudentInventory.tsx b/react-ystemandchess/src/features/student/student-inventory/Student-Inventory/StudentInventory.tsx new file mode 100644 index 00000000..8651e0b8 --- /dev/null +++ b/react-ystemandchess/src/features/student/student-inventory/Student-Inventory/StudentInventory.tsx @@ -0,0 +1,820 @@ +import React, { useState, useEffect } from "react"; +import "./StudentInventory.scss"; +import Images from "../../../../assets/images/imageImporter"; +import { createChessBoard, isInBounds, getPawnMoves, getRookMoves, getKnightMoves, getBishopMoves, getKingMoves, getQueenMoves } from '../../../lessons/lessons-main/Lessons' +import LessonSelection from "../../../lessons/lessons-selection/LessonsSelection"; +import Lessons from '../../../lessons/lessons-main/Lessons'; + +type Board = (string | null)[][]; +type Piece = { + color: string; + type: string; // e.g., 'Pawn', 'Rook', etc. +}; + + +const StudentInventory = ({ userPortraitSrc, userName }: any) => { + // bring chessboard + const [board, setBoard] = useState(initializeBoard()); // Initialize the board with chess pieces + const [highlightedSquares, setHighlightedSquares] = useState([]); + const [draggingPiece, setDraggingPiece] = useState(null); // Track which piece is being dragged + + // Description for each Scenarios + const [scenarioDescription, setScenarioDescription] = useState(""); + const [scenarioDescription_2, setScenarioDescription_2] = useState(""); + const [pieceDescription, setpieceDescription] = useState(""); + + // State for showing scenario buttons for pieces + const [showScenarios, setShowScenarios] = useState({ + pawn: false, + rook: false, + bishop: false, + knight: false, + queen: false, + king: false, + }); + + const [showPopup, setShowPopup] = useState(false); // Popup state + const [trainingStarted, setTrainingStarted] = useState(false); // Training state + + // Initialize the chessboard + function initializeBoard(): any { + return [ + [null, null, null, null, null, null, null, null], // Empty row + [null, null, null, null, null, null, null, null], // Empty row + [null, null, null, null, null, null, null, null], // Empty row + [null, null, null, null, null, null, null, null], // Empty row + [null, null, null, null, null, null, null, null], // Empty row + [null, null, null, null, null, null, null, null], // Empty row + [null, null, null, null, null, null, null, null], // Empty row + [null, null, null, null, null, null, null, null], // Empty row + ]; + } + + // Popup + // Function to check if all black pieces are removed + const checkBlackPieces = () => { + const blackPieces = board.flat().filter((piece: string[]) => piece && piece[0] === 'b'); // Filter out black pieces + if (blackPieces.length === 0 && trainingStarted === true) { + setShowPopup(true); // Show the popup + } + }; + + // Reset the chessboard when the popup confirm button is clicked + const handlePopupConfirm = () => { + if (trainingStarted === true) { + setShowPopup(false); + setBoard(initializeBoard()); // Reset the chessboard + setTrainingStarted(false); // Reset training state + setScenarioDescription(""); + setScenarioDescription_2(""); + setpieceDescription(""); + } + }; + + // Check for black pieces every time the board state changes + useEffect(() => { + checkBlackPieces(); + }, [board]); + + // Update the setupScenario function to handle both Pawn and Rook + const setupScenario = (piece: any, scenario: any) => { + const updatedBoard = initializeBoard(); // Reset board + + // setup the board by scenario + switch (piece) { + case 'pawn': + setpieceDescription("Pawn - It moves forward only") + setScenarioDescription("Try this!"); + switch (scenario) { + case 'basic': + updatedBoard[4][0] = 'wP'; // a5 + updatedBoard[5][5] = 'bP'; // f3 + setScenarioDescription_2("Pawns move one square only. But when they reach the other side of the board, they become a stronger piece!"); + setTrainingStarted(true); // Mark training as started + break; + case 'capture': + updatedBoard[5][4] = 'wP'; // e3 + updatedBoard[3][3] = 'bP'; // d5 + updatedBoard[2][2] = 'bP'; // c6 + updatedBoard[1][3] = 'bP'; // d7 + setScenarioDescription_2("A pawn on the second rank can move 2 squares at once!"); + setTrainingStarted(true); // Mark training as started + break; + case 'training_1': + updatedBoard[5][1] = 'wP'; // b3 + updatedBoard[1][2] = 'bP'; // c7 + updatedBoard[2][1] = 'bP'; // b6 + updatedBoard[2][2] = 'bP'; // c6 + updatedBoard[2][3] = 'bP'; // d6 + updatedBoard[4][1] = 'bP'; // b4 + updatedBoard[4][2] = 'bP'; // c4 + setScenarioDescription_2("Capture black pawns and promote!"); + setTrainingStarted(true); // Mark training as started + break; + case 'training_2': + updatedBoard[5][3] = 'wP'; // d3 + updatedBoard[0][2] = 'bP'; // c8 + updatedBoard[1][3] = 'bP'; // d7 + updatedBoard[2][1] = 'bP'; // b6 + updatedBoard[2][4] = 'bP'; // e6 + updatedBoard[3][1] = 'bP'; // b5 + updatedBoard[3][3] = 'bP'; // d5 + updatedBoard[4][2] = 'bP'; // c4 + setScenarioDescription_2("Capture black pawns and promote!"); + setTrainingStarted(true); // Mark training as started + break; + case 'training_3': + updatedBoard[5][0] = 'wP'; // a3 + updatedBoard[5][2] = 'wP'; // c3 + updatedBoard[5][3] = 'wP'; // d3 + updatedBoard[5][7] = 'wP'; // h3 + updatedBoard[3][1] = 'bP'; // b5 + updatedBoard[3][2] = 'bP'; // c5 + updatedBoard[3][4] = 'bP'; // e5 + updatedBoard[4][3] = 'bP'; // d4 + updatedBoard[4][6] = 'bP'; // g4 + setScenarioDescription_2("No need to promote. Capture all black pawns."); + setTrainingStarted(true); // Mark training as started + break; + case 'special_move': + updatedBoard[6][4] = 'wP'; // e2 + updatedBoard[2][3] = 'bP'; // d6 + setScenarioDescription_2("A pawn on the second rank can move two squares forward."); + setTrainingStarted(true); // Mark training as started + break; + default: + break; + } + break; + case 'rook': + setpieceDescription("Rook - It moves in straight lines") + setScenarioDescription("Try this!"); + switch (scenario) { + case 'basic': + updatedBoard[6][4] = 'wR'; // e2 + updatedBoard[2][4] = 'bP'; // e6 + setScenarioDescription_2("Click on the rook to bring it to the pawn!"); + setTrainingStarted(true); + break; + case 'training_1': + updatedBoard[1][2] = 'wR'; // c7 + updatedBoard[3][2] = 'bP'; // c5 + updatedBoard[3][5] = 'bP'; // f5 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + case 'training_2': + updatedBoard[5][4] = 'wR'; // e3 + updatedBoard[5][1] = 'bP'; // b3 + updatedBoard[6][7] = 'bP'; // h2 + updatedBoard[5][7] = 'bP'; // h3 + setScenarioDescription_2("The fewer moves you make, the better!"); + setTrainingStarted(true); + break; + case 'training_3': + updatedBoard[0][7] = 'wR'; // h8 + updatedBoard[0][5] = 'bP'; // f8 + updatedBoard[7][6] = 'bP'; // g1 + updatedBoard[1][6] = 'bP'; // g7 + updatedBoard[0][6] = 'bP'; // g8 + updatedBoard[1][7] = 'bP'; // h7 + setScenarioDescription_2("The fewer moves you make, the better!"); + setTrainingStarted(true); + break; + case 'training_4': + updatedBoard[1][2] = 'wR'; // c7 + updatedBoard[4][4] = 'wR'; // e4 + updatedBoard[4][0] = 'bP'; // a4 + updatedBoard[5][6] = 'bP'; // g3 + updatedBoard[1][6] = 'bP'; // g7 + updatedBoard[4][7] = 'bP'; // h4 + setScenarioDescription_2("Use two rooks to speed things up!"); + setTrainingStarted(true); + break; + case 'final': + updatedBoard[7][0] = 'wR'; // a1 + updatedBoard[5][5] = 'wR'; // f3 + updatedBoard[1][1] = 'bP'; // b7 + updatedBoard[7][3] = 'bP'; // d1 + updatedBoard[3][3] = 'bP'; // d5 + updatedBoard[6][5] = 'bP'; // f2 + updatedBoard[1][5] = 'bP'; // f7 + updatedBoard[3][6] = 'bP'; // g4 + updatedBoard[1][6] = 'bP'; // g7 + setScenarioDescription_2("Use two rooks to speed things up!"); + setTrainingStarted(true); + break; + + default: + break; + } + break; + case "bishop": + setpieceDescription("Bishop - It moves diagonally"); + setScenarioDescription("Try this!"); + switch (scenario) { + case 'basic': + updatedBoard[6][6] = 'wB'; // g2 + updatedBoard[1][7] = 'bP'; // h7 + updatedBoard[4][4] = 'bP'; // e4 + setScenarioDescription_2("Grab all the black pawns!"); + setTrainingStarted(true); + break; + case 'training_1': + updatedBoard[5][1] = 'wB'; // b3 + updatedBoard[6][0] = 'bP'; // a2 + updatedBoard[7][1] = 'bP'; // b1 + updatedBoard[3][1] = 'bP'; // b5 + updatedBoard[7][3] = 'bP'; // d1 + updatedBoard[5][3] = 'bP'; // d3 + updatedBoard[6][4] = 'bP'; // e2 + setScenarioDescription_2("The fewer moves you make, the better!"); + setTrainingStarted(true); + break; + case 'training_2': + updatedBoard[4][2] = 'wB'; // c4 + updatedBoard[4][0] = 'bP'; // a4 + updatedBoard[7][1] = 'bP'; // b1 + updatedBoard[5][1] = 'bP'; // b3 + updatedBoard[6][2] = 'bP'; // c2 + updatedBoard[5][3] = 'bP'; // d3 + updatedBoard[4][4] = 'bP'; // e2 + setScenarioDescription_2("Grab all the black pawns!"); + setTrainingStarted(true); + break; + case 'training_3': + updatedBoard[7][2] = 'wB'; // c1 + updatedBoard[7][5] = 'wB'; // f1 + updatedBoard[5][3] = 'bP'; // d3 + updatedBoard[5][4] = 'bP'; // e3 + updatedBoard[4][3] = 'bP'; // d4 + updatedBoard[4][4] = 'bP'; // e4 + updatedBoard[3][3] = 'bP'; // d5 + updatedBoard[3][4] = 'bP'; // e5 + setScenarioDescription_2("One light-squared bishop, one dark-squared bishop. You need both!"); + setTrainingStarted(true); + break; + case 'training_4': + updatedBoard[4][3] = 'wB'; // d4 + updatedBoard[7][0] = 'bP'; // a1 + updatedBoard[2][1] = 'bP'; // b6 + updatedBoard[7][2] = 'bP'; // c1 + updatedBoard[5][4] = 'bP'; // e3 + updatedBoard[1][6] = 'bP'; // g7 + updatedBoard[2][7] = 'bP'; // h6 + setScenarioDescription_2("Grab all the black pawns!"); + setTrainingStarted(true); + break; + case 'final': + updatedBoard[5][2] = 'wB'; // c3 + updatedBoard[1][3] = 'wB'; // d7 + updatedBoard[5][0] = 'bP'; // a3 + updatedBoard[6][2] = 'bP'; // c2 + updatedBoard[1][4] = 'bP'; // e7 + updatedBoard[3][5] = 'bP'; // f5 + updatedBoard[2][5] = 'bP'; // f6 + updatedBoard[0][6] = 'bP'; // g8 + updatedBoard[4][7] = 'bP'; // h4 + updatedBoard[1][7] = 'bP'; // h7 + setScenarioDescription_2("One light-squared bishop, one dark-squared bishop. You need both!"); + setTrainingStarted(true); + break; + default: + break; + } + break; + + case 'knight': + setpieceDescription("Knight - It moves in an 'L' shape"); + setScenarioDescription("Try this!"); + switch (scenario) { + case 'basic': + updatedBoard[4][4] = 'wN'; // e4 + updatedBoard[3][2] = 'bP'; // c5 + updatedBoard[1][3] = 'bP'; // d7 + setScenarioDescription_2("Knights have a fancy way of jumping around!"); + setTrainingStarted(true); + break; + + case 'training_1': + updatedBoard[7][1] = 'wN'; // b1 + updatedBoard[5][2] = 'bP'; // c3 + updatedBoard[4][3] = 'bP'; // d4 + updatedBoard[6][4] = 'bP'; // e2 + updatedBoard[5][5] = 'bP'; // f3 + updatedBoard[3][6] = 'bP'; // g5 + updatedBoard[1][5] = 'bP'; // f7 + updatedBoard[0][7] = 'bP'; // h8 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + + case 'training_2': + updatedBoard[1][2] = 'wN'; // c7 + updatedBoard[2][1] = 'bP'; // b6 + updatedBoard[3][3] = 'bP'; // d5 + updatedBoard[1][3] = 'bP'; // d7 + updatedBoard[2][4] = 'bP'; // e6 + updatedBoard[4][5] = 'bP'; // f4 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + + case 'training_3': + updatedBoard[5][5] = 'wN'; // f3 + updatedBoard[6][4] = 'bP'; // e2 + updatedBoard[5][4] = 'bP'; // e3 + updatedBoard[4][4] = 'bP'; // e4 + updatedBoard[6][5] = 'bP'; // f2 + updatedBoard[4][5] = 'bP'; // f4 + updatedBoard[6][6] = 'bP'; // g2 + updatedBoard[5][6] = 'bP'; // g3 + updatedBoard[4][6] = 'bP'; // g4 + setScenarioDescription_2("Knights can jump over obstacles! Escape and vanquish the pawns!"); + setTrainingStarted(true); + break; + + case 'training_4': + updatedBoard[5][3] = 'wN'; // d3 + updatedBoard[5][2] = 'bP'; // c3 + updatedBoard[6][4] = 'bP'; // e2 + updatedBoard[4][4] = 'bP'; // e4 + updatedBoard[6][5] = 'bP'; // f2 + updatedBoard[4][5] = 'bP'; // f4 + updatedBoard[2][6] = 'bP'; // g6 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + + case 'final': + updatedBoard[1][2] = 'wN'; // c7 + updatedBoard[4][1] = 'bP'; // b4 + updatedBoard[3][1] = 'bP'; // b5 + updatedBoard[2][2] = 'bP'; // c6 + updatedBoard[0][2] = 'bP'; // c8 + updatedBoard[4][3] = 'bP'; // d4 + updatedBoard[3][3] = 'bP'; // d5 + updatedBoard[5][4] = 'bP'; // e3 + updatedBoard[1][4] = 'bP'; // e7 + updatedBoard[3][5] = 'bP'; // f5 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + + } + break; + + case 'queen': + setpieceDescription("Queen - Queen = rook + bishop"); + setScenarioDescription("Try this!"); + switch (scenario) { + case 'basic': + updatedBoard[6][4] = 'wQ'; // e2 + updatedBoard[1][2] = 'bP'; // c7 + updatedBoard[3][4] = 'bP'; // e5 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + case 'training_1': + updatedBoard[4][3] = 'wQ'; // d4 + updatedBoard[5][0] = 'bP'; // a3 + updatedBoard[6][5] = 'bP'; // f2 + updatedBoard[0][5] = 'bP'; // f8 + updatedBoard[5][7] = 'bP'; // h3 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + case 'training_2': + updatedBoard[4][2] = 'wQ'; // c4 + updatedBoard[5][0] = 'bP'; // a3 + updatedBoard[2][3] = 'bP'; // d6 + updatedBoard[7][5] = 'bP'; // f1 + updatedBoard[0][5] = 'bP'; // f8 + updatedBoard[5][6] = 'bP'; // g3 + updatedBoard[2][7] = 'bP'; // h6 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + case 'training_3': + updatedBoard[1][6] = 'wQ'; // g7 + updatedBoard[6][0] = 'bP'; // a2 + updatedBoard[3][1] = 'bP'; // b5 + updatedBoard[5][3] = 'bP'; // d3 + updatedBoard[7][6] = 'bP'; // g1 + updatedBoard[0][6] = 'bP'; // g8 + updatedBoard[6][7] = 'bP'; // h2 + updatedBoard[3][7] = 'bP'; // h5 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + case 'final': + updatedBoard[7][4] = 'wQ'; // e1 + updatedBoard[2][0] = 'bP'; // a6 + updatedBoard[7][3] = 'bP'; // d1 + updatedBoard[6][5] = 'bP'; // f2 + updatedBoard[2][5] = 'bP'; // f6 + updatedBoard[2][6] = 'bP'; // g6 + updatedBoard[0][6] = 'bP'; // g8 + updatedBoard[7][7] = 'bP'; // h1 + updatedBoard[4][7] = 'bP'; // h4 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + + default: + break; + } + break; + + case 'king': + setpieceDescription("King - The most important piece"); + setScenarioDescription("Try this!"); + switch (scenario) { + case 'basic': + updatedBoard[6][3] = 'wK'; // d2 + updatedBoard[2][3] = 'bP'; // d6 + setScenarioDescription_2("The king is slow."); + setTrainingStarted(true); + break; + case 'training': + updatedBoard[7][4] = 'wK'; // e1 + updatedBoard[6][2] = 'bP'; // c2 + updatedBoard[5][3] = 'bP'; // d3 + updatedBoard[6][4] = 'bP'; // e2 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + case 'final': + updatedBoard[3][4] = 'wK'; // e5 + updatedBoard[4][2] = 'bP'; // c4 + updatedBoard[3][2] = 'bP'; // c5 + updatedBoard[3][3] = 'bP'; // d5 + updatedBoard[5][4] = 'bP'; // e3 + updatedBoard[5][5] = 'bP'; // f3 + updatedBoard[4][6] = 'bP'; // g4 + setScenarioDescription_2("Grab all the pawns!"); + setTrainingStarted(true); + break; + + default: + break; + } + break; + + case 'basic_checkmate_1': + setpieceDescription("piece checkmate 1 Basic checkmates"); + setScenarioDescription("Try this!"); + + switch (scenario) { + case 'queen_and_rook_mate': + updatedBoard[7][0] = 'wQ'; // a1 + updatedBoard[5][4] = 'wK'; // e3 + updatedBoard[7][7] = 'wR'; // h1 + updatedBoard[2][3] = 'bK'; // d6 + setScenarioDescription("Use your queen and rook to restrict the king and deliver checkmate. Mate in 3 if played perfectly."); + setTrainingStarted(true); + break; + + case 'two_rook_mate': + updatedBoard[7][0] = 'wR'; // a1 + updatedBoard[7][7] = 'wR'; // h1 + updatedBoard[5][4] = 'wK'; // e3 + updatedBoard[2][3] = 'bK'; // d6 + setScenarioDescription("Use your rooks to restrict the king and deliver checkmate. Mate in 4 if played perfectly."); + setTrainingStarted(true); + break; + + case 'queen_and_bishop_mate': + updatedBoard[5][2] = 'wQ'; // c3 + updatedBoard[5][3] = 'wB'; // d3 + updatedBoard[5][4] = 'wK'; // e3 + updatedBoard[2][3] = 'bK'; // d6 + setScenarioDescription("Use your queen and bishop to restrict the king and deliver checkmate. Mate in 5 if played perfectly."); + setTrainingStarted(true); + break; + + case 'queen_and_knight_mate': + updatedBoard[5][2] = 'wQ'; // c3 + updatedBoard[5][3] = 'wN'; // d3 + updatedBoard[5][4] = 'wK'; // e3 + updatedBoard[2][3] = 'bK'; // d6 + setScenarioDescription("Use your queen and knight to restrict the king and deliver checkmate. Mate in 5 if played perfectly."); + setTrainingStarted(true); + break; + + case 'queen_mate': + updatedBoard[7][4] = 'wQ'; // e1 + updatedBoard[5][4] = 'wK'; // e3 + updatedBoard[2][3] = 'bK'; // d6 + setScenarioDescription("Use your queen to restrict the king, force it to the edge of the board and deliver checkmate. The queen can't do it alone, so use your king to help. Mate in 6 if played perfectly."); + setTrainingStarted(true); + break; + + case 'rook_mate': + updatedBoard[7][4] = 'wR'; // e1 + updatedBoard[5][4] = 'wK'; // e3 + updatedBoard[2][3] = 'bK'; // d6 + setScenarioDescription("Use your rook to restrict the king, force it to the edge of the board and deliver checkmate. The rook can't do it alone, so use your king to help. Mate in 11 if played perfectly."); + setTrainingStarted(true); + break; + + default: + console.error("Scenario not found."); + break; + } + break; + + + default: + break; + } + setBoard(updatedBoard); + setHighlightedSquares([]); // clear highlight + }; + + // Button click handlers + // Generic function to handle all piece button clicks + const handlePieceClick = (piece: string) => { + setShowScenarios({ + pawn: piece === 'pawn', + rook: piece === 'rook', + bishop: piece === 'bishop', + knight: piece === 'knight', + queen: piece === 'queen', + king: piece === 'king', + }); + }; + + const handlePawnClick = () => handlePieceClick('pawn'); + const handleRookClick = () => handlePieceClick('rook'); + const handleBishopClick = () => handlePieceClick('bishop'); + const handleKnightClick = () => handlePieceClick('knight'); + const handleQueenClick = () => handlePieceClick('queen'); + const handleKingClick = () => handlePieceClick('king'); + + // Helper function to get possible moves for a piece + const getPieceMoves = (piece: any[], position: { split: any; }) => { + const color = piece[0]; // Get color from the piece (first character) + switch (piece[1]) { + case 'P': + return getPawnMoves(position, color === 'w', board); + case 'R': + return getRookMoves(position, color === 'w', board); // Pass color directly + case 'N': + return getKnightMoves(position, color === 'w', board); // Pass color directly + case 'B': + return getBishopMoves(position, color === 'w', board); // Pass color directly + case 'K': + return getKingMoves(position, color === 'w', board); // Pass color directly + case 'Q': + return getQueenMoves(position, color === 'w', board); // Pass color directly + default: + return []; + } + }; + + // Handle hover to show possible moves + const handleSquareHover = (key: { split: any; }) => { + const [row, col] = key.split('-').map(Number); + const piece = board[row][col]; + + // Clear previous highlights + setHighlightedSquares([]); + + if (piece) { + const possibleMoves = getPieceMoves(piece, key); + setHighlightedSquares(possibleMoves); // Highlight valid move squares + } else { + // Check if the square has an opponent's piece + const targetPiece = board[row][col]; + if (targetPiece && targetPiece[0] !== draggingPiece?.piece[0]) { + setHighlightedSquares((prev: any) => [...prev, key]); // Highlight the opponent's piece square + } + } + }; + + // Handle drag start + const handleDragStart = (e: any, piece: any, position: any) => { + setDraggingPiece({ piece, position }); + e.dataTransfer.setDragImage(e.target, 20, 20); // Set the drag image with a specified offset + }; + + // Handle drop on a square + const handleDrop = (key: any) => { + if (highlightedSquares.includes(key)) { + const [startRow, startCol] = draggingPiece.position.split('-').map(Number); + const [endRow, endCol] = key.split('-').map(Number); + + const targetPiece = board[endRow][endCol]; + + if (targetPiece && targetPiece.color !== draggingPiece.piece.color) { + console.log(`Captured ${targetPiece.type}`); + } + + const updatedBoard = [...board]; + updatedBoard[endRow][endCol] = draggingPiece.piece; // Move piece to new square + updatedBoard[startRow][startCol] = null; // Clear old square + + // Check if the moved piece is a pawn reaching the promotion rank + if (draggingPiece.piece[1] === 'P' && (endRow === 0 || endRow === 7)) { + promotePawn(key); // Call promote function + } else { + setBoard(updatedBoard); // Update board state + } + } + setDraggingPiece(null); + setHighlightedSquares([]); + }; + + // Handle drag over a square (allow dropping) + const handleDragOver = (e: any) => { + e.preventDefault(); // Prevent default behavior to allow dropping + }; + + // Update promotePawn function to set the board state + function promotePawn(position: any) { + const [row, col] = position.split('-').map(Number); + const updatedBoard = [...board]; + const color = board[row][col][0]; // Determine color of the pawn + const newPiece = color === 'w' ? 'wQ' : 'bQ'; // Promote to Queen + + updatedBoard[row][col] = newPiece; // Update the board with the new queen + setBoard(updatedBoard); // Set the new board state + } + + const [activeTab, setActiveTab] = useState("activity"); + + const handleTabClick = (tab: any) => { + setActiveTab(tab); + }; + + const renderTabContent = () => { + switch (activeTab) { + case "activity": + return ( +
+
+

Activity

+

May 2024

+
+
+
+
+
+
+

May 24 2024

+

7:00 PM

+
+
+

Solved 2 tactical puzzles.

+
+
+
+
+
+

May 19 2024

+

3:00 PM

+
+
+

Practiced 7 positions on Piece Checkmates I.

+
+
+
+
+
+

May 16 2024

+

4:00 PM

+
+
+

Completed 100 games of chess.

+
+
+
+
+ ); + 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 ( +
+

Learning

+

This is the content for the 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 "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.

+
+ ); + default: + return ( +
+

Select a tab to view its content.

+
+ ); + } + }; + + return ( +
+
+
+ user portrait + user portrait camera icon +
+
+

Hello, {userName}!

+
+
+ +
+
+

Your Progress

+
+
+ + +
{renderTabContent()}
+
+
+
+ ); +}; + +export default StudentInventory; diff --git a/react-ystemandchess/src/Pages/Student-Inventory/StudentInventory.scss b/react-ystemandchess/src/features/student/student-inventory/StudentInventory.scss similarity index 99% rename from react-ystemandchess/src/Pages/Student-Inventory/StudentInventory.scss rename to react-ystemandchess/src/features/student/student-inventory/StudentInventory.scss index 279c1471..a99b4f42 100644 --- a/react-ystemandchess/src/Pages/Student-Inventory/StudentInventory.scss +++ b/react-ystemandchess/src/features/student/student-inventory/StudentInventory.scss @@ -15,7 +15,7 @@ $inventory-tab-active: linear-gradient( #main-inventory-content { background-color: $brand-color-primary; - background-image: url("../../images/chess-piece-pattern.png"); + background-image: url("../../../assets/images/chess-piece-pattern.png"); background-size: 95%; background-repeat: repeat; font-family: "Roboto", sans-serif; diff --git a/react-ystemandchess/src/Pages/Student-Inventory/StudentInventory.tsx b/react-ystemandchess/src/features/student/student-inventory/StudentInventory.tsx similarity index 99% rename from react-ystemandchess/src/Pages/Student-Inventory/StudentInventory.tsx rename to react-ystemandchess/src/features/student/student-inventory/StudentInventory.tsx index ed4d402e..900c2ccc 100644 --- a/react-ystemandchess/src/Pages/Student-Inventory/StudentInventory.tsx +++ b/react-ystemandchess/src/features/student/student-inventory/StudentInventory.tsx @@ -1,9 +1,9 @@ import React, { useState, useEffect } from "react"; import "./StudentInventory.scss"; -import Images from "../../images/imageImporter"; -import { createChessBoard, isInBounds, getPawnMoves, getRookMoves, getKnightMoves, getBishopMoves, getKingMoves, getQueenMoves } from '../Lessons/Lessons' -import LessonSelection from "../LessonsSelection/LessonsSelection"; -import Lessons from '../Lessons/Lessons'; +import Images from "../../../assets/images/imageImporter"; +import { createChessBoard, isInBounds, getPawnMoves, getRookMoves, getKnightMoves, getBishopMoves, getKingMoves, getQueenMoves } from '../../lessons/lessons-main/Lessons' +import LessonSelection from "../../lessons/lessons-selection/LessonsSelection"; +import Lessons from '../../lessons/lessons-main/Lessons'; type Board = (string | null)[][]; type Piece = { diff --git a/react-ystemandchess/src/Pages/Student/Student.scss b/react-ystemandchess/src/features/student/student-page/Student.scss similarity index 100% rename from react-ystemandchess/src/Pages/Student/Student.scss rename to react-ystemandchess/src/features/student/student-page/Student.scss diff --git a/react-ystemandchess/src/Pages/Student/Student.tsx b/react-ystemandchess/src/features/student/student-page/Student.tsx similarity index 93% rename from react-ystemandchess/src/Pages/Student/Student.tsx rename to react-ystemandchess/src/features/student/student-page/Student.tsx index 889540c4..619bf108 100644 --- a/react-ystemandchess/src/Pages/Student/Student.tsx +++ b/react-ystemandchess/src/features/student/student-page/Student.tsx @@ -1,6 +1,6 @@ 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; diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/ActivitiesModal.scss b/react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.scss similarity index 98% rename from react-ystemandchess/src/Pages/NewStudentProfile/Modals/ActivitiesModal.scss rename to react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.scss index 07a82ee1..98b8ee12 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/ActivitiesModal.scss +++ b/react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.scss @@ -25,7 +25,7 @@ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25); animation: slideUp 0.3s ease; background-color: #E5F3D2; - background-image: url("../../../images/ActivitiesAssets/vine_background.png"); + background-image: url("../../../../assets/images/ActivitiesAssets/vine_background.png"); background-repeat: no-repeat; background-size: cover; background-position: center; diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/ActivitiesModal.tsx b/react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.tsx similarity index 80% rename from react-ystemandchess/src/Pages/NewStudentProfile/Modals/ActivitiesModal.tsx rename to react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.tsx index a8cb35a3..f7b26883 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/ActivitiesModal.tsx +++ b/react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.tsx @@ -16,18 +16,18 @@ import React, { useEffect, useState } from "react"; import "./ActivitiesModal.scss"; -import { ReactComponent as GrowthBox } from "../../../images/ActivitiesAssets/growth_box.svg"; -import { ReactComponent as WaterMeter } from "../../../images/ActivitiesAssets/water_meter.svg"; -import { ReactComponent as MiddleVine} from "../../../images/ActivitiesAssets/middle_vine.svg"; -import { ReactComponent as TopVine} from "../../../images/ActivitiesAssets/top_vine.svg"; -import { ReactComponent as HangingVine} from "../../../images/ActivitiesAssets/hanging_vine.svg"; -import { ReactComponent as TopicBag } from "../../../images/ActivitiesAssets/topic_bag.svg"; -import { ReactComponent as ShortBottomVine} from "../../../images/ActivitiesAssets/short_bottom_vine.svg"; -import { ReactComponent as BottomVine} from "../../../images/ActivitiesAssets/bottom_vine.svg"; -import { ReactComponent as Stemmy} from "../../../images/ActivitiesAssets/stemmy.svg"; -import { environment } from "../../../environments/environment"; +import { ReactComponent as GrowthBox } from "../../../../assets/images/ActivitiesAssets/growth_box.svg"; +import { ReactComponent as WaterMeter } from "../../../../assets/images/ActivitiesAssets/water_meter.svg"; +import { ReactComponent as MiddleVine} from "../../../../assets/images/ActivitiesAssets/middle_vine.svg"; +import { ReactComponent as TopVine} from "../../../../assets/images/ActivitiesAssets/top_vine.svg"; +import { ReactComponent as HangingVine} from "../../../../assets/images/ActivitiesAssets/hanging_vine.svg"; +import { ReactComponent as TopicBag } from "../../../../assets/images/ActivitiesAssets/topic_bag.svg"; +import { ReactComponent as ShortBottomVine} from "../../../../assets/images/ActivitiesAssets/short_bottom_vine.svg"; +import { ReactComponent as BottomVine} from "../../../../assets/images/ActivitiesAssets/bottom_vine.svg"; +import { ReactComponent as Stemmy} from "../../../../assets/images/ActivitiesAssets/stemmy.svg"; +import { environment } from "../../../../environments/environment"; import { useCookies } from "react-cookie"; -import { parseActivities } from "../../../utils/activityNames"; +import { parseActivities } from "../../../../core/utils/activityNames"; /** * ActivitiesModal component - displays daily activities in garden theme diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/BadgesModal.scss b/react-ystemandchess/src/features/student/student-profile/Modals/BadgesModal.scss similarity index 100% rename from react-ystemandchess/src/Pages/NewStudentProfile/Modals/BadgesModal.scss rename to react-ystemandchess/src/features/student/student-profile/Modals/BadgesModal.scss diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/BadgesModal.tsx b/react-ystemandchess/src/features/student/student-profile/Modals/BadgesModal.tsx similarity index 96% rename from react-ystemandchess/src/Pages/NewStudentProfile/Modals/BadgesModal.tsx rename to react-ystemandchess/src/features/student/student-profile/Modals/BadgesModal.tsx index 1dde8a3e..54abf338 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/BadgesModal.tsx +++ b/react-ystemandchess/src/features/student/student-profile/Modals/BadgesModal.tsx @@ -15,7 +15,7 @@ import React, { useEffect, useState, useMemo } from "react"; import "./BadgesModal.scss"; -import { getBadgeCatalog, getUserBadges } from "../../../services/badgesApi"; +import { getBadgeCatalog, getUserBadges } from "../../../../core/services/badgesApi"; /** * BadgesModal component - displays user's badge achievements diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/LeaderboardModal.scss b/react-ystemandchess/src/features/student/student-profile/Modals/LeaderboardModal.scss similarity index 100% rename from react-ystemandchess/src/Pages/NewStudentProfile/Modals/LeaderboardModal.scss rename to react-ystemandchess/src/features/student/student-profile/Modals/LeaderboardModal.scss diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/LeaderboardModal.tsx b/react-ystemandchess/src/features/student/student-profile/Modals/LeaderboardModal.tsx similarity index 94% rename from react-ystemandchess/src/Pages/NewStudentProfile/Modals/LeaderboardModal.tsx rename to react-ystemandchess/src/features/student/student-profile/Modals/LeaderboardModal.tsx index 1c2ef837..f6ec2884 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/LeaderboardModal.tsx +++ b/react-ystemandchess/src/features/student/student-profile/Modals/LeaderboardModal.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useMemo, useState } from "react"; import "./LeaderboardModal.scss"; -import { ReactComponent as LeaderboardIcon } from "../../../images/student/leaderboard_sidebar_icon.svg"; +import { ReactComponent as LeaderboardIcon } from "../../../../assets/images/student/leaderboard_sidebar_icon.svg"; -import rank1Img from "../../../images/student/Leaderboard_rank_1.svg"; -import rank2Img from "../../../images/student/Leaderboard_rank_2.svg"; -import rank3Img from "../../../images/student/Leaderboard_rank_3.svg"; +import rank1Img from "../../../../assets/images/student/Leaderboard_rank_1.svg"; +import rank2Img from "../../../../assets/images/student/Leaderboard_rank_2.svg"; +import rank3Img from "../../../../assets/images/student/Leaderboard_rank_3.svg"; type Props = { onClose: () => void }; diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/StreakModal.scss b/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.scss similarity index 100% rename from react-ystemandchess/src/Pages/NewStudentProfile/Modals/StreakModal.scss rename to react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.scss diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/StreakModal.tsx b/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.tsx similarity index 81% rename from react-ystemandchess/src/Pages/NewStudentProfile/Modals/StreakModal.tsx rename to react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.tsx index 5349b55b..3e520489 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/Modals/StreakModal.tsx +++ b/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.tsx @@ -14,14 +14,14 @@ import React from "react"; import "./StreakModal.scss"; -import { ReactComponent as Polygon } from "../../../images/StreakProgressAssets/polygon.svg"; -import { ReactComponent as Polygon_2 } from "../../../images/StreakProgressAssets/polygon_2.svg"; -import streakClock from "../../../images/StreakProgressAssets/streak_progress_clock.png"; -import { ReactComponent as Stemette } from "../../../images/StreakProgressAssets/stemette.svg"; -import { ReactComponent as Stemmy } from "../../../images/StreakProgressAssets/stemmy.svg"; +import { ReactComponent as Polygon } from "../../../../assets/images/StreakProgressAssets/polygon.svg"; +import { ReactComponent as Polygon_2 } from "../../../../assets/images/StreakProgressAssets/polygon_2.svg"; +import streakClock from "../../../../assets/images/StreakProgressAssets/streak_progress_clock.png"; +import { ReactComponent as Stemette } from "../../../../assets/images/StreakProgressAssets/stemette.svg"; +import { ReactComponent as Stemmy } from "../../../../assets/images/StreakProgressAssets/stemmy.svg"; // Calendar placeholder. Delete once an actual calendar is implemented -import calendarIcon from "../../../images/StreakProgressAssets/Calendar.png"; +import calendarIcon from "../../../../assets/images/StreakProgressAssets/Calendar.png"; /** * StreakModal component - displays user's streak progress diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.scss b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.scss similarity index 56% rename from react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.scss rename to react-ystemandchess/src/features/student/student-profile/NewStudentProfile.scss index 187873f6..9b684035 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.scss +++ b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.scss @@ -9,10 +9,13 @@ // Color Variables // ===================================== -$brand-color-primary: #e5f3d2; -$brand-color-secondary: #C8F2AE; -$brand-color-tertiary: #7fcc26; -$brand-color-background-grey: #d4dddd; +$brand-color-primary: #ffffff; +$brand-color-secondary: #eaf4f4; +$brand-color-tertiary: #3a7cca; +$brand-color-accent: #d64309; +$brand-color-background-grey: #f7f7f7; +$inventory-tab-color: #ffffff; +$inventory-tab-active: #3a7cca; // ===================================== // Main Container @@ -20,11 +23,8 @@ $brand-color-background-grey: #d4dddd; #main-inventory-content { background-color: $brand-color-primary; - background-image: url("../../images/chess-piece-pattern.png"); - background-size: 95%; - background-repeat: repeat; font-family: "Roboto", sans-serif; - padding-bottom: 3.0rem; + min-height: 100vh; position: relative; // =========================== @@ -86,38 +86,49 @@ $brand-color-background-grey: #d4dddd; .inv-intro-portrait { position: relative; - border: 2px solid #000; - background: $brand-color-background-grey; - border-radius: 6px; - width: 9.375rem; - height: 9.375rem; + width: 120px; + height: 120px; + border-radius: 50%; + background: #ffffff; + border: 4px solid #3a7cca; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 12px rgba(58, 124, 202, 0.3); margin-top: 2rem; .inv-intro-portrait-face { - width: 100%; - height: 100%; + width: 60%; + height: 60%; + object-fit: contain; + filter: brightness(0) saturate(100%) invert(29%) sepia(81%) saturate(746%) hue-rotate(189deg) brightness(95%) contrast(91%); } .inv-intro-portrait-camera { position: absolute; - background: rgba(255, 255, 255, 0.85); - border-radius: 6px; bottom: 0; right: 0; - width: 2.9869rem; - height: 2.9869rem; - padding: 0.0625rem; - margin: 0.25rem; + transform: translate(30%, 30%); + width: 30px; + height: 30px; + background: #ffffff; + border: 2px solid #3a7cca; + border-radius: 50%; + padding: 4px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } } - .inv-intro-welcome > h1 { - font-family: "Lato", sans-serif; + .inv-intro-welcome { + margin-top: 1.5rem; + + h1 { + font-size: 1.8rem; + font-weight: 700; + color: #333; margin: 0 0 1rem 0; padding: 0; - font-size: 2.5rem; - font-weight: bold; - color: #000; + } } } @@ -127,29 +138,32 @@ $brand-color-background-grey: #d4dddd; .inv-inventory { background: $brand-color-background-grey; - width: clamp(1100px, 80%, 1200px); - margin: auto; - border-radius: 0.8rem; + width: clamp(300px, 80%, 1200px); + margin: 2rem auto; + border-radius: 1rem; + overflow: hidden; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); .inv-inventory-topbar { background: $brand-color-tertiary; - padding: 0.2em 0; + padding: 1rem 0; margin: 0; .progress-heading { font-family: "Lato", sans-serif; font-weight: 700; font-size: 2.25rem; - color: #000; + color: #ffffff; margin: 0; } } .inv-inventory-analytics { display: flex; + flex-wrap: wrap; align-items: center; justify-content: center; - padding: 3% 0 0; + padding: 2rem; .inv-inventory-analytics-graph { height: 18rem; @@ -164,27 +178,21 @@ $brand-color-background-grey: #d4dddd; h3 { font-size: 1.2rem; font-weight: bold; - margin: 0 0 1.5rem 0; + margin: 0 0 1rem 0; + color: #333; } ul { - padding: 0 0.2rem; + list-style: none; + padding: 0; li { - position: relative; - margin: 0 0 12px 0; - list-style: none; - - &::before { - content: ""; - position: relative; - display: inline-block; - width: 0.9375rem; - height: 0.9375rem; - border-radius: 50%; - margin-right: 1rem; - background-color: $brand-color-tertiary; - border: 4px solid #000; + margin-bottom: 0.5rem; + color: #333; + font-weight: 500; + + strong { + color: $brand-color-accent; } } } @@ -196,30 +204,27 @@ $brand-color-background-grey: #d4dddd; // =========================== .inv-inventory-content-section { - background: $brand-color-secondary; - width: 100%; - height: 100%; - margin: 2rem auto; - border-radius: 1rem; display: grid; - grid-template-columns: 35% 65%; + grid-template-columns: 30% 70%; + background: $brand-color-primary; .inv-inventory-content-tabs { position: relative; ul { - background: $brand-color-background-grey; - padding: 0; - margin: 0; list-style: none; + padding: 1rem; + background: $brand-color-secondary; + border-right: 1px solid #ddd; + margin: 0; li { &:first-child .inventory-tab { - border-top-left-radius: 1rem; + border-top-left-radius: 0.5rem; } &:last-child .inventory-tab { - border-bottom-left-radius: 1rem; + border-bottom-left-radius: 0.5rem; margin-bottom: 0; } } @@ -231,35 +236,51 @@ $brand-color-background-grey: #d4dddd; position: relative; display: flex; align-items: center; - height: 4.6875rem; - width: 100%; - margin-bottom: 1rem; - overflow: hidden; cursor: pointer; + padding: 1rem; + margin-bottom: 1rem; + border-radius: 0.5rem; + transition: background 0.3s; img { - width: 100%; - height: 100%; - object-fit: cover; - margin-right: 0.25rem; - transition: width 0.2s ease; - filter: none !important; + width: 32px; + height: 32px; + max-width: 32px; + max-height: 32px; + object-fit: contain; + margin-right: 0.75rem; + flex-shrink: 0; + filter: grayscale(1); + transition: filter 0.3s, transform 0.3s; + } + + li { + list-style: none; + font-size: 1rem; + font-weight: 500; + color: #333; + } + + &:hover { + background: lighten($brand-color-tertiary, 40%); } &.active-tab { - background-color: $brand-color-secondary; + background: $brand-color-tertiary; img { - transform: none; - margin-right: 0; + filter: none; + } + + li { + color: white; } } } .inv-inventory-content-content { - padding: 1rem 4rem; - display: flex; - align-items: center; + padding: 2rem; + overflow-y: auto; } .inventory-content { @@ -273,37 +294,33 @@ $brand-color-background-grey: #d4dddd; } .inventory-content-headingbar { - min-width: 35rem; display: flex; justify-content: space-between; - align-items: flex-end; - padding: 1rem 0 0.7rem 0; - border-bottom: 1px solid rgb(120, 120, 120); + align-items: baseline; + border-bottom: 1px solid #ccc; + margin-bottom: 1rem; + padding: 0; h2 { - font-family: "Roboto", sans-serif; + font-size: 1.5rem; font-weight: 700; - font-size: 2.0rem; + color: #333; margin: 0; padding: 0; - position: relative; - top: 0.6875rem; } h4 { - color: #B0B0B0; - font-family: "Roboto", sans-serif; - font-weight: 500; - font-size: 1.0rem; - position: relative; - top: 0.3125rem; + font-size: 0.9rem; + color: #666; + margin: 0; + padding: 0; } } .inventory-content-body { position: relative; display: grid; - height: 38.55rem; + max-height: 500px; overflow-y: auto; scrollbar-width: none; } @@ -326,44 +343,25 @@ $brand-color-background-grey: #d4dddd; } .inventory-content-timecard { - display: grid; - grid-template-columns: 10% 45% 45%; - text-align: left; - margin: 3rem 0; - - > div { display: flex; - justify-content: flex-start; + justify-content: space-between; align-items: center; - } + background: #fff; + border: 1px solid #ddd; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1rem; - .inventory-content-col1 { - &::before { - content: ""; - position: relative; - display: inline-block; - width: 2.375rem; - height: 2.375rem; - border-radius: 50%; - margin-right: 1rem; - background-color: $brand-color-tertiary; - border: 9px solid #000; - } - } - - .inventory-content-col2 { - margin-left: 2rem; - position: relative; - top: 0.25rem; - } - - .inventory-content-col3 { - position: relative; - top: 0.1875rem; + p { + margin: 0; - p > strong { + strong { text-decoration: underline; cursor: pointer; + + &:hover { + color: $brand-color-tertiary; + } } } } @@ -384,4 +382,54 @@ $brand-color-background-grey: #d4dddd; } } } + + // =========================== + // Animation States + // =========================== + + // Celebration pulse animation + .inv-inventory.celebrate { + animation: celebrate-pulse 1s ease-in-out; + } + + @keyframes celebrate-pulse { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.02); + } + } + + // Portrait click animation + .inv-intro-portrait { + cursor: pointer; + transition: transform 0.2s ease; + + &:hover { + transform: scale(1.05); + } + + &:active { + transform: scale(0.95); + } + } + + // Enhanced loading spinner + .loading-spinner { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid rgba(58, 124, 202, 0.3); + border-radius: 50%; + border-top-color: $brand-color-tertiary; + animation: spin 1s ease-in-out infinite; + } + + @keyframes spin { + to { + transform: rotate(360deg); + } + } } diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.test.tsx b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.test.tsx similarity index 79% rename from react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.test.tsx rename to react-ystemandchess/src/features/student/student-profile/NewStudentProfile.test.tsx index e4ac4454..6124cd95 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.test.tsx +++ b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, screen, act } from '@testing-library/react'; import { MemoryRouter } from 'react-router'; import { useCookies } from 'react-cookie'; -import { SetPermissionLevel } from '../../globals'; +import { SetPermissionLevel } from '../../../globals'; import NewStudentProfile from './NewStudentProfile'; // ------------- MOCKS ------------- @@ -13,17 +13,52 @@ jest.mock('react-cookie', () => ({ })); // mock globals (SetPermissionLevel) -jest.mock('../../globals', () => ({ +jest.mock('../../../globals', () => ({ __esModule: true, SetPermissionLevel: jest.fn(), })); +// mock SweetAlert2 to avoid CSS parsing errors in jsdom +jest.mock('sweetalert2', () => { + const mockSwal = { + fire: jest.fn(() => Promise.resolve({ isConfirmed: true, isDenied: false, isDismissed: false })), + mixin: jest.fn(), + bindClickHandler: jest.fn(), + stopTimer: jest.fn(), + resumeTimer: jest.fn(), + toggleTimer: jest.fn(), + isTimerRunning: jest.fn(), + incrementTimer: jest.fn(), + showLoading: jest.fn(), + hideLoading: jest.fn(), + clickConfirm: jest.fn(), + clickCancel: jest.fn(), + clickDeny: jest.fn(), + close: jest.fn(), + enableButtons: jest.fn(), + disableButtons: jest.fn(), + showValidationMessage: jest.fn(), + resetValidationMessage: jest.fn(), + getInput: jest.fn(), + }; + return { + __esModule: true, + default: mockSwal, + }; +}); + // mock the chart jest.mock('react-chartjs-2', () => ({ __esModule: true, Line: () =>
, })); +// mock Puzzles component to avoid SweetAlert2 CSS parsing issues +jest.mock('../../puzzles/puzzles-page/Puzzles', () => ({ + __esModule: true, + default: () =>
, +})); + // ------------- HELPER FUNCTIONS ------------- // helper to for date formatting to match with component @@ -141,9 +176,9 @@ describe('NewStudentProfile', () => { expect(await screen.findByText(/Time Spent:/i)).toBeInTheDocument(); // check if some of the tabs (by aria-label) are rendered - expect(await screen.findByRole('button', { name: /activity/i })).toBeInTheDocument(); - expect(await screen.findByRole('button', { name: /games/i })).toBeInTheDocument(); - expect(await screen.findByRole('button', { name: /mentor/i })).toBeInTheDocument(); + expect(await screen.findByLabelText(/activity/i)).toBeInTheDocument(); + expect(await screen.findByLabelText(/games/i)).toBeInTheDocument(); + expect(await screen.findByLabelText(/mentor/i)).toBeInTheDocument(); }); test('renders time stats', async () => { diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.tsx b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.tsx similarity index 81% rename from react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.tsx rename to react-ystemandchess/src/features/student/student-profile/NewStudentProfile.tsx index ee5ed419..527e7f41 100644 --- a/react-ystemandchess/src/Pages/NewStudentProfile/NewStudentProfile.tsx +++ b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.tsx @@ -1,38 +1,30 @@ import React, { useState, useEffect, useRef, useMemo, lazy, Suspense } from "react"; import "./NewStudentProfile.scss"; -import Images from "../../images/imageImporter"; -import { SetPermissionLevel } from '../../globals'; +import Images from "../../../assets/images/imageImporter"; +import { SetPermissionLevel } from '../../../globals'; import { useCookies } from 'react-cookie'; -import { environment } from '../../environments/environment'; +import { environment } from "../../../environments/environment"; import { useNavigate } from "react-router"; import { useLocation } from "react-router"; import StatsChart from "./StatsChart"; -import Puzzles from "../Puzzles/Puzzles"; +import Puzzles from "../../puzzles/puzzles-page/Puzzles"; import StreakModal from "./Modals/StreakModal"; import ActivitiesModal from "./Modals/ActivitiesModal"; import BadgesModal from "./Modals/BadgesModal"; import LeaderboardModal from "./Modals/LeaderboardModal"; +import Confetti from "../../../components/animations/Confetti/Confetti"; // Toolbar buttons -import { ReactComponent as StreakIcon } from "../../images/student/streak_button.svg"; -import { ReactComponent as ActivitiesIcon } from "../../images/student/activities_button.svg"; -import { ReactComponent as BadgesIcon } from "../../images/student/badges_button.svg"; -import { ReactComponent as LeaderboardIcon } from "../../images/student/leaderboard_button.svg"; - -// Tab images -import activityTab from "../../images/student/activity_tab.png"; -import mentorTab from "../../images/student/mento_tab.png"; -import prodevTab from "../../images/student/prodev_tab.png"; -import chessTab from "../../images/student/chess_tab.png"; -import mathTab from "../../images/student/math_tab.png"; -import gamesTab from "../../images/student/games_tab.png"; -import puzzlesTab from "../../images/student/puzzles_tab.png"; -import playTab from "../../images/student/play_tab.png"; -import recordingsTab from "../../images/student/recordings_tab.png"; - -const Lessons = lazy(() => import("../Lessons/Lessons")); -const LessonsSelection = lazy(() => import("../LessonsSelection/LessonsSelection")); -const LessonOverlay = lazy(() => import("../piece-lessons/lesson-overlay/Lesson-overlay")); +import { ReactComponent as StreakIcon } from "../../../assets/images/student/streak_button.svg"; +import { ReactComponent as ActivitiesIcon } from "../../../assets/images/student/activities_button.svg"; +import { ReactComponent as BadgesIcon } from "../../../assets/images/student/badges_button.svg"; +import { ReactComponent as LeaderboardIcon } from "../../../assets/images/student/leaderboard_button.svg"; + +// Tab images - using Images from imageImporter (same as mentor) + +const Lessons = lazy(() => import("../../lessons/lessons-main/Lessons")); +const LessonsSelection = lazy(() => import("../../lessons/lessons-selection/LessonsSelection")); +const LessonOverlay = lazy(() => import("../../lessons/piece-lessons/lesson-overlay/Lesson-overlay")); // Main Student Profile Component const NewStudentProfile = ({ userPortraitSrc }: any) => { @@ -76,18 +68,12 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { const [hasMore, setHasMore] = useState(true); const containerRef = useRef(null); - // Mapping of tab keys to custom image imports - const tabImages: Record = { - activity: activityTab, - mentor: mentorTab, - prodev: prodevTab, - chessLessons: chessTab, - mathLessons: mathTab, - games: gamesTab, - puzzles: puzzlesTab, - playComputer: playTab, - recordings: recordingsTab, - }; + // Animation states + const [showConfetti, setShowConfetti] = useState(false); + const [celebrateAction, setCelebrateAction] = useState(false); + + // Tab keys - using Images from imageImporter (same pattern as mentor) + const tabKeys = ["activity", "mentor", "prodev", "chessLessons", "mathLessons", "games", "puzzles", "playComputer", "recordings"]; // states for lessons tab const [lessonSelected, setLessonSelected] = useState(false); // whether user has navigated into a lesson @@ -232,6 +218,11 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { // Handle tab switch const handleTabClick = (tab: any) => { setActiveTab(tab); + // Add celebration animation for learning tabs + if (tab === "prodev" || tab === "chessLessons") { + setCelebrateAction(true); + setTimeout(() => setCelebrateAction(false), 1000); + } }; // Redirect user when clicking on an activity item (e.g., lesson) @@ -243,6 +234,12 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { } } + // Fun click handler for portrait + const handlePortraitClick = () => { + setShowConfetti(true); + setTimeout(() => setShowConfetti(false), 2000); + }; + // Render content based on active tab const renderTabContent = () => { switch (activeTab) { @@ -290,8 +287,17 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { ); })} - {loading &&

Loading more...

} - {!hasMore &&

No more activity left!

} + {loading && ( +
+
+

Loading more activities...

+
+ )} + {!hasMore && events.length > 0 && ( +

+ πŸŽ‰ You've seen all your activities! +

+ )}
); @@ -383,9 +389,12 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => {
+ {/* Confetti effect */} + + {/* Welcome section with portrait */}
-
+
user portrait user portrait camera icon
@@ -395,7 +404,7 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => {
{/* Analytics section: graph and metrics */} -
+

Your Progress

@@ -419,17 +428,38 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => {
+ ); + })}
{tabContent}
diff --git a/react-ystemandchess/src/features/student/student-profile/NewStudentProfile/Modals/BadgesModal.tsx b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile/Modals/BadgesModal.tsx new file mode 100644 index 00000000..a2ab3e36 --- /dev/null +++ b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile/Modals/BadgesModal.tsx @@ -0,0 +1,89 @@ +/** + * Badges Modal Component + * + * Displays a modal showing all available badges and which ones the user has earned. + * Fetches badge catalog and user's earned badges from the API. + * Earned badges are displayed in color, unearned badges are grayed out. + * + * Features: + * - Fetches badge catalog and user's earned badges + * - Visual distinction between earned and unearned badges + * - Badge details including name, description, and icon + * - Loading state while fetching data + * - Click outside to close functionality + */ + +import React, { useEffect, useState, useMemo } from "react"; +import "./BadgesModal.scss"; +import { getBadgeCatalog, getUserBadges } from "../../../../../core/services/badgesApi"; + +/** + * BadgesModal component - displays user's badge achievements + * @param {Function} onClose - Callback to close the modal + */ +const BadgesModal = ({ onClose }: { onClose: () => void }) => { + const [catalog, setCatalog] = useState([]); + const [earnedIds, setEarnedIds] = useState([]); + const [loading, setLoading] = useState(true); + + const handleOverlayClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + useEffect(() => { + (async () => { + try { + const [badges, earned] = await Promise.all([ + getBadgeCatalog(), + getUserBadges("teststudent") // replace with dynamic userId later if needed + ]); + setCatalog(badges); + setEarnedIds(earned.map((b: any) => b.badgeId)); + } finally { + setLoading(false); + } + })(); + }, []); + + const badgeList = useMemo(() => { + return catalog.map(b => ({ + ...b, + isEarned: earnedIds.includes(b.id) + })); + }, [catalog, earnedIds]); + + console.log(" catalog:", catalog); + console.log(" earnedIds:", earnedIds); + console.log(" badgeList:", badgeList); + + return ( +
+
+ +

Badges

+ + {loading ? ( +

Loading...

+ ) : ( +
+ {badgeList.map(b => ( +
+
+ {b.name} +
+
{b.name}
+ {!b.isEarned &&
Locked
} +
+ ))} +
+ )} +
+
+ ); +}; + +export default BadgesModal; diff --git a/react-ystemandchess/src/Pages/NewStudentProfile/StatsChart.tsx b/react-ystemandchess/src/features/student/student-profile/StatsChart.tsx similarity index 100% rename from react-ystemandchess/src/Pages/NewStudentProfile/StatsChart.tsx rename to react-ystemandchess/src/features/student/student-profile/StatsChart.tsx diff --git a/stockfishServer/package.json b/stockfishServer/package.json index 44634ce6..f154aab4 100644 --- a/stockfishServer/package.json +++ b/stockfishServer/package.json @@ -2,9 +2,9 @@ "name": "stockfishserver", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "src/index.js", "scripts": { - "start": "nodemon index.js", + "start": "nodemon src/index.js", "test": "jest --detectOpenHandles --forceExit" }, "author": "", diff --git a/stockfishServer/index_new.js b/stockfishServer/src/archive/index_new.js similarity index 100% rename from stockfishServer/index_new.js rename to stockfishServer/src/archive/index_new.js diff --git a/stockfishServer/bin/stockfish_11_linux b/stockfishServer/src/bin/stockfish_11_linux similarity index 100% rename from stockfishServer/bin/stockfish_11_linux rename to stockfishServer/src/bin/stockfish_11_linux diff --git a/stockfishServer/bin/stockfish_11_mac b/stockfishServer/src/bin/stockfish_11_mac similarity index 100% rename from stockfishServer/bin/stockfish_11_mac rename to stockfishServer/src/bin/stockfish_11_mac diff --git a/stockfishServer/bin/stockfish_11_win.exe b/stockfishServer/src/bin/stockfish_11_win.exe similarity index 100% rename from stockfishServer/bin/stockfish_11_win.exe rename to stockfishServer/src/bin/stockfish_11_win.exe diff --git a/stockfishServer/index.js b/stockfishServer/src/index.js similarity index 100% rename from stockfishServer/index.js rename to stockfishServer/src/index.js diff --git a/stockfishServer/StockfishManager.js b/stockfishServer/src/managers/StockfishManager.js similarity index 100% rename from stockfishServer/StockfishManager.js rename to stockfishServer/src/managers/StockfishManager.js diff --git a/stockfishServer/socket.js b/stockfishServer/src/managers/socket.js similarity index 100% rename from stockfishServer/socket.js rename to stockfishServer/src/managers/socket.js diff --git a/stockfishServer/__tests__/StockfishManager.test.js b/stockfishServer/src/tests/StockfishManager.test.js similarity index 97% rename from stockfishServer/__tests__/StockfishManager.test.js rename to stockfishServer/src/tests/StockfishManager.test.js index 15b38b5a..d9933fe5 100644 --- a/stockfishServer/__tests__/StockfishManager.test.js +++ b/stockfishServer/src/tests/StockfishManager.test.js @@ -6,7 +6,7 @@ jest.mock("child_process", () => ({ })), })); -const StockfishManager = require("../StockfishManager"); +const StockfishManager = require("../managers/StockfishManager"); const { spawn } = require("child_process"); const newSocket = () => ({ diff --git a/stockfishServer/__tests__/index.test.js b/stockfishServer/src/tests/index.test.js similarity index 98% rename from stockfishServer/__tests__/index.test.js rename to stockfishServer/src/tests/index.test.js index 481a56da..9f6d3b29 100644 --- a/stockfishServer/__tests__/index.test.js +++ b/stockfishServer/src/tests/index.test.js @@ -1,7 +1,7 @@ const ioClient = require("socket.io-client"); const http = require("http"); const express = require("express"); -const socketHandler = require("../socket"); +const socketHandler = require("../managers/socket"); describe("Server and socket", () => { let server, ioServer, clientSocket;