From f3624b7a95b338b229bbd4deba57d05df0477489 Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Sat, 6 Dec 2025 09:57:06 -0600 Subject: [PATCH 01/20] feat: add basic info validation schema --- package-lock.json | 74 ++++++++++++++++++++++++++++++------- package.json | 5 ++- src/schemas/mentorSchema.ts | 18 +++++++++ 3 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 src/schemas/mentorSchema.ts diff --git a/package-lock.json b/package-lock.json index 3b3efd1..cda023d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@hookform/resolvers": "^5.2.2", "@mui/icons-material": "^5.15.18", "@mui/material": "^5.15.18", "@testing-library/dom": "^10.1.0", @@ -20,10 +21,12 @@ "next": "14.2.3", "react": "18.2.0", "react-dom": "18.2.0", - "ts-jest": "^29.1.2" + "react-hook-form": "^7.68.0", + "ts-jest": "^29.1.2", + "zod": "^4.1.13" }, "devDependencies": { - "@playwright/test": "^1.52.0", + "@playwright/test": "^1.57.0", "@svgr/webpack": "^8.1.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.7", @@ -43,6 +46,7 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "next-router-mock": "^0.9.13", + "playwright": "~1.57.0", "postcss": "^8", "prettier": "^3.2.5", "ts-node": "^10.9.2", @@ -2149,6 +2153,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3418,13 +3434,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", - "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.52.0" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -3469,6 +3485,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -10973,13 +10995,13 @@ } }, "node_modules/playwright": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", - "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.52.0" + "playwright-core": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -10992,9 +11014,9 @@ } }, "node_modules/playwright-core": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", - "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -11008,6 +11030,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -11226,6 +11249,22 @@ "react": "^18.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.68.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz", + "integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -13020,6 +13059,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index b58a81a..a9be6a8 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@hookform/resolvers": "^5.2.2", "@mui/icons-material": "^5.15.18", "@mui/material": "^5.15.18", "@testing-library/dom": "^10.1.0", @@ -27,7 +28,9 @@ "next": "14.2.3", "react": "18.2.0", "react-dom": "18.2.0", - "ts-jest": "^29.1.2" + "react-hook-form": "^7.68.0", + "ts-jest": "^29.1.2", + "zod": "^4.1.13" }, "devDependencies": { "@playwright/test": "^1.57.0", diff --git a/src/schemas/mentorSchema.ts b/src/schemas/mentorSchema.ts new file mode 100644 index 0000000..03e7385 --- /dev/null +++ b/src/schemas/mentorSchema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; + +export const basicInfoSchema = z.object({ + firstName: z.string().min(1, "First name is required"), + lastName: z.string().min(1, "Last name is required"), + + email: z.string().email("Invalid email address"), + + slackName: z.string().min(1, "Slack name is required"), + + country: z.string().min(1, "Country is required"), + city: z.string().min(1, "City is required"), + + jobTitle: z.string().min(1, "Job title is required"), + company: z.string().min(1, "Company is required"), +}); + +export type BasicInfoData = z.infer; \ No newline at end of file From 645981cb1f4dc3e03a277145aeaa377d5c26e52f Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Thu, 1 Jan 2026 11:01:16 -0600 Subject: [PATCH 02/20] feat: add mentor schemas and fix husky config --- .husky/pre-commit | 5 +---- src/schemas/mentorSchema.ts | 33 +++++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 9d8f28a..98475b5 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh - . "$(dirname "$0")/_/husky.sh" - -pnpm lint:fix && pnpm format && pnpm type-check +pnpm test diff --git a/src/schemas/mentorSchema.ts b/src/schemas/mentorSchema.ts index 03e7385..ceb5648 100644 --- a/src/schemas/mentorSchema.ts +++ b/src/schemas/mentorSchema.ts @@ -1,18 +1,31 @@ import { z } from 'zod'; export const basicInfoSchema = z.object({ - firstName: z.string().min(1, "First name is required"), - lastName: z.string().min(1, "Last name is required"), + firstName: z.string().min(1, 'First name is required'), + lastName: z.string().min(1, 'Last name is required'), + email: z.string().email('Invalid email address'), + slackName: z.string().min(1, 'Slack name is required'), + country: z.string().min(1, 'Country is required'), + city: z.string().min(1, 'City is required'), + jobTitle: z.string().min(1, 'Job title is required'), + company: z.string().min(1, 'Company is required'), - email: z.string().email("Invalid email address"), - - slackName: z.string().min(1, "Slack name is required"), + mentorType: z.string().min(1, 'Please select a mentorship type'), + maxMentees: z.string().optional(), + calendlyLink: z.string().url("Please enter a valid Calendly URL"), + menteeExpectations: z.string().min(1, "Please describe the mentee you are looking for"), + openToNonWomen: z.string().min(1, "Please select an option"), +}); - country: z.string().min(1, "Country is required"), - city: z.string().min(1, "City is required"), +export type BasicInfoData = z.infer; - jobTitle: z.string().min(1, "Job title is required"), - company: z.string().min(1, "Company is required"), +export const profileSchema = z.object({ + languages: z.array(z.string()).min(1, "Select at least one language"), + yearsOfExperience: z.string().min(1, "Please select your experience"), + bio: z.string().min(10, "Bio must be at least 10 characters"), + mentoringTopics: z.string().optional(), + photoSource: z.string().min(1, "Please select a photo source"), + customPhotoUrl: z.string().url("Invalid URL").optional().or(z.literal('')), }); -export type BasicInfoData = z.infer; \ No newline at end of file +export type ProfileData = z.infer; \ No newline at end of file From bb8562bb97e4c35430395f3409b1bc3abc20496c Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Thu, 1 Jan 2026 13:38:16 -0600 Subject: [PATCH 03/20] feat: complete mentor schemas --- src/schemas/mentorSchema.ts | 90 +++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/src/schemas/mentorSchema.ts b/src/schemas/mentorSchema.ts index ceb5648..b96e40f 100644 --- a/src/schemas/mentorSchema.ts +++ b/src/schemas/mentorSchema.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; export const basicInfoSchema = z.object({ firstName: z.string().min(1, 'First name is required'), lastName: z.string().min(1, 'Last name is required'), - email: z.string().email('Invalid email address'), + email: z.string().email({ message: 'Invalid email address' }), slackName: z.string().min(1, 'Slack name is required'), country: z.string().min(1, 'Country is required'), city: z.string().min(1, 'City is required'), @@ -12,7 +12,7 @@ export const basicInfoSchema = z.object({ mentorType: z.string().min(1, 'Please select a mentorship type'), maxMentees: z.string().optional(), - calendlyLink: z.string().url("Please enter a valid Calendly URL"), + calendlyLink: z.string().url({ message: "Please enter a valid Calendly URL" }), menteeExpectations: z.string().min(1, "Please describe the mentee you are looking for"), openToNonWomen: z.string().min(1, "Please select an option"), }); @@ -25,7 +25,89 @@ export const profileSchema = z.object({ bio: z.string().min(10, "Bio must be at least 10 characters"), mentoringTopics: z.string().optional(), photoSource: z.string().min(1, "Please select a photo source"), - customPhotoUrl: z.string().url("Invalid URL").optional().or(z.literal('')), + customPhotoUrl: z.string().url({ message: "Invalid URL" }).optional().or(z.literal('')), }); -export type ProfileData = z.infer; \ No newline at end of file +export type ProfileData = z.infer; + +const skillLevel = z.string().optional(); + +export const skillsSchema = z.object({ + dataEngineering: skillLevel, + dataScience: skillLevel, + genAI: skillLevel, + machineLearning: skillLevel, + mlOps: skillLevel, + + cloudComputing: skillLevel, + devOps: skillLevel, + networkEngineering: skillLevel, + platformEngineering: skillLevel, + security: skillLevel, + sre: skillLevel, + + agile: skillLevel, + businessAnalysis: skillLevel, + engineeringMgmt: skillLevel, + productMgmt: skillLevel, + projectMgmt: skillLevel, + technicalLeadership: skillLevel, + + backend: skillLevel, + frontend: skillLevel, + fullstack: skillLevel, + mobileAndroid: skillLevel, + mobileIos: skillLevel, + qaAutomation: skillLevel, + systemDesign: skillLevel, +}); +export type SkillsData = z.infer; + +export const programmingSchema = z.object({ + careerSwitch: skillLevel, + beginnerToMid: skillLevel, + midToSenior: skillLevel, + seniorPlus: skillLevel, + icToManager: skillLevel, + specialisationSwitch: skillLevel, + + c: skillLevel, + cSharp: skillLevel, + go: skillLevel, + java: skillLevel, + javascript: skillLevel, + kotlin: skillLevel, + python: skillLevel, + rust: skillLevel, + scala: skillLevel, + sql: skillLevel, + swift: skillLevel, + typescript: skillLevel, +}); +export type ProgrammingData = z.infer; + +export const reviewSchema = z.object({ + linkedin: z.string().url({ message: "Invalid LinkedIn URL" }), + github: z.string().url({ message: "Invalid URL" }).optional().or(z.literal('')), + instagram: z.string().url({ message: "Invalid URL" }).optional().or(z.literal('')), + medium: z.string().url({ message: "Invalid URL" }).optional().or(z.literal('')), + website: z.string().url({ message: "Invalid URL" }).optional().or(z.literal('')), + otherSocial: z.string().optional(), + identity: z.string().min(1, "Please select an option"), + pronouns: z.string().min(1, "Pronouns are required"), + socialHighlight: z.string().min(1, "Please select Yes or No"), + termsAgreed: z.boolean().refine((val) => val === true, { + message: "You must agree to the code of conduct and terms", + }), +}); +export type ReviewData = z.infer; + +export const mentorRegistrationSchema = z.object({ + ...basicInfoSchema.shape, + ...profileSchema.shape, + ...skillsSchema.shape, + ...programmingSchema.shape, + ...reviewSchema.shape, +}); + +export type MentorRegistrationData = z.infer; \ No newline at end of file From cb391888b4e357c955deaca19a5c24146f20e4ab Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Sun, 4 Jan 2026 15:31:55 -0600 Subject: [PATCH 04/20] feat: create mentor registration page structure - Set up react-hook-form/zod resolver - navigation logic --- package.json | 2 +- pnpm-lock.yaml | 39 +++++++++ src/pages/mentorship/mentor-registration.tsx | 87 ++++++++++++++++++-- 3 files changed, 121 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index a9be6a8..6078550 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "next": "14.2.3", "react": "18.2.0", "react-dom": "18.2.0", - "react-hook-form": "^7.68.0", + "react-hook-form": "^7.70.0", "ts-jest": "^29.1.2", "zod": "^4.1.13" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c94e8b2..d7926c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@emotion/styled': specifier: ^11.11.5 version: 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.2.0))(@types/react@18.3.26)(react@18.2.0) + '@hookform/resolvers': + specifier: ^5.2.2 + version: 5.2.2(react-hook-form@7.70.0(react@18.2.0)) '@mui/icons-material': specifier: ^5.15.18 version: 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.2.0))(@types/react@18.3.26)(react@18.2.0))(@types/react@18.3.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.26)(react@18.2.0) @@ -44,9 +47,15 @@ importers: react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.70.0 + version: 7.70.0(react@18.2.0) ts-jest: specifier: ^29.1.2 version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.24)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.24)(typescript@5.9.3)))(typescript@5.9.3) + zod: + specifier: ^4.1.13 + version: 4.3.5 devDependencies: '@playwright/test': specifier: ^1.57.0 @@ -845,6 +854,11 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@hookform/resolvers@5.2.2': + resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + peerDependencies: + react-hook-form: ^7.55.0 + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1153,6 +1167,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} @@ -3273,6 +3290,12 @@ packages: peerDependencies: react: ^18.2.0 + react-hook-form@7.70.0: + resolution: {integrity: sha512-COOMajS4FI3Wuwrs3GPpi/Jeef/5W1DRR84Yl5/ShlT3dKVFUfoGiEZ/QE6Uw8P4T2/CLJdcTVYKvWBMQTEpvw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -3887,6 +3910,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@4.3.5: + resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -4821,6 +4847,11 @@ snapshots: '@eslint/js@8.57.1': {} + '@hookform/resolvers@5.2.2(react-hook-form@7.70.0(react@18.2.0))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.70.0(react@18.2.0) + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -5201,6 +5232,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@standard-schema/utils@0.3.0': {} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -7735,6 +7768,10 @@ snapshots: react: 18.2.0 scheduler: 0.23.2 + react-hook-form@7.70.0(react@18.2.0): + dependencies: + react: 18.2.0 + react-is@16.13.1: {} react-is@17.0.2: {} @@ -8413,3 +8450,5 @@ snapshots: yn@3.1.1: {} yocto-queue@0.1.0: {} + + zod@4.3.5: {} diff --git a/src/pages/mentorship/mentor-registration.tsx b/src/pages/mentorship/mentor-registration.tsx index 9294af1..c3d528c 100644 --- a/src/pages/mentorship/mentor-registration.tsx +++ b/src/pages/mentorship/mentor-registration.tsx @@ -1,14 +1,89 @@ // path: /mentorship/mentor-registration -import { Typography } from '@mui/material'; +import React, { useState } from 'react'; +import { useForm, FormProvider } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Box, Container, Paper, Typography, Button, Stack } from '@mui/material'; + +import { mentorRegistrationSchema, MentorRegistrationData } from '../../schemas/mentorSchema' const MentorRegistrationPage = () => { + const formMethods = useForm({ + resolver: zodResolver(mentorRegistrationSchema), + mode: 'onChange', + }); + + const [activeStep, setActiveStep] = useState(1); + const totalSteps = 6; + + const handleNext = async () => { + if (activeStep < totalSteps) { + setActiveStep((prev) => prev + 1); + } + }; + + const handleBack = () => { + if (activeStep > 1) { + setActiveStep((prev) => prev - 1); + } + }; + + const onSubmit = (data: MentorRegistrationData) => { + console.log("Form Data:", data); + alert("Check console for form data!"); + }; + return ( -
- - Welcome to the MentorRegistrationPage - -
+ + + + + + Mentor Registration + + + Step {activeStep} of {totalSteps} + + + + + + + Screen {activeStep} Content + + + + + + {activeStep === totalSteps ? ( + + ) : ( + + )} + + + + + + ); }; From ac205291836aab44a4b6e0c8f903b096ea6f9413 Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Mon, 12 Jan 2026 20:29:15 -0600 Subject: [PATCH 05/20] feat: complete mentor registration step 1 and validation --- src/components/mentorship/Step1BasicInfo.tsx | 156 +++++++++++++++++++ src/pages/mentorship/mentor-registration.tsx | 42 +++-- src/schemas/mentorSchema.ts | 10 +- 3 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 src/components/mentorship/Step1BasicInfo.tsx diff --git a/src/components/mentorship/Step1BasicInfo.tsx b/src/components/mentorship/Step1BasicInfo.tsx new file mode 100644 index 0000000..bcc10cb --- /dev/null +++ b/src/components/mentorship/Step1BasicInfo.tsx @@ -0,0 +1,156 @@ +import React from 'react'; +import { useFormContext } from 'react-hook-form'; +import { + Grid, TextField, MenuItem, Typography, Box, FormHelperText, + FormGroup, FormControlLabel, Checkbox, Radio, RadioGroup, FormControl, FormLabel, + Table, TableBody, TableCell, TableHead, TableRow +} from '@mui/material'; + +const Step1BasicInfo = () => { + const { register, watch, formState: { errors } } = useFormContext(); + const isLongTerm = watch("isLongTermMentor"); + const isAdHoc = watch("isAdHocMentor"); + + const months = ["May", "June", "July", "August", "September", "October", "November"]; + + return ( + + + WCC: Registration Form for Mentors + + + Thank you for your interest in long-term mentoring sessions. + We appreciate your enthusiasm and look forward to connecting with the mentor of your choice. + + + * Indicates a required field + + + + + What is your full name? * + + + + What is your email address? * + + + + Slack Name * + + Please note your application will be rejected if you are not in our Slack community. + + + Location * + + + + United Kingdom + United States + Canada + Other + + + + + + + + + What is your current job title? * + + + + Company name * + + + + + + Which kind of mentor you want to be? * + + + + } label="Long-Term Format" /> + + {isLongTerm && ( + + + Maximum number of mentees you are available to support. * + + + {[1, 2, 3, 4, 5, 6].map((num) => ( + {num === 6 ? '6+' : num} + ))} + + + )} + + } label="Ad-Hoc Format" /> + + {isAdHoc && ( + + + For each month below, please indicate the maximum number of mentees you are available to support. * + + + + + + Month + {[1, 2, 3, 4, '5+'].map(num => ( + {num} + ))} + + + + {months.map((month) => ( + + + {month} + + + + {[1, 2, 3, 4, 5].map((val) => ( + + ))} + + + + ))} + +
+
+ )} +
+ + {errors.isLongTermMentor && {errors.isLongTermMentor.message as string}} +
+ + + Calendly schedule link * + + + + + What kind of Mentee are you looking for? * + + + + + + + Are you open to mentoring individuals who do not identify as women? * + + + } label="Yes" /> + } label="No" /> + + + +
+
+ ); +}; + +export default Step1BasicInfo; \ No newline at end of file diff --git a/src/pages/mentorship/mentor-registration.tsx b/src/pages/mentorship/mentor-registration.tsx index c3d528c..19db6eb 100644 --- a/src/pages/mentorship/mentor-registration.tsx +++ b/src/pages/mentorship/mentor-registration.tsx @@ -4,6 +4,7 @@ import React, { useState } from 'react'; import { useForm, FormProvider } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Box, Container, Paper, Typography, Button, Stack } from '@mui/material'; +import Step1BasicInfo from '../../components/mentorship/Step1BasicInfo'; import { mentorRegistrationSchema, MentorRegistrationData } from '../../schemas/mentorSchema' @@ -16,9 +17,32 @@ const MentorRegistrationPage = () => { const [activeStep, setActiveStep] = useState(1); const totalSteps = 6; - const handleNext = async () => { - if (activeStep < totalSteps) { +const handleNext = async () => { + let isStepValid = false; + + if (activeStep === 1) { + isStepValid = await formMethods.trigger([ + "firstName", + "email", + "slackName", + "country", + "jobTitle", + "company", + "isLongTermMentor", + "isAdHocMentor", + "maxMentees", + "adHocAvailability", + "calendlyLink", + "menteeExpectations", + "openToNonWomen" + ]); + } else { + isStepValid = true; + } + + if (isStepValid) { setActiveStep((prev) => prev + 1); + window.scrollTo(0, 0); } }; @@ -35,12 +59,9 @@ const MentorRegistrationPage = () => { return ( - + - - Mentor Registration - Step {activeStep} of {totalSteps} @@ -49,17 +70,20 @@ const MentorRegistrationPage = () => { sx={{ width: `${(activeStep / totalSteps) * 100}%`, height: '100%', - bgcolor: '#1976d2', + bgcolor: 'primary.main', borderRadius: 4, transition: 'width 0.3s ease' }} /> + {activeStep === 1 && } + {activeStep > 1 && ( - Screen {activeStep} Content + Screen {activeStep} Content (Coming Soon) - + )} + - + {activeStep === totalSteps ? ( - ) : ( - )} - @@ -111,4 +196,4 @@ const handleNext = async () => { ); }; -export default MentorRegistrationPage; +export default MentorRegistrationPage; \ No newline at end of file diff --git a/src/schemas/mentorSchema.ts b/src/schemas/mentorSchema.ts index 52f47a5..494d6f5 100644 --- a/src/schemas/mentorSchema.ts +++ b/src/schemas/mentorSchema.ts @@ -17,7 +17,8 @@ export const basicInfoSchema = z.object({ calendlyLink: z.string().url({ message: "Please enter a valid Calendly URL" }), menteeExpectations: z.string().min(1, "Please describe the mentee you are looking for"), - openToNonWomen: z.string().min(1, "Please select an option"), + openToNonWomen: z.string().transform((val) => val === 'true'), + }).refine((data) => data.isLongTermMentor || data.isAdHocMentor, { message: "Please select at least one mentorship type", path: ["isLongTermMentor"], From 15d5f717ce4816b5b6bad567a291b8b28fe1f89d Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Tue, 20 Jan 2026 15:06:39 -0600 Subject: [PATCH 08/20] feat: complete Step 2 (Skills) UI with Zod validation and error handling --- src/components/mentorship/Step1BasicInfo.tsx | 89 ++++++---- src/components/mentorship/Step2Skills.tsx | 178 +++++++++++++++++++ src/components/mentorship/StepSection.tsx | 34 ++++ src/pages/mentorship/mentor-registration.tsx | 63 ++++++- src/schemas/mentorSchema.ts | 100 ++++++++--- 5 files changed, 401 insertions(+), 63 deletions(-) create mode 100644 src/components/mentorship/Step2Skills.tsx create mode 100644 src/components/mentorship/StepSection.tsx diff --git a/src/components/mentorship/Step1BasicInfo.tsx b/src/components/mentorship/Step1BasicInfo.tsx index ddcf2bd..0b90b82 100644 --- a/src/components/mentorship/Step1BasicInfo.tsx +++ b/src/components/mentorship/Step1BasicInfo.tsx @@ -5,6 +5,7 @@ import { FormGroup, FormControlLabel, Checkbox, Radio, RadioGroup, FormControl, FormLabel, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; +import StepSection from './StepSection'; const Step1BasicInfo = () => { const { register, watch, control, formState: { errors } } = useFormContext(); @@ -14,15 +15,10 @@ const Step1BasicInfo = () => { const months = ["May", "June", "July", "August", "September", "October", "November"]; - return ( - - - WCC: Registration Form for Mentors - - - Thank you for your interest in long-term mentoring sessions. - We appreciate your enthusiasm and look forward to connecting with the mentor of your choice. - + return ( + * Indicates a required field @@ -38,14 +34,16 @@ const Step1BasicInfo = () => { Slack Name * - - Please note your application will be rejected if you are not in our Slack community. + + {!errors.slackName && ( + Please note your application will be rejected if you are not in our Slack community. + )} Location * - + United Kingdom United States Canada @@ -53,13 +51,13 @@ const Step1BasicInfo = () => { - + What is your current job title? * - + Company name * @@ -67,7 +65,7 @@ const Step1BasicInfo = () => { - + Which kind of mentor you want to be? * @@ -79,11 +77,27 @@ const Step1BasicInfo = () => { Maximum number of mentees you are available to support. * - - {[1, 2, 3, 4, 5, 6].map((num) => ( - {num === 6 ? '6+' : num} - ))} - + ( + + {[1, 2, 3, 4, 5, 6].map((num) => ( + + {num === 6 ? '6+' : num} + + ))} + + )} + /> )} @@ -119,7 +133,7 @@ const Step1BasicInfo = () => { ))} - + @@ -156,28 +170,34 @@ const Step1BasicInfo = () => { ))} + {errors.adHocAvailability && ( + + Please select availability for at least one month + + )} + )} - {errors.isLongTermMentor && {errors.isLongTermMentor.message as string}} + {errors.isLongTermMentor && (Please select at least one mentorship format.)} Calendly schedule link * - + What kind of Mentee are you looking for? * - + - - + + Are you open to mentoring individuals who do not identify as women? * - + { render={({ field }) => ( field.onChange(e.target.value === 'true')} + aria-label="open to mentoring non-women" + value={field.value !== undefined && field.value !== null ? String(field.value) : ''} + onChange={(e) => field.onChange(e.target.value)} > } label="Yes" /> } label="No" /> @@ -194,15 +215,15 @@ const Step1BasicInfo = () => { )} /> - - {errors.openToNonWomen?.message as string} - + + {errors.openToNonWomen?.message as string || ''} + - + ); }; -export default Step1BasicInfo; \ No newline at end of file +export default Step1BasicInfo; diff --git a/src/components/mentorship/Step2Skills.tsx b/src/components/mentorship/Step2Skills.tsx new file mode 100644 index 0000000..3d4b867 --- /dev/null +++ b/src/components/mentorship/Step2Skills.tsx @@ -0,0 +1,178 @@ +import React from 'react'; +import { useFormContext, Controller } from 'react-hook-form'; +import { + Grid, TextField, MenuItem, Typography, Box, + RadioGroup, FormControlLabel, Radio, + Select, Checkbox, ListItemText, OutlinedInput +} from '@mui/material'; +import StepSection from './StepSection'; + +const inputStyle = { + '& .MuiOutlinedInput-root': { + backgroundColor: '#F5F5F5', + borderRadius: '4px', + '& fieldset': { border: 'none' }, + '&:hover fieldset': { border: 'none' }, + '&.Mui-focused fieldset': { border: '1px solid #333' }, + }, + '& .MuiInputBase-input': { padding: '12px 14px' } +}; + +const boldLabelStyle = { + fontWeight: 700, color: '#1B1919', mb: 0.5, display: 'block', fontSize: '16px', fontFamily: 'Roboto' +}; + +const helperTextStyle = { + fontSize: '12px', color: '#666', mt: 1, lineHeight: 1.5 +}; + +const LANGUAGES = ['English', 'Spanish', 'Russian', 'Polish', 'Ukrainian', 'French', 'Portuguese', 'Other']; +const EXPERIENCE = ['0–2 years', '3–5 years', '6–10 years', '10+ years']; + +const Step2Skills = () => { + const { register, watch, control, formState: { errors } } = useFormContext(); + const photoSource = watch('photoSource'); + + return ( + + + + Which languages do you speak? * + ( + + )} + /> + {errors.languages && ( + {errors.languages.message as string} + )} + + + + How many years of experience do you have in tech? * + + {EXPERIENCE.map((exp) => ( + {exp} + ))} + + + + + + Please share a brief summary of your professional background and expertise. * + + + + You may include: + +
    +
  • Areas of expertise and specializations
  • +
  • Years of experience
  • +
  • Notable achievements or certifications
  • +
  • Successful projects or career highlights
  • +
+
+ + + This information will be displayed in your mentor profile on the programme website and serve as your personal introduction to mentees + +
+ + + + List potential mentoring topics you can discuss with your mentee + + + Software Development Strategies, Resume Review, Preparation for The Technical Interview etc. + + + + This information will be displayed in your mentor profile on the programme website. + + + + + + Which photo should we use to show your profile as a mentor on our website? * + + + ( + + } label="Linked In" /> + } label="Slack" /> + } label="Image link from a public profile" /> + + )} + /> + {errors.photoSource && ( + {errors.photoSource.message as string} + )} + + {photoSource === 'other' && ( + + )} + +
+
+ ); +}; + +export default Step2Skills; diff --git a/src/components/mentorship/StepSection.tsx b/src/components/mentorship/StepSection.tsx new file mode 100644 index 0000000..4085caf --- /dev/null +++ b/src/components/mentorship/StepSection.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Box, Typography } from '@mui/material'; + +interface StepSectionProps { + title: string; + description?: string; + children: React.ReactNode; +} + +const StepSection = ({ title, description, children }: StepSectionProps) => ( + + + {title} + + + {description && ( + + {description} + + )} + + {children} + +); + +export default StepSection; diff --git a/src/pages/mentorship/mentor-registration.tsx b/src/pages/mentorship/mentor-registration.tsx index 2d9447b..e35de28 100644 --- a/src/pages/mentorship/mentor-registration.tsx +++ b/src/pages/mentorship/mentor-registration.tsx @@ -3,6 +3,7 @@ import { useForm, FormProvider } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Box, Container, Paper, Typography, Button, Stack, useMediaQuery, useTheme } from '@mui/material'; import Step1BasicInfo from '../../components/mentorship/Step1BasicInfo'; +import Step2Skills from '../../components/mentorship/Step2Skills'; import { mentorRegistrationSchema, MentorRegistrationData } from '../../schemas/mentorSchema'; const MentorRegistrationPage = () => { @@ -21,22 +22,71 @@ const MentorRegistrationPage = () => { let isStepValid = false; if (activeStep === 1) { - isStepValid = await formMethods.trigger([ + const standardValidation = await formMethods.trigger([ 'firstName', 'email', 'slackName', 'country', + 'city', 'jobTitle', 'company', 'isLongTermMentor', 'isAdHocMentor', - 'maxMentees', - 'adHocAvailability', 'calendlyLink', 'menteeExpectations', 'openToNonWomen', ]); - } else { + + const isLongTerm = formMethods.getValues('isLongTermMentor'); + const isAdHoc = formMethods.getValues('isAdHocMentor'); + + formMethods.clearErrors(['isLongTermMentor', 'maxMentees', 'adHocAvailability']); + + let manualChecksValid = true; + + if (!isLongTerm && !isAdHoc) { + formMethods.setError('isLongTermMentor', { + type: 'manual', + message: 'Please select at least one mentorship format.' + }); + manualChecksValid = false; + } + + if (isLongTerm) { + const maxMentees = formMethods.getValues('maxMentees'); + if (!maxMentees) { + formMethods.setError('maxMentees', { + type: 'manual', + message: 'Please select the number of mentees' + }); + manualChecksValid = false; + } + } + + if (isAdHoc) { + const adHoc = formMethods.getValues('adHocAvailability'); + if (!adHoc || Object.keys(adHoc).length === 0) { + formMethods.setError('adHocAvailability', { + type: 'manual', + message: 'Please select availability for at least one month' + }); + manualChecksValid = false; + } + } + + isStepValid = standardValidation && manualChecksValid; + } + else if (activeStep === 2) { + isStepValid = await formMethods.trigger([ + 'languages', + 'yearsOfExperience', + 'bio', + 'mentoringTopics', + 'photoSource', + 'customPhotoUrl' + ] as const); + } + else { isStepValid = true; } @@ -137,7 +187,8 @@ const MentorRegistrationPage = () => { {activeStep === 1 && } - {activeStep > 1 && ( + {activeStep === 2 && } + {activeStep > 2 && ( Screen {activeStep} Content (Coming Soon) @@ -196,4 +247,4 @@ const MentorRegistrationPage = () => { ); }; -export default MentorRegistrationPage; \ No newline at end of file +export default MentorRegistrationPage; diff --git a/src/schemas/mentorSchema.ts b/src/schemas/mentorSchema.ts index 494d6f5..3d41840 100644 --- a/src/schemas/mentorSchema.ts +++ b/src/schemas/mentorSchema.ts @@ -1,13 +1,13 @@ import { z } from 'zod'; -export const basicInfoSchema = z.object({ - firstName: z.string().min(1, 'First name is required'), - email: z.string().email({ message: 'Invalid email address' }), - slackName: z.string().min(1, 'Slack name is required'), - country: z.string().min(1, 'Country is required'), - city: z.string().min(1, 'City is required'), - jobTitle: z.string().min(1, 'Job title is required'), - company: z.string().min(1, 'Company is required'), +export const basicInfoObj = z.object({ + firstName: z.string().min(1, 'Please enter your full name'), + email: z.string().email('Please enter a valid email address'), + slackName: z.string().min(1, 'Please enter your Slack name'), + country: z.string().min(1, 'Please select your country'), + city: z.string().min(1, 'Please enter your city'), + jobTitle: z.string().min(1, 'Please enter your job title'), + company: z.string().min(1, 'Please enter your company name'), isLongTermMentor: z.boolean().optional(), isAdHocMentor: z.boolean().optional(), @@ -15,25 +15,79 @@ export const basicInfoSchema = z.object({ maxMentees: z.string().optional(), adHocAvailability: z.record(z.string(), z.string()).optional(), - calendlyLink: z.string().url({ message: "Please enter a valid Calendly URL" }), - menteeExpectations: z.string().min(1, "Please describe the mentee you are looking for"), - openToNonWomen: z.string().transform((val) => val === 'true'), + calendlyLink: z.string() + .url('Please enter a valid URL') + .refine( + (url) => url.includes('calendly.com'), + 'Please enter a valid Calendly URL (e.g., https://calendly.com/yourname)' + ), + menteeExpectations: z.string() + .min(10, 'Please provide at least 10 characters describing your ideal mentee'), + openToNonWomen: z.enum(['true', 'false'], { + message: 'Please select an option', +}).transform((val) => val === 'true'), -}).refine((data) => data.isLongTermMentor || data.isAdHocMentor, { - message: "Please select at least one mentorship type", - path: ["isLongTermMentor"], }); +const validateBasicInfo = (data: z.infer, ctx: z.RefinementCtx) => { + if (!data.isLongTermMentor && !data.isAdHocMentor) { + ctx.addIssue({ + code: "custom", + message: "Please select at least one mentorship type", + path: ["isLongTermMentor"], + }); + } + + if (data.isLongTermMentor) { + if (!data.maxMentees || data.maxMentees.length === 0) { + ctx.addIssue({ + code: "custom", + message: "Please select the number of mentees", + path: ["maxMentees"], + }); + } + } + + if (data.isAdHocMentor) { + const hasAvailability = data.adHocAvailability && Object.keys(data.adHocAvailability).length > 0; + if (!hasAvailability) { + ctx.addIssue({ + code: "custom", + message: "Please select availability for at least one month", + path: ["adHocAvailability"], + }); + } + } +}; +export const basicInfoSchema = basicInfoObj.superRefine(validateBasicInfo); export type BasicInfoData = z.infer; export const profileSchema = z.object({ - languages: z.array(z.string()).min(1, "Select at least one language"), - yearsOfExperience: z.string().min(1, "Please select your experience"), - bio: z.string().min(10, "Bio must be at least 10 characters"), + languages: z.array(z.string()).min(1, "Please select at least one language"), + yearsOfExperience: z.string().min(1, "Please select your years of experience"), + bio: z.string() + .min(10, "Please provide at least 10 characters for your bio") + .max(1000, "Bio must not exceed 1000 characters"), mentoringTopics: z.string().optional(), - photoSource: z.string().min(1, "Please select a photo source"), - customPhotoUrl: z.string().url({ message: "Invalid URL" }).optional().or(z.literal('')), -}); + photoSource: z.enum(['linkedin', 'slack', 'other'], { + message: 'Please select a photo source', +}), + customPhotoUrl: z.string() + .url('Please enter a valid URL') + .optional() + .or(z.literal('')), +}).refine( + (data) => { + if (data.photoSource === 'other') { + return data.customPhotoUrl && data.customPhotoUrl.length > 0; + } + return true; + }, + { + message: 'Please provide a photo URL', + path: ['customPhotoUrl'], + } +); export type ProfileData = z.infer; @@ -110,11 +164,11 @@ export const reviewSchema = z.object({ export type ReviewData = z.infer; export const mentorRegistrationSchema = z.object({ - ...basicInfoSchema.shape, + ...basicInfoObj.shape, ...profileSchema.shape, ...skillsSchema.shape, ...programmingSchema.shape, ...reviewSchema.shape, -}); +}).superRefine(validateBasicInfo); -export type MentorRegistrationData = z.infer; \ No newline at end of file +export type MentorRegistrationData = z.infer; From 1d3f0f606b17407818060263795a8d1c86731a1d Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Wed, 21 Jan 2026 08:21:11 -0600 Subject: [PATCH 09/20] feat: implement Step 3 Domain Skills UI --- .../mentorship/Step3DomainSkills.tsx | 162 ++++++++++++++++++ src/pages/mentorship/mentor-registration.tsx | 7 +- 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/components/mentorship/Step3DomainSkills.tsx diff --git a/src/components/mentorship/Step3DomainSkills.tsx b/src/components/mentorship/Step3DomainSkills.tsx new file mode 100644 index 0000000..6264921 --- /dev/null +++ b/src/components/mentorship/Step3DomainSkills.tsx @@ -0,0 +1,162 @@ +import React from 'react'; +import { useFormContext, Controller } from 'react-hook-form'; +import { + MenuItem, Typography, Box, TextField +} from '@mui/material'; +import StepSection from './StepSection'; + +const inputStyle = { + '& .MuiOutlinedInput-root': { + backgroundColor: 'custom.softGray', + borderRadius: '4px', + '& fieldset': { border: 'none' }, + '&:hover fieldset': { border: 'none' }, + '&.Mui-focused fieldset': { + border: '1px solid', + borderColor: 'text.primary' + }, + }, + '& .MuiInputBase-input': { + padding: '16px 14px', + fontSize: '16px', + color: 'text.primary' + }, + mb: 2 +}; + +const boldLabelStyle = { + fontWeight: 600, + color: 'text.primary', + mb: 1, + display: 'block', + fontSize: '15px', + fontFamily: 'Roboto' +}; + +const categoryHeaderStyle = { + fontWeight: 700, + color: 'text.primary', + fontSize: '18px', + mt: 4, + mb: 2, + fontFamily: 'Roboto' +}; + +const SKILL_LEVELS = [ + 'Expert', + 'Proficient', + 'Experienced', + 'Familiar', + 'Not Applicable' +]; + +const DOMAIN_GROUPS = [ + { + title: "AI, Data & ML", + fields: [ + { name: 'dataEngineering', label: 'Data Engineering' }, + { name: 'dataScience', label: 'Data Science' }, + { name: 'genAI', label: 'Generative AI and LLMs' }, + { name: 'machineLearning', label: 'Machine Learning and AI' }, + { name: 'mlOps', label: 'MLOps and AI Deployment' }, + ] + }, + { + title: "Infrastructure & Operations", + fields: [ + { name: 'cloudComputing', label: 'Cloud Computing' }, + { name: 'devOps', label: 'DevOps' }, + { name: 'networkEngineering', label: 'Network Engineering' }, + { name: 'platformEngineering', label: 'Platform Engineering' }, + { name: 'security', label: 'Security and Cybersecurity' }, + { name: 'sre', label: 'Site Reliability Engineering' }, + ] + }, + { + title: "Product, Leadership & Delivery", + fields: [ + { name: 'agile', label: 'Agile and Scrum Practices' }, + { name: 'businessAnalysis', label: 'Business Analysis' }, + { name: 'engineeringMgmt', label: 'Engineering Management' }, + { name: 'productMgmt', label: 'Product Management' }, + { name: 'projectMgmt', label: 'Project Management' }, + { name: 'technicalLeadership', label: 'Technical Leadership' }, + ] + }, + { + title: "Software Development", + fields: [ + { name: 'backend', label: 'Backend Development' }, + { name: 'frontend', label: 'Frontend Development' }, + { name: 'fullstack', label: 'Fullstack Development' }, + { name: 'mobileAndroid', label: 'Mobile Development - Android' }, + { name: 'mobileIos', label: 'Mobile Development - iOS' }, + { name: 'qaAutomation', label: 'QA and Test Automation' }, + { name: 'systemDesign', label: 'System Design and Software Architecture' }, + ] + }, +]; + +const Step3DomainSkills = () => { + const { control } = useFormContext(); + + return ( + + + {DOMAIN_GROUPS.map((group) => ( + + + {group.title} + + + {group.fields.map((skill) => ( + + + {skill.label} + + + ( + { + if (!selected) return "Not Applicable"; + return selected; + } + }} + > + {SKILL_LEVELS.map((level) => ( + + {level} + + ))} + + )} + /> + + ))} + + ))} + + + + Page 3 of 6 + + + + + ); +}; + +export default Step3DomainSkills; diff --git a/src/pages/mentorship/mentor-registration.tsx b/src/pages/mentorship/mentor-registration.tsx index e35de28..d7ffb6e 100644 --- a/src/pages/mentorship/mentor-registration.tsx +++ b/src/pages/mentorship/mentor-registration.tsx @@ -4,6 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { Box, Container, Paper, Typography, Button, Stack, useMediaQuery, useTheme } from '@mui/material'; import Step1BasicInfo from '../../components/mentorship/Step1BasicInfo'; import Step2Skills from '../../components/mentorship/Step2Skills'; +import Step3DomainSkills from 'components/mentorship/Step3DomainSkills'; import { mentorRegistrationSchema, MentorRegistrationData } from '../../schemas/mentorSchema'; const MentorRegistrationPage = () => { @@ -86,6 +87,9 @@ const MentorRegistrationPage = () => { 'customPhotoUrl' ] as const); } + else if (activeStep === 3) { + isStepValid = true; + } else { isStepValid = true; } @@ -188,7 +192,8 @@ const MentorRegistrationPage = () => { {activeStep === 1 && } {activeStep === 2 && } - {activeStep > 2 && ( + {activeStep === 3 && } + {activeStep > 3 && ( Screen {activeStep} Content (Coming Soon) From a0551fbeb1f8e0d7c48ceaf0f8306d2df50b0f9c Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Wed, 21 Jan 2026 13:58:06 -0600 Subject: [PATCH 10/20] feat: step 4 and 5 --- .../mentorship/Step4ProgrammingSkills.tsx | 178 ++++++++++++++ src/components/mentorship/Step5Review.tsx | 231 ++++++++++++++++++ src/pages/mentorship/mentor-registration.tsx | 29 ++- 3 files changed, 428 insertions(+), 10 deletions(-) create mode 100644 src/components/mentorship/Step4ProgrammingSkills.tsx create mode 100644 src/components/mentorship/Step5Review.tsx diff --git a/src/components/mentorship/Step4ProgrammingSkills.tsx b/src/components/mentorship/Step4ProgrammingSkills.tsx new file mode 100644 index 0000000..9d4d8a2 --- /dev/null +++ b/src/components/mentorship/Step4ProgrammingSkills.tsx @@ -0,0 +1,178 @@ +import React from 'react'; +import { useFormContext, Controller } from 'react-hook-form'; +import { + MenuItem, Typography, Box, TextField, Divider +} from '@mui/material'; +import StepSection from './StepSection'; + +const inputStyle = { + '& .MuiOutlinedInput-root': { + backgroundColor: 'custom.softGray', + borderRadius: '4px', + '& fieldset': { border: 'none' }, + '&:hover fieldset': { border: 'none' }, + '&.Mui-focused fieldset': { + border: '1px solid', + borderColor: 'text.primary' + }, + }, + '& .MuiInputBase-input': { + padding: '16px 14px', + fontSize: '16px', + color: 'text.primary' + }, + mb: 2 +}; + +const boldLabelStyle = { + fontWeight: 600, + color: 'text.primary', + mb: 1, + display: 'block', + fontSize: '15px', + fontFamily: 'Roboto' +}; + +const sectionHeaderStyle = { + fontWeight: 700, + color: 'text.primary', + fontSize: '18px', + mt: 4, + mb: 2, + fontFamily: 'Roboto' +}; + +const PREFERENCE_LEVELS = [ + 'Low', + 'Medium', + 'High', + 'Not Applicable' +]; + +const CAREER_GOALS = [ + { name: 'careerSwitch', label: 'Switch career to IT' }, + { name: 'beginnerToMid', label: 'Grow from beginner to mid-level' }, + { name: 'midToSenior', label: 'Grow from mid-level to senior-level' }, + { name: 'seniorPlus', label: 'Grow beyond senior level' }, + { name: 'icToManager', label: 'Switch from IC to management position' }, + { name: 'specialisationSwitch', label: 'Change specialisation within IT' }, +]; + +const PROGRAMMING_LANGUAGES = [ + { name: 'c', label: 'C' }, + { name: 'cSharp', label: 'C#' }, + { name: 'go', label: 'Go' }, + { name: 'java', label: 'Java' }, + { name: 'javascript', label: 'JavaScript' }, + { name: 'kotlin', label: 'Kotlin' }, + { name: 'python', label: 'Python' }, + { name: 'rust', label: 'Rust' }, + { name: 'scala', label: 'Scala' }, + { name: 'sql', label: 'SQL' }, + { name: 'swift', label: 'Swift' }, + { name: 'typescript', label: 'TypeScript' }, +]; + +const Step4ProgrammingSkills = () => { + const { control } = useFormContext(); + + return ( + + + + {CAREER_GOALS.map((goal) => ( + + + {goal.label} + + ( + { + if (!selected) return "Not Applicable"; + return selected; + } + }} + > + {PREFERENCE_LEVELS.map((level) => ( + + {level} + + ))} + + )} + /> + + ))} + + + + + + Add programming languages you can help your mentee with. + Mark your preference from Expert to Not Applicable. * + + + + Programming Languages: + + + {PROGRAMMING_LANGUAGES.map((lang) => ( + + + {lang.label} + + ( + { + if (!selected) return "Not Applicable"; + return selected; + } + }} + > + {PREFERENCE_LEVELS.map((level) => ( + + {level} + + ))} + + )} + /> + + ))} + + + + + Page 4 of 6 + + + + + + ); +}; + +export default Step4ProgrammingSkills; \ No newline at end of file diff --git a/src/components/mentorship/Step5Review.tsx b/src/components/mentorship/Step5Review.tsx new file mode 100644 index 0000000..bc5ad61 --- /dev/null +++ b/src/components/mentorship/Step5Review.tsx @@ -0,0 +1,231 @@ +import React from 'react'; +import { useFormContext, Controller } from 'react-hook-form'; +import { + Grid, TextField, Typography, Box, Radio, RadioGroup, + FormControlLabel, FormControl, FormHelperText, Checkbox, + Accordion, AccordionSummary, AccordionDetails, Link +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import StepSection from './StepSection'; + +const inputStyle = { + '& .MuiOutlinedInput-root': { + backgroundColor: 'custom.softGray', + borderRadius: '4px', + '& fieldset': { border: 'none' }, + '&:hover fieldset': { border: 'none' }, + '&.Mui-focused fieldset': { + border: '1px solid', + borderColor: 'text.primary' + }, + }, + '& .MuiInputBase-input': { + padding: '16px 14px', + fontSize: '16px', + color: 'text.primary' + }, +}; + +const boldLabelStyle = { + fontWeight: 600, + color: 'text.primary', + mb: 1, + display: 'block', + fontSize: '15px', + fontFamily: 'Roboto' +}; + +const accordionStyle = { + backgroundColor: 'custom.softGray', + boxShadow: 'none', + borderRadius: '4px !important', + '&:before': { display: 'none' }, + mb: 3 +}; + +const Step5Review = () => { + const { register, control, formState: { errors } } = useFormContext(); + + return ( + + + + + + LinkedIn * + + + + + + + Other social media + + + + } + aria-controls="social-media-content" + id="social-media-header" + sx={{ minHeight: '56px' }} + > + Other social media + + + + + Github + + + + + Instagram + + + + + Medium + + + + + Website + + + + Other + + + + + + + + + + + Do you identify as a woman or non-binary? * + + ( + + } label="Yes" /> + } label="No" /> + } label="Prefer not to say" /> + + )} + /> + {errors.identity?.message as string} + + + + + + What are your preferred pronouns? e.g., he/him, she/her, they/them * + + + + We ask for your pronouns to ensure we address you correctly and respectfully. + + {errors.pronouns && ( + {errors.pronouns.message as string} + )} + + + + + + Are you happy for us to highlight/promote you as our mentor on our social media? * + + ( + + } label="Yes" /> + } label="No" /> + + )} + /> + + Please make sure your details are always up to date on our website. + + {errors.socialHighlight && ( + {errors.socialHighlight.message as string} + )} + + + + + + By selecting the checkbox below, you confirm that you: + + + +
  • + Have read and agree to the code of conduct{' '} and the {' '}mentorship code of conduct. +
  • +
  • + Grant Women Coding Community permission to store my contact information, use it to reach out to me, and publish mine mentor profile on the community's website. +
  • +
  • + I hereby grant the Women Coding Community the right to store the above-mentioned contact details to contact me and include my mentor profile on our website. +
  • +
    + + + ( + + )} + /> + } + label={ + + I accept and agree to the above terms * + + } + /> + {errors.termsAgreed?.message as string} + +
    + +
    + + + + Page 5 of 5 + + +
    + ); +}; + +export default Step5Review; diff --git a/src/pages/mentorship/mentor-registration.tsx b/src/pages/mentorship/mentor-registration.tsx index d7ffb6e..3119baa 100644 --- a/src/pages/mentorship/mentor-registration.tsx +++ b/src/pages/mentorship/mentor-registration.tsx @@ -5,7 +5,11 @@ import { Box, Container, Paper, Typography, Button, Stack, useMediaQuery, useThe import Step1BasicInfo from '../../components/mentorship/Step1BasicInfo'; import Step2Skills from '../../components/mentorship/Step2Skills'; import Step3DomainSkills from 'components/mentorship/Step3DomainSkills'; +import Step4ProgrammingSkills from 'components/mentorship/Step4ProgrammingSkills'; + import { mentorRegistrationSchema, MentorRegistrationData } from '../../schemas/mentorSchema'; +import Step5Review from 'components/mentorship/Step5Review'; + const MentorRegistrationPage = () => { const theme = useTheme(); @@ -17,7 +21,7 @@ const MentorRegistrationPage = () => { }); const [activeStep, setActiveStep] = useState(1); - const totalSteps = 6; + const totalSteps = 5; const handleNext = async () => { let isStepValid = false; @@ -90,11 +94,21 @@ const MentorRegistrationPage = () => { else if (activeStep === 3) { isStepValid = true; } - else { + else if (activeStep === 4) { isStepValid = true; } + else if (activeStep === 5) { + isStepValid = await formMethods.trigger([ + 'linkedin', 'github', 'instagram', 'medium', 'website', 'otherSocial', + 'identity', 'pronouns', 'socialHighlight', 'termsAgreed' + ]); + } + if (isStepValid) { + if (activeStep === totalSteps) { + return; + } setActiveStep(prev => prev + 1); window.scrollTo(0, 0); } @@ -105,7 +119,7 @@ const MentorRegistrationPage = () => { }; const onSubmit = (data: MentorRegistrationData) => { - console.log('Form Data:', data); + console.log('Form Data Submitted:', data); }; return ( @@ -193,13 +207,8 @@ const MentorRegistrationPage = () => { {activeStep === 1 && } {activeStep === 2 && } {activeStep === 3 && } - {activeStep > 3 && ( - - - Screen {activeStep} Content (Coming Soon) - - - )} + {activeStep === 4 && } + {activeStep === 5 && }
    Date: Wed, 21 Jan 2026 15:21:59 -0600 Subject: [PATCH 11/20] refactor: extract shared styles to fix code duplication issues --- .../mentorship/Step3DomainSkills.tsx | 40 +------------------ .../mentorship/Step4ProgrammingSkills.tsx | 38 +----------------- src/components/mentorship/mentorshipStyles.ts | 38 ++++++++++++++++++ 3 files changed, 41 insertions(+), 75 deletions(-) create mode 100644 src/components/mentorship/mentorshipStyles.ts diff --git a/src/components/mentorship/Step3DomainSkills.tsx b/src/components/mentorship/Step3DomainSkills.tsx index 6264921..04ab8cd 100644 --- a/src/components/mentorship/Step3DomainSkills.tsx +++ b/src/components/mentorship/Step3DomainSkills.tsx @@ -3,45 +3,9 @@ import { useFormContext, Controller } from 'react-hook-form'; import { MenuItem, Typography, Box, TextField } from '@mui/material'; +import { inputStyle, boldLabelStyle, sectionHeaderStyle } from './mentorshipStyles'; import StepSection from './StepSection'; -const inputStyle = { - '& .MuiOutlinedInput-root': { - backgroundColor: 'custom.softGray', - borderRadius: '4px', - '& fieldset': { border: 'none' }, - '&:hover fieldset': { border: 'none' }, - '&.Mui-focused fieldset': { - border: '1px solid', - borderColor: 'text.primary' - }, - }, - '& .MuiInputBase-input': { - padding: '16px 14px', - fontSize: '16px', - color: 'text.primary' - }, - mb: 2 -}; - -const boldLabelStyle = { - fontWeight: 600, - color: 'text.primary', - mb: 1, - display: 'block', - fontSize: '15px', - fontFamily: 'Roboto' -}; - -const categoryHeaderStyle = { - fontWeight: 700, - color: 'text.primary', - fontSize: '18px', - mt: 4, - mb: 2, - fontFamily: 'Roboto' -}; - const SKILL_LEVELS = [ 'Expert', 'Proficient', @@ -108,7 +72,7 @@ const Step3DomainSkills = () => { {DOMAIN_GROUPS.map((group) => ( - + {group.title} diff --git a/src/components/mentorship/Step4ProgrammingSkills.tsx b/src/components/mentorship/Step4ProgrammingSkills.tsx index 9d4d8a2..3c3f3e8 100644 --- a/src/components/mentorship/Step4ProgrammingSkills.tsx +++ b/src/components/mentorship/Step4ProgrammingSkills.tsx @@ -3,45 +3,9 @@ import { useFormContext, Controller } from 'react-hook-form'; import { MenuItem, Typography, Box, TextField, Divider } from '@mui/material'; +import { inputStyle, boldLabelStyle, sectionHeaderStyle } from './mentorshipStyles'; import StepSection from './StepSection'; -const inputStyle = { - '& .MuiOutlinedInput-root': { - backgroundColor: 'custom.softGray', - borderRadius: '4px', - '& fieldset': { border: 'none' }, - '&:hover fieldset': { border: 'none' }, - '&.Mui-focused fieldset': { - border: '1px solid', - borderColor: 'text.primary' - }, - }, - '& .MuiInputBase-input': { - padding: '16px 14px', - fontSize: '16px', - color: 'text.primary' - }, - mb: 2 -}; - -const boldLabelStyle = { - fontWeight: 600, - color: 'text.primary', - mb: 1, - display: 'block', - fontSize: '15px', - fontFamily: 'Roboto' -}; - -const sectionHeaderStyle = { - fontWeight: 700, - color: 'text.primary', - fontSize: '18px', - mt: 4, - mb: 2, - fontFamily: 'Roboto' -}; - const PREFERENCE_LEVELS = [ 'Low', 'Medium', diff --git a/src/components/mentorship/mentorshipStyles.ts b/src/components/mentorship/mentorshipStyles.ts new file mode 100644 index 0000000..0c3a018 --- /dev/null +++ b/src/components/mentorship/mentorshipStyles.ts @@ -0,0 +1,38 @@ +// src/components/mentorship/mentorshipStyles.ts + +export const inputStyle = { + '& .MuiOutlinedInput-root': { + backgroundColor: 'custom.softGray', + borderRadius: '4px', + '& fieldset': { border: 'none' }, + '&:hover fieldset': { border: 'none' }, + '&.Mui-focused fieldset': { + border: '1px solid', + borderColor: 'text.primary', + }, + }, + '& .MuiInputBase-input': { + padding: '16px 14px', + fontSize: '16px', + color: 'text.primary', + }, + mb: 2, +}; + +export const boldLabelStyle = { + fontWeight: 600, + color: 'text.primary', + mb: 1, + display: 'block', + fontSize: '15px', + fontFamily: 'Roboto', +}; + +export const sectionHeaderStyle = { + fontWeight: 700, + color: 'text.primary', + fontSize: '18px', + mt: 4, + mb: 2, + fontFamily: 'Roboto', +}; \ No newline at end of file From d2e24a0ce908b3000a800257c400b7606dce81c2 Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Wed, 21 Jan 2026 15:35:09 -0600 Subject: [PATCH 12/20] refactor: extract dropdown options to mentorshipConstants.ts to fix duplication --- .../mentorship/Step3DomainSkills.tsx | 56 +---------- .../mentorship/Step4ProgrammingSkills.tsx | 32 +------ src/utils/mentorshipConstants.ts | 93 +++++++++++++++++++ 3 files changed, 95 insertions(+), 86 deletions(-) create mode 100644 src/utils/mentorshipConstants.ts diff --git a/src/components/mentorship/Step3DomainSkills.tsx b/src/components/mentorship/Step3DomainSkills.tsx index 04ab8cd..d8ed18c 100644 --- a/src/components/mentorship/Step3DomainSkills.tsx +++ b/src/components/mentorship/Step3DomainSkills.tsx @@ -5,61 +5,7 @@ import { } from '@mui/material'; import { inputStyle, boldLabelStyle, sectionHeaderStyle } from './mentorshipStyles'; import StepSection from './StepSection'; - -const SKILL_LEVELS = [ - 'Expert', - 'Proficient', - 'Experienced', - 'Familiar', - 'Not Applicable' -]; - -const DOMAIN_GROUPS = [ - { - title: "AI, Data & ML", - fields: [ - { name: 'dataEngineering', label: 'Data Engineering' }, - { name: 'dataScience', label: 'Data Science' }, - { name: 'genAI', label: 'Generative AI and LLMs' }, - { name: 'machineLearning', label: 'Machine Learning and AI' }, - { name: 'mlOps', label: 'MLOps and AI Deployment' }, - ] - }, - { - title: "Infrastructure & Operations", - fields: [ - { name: 'cloudComputing', label: 'Cloud Computing' }, - { name: 'devOps', label: 'DevOps' }, - { name: 'networkEngineering', label: 'Network Engineering' }, - { name: 'platformEngineering', label: 'Platform Engineering' }, - { name: 'security', label: 'Security and Cybersecurity' }, - { name: 'sre', label: 'Site Reliability Engineering' }, - ] - }, - { - title: "Product, Leadership & Delivery", - fields: [ - { name: 'agile', label: 'Agile and Scrum Practices' }, - { name: 'businessAnalysis', label: 'Business Analysis' }, - { name: 'engineeringMgmt', label: 'Engineering Management' }, - { name: 'productMgmt', label: 'Product Management' }, - { name: 'projectMgmt', label: 'Project Management' }, - { name: 'technicalLeadership', label: 'Technical Leadership' }, - ] - }, - { - title: "Software Development", - fields: [ - { name: 'backend', label: 'Backend Development' }, - { name: 'frontend', label: 'Frontend Development' }, - { name: 'fullstack', label: 'Fullstack Development' }, - { name: 'mobileAndroid', label: 'Mobile Development - Android' }, - { name: 'mobileIos', label: 'Mobile Development - iOS' }, - { name: 'qaAutomation', label: 'QA and Test Automation' }, - { name: 'systemDesign', label: 'System Design and Software Architecture' }, - ] - }, -]; +import { DOMAIN_GROUPS, SKILL_LEVELS } from '../../utils/mentorshipConstants'; const Step3DomainSkills = () => { const { control } = useFormContext(); diff --git a/src/components/mentorship/Step4ProgrammingSkills.tsx b/src/components/mentorship/Step4ProgrammingSkills.tsx index 3c3f3e8..af3f93e 100644 --- a/src/components/mentorship/Step4ProgrammingSkills.tsx +++ b/src/components/mentorship/Step4ProgrammingSkills.tsx @@ -5,37 +5,7 @@ import { } from '@mui/material'; import { inputStyle, boldLabelStyle, sectionHeaderStyle } from './mentorshipStyles'; import StepSection from './StepSection'; - -const PREFERENCE_LEVELS = [ - 'Low', - 'Medium', - 'High', - 'Not Applicable' -]; - -const CAREER_GOALS = [ - { name: 'careerSwitch', label: 'Switch career to IT' }, - { name: 'beginnerToMid', label: 'Grow from beginner to mid-level' }, - { name: 'midToSenior', label: 'Grow from mid-level to senior-level' }, - { name: 'seniorPlus', label: 'Grow beyond senior level' }, - { name: 'icToManager', label: 'Switch from IC to management position' }, - { name: 'specialisationSwitch', label: 'Change specialisation within IT' }, -]; - -const PROGRAMMING_LANGUAGES = [ - { name: 'c', label: 'C' }, - { name: 'cSharp', label: 'C#' }, - { name: 'go', label: 'Go' }, - { name: 'java', label: 'Java' }, - { name: 'javascript', label: 'JavaScript' }, - { name: 'kotlin', label: 'Kotlin' }, - { name: 'python', label: 'Python' }, - { name: 'rust', label: 'Rust' }, - { name: 'scala', label: 'Scala' }, - { name: 'sql', label: 'SQL' }, - { name: 'swift', label: 'Swift' }, - { name: 'typescript', label: 'TypeScript' }, -]; +import { CAREER_GOALS, PROGRAMMING_LANGUAGES, PREFERENCE_LEVELS } from '../../utils/mentorshipConstants'; const Step4ProgrammingSkills = () => { const { control } = useFormContext(); diff --git a/src/utils/mentorshipConstants.ts b/src/utils/mentorshipConstants.ts new file mode 100644 index 0000000..cfe9409 --- /dev/null +++ b/src/utils/mentorshipConstants.ts @@ -0,0 +1,93 @@ +// src/utils/mentorshipConstants.ts + +// --- SHARED DROPDOWN OPTIONS --- + +export const SKILL_LEVELS = [ + 'Expert', + 'Proficient', + 'Experienced', + 'Familiar', + 'Not Applicable' +]; + +export const PREFERENCE_LEVELS = [ + 'Low', + 'Medium', + 'High', + 'Not Applicable' +]; + +// --- STEP 3 DATA (Domain Skills) --- + +export const DOMAIN_GROUPS = [ + { + title: "AI, Data & ML", + fields: [ + { name: 'dataEngineering', label: 'Data Engineering' }, + { name: 'dataScience', label: 'Data Science' }, + { name: 'genAI', label: 'Generative AI and LLMs' }, + { name: 'machineLearning', label: 'Machine Learning and AI' }, + { name: 'mlOps', label: 'MLOps and AI Deployment' }, + ] + }, + { + title: "Infrastructure & Operations", + fields: [ + { name: 'cloudComputing', label: 'Cloud Computing' }, + { name: 'devOps', label: 'DevOps' }, + { name: 'networkEngineering', label: 'Network Engineering' }, + { name: 'platformEngineering', label: 'Platform Engineering' }, + { name: 'security', label: 'Security and Cybersecurity' }, + { name: 'sre', label: 'Site Reliability Engineering' }, + ] + }, + { + title: "Product, Leadership & Delivery", + fields: [ + { name: 'agile', label: 'Agile and Scrum Practices' }, + { name: 'businessAnalysis', label: 'Business Analysis' }, + { name: 'engineeringMgmt', label: 'Engineering Management' }, + { name: 'productMgmt', label: 'Product Management' }, + { name: 'projectMgmt', label: 'Project Management' }, + { name: 'technicalLeadership', label: 'Technical Leadership' }, + ] + }, + { + title: "Software Development", + fields: [ + { name: 'backend', label: 'Backend Development' }, + { name: 'frontend', label: 'Frontend Development' }, + { name: 'fullstack', label: 'Fullstack Development' }, + { name: 'mobileAndroid', label: 'Mobile Development - Android' }, + { name: 'mobileIos', label: 'Mobile Development - iOS' }, + { name: 'qaAutomation', label: 'QA and Test Automation' }, + { name: 'systemDesign', label: 'System Design and Software Architecture' }, + ] + }, +]; + +// --- STEP 4 DATA (Programming Skills) --- + +export const CAREER_GOALS = [ + { name: 'careerSwitch', label: 'Switch career to IT' }, + { name: 'beginnerToMid', label: 'Grow from beginner to mid-level' }, + { name: 'midToSenior', label: 'Grow from mid-level to senior-level' }, + { name: 'seniorPlus', label: 'Grow beyond senior level' }, + { name: 'icToManager', label: 'Switch from IC to management position' }, + { name: 'specialisationSwitch', label: 'Change specialisation within IT' }, +]; + +export const PROGRAMMING_LANGUAGES = [ + { name: 'c', label: 'C' }, + { name: 'cSharp', label: 'C#' }, + { name: 'go', label: 'Go' }, + { name: 'java', label: 'Java' }, + { name: 'javascript', label: 'JavaScript' }, + { name: 'kotlin', label: 'Kotlin' }, + { name: 'python', label: 'Python' }, + { name: 'rust', label: 'Rust' }, + { name: 'scala', label: 'Scala' }, + { name: 'sql', label: 'SQL' }, + { name: 'swift', label: 'Swift' }, + { name: 'typescript', label: 'TypeScript' }, +]; From b986edc419ee18e54966d25e75f27a1a5e934e76 Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Wed, 21 Jan 2026 15:55:39 -0600 Subject: [PATCH 13/20] refactor: extract MentorshipSelect component to fix code duplication --- .../mentorship/MentorshipSelect.tsx | 44 +++++++++++++ .../mentorship/Step3DomainSkills.tsx | 38 ++--------- .../mentorship/Step4ProgrammingSkills.tsx | 65 +++---------------- 3 files changed, 61 insertions(+), 86 deletions(-) create mode 100644 src/components/mentorship/MentorshipSelect.tsx diff --git a/src/components/mentorship/MentorshipSelect.tsx b/src/components/mentorship/MentorshipSelect.tsx new file mode 100644 index 0000000..93e2d5a --- /dev/null +++ b/src/components/mentorship/MentorshipSelect.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { TextField, MenuItem } from '@mui/material'; +import { inputStyle } from './mentorshipStyles'; + +interface MentorshipSelectProps { + name: string; + label?: string; + options: string[]; +} + +export const MentorshipSelect = ({ name, label, options }: MentorshipSelectProps) => { + const { control } = useFormContext(); + + return ( + ( + { + if (!selected) return "Not Applicable"; + return selected; + } + }} + > + {options.map((option) => ( + + {option} + + ))} + + )} + /> + ); +}; diff --git a/src/components/mentorship/Step3DomainSkills.tsx b/src/components/mentorship/Step3DomainSkills.tsx index d8ed18c..1fa73e1 100644 --- a/src/components/mentorship/Step3DomainSkills.tsx +++ b/src/components/mentorship/Step3DomainSkills.tsx @@ -1,14 +1,13 @@ import React from 'react'; -import { useFormContext, Controller } from 'react-hook-form'; import { - MenuItem, Typography, Box, TextField + Typography, Box } from '@mui/material'; -import { inputStyle, boldLabelStyle, sectionHeaderStyle } from './mentorshipStyles'; +import { boldLabelStyle, sectionHeaderStyle } from './mentorshipStyles'; import StepSection from './StepSection'; import { DOMAIN_GROUPS, SKILL_LEVELS } from '../../utils/mentorshipConstants'; +import { MentorshipSelect } from './MentorshipSelect'; const Step3DomainSkills = () => { - const { control } = useFormContext(); return ( { {skill.label} - - - ( - { - if (!selected) return "Not Applicable"; - return selected; - } - }} - > - {SKILL_LEVELS.map((level) => ( - - {level} - - ))} - - )} + + ))} diff --git a/src/components/mentorship/Step4ProgrammingSkills.tsx b/src/components/mentorship/Step4ProgrammingSkills.tsx index af3f93e..cd6a050 100644 --- a/src/components/mentorship/Step4ProgrammingSkills.tsx +++ b/src/components/mentorship/Step4ProgrammingSkills.tsx @@ -1,14 +1,13 @@ import React from 'react'; -import { useFormContext, Controller } from 'react-hook-form'; import { - MenuItem, Typography, Box, TextField, Divider + Typography, Box, Divider } from '@mui/material'; -import { inputStyle, boldLabelStyle, sectionHeaderStyle } from './mentorshipStyles'; +import { boldLabelStyle, sectionHeaderStyle } from './mentorshipStyles'; import StepSection from './StepSection'; import { CAREER_GOALS, PROGRAMMING_LANGUAGES, PREFERENCE_LEVELS } from '../../utils/mentorshipConstants'; +import { MentorshipSelect } from './MentorshipSelect'; const Step4ProgrammingSkills = () => { - const { control } = useFormContext(); return ( { {goal.label} - ( - { - if (!selected) return "Not Applicable"; - return selected; - } - }} - > - {PREFERENCE_LEVELS.map((level) => ( - - {level} - - ))} - - )} + ))} @@ -68,31 +45,9 @@ const Step4ProgrammingSkills = () => { {lang.label} - ( - { - if (!selected) return "Not Applicable"; - return selected; - } - }} - > - {PREFERENCE_LEVELS.map((level) => ( - - {level} - - ))} - - )} +
    ))} @@ -109,4 +64,4 @@ const Step4ProgrammingSkills = () => { ); }; -export default Step4ProgrammingSkills; \ No newline at end of file +export default Step4ProgrammingSkills; From 1af4a15b88ff4edbc2b3c073e5ba422b395a7ad4 Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Thu, 22 Jan 2026 15:20:48 -0600 Subject: [PATCH 14/20] fix: address PR reviewcomments - Add dynamic country dropdown using COUNTRIES constant - Update code of conduct links --- src/components/mentorship/Step1BasicInfo.tsx | 23 ++++++++--- src/components/mentorship/Step5Review.tsx | 2 +- src/utils/mentorshipConstants.ts | 40 +++++++++++++++++--- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/components/mentorship/Step1BasicInfo.tsx b/src/components/mentorship/Step1BasicInfo.tsx index 0b90b82..f43a70a 100644 --- a/src/components/mentorship/Step1BasicInfo.tsx +++ b/src/components/mentorship/Step1BasicInfo.tsx @@ -6,6 +6,7 @@ import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; import StepSection from './StepSection'; +import { COUNTRIES } from '../../utils/mentorshipConstants'; const Step1BasicInfo = () => { const { register, watch, control, formState: { errors } } = useFormContext(); @@ -43,11 +44,23 @@ const Step1BasicInfo = () => { Location * - - United Kingdom - United States - Canada - Other + + + Select a country + + {COUNTRIES.map((country) => ( + + {country.name} + + ))} diff --git a/src/components/mentorship/Step5Review.tsx b/src/components/mentorship/Step5Review.tsx index bc5ad61..17e445a 100644 --- a/src/components/mentorship/Step5Review.tsx +++ b/src/components/mentorship/Step5Review.tsx @@ -181,7 +181,7 @@ const Step5Review = () => {
  • - Have read and agree to the code of conduct{' '} and the {' '}mentorship code of conduct. + Have read and agree to the code of conduct{' '} and the {' '}mentorship code of conduct.
  • Grant Women Coding Community permission to store my contact information, use it to reach out to me, and publish mine mentor profile on the community's website. diff --git a/src/utils/mentorshipConstants.ts b/src/utils/mentorshipConstants.ts index cfe9409..f220c86 100644 --- a/src/utils/mentorshipConstants.ts +++ b/src/utils/mentorshipConstants.ts @@ -1,7 +1,5 @@ // src/utils/mentorshipConstants.ts -// --- SHARED DROPDOWN OPTIONS --- - export const SKILL_LEVELS = [ 'Expert', 'Proficient', @@ -17,8 +15,6 @@ export const PREFERENCE_LEVELS = [ 'Not Applicable' ]; -// --- STEP 3 DATA (Domain Skills) --- - export const DOMAIN_GROUPS = [ { title: "AI, Data & ML", @@ -66,8 +62,6 @@ export const DOMAIN_GROUPS = [ }, ]; -// --- STEP 4 DATA (Programming Skills) --- - export const CAREER_GOALS = [ { name: 'careerSwitch', label: 'Switch career to IT' }, { name: 'beginnerToMid', label: 'Grow from beginner to mid-level' }, @@ -91,3 +85,37 @@ export const PROGRAMMING_LANGUAGES = [ { name: 'swift', label: 'Swift' }, { name: 'typescript', label: 'TypeScript' }, ]; + +export const COUNTRIES = [ + { code: 'GB', name: 'United Kingdom' }, + { code: 'US', name: 'United States' }, + { code: 'CA', name: 'Canada' }, + { code: 'AU', name: 'Australia' }, + { code: 'DE', name: 'Germany' }, + { code: 'FR', name: 'France' }, + { code: 'ES', name: 'Spain' }, + { code: 'IT', name: 'Italy' }, + { code: 'NL', name: 'Netherlands' }, + { code: 'SE', name: 'Sweden' }, + { code: 'NO', name: 'Norway' }, + { code: 'DK', name: 'Denmark' }, + { code: 'FI', name: 'Finland' }, + { code: 'IE', name: 'Ireland' }, + { code: 'PL', name: 'Poland' }, + { code: 'PT', name: 'Portugal' }, + { code: 'GR', name: 'Greece' }, + { code: 'CH', name: 'Switzerland' }, + { code: 'AT', name: 'Austria' }, + { code: 'BE', name: 'Belgium' }, + { code: 'CZ', name: 'Czech Republic' }, + { code: 'IN', name: 'India' }, + { code: 'SG', name: 'Singapore' }, + { code: 'JP', name: 'Japan' }, + { code: 'CN', name: 'China' }, + { code: 'BR', name: 'Brazil' }, + { code: 'MX', name: 'Mexico' }, + { code: 'AR', name: 'Argentina' }, + { code: 'ZA', name: 'South Africa' }, + { code: 'NZ', name: 'New Zealand' }, + { code: 'OTHER', name: 'Other' }, +]; From 89533be24399087428cc6dac20cbb439acc2f6bc Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Thu, 22 Jan 2026 15:55:01 -0600 Subject: [PATCH 15/20] fix: sonar fixes uses .includes() instread of .indexOf() --- src/components/mentorship/Step2Skills.tsx | 2 +- src/pages/mentorship/mentor-registration.tsx | 157 +++++++++---------- 2 files changed, 77 insertions(+), 82 deletions(-) diff --git a/src/components/mentorship/Step2Skills.tsx b/src/components/mentorship/Step2Skills.tsx index 3d4b867..46728c3 100644 --- a/src/components/mentorship/Step2Skills.tsx +++ b/src/components/mentorship/Step2Skills.tsx @@ -60,7 +60,7 @@ const Step2Skills = () => { > {LANGUAGES.map((lang) => ( - -1} /> + ))} diff --git a/src/pages/mentorship/mentor-registration.tsx b/src/pages/mentorship/mentor-registration.tsx index 3119baa..6276167 100644 --- a/src/pages/mentorship/mentor-registration.tsx +++ b/src/pages/mentorship/mentor-registration.tsx @@ -10,6 +10,65 @@ import Step4ProgrammingSkills from 'components/mentorship/Step4ProgrammingSkills import { mentorRegistrationSchema, MentorRegistrationData } from '../../schemas/mentorSchema'; import Step5Review from 'components/mentorship/Step5Review'; +const validateStep1 = async (formMethods: any) => { + const standardFields = [ + 'firstName', 'email', 'slackName', 'country', 'city', + 'jobTitle', 'company', 'isLongTermMentor', 'isAdHocMentor', + 'calendlyLink', 'menteeExpectations', 'openToNonWomen', + ] as const; + + const standardValidation = await formMethods.trigger(standardFields); + const isLongTerm = formMethods.getValues('isLongTermMentor'); + const isAdHoc = formMethods.getValues('isAdHocMentor'); + + formMethods.clearErrors(['isLongTermMentor', 'maxMentees', 'adHocAvailability']); + + let manualChecksValid = true; + + if (!isLongTerm && !isAdHoc) { + formMethods.setError('isLongTermMentor', { + type: 'manual', + message: 'Please select at least one mentorship format.' + }); + manualChecksValid = false; + } + + if (isLongTerm && !formMethods.getValues('maxMentees')) { + formMethods.setError('maxMentees', { + type: 'manual', + message: 'Please select the number of mentees' + }); + manualChecksValid = false; + } + + if (isAdHoc) { + const adHoc = formMethods.getValues('adHocAvailability'); + if (!adHoc || Object.keys(adHoc).length === 0) { + formMethods.setError('adHocAvailability', { + type: 'manual', + message: 'Please select availability for at least one month' + }); + manualChecksValid = false; + } + } + + return standardValidation && manualChecksValid; +}; + +const validateStep2 = async (formMethods: any) => { + return await formMethods.trigger([ + 'languages', 'yearsOfExperience', 'bio', + 'mentoringTopics', 'photoSource', 'customPhotoUrl' + ] as const); +}; + +const validateStep5 = async (formMethods: any) => { + return await formMethods.trigger([ + 'linkedin', 'github', 'instagram', 'medium', + 'website', 'otherSocial', 'identity', 'pronouns', + 'socialHighlight', 'termsAgreed' + ]); +}; const MentorRegistrationPage = () => { const theme = useTheme(); @@ -26,89 +85,25 @@ const MentorRegistrationPage = () => { const handleNext = async () => { let isStepValid = false; - if (activeStep === 1) { - const standardValidation = await formMethods.trigger([ - 'firstName', - 'email', - 'slackName', - 'country', - 'city', - 'jobTitle', - 'company', - 'isLongTermMentor', - 'isAdHocMentor', - 'calendlyLink', - 'menteeExpectations', - 'openToNonWomen', - ]); - - const isLongTerm = formMethods.getValues('isLongTermMentor'); - const isAdHoc = formMethods.getValues('isAdHocMentor'); - - formMethods.clearErrors(['isLongTermMentor', 'maxMentees', 'adHocAvailability']); - - let manualChecksValid = true; - - if (!isLongTerm && !isAdHoc) { - formMethods.setError('isLongTermMentor', { - type: 'manual', - message: 'Please select at least one mentorship format.' - }); - manualChecksValid = false; - } - - if (isLongTerm) { - const maxMentees = formMethods.getValues('maxMentees'); - if (!maxMentees) { - formMethods.setError('maxMentees', { - type: 'manual', - message: 'Please select the number of mentees' - }); - manualChecksValid = false; - } - } - - if (isAdHoc) { - const adHoc = formMethods.getValues('adHocAvailability'); - if (!adHoc || Object.keys(adHoc).length === 0) { - formMethods.setError('adHocAvailability', { - type: 'manual', - message: 'Please select availability for at least one month' - }); - manualChecksValid = false; - } - } - - isStepValid = standardValidation && manualChecksValid; - } - else if (activeStep === 2) { - isStepValid = await formMethods.trigger([ - 'languages', - 'yearsOfExperience', - 'bio', - 'mentoringTopics', - 'photoSource', - 'customPhotoUrl' - ] as const); - } - else if (activeStep === 3) { - isStepValid = true; - } - else if (activeStep === 4) { - isStepValid = true; - } - - else if (activeStep === 5) { - isStepValid = await formMethods.trigger([ - 'linkedin', 'github', 'instagram', 'medium', 'website', 'otherSocial', - 'identity', 'pronouns', 'socialHighlight', 'termsAgreed' - ]); + switch (activeStep) { + case 1: + isStepValid = await validateStep1(formMethods); + break; + case 2: + isStepValid = await validateStep2(formMethods); + break; + case 3: + case 4: + isStepValid = true; + break; + case 5: + isStepValid = await validateStep5(formMethods); + break; + default: + isStepValid = false; } - if (isStepValid) { - if (activeStep === totalSteps) { - return; - } + if (isStepValid && activeStep < totalSteps) { setActiveStep(prev => prev + 1); window.scrollTo(0, 0); } From db6c977f93ff3763d18e4c0af52caa6d032df49a Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Fri, 23 Jan 2026 06:09:05 -0600 Subject: [PATCH 16/20] refactor: fix code duplication --- src/pages/mentorship/mentor-registration.tsx | 45 ++++++++++---------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/pages/mentorship/mentor-registration.tsx b/src/pages/mentorship/mentor-registration.tsx index 6276167..9f1abb5 100644 --- a/src/pages/mentorship/mentor-registration.tsx +++ b/src/pages/mentorship/mentor-registration.tsx @@ -11,36 +11,37 @@ import { mentorRegistrationSchema, MentorRegistrationData } from '../../schemas/ import Step5Review from 'components/mentorship/Step5Review'; const validateStep1 = async (formMethods: any) => { - const standardFields = [ + const isStandardValid = await formMethods.trigger([ 'firstName', 'email', 'slackName', 'country', 'city', - 'jobTitle', 'company', 'isLongTermMentor', 'isAdHocMentor', - 'calendlyLink', 'menteeExpectations', 'openToNonWomen', - ] as const; - - const standardValidation = await formMethods.trigger(standardFields); + 'jobTitle', 'company', 'calendlyLink', 'menteeExpectations', 'openToNonWomen', + ]); + const isLongTerm = formMethods.getValues('isLongTermMentor'); const isAdHoc = formMethods.getValues('isAdHocMentor'); - + formMethods.clearErrors(['isLongTermMentor', 'maxMentees', 'adHocAvailability']); - let manualChecksValid = true; - + let isTypeValid = true; + if (!isLongTerm && !isAdHoc) { formMethods.setError('isLongTermMentor', { type: 'manual', message: 'Please select at least one mentorship format.' }); - manualChecksValid = false; + isTypeValid = false; } - - if (isLongTerm && !formMethods.getValues('maxMentees')) { - formMethods.setError('maxMentees', { - type: 'manual', - message: 'Please select the number of mentees' - }); - manualChecksValid = false; + + if (isLongTerm) { + const maxMentees = formMethods.getValues('maxMentees'); + if (!maxMentees) { + formMethods.setError('maxMentees', { + type: 'manual', + message: 'Please select the number of mentees' + }); + isTypeValid = false; + } } - + if (isAdHoc) { const adHoc = formMethods.getValues('adHocAvailability'); if (!adHoc || Object.keys(adHoc).length === 0) { @@ -48,11 +49,11 @@ const validateStep1 = async (formMethods: any) => { type: 'manual', message: 'Please select availability for at least one month' }); - manualChecksValid = false; + isTypeValid = false; } } - - return standardValidation && manualChecksValid; + + return isStandardValid && isTypeValid; }; const validateStep2 = async (formMethods: any) => { @@ -100,7 +101,7 @@ const MentorRegistrationPage = () => { isStepValid = await validateStep5(formMethods); break; default: - isStepValid = false; + break; } if (isStepValid && activeStep < totalSteps) { From b1545fd97ef384974336198f614853ed056d10d5 Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Fri, 23 Jan 2026 15:15:54 -0600 Subject: [PATCH 17/20] refactor: code duplications in mentorshipConstants --- src/utils/mentorshipConstants.ts | 124 +++++++++++++++++-------------- 1 file changed, 69 insertions(+), 55 deletions(-) diff --git a/src/utils/mentorshipConstants.ts b/src/utils/mentorshipConstants.ts index f220c86..5c5cf0d 100644 --- a/src/utils/mentorshipConstants.ts +++ b/src/utils/mentorshipConstants.ts @@ -15,76 +15,90 @@ export const PREFERENCE_LEVELS = [ 'Not Applicable' ]; -export const DOMAIN_GROUPS = [ +interface DomainField { + name: string; + label: string; +} + +interface DomainGroup { + title: string; + fields: DomainField[]; +} + +const createFields = (fields: Array<[string, string]>): DomainField[] => { + return fields.map(([name, label]) => ({ name, label })); +}; + +export const DOMAIN_GROUPS: DomainGroup[] = [ { title: "AI, Data & ML", - fields: [ - { name: 'dataEngineering', label: 'Data Engineering' }, - { name: 'dataScience', label: 'Data Science' }, - { name: 'genAI', label: 'Generative AI and LLMs' }, - { name: 'machineLearning', label: 'Machine Learning and AI' }, - { name: 'mlOps', label: 'MLOps and AI Deployment' }, - ] + fields: createFields([ + ['dataEngineering', 'Data Engineering'], + ['dataScience', 'Data Science'], + ['genAI', 'Generative AI and LLMs'], + ['machineLearning', 'Machine Learning and AI'], + ['mlOps', 'MLOps and AI Deployment'], + ]) }, { title: "Infrastructure & Operations", - fields: [ - { name: 'cloudComputing', label: 'Cloud Computing' }, - { name: 'devOps', label: 'DevOps' }, - { name: 'networkEngineering', label: 'Network Engineering' }, - { name: 'platformEngineering', label: 'Platform Engineering' }, - { name: 'security', label: 'Security and Cybersecurity' }, - { name: 'sre', label: 'Site Reliability Engineering' }, - ] + fields: createFields([ + ['cloudComputing', 'Cloud Computing'], + ['devOps', 'DevOps'], + ['networkEngineering', 'Network Engineering'], + ['platformEngineering', 'Platform Engineering'], + ['security', 'Security and Cybersecurity'], + ['sre', 'Site Reliability Engineering'], + ]) }, { title: "Product, Leadership & Delivery", - fields: [ - { name: 'agile', label: 'Agile and Scrum Practices' }, - { name: 'businessAnalysis', label: 'Business Analysis' }, - { name: 'engineeringMgmt', label: 'Engineering Management' }, - { name: 'productMgmt', label: 'Product Management' }, - { name: 'projectMgmt', label: 'Project Management' }, - { name: 'technicalLeadership', label: 'Technical Leadership' }, - ] + fields: createFields([ + ['agile', 'Agile and Scrum Practices'], + ['businessAnalysis', 'Business Analysis'], + ['engineeringMgmt', 'Engineering Management'], + ['productMgmt', 'Product Management'], + ['projectMgmt', 'Project Management'], + ['technicalLeadership', 'Technical Leadership'], + ]) }, { title: "Software Development", - fields: [ - { name: 'backend', label: 'Backend Development' }, - { name: 'frontend', label: 'Frontend Development' }, - { name: 'fullstack', label: 'Fullstack Development' }, - { name: 'mobileAndroid', label: 'Mobile Development - Android' }, - { name: 'mobileIos', label: 'Mobile Development - iOS' }, - { name: 'qaAutomation', label: 'QA and Test Automation' }, - { name: 'systemDesign', label: 'System Design and Software Architecture' }, - ] + fields: createFields([ + ['backend', 'Backend Development'], + ['frontend', 'Frontend Development'], + ['fullstack', 'Fullstack Development'], + ['mobileAndroid', 'Mobile Development - Android'], + ['mobileIos', 'Mobile Development - iOS'], + ['qaAutomation', 'QA and Test Automation'], + ['systemDesign', 'System Design and Software Architecture'], + ]) }, ]; -export const CAREER_GOALS = [ - { name: 'careerSwitch', label: 'Switch career to IT' }, - { name: 'beginnerToMid', label: 'Grow from beginner to mid-level' }, - { name: 'midToSenior', label: 'Grow from mid-level to senior-level' }, - { name: 'seniorPlus', label: 'Grow beyond senior level' }, - { name: 'icToManager', label: 'Switch from IC to management position' }, - { name: 'specialisationSwitch', label: 'Change specialisation within IT' }, -]; +export const CAREER_GOALS = createFields([ + ['careerSwitch', 'Switch career to IT'], + ['beginnerToMid', 'Grow from beginner to mid-level'], + ['midToSenior', 'Grow from mid-level to senior-level'], + ['seniorPlus', 'Grow beyond senior level'], + ['icToManager', 'Switch from IC to management position'], + ['specialisationSwitch', 'Change specialisation within IT'], +]); -export const PROGRAMMING_LANGUAGES = [ - { name: 'c', label: 'C' }, - { name: 'cSharp', label: 'C#' }, - { name: 'go', label: 'Go' }, - { name: 'java', label: 'Java' }, - { name: 'javascript', label: 'JavaScript' }, - { name: 'kotlin', label: 'Kotlin' }, - { name: 'python', label: 'Python' }, - { name: 'rust', label: 'Rust' }, - { name: 'scala', label: 'Scala' }, - { name: 'sql', label: 'SQL' }, - { name: 'swift', label: 'Swift' }, - { name: 'typescript', label: 'TypeScript' }, -]; +export const PROGRAMMING_LANGUAGES = createFields([ + ['c', 'C'], + ['cSharp', 'C#'], + ['go', 'Go'], + ['java', 'Java'], + ['javascript', 'JavaScript'], + ['kotlin', 'Kotlin'], + ['python', 'Python'], + ['rust', 'Rust'], + ['scala', 'Scala'], + ['sql', 'SQL'], + ['swift', 'Swift'], + ['typescript', 'TypeScript'], +]); export const COUNTRIES = [ { code: 'GB', name: 'United Kingdom' }, From e40f5229821e173fe1fcbccf6ee340f6392d804c Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Fri, 23 Jan 2026 15:28:39 -0600 Subject: [PATCH 18/20] refactor: restructure COUNTRIES to fix duplication --- src/utils/mentorshipConstants.ts | 75 ++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/src/utils/mentorshipConstants.ts b/src/utils/mentorshipConstants.ts index 5c5cf0d..bdfc70c 100644 --- a/src/utils/mentorshipConstants.ts +++ b/src/utils/mentorshipConstants.ts @@ -25,10 +25,19 @@ interface DomainGroup { fields: DomainField[]; } +interface Country { + code: string; + name: string; +} + const createFields = (fields: Array<[string, string]>): DomainField[] => { return fields.map(([name, label]) => ({ name, label })); }; +const createCountries = (data: Array<[string, string]>): Country[] => { + return data.map(([code, name]) => ({ code, name })); +}; + export const DOMAIN_GROUPS: DomainGroup[] = [ { title: "AI, Data & ML", @@ -100,36 +109,36 @@ export const PROGRAMMING_LANGUAGES = createFields([ ['typescript', 'TypeScript'], ]); -export const COUNTRIES = [ - { code: 'GB', name: 'United Kingdom' }, - { code: 'US', name: 'United States' }, - { code: 'CA', name: 'Canada' }, - { code: 'AU', name: 'Australia' }, - { code: 'DE', name: 'Germany' }, - { code: 'FR', name: 'France' }, - { code: 'ES', name: 'Spain' }, - { code: 'IT', name: 'Italy' }, - { code: 'NL', name: 'Netherlands' }, - { code: 'SE', name: 'Sweden' }, - { code: 'NO', name: 'Norway' }, - { code: 'DK', name: 'Denmark' }, - { code: 'FI', name: 'Finland' }, - { code: 'IE', name: 'Ireland' }, - { code: 'PL', name: 'Poland' }, - { code: 'PT', name: 'Portugal' }, - { code: 'GR', name: 'Greece' }, - { code: 'CH', name: 'Switzerland' }, - { code: 'AT', name: 'Austria' }, - { code: 'BE', name: 'Belgium' }, - { code: 'CZ', name: 'Czech Republic' }, - { code: 'IN', name: 'India' }, - { code: 'SG', name: 'Singapore' }, - { code: 'JP', name: 'Japan' }, - { code: 'CN', name: 'China' }, - { code: 'BR', name: 'Brazil' }, - { code: 'MX', name: 'Mexico' }, - { code: 'AR', name: 'Argentina' }, - { code: 'ZA', name: 'South Africa' }, - { code: 'NZ', name: 'New Zealand' }, - { code: 'OTHER', name: 'Other' }, -]; +export const COUNTRIES = createCountries([ + ['GB', 'United Kingdom'], + ['US', 'United States'], + ['CA', 'Canada'], + ['AU', 'Australia'], + ['DE', 'Germany'], + ['FR', 'France'], + ['ES', 'Spain'], + ['IT', 'Italy'], + ['NL', 'Netherlands'], + ['SE', 'Sweden'], + ['NO', 'Norway'], + ['DK', 'Denmark'], + ['FI', 'Finland'], + ['IE', 'Ireland'], + ['PL', 'Poland'], + ['PT', 'Portugal'], + ['GR', 'Greece'], + ['CH', 'Switzerland'], + ['AT', 'Austria'], + ['BE', 'Belgium'], + ['CZ', 'Czech Republic'], + ['IN', 'India'], + ['SG', 'Singapore'], + ['JP', 'Japan'], + ['CN', 'China'], + ['BR', 'Brazil'], + ['MX', 'Mexico'], + ['AR', 'Argentina'], + ['ZA', 'South Africa'], + ['NZ', 'New Zealand'], + ['OTHER', 'Other'], +]); From 8d2b2ed7d6c3ba9dd6dc253cebc49e79070980d7 Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Fri, 23 Jan 2026 15:35:07 -0600 Subject: [PATCH 19/20] refactor: optimize structure --- src/utils/mentorshipConstants.ts | 84 +++++++++++++++----------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/src/utils/mentorshipConstants.ts b/src/utils/mentorshipConstants.ts index bdfc70c..b45e3b0 100644 --- a/src/utils/mentorshipConstants.ts +++ b/src/utils/mentorshipConstants.ts @@ -1,5 +1,4 @@ // src/utils/mentorshipConstants.ts - export const SKILL_LEVELS = [ 'Expert', 'Proficient', @@ -38,51 +37,46 @@ const createCountries = (data: Array<[string, string]>): Country[] => { return data.map(([code, name]) => ({ code, name })); }; +const createGroup = (title: string, fieldData: Array<[string, string]>): DomainGroup => { + return { + title, + fields: createFields(fieldData) + }; +}; + export const DOMAIN_GROUPS: DomainGroup[] = [ - { - title: "AI, Data & ML", - fields: createFields([ - ['dataEngineering', 'Data Engineering'], - ['dataScience', 'Data Science'], - ['genAI', 'Generative AI and LLMs'], - ['machineLearning', 'Machine Learning and AI'], - ['mlOps', 'MLOps and AI Deployment'], - ]) - }, - { - title: "Infrastructure & Operations", - fields: createFields([ - ['cloudComputing', 'Cloud Computing'], - ['devOps', 'DevOps'], - ['networkEngineering', 'Network Engineering'], - ['platformEngineering', 'Platform Engineering'], - ['security', 'Security and Cybersecurity'], - ['sre', 'Site Reliability Engineering'], - ]) - }, - { - title: "Product, Leadership & Delivery", - fields: createFields([ - ['agile', 'Agile and Scrum Practices'], - ['businessAnalysis', 'Business Analysis'], - ['engineeringMgmt', 'Engineering Management'], - ['productMgmt', 'Product Management'], - ['projectMgmt', 'Project Management'], - ['technicalLeadership', 'Technical Leadership'], - ]) - }, - { - title: "Software Development", - fields: createFields([ - ['backend', 'Backend Development'], - ['frontend', 'Frontend Development'], - ['fullstack', 'Fullstack Development'], - ['mobileAndroid', 'Mobile Development - Android'], - ['mobileIos', 'Mobile Development - iOS'], - ['qaAutomation', 'QA and Test Automation'], - ['systemDesign', 'System Design and Software Architecture'], - ]) - }, + createGroup("AI, Data & ML", [ + ['dataEngineering', 'Data Engineering'], + ['dataScience', 'Data Science'], + ['genAI', 'Generative AI and LLMs'], + ['machineLearning', 'Machine Learning and AI'], + ['mlOps', 'MLOps and AI Deployment'], + ]), + createGroup("Infrastructure & Operations", [ + ['cloudComputing', 'Cloud Computing'], + ['devOps', 'DevOps'], + ['networkEngineering', 'Network Engineering'], + ['platformEngineering', 'Platform Engineering'], + ['security', 'Security and Cybersecurity'], + ['sre', 'Site Reliability Engineering'], + ]), + createGroup("Product, Leadership & Delivery", [ + ['agile', 'Agile and Scrum Practices'], + ['businessAnalysis', 'Business Analysis'], + ['engineeringMgmt', 'Engineering Management'], + ['productMgmt', 'Product Management'], + ['projectMgmt', 'Project Management'], + ['technicalLeadership', 'Technical Leadership'], + ]), + createGroup("Software Development", [ + ['backend', 'Backend Development'], + ['frontend', 'Frontend Development'], + ['fullstack', 'Fullstack Development'], + ['mobileAndroid', 'Mobile Development - Android'], + ['mobileIos', 'Mobile Development - iOS'], + ['qaAutomation', 'QA and Test Automation'], + ['systemDesign', 'System Design and Software Architecture'], + ]), ]; export const CAREER_GOALS = createFields([ From ea4c82ed610fbebc04471a3833acf3d72eed371e Mon Sep 17 00:00:00 2001 From: Mitali Shah Date: Fri, 23 Jan 2026 16:04:46 -0600 Subject: [PATCH 20/20] fix: resolve e2e test failures and nextjs serialization crash - Update 'home.page.spec.ts' to assert the correct page title ('WCC: Registration Form for Mentors') following the UI from figma design. - Fix a Next.js serialization error in timeline pages (e.g., 'long-term-timeline.tsx'). - Ensure 'getServerSideProps' returns 'null' instead of 'undefined' when API data is missing, preventing Status 500 crashes during the test run. --- playwright-tests/tests/home.page.spec.ts | 2 +- src/pages/mentorship/long-term-timeline.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/playwright-tests/tests/home.page.spec.ts b/playwright-tests/tests/home.page.spec.ts index 462316e..6144c1a 100644 --- a/playwright-tests/tests/home.page.spec.ts +++ b/playwright-tests/tests/home.page.spec.ts @@ -53,7 +53,7 @@ test.describe('Validate Home Page', () => { await basePage.verifyURL('/mentorship/mentor-registration'); await basePage.verifyPageContainsText( - 'Welcome to the MentorRegistrationPage', + 'WCC: Registration Form for Mentors', ); }); diff --git a/src/pages/mentorship/long-term-timeline.tsx b/src/pages/mentorship/long-term-timeline.tsx index 8b15104..7279c53 100644 --- a/src/pages/mentorship/long-term-timeline.tsx +++ b/src/pages/mentorship/long-term-timeline.tsx @@ -26,8 +26,8 @@ export const getServerSideProps: GetServerSideProps = async () => { try { const response = await fetchData('mentorship/long-term-timeline'); const props: CombinedResponse = { - data: response.data, - footer: response.footer, + data: response.data || null, + footer: response.footer || null, }; return { props,