Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mentor-match-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ yarn-error.log*

server/firebase-service-account-key.json
functions/node_modules/

2,789 changes: 1,595 additions & 1,194 deletions mentor-match-app/package-lock.json

Large diffs are not rendered by default.

21 changes: 13 additions & 8 deletions mentor-match-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,37 @@
"@chatscope/chat-ui-kit-react": "^2.1.1",
"@chatscope/chat-ui-kit-styles": "^1.4.0",
"@chatscope/use-chat": "^3.1.2",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fontsource-variable/onest": "^5.1.0",
"@mui/icons-material": "^6.1.0",
"@mui/lab": "^6.0.0-beta.9",
"@mui/material": "^6.1.0",
"@mui/icons-material": "^7.3.4",
"@mui/lab": "^7.0.1-beta.18",
"@mui/material": "^7.3.4",
"@mui/x-date-pickers": "^8.12.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"ajv": "^8.17.1",
"ajv-keywords": "^5.1.0",
"cors": "^2.8.5",
"dayjs": "^1.11.18",
"dotenv": "^17.2.1",
"export-to-csv": "^1.4.0",
"express": "^5.1.0",
"firebase": "^10.13.1",
"firebase-admin": "^13.5.0",
"formik": "^2.4.6",
"lottie-react": "^2.4.1",
"material-react-table": "^3.2.1",
"node-fetch": "^2.6.7",
"notistack": "^3.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "18.2.0",
"react-dom": "18.2.0",
Comment on lines +33 to +34
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React and react-dom versions are pinned to exact versions (removed ^ prefix). This prevents automatic minor and patch updates, which may cause issues with dependency resolution and miss important bug fixes. Consider using semantic versioning with the caret operator (^18.2.0) unless there's a specific reason for pinning.

Suggested change
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

Copilot uses AI. Check for mistakes.
"react-router-dom": "^6.26.2",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4",
"yup": "^1.4.0"
"yup": "^1.4.0",
"yaml": "^2.8.1"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
Expand Down
22 changes: 15 additions & 7 deletions mentor-match-app/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import './App.css'
import { BrowserRouter, Route, Routes, Outlet } from 'react-router-dom'
import { ThemeContextProvider } from './hooks/useTheme'
import { UserProvider } from './hooks/useUser'
import { CssBaseline } from '@mui/material'

// Pages
import Dashboard from './pages/Dashboard'
import MenteeForm from './pages/MenteeForm'
import MentorForm from './pages/MentorForm'
import CombinedForm from './pages/CombinedForm'
import Login from './pages/Login'
import GetStarted from './pages/GetStarted'
import Signup from './pages/Signup'
import { CssBaseline } from '@mui/material'
import UnAuthPage from './components/UnAuthPage'
import AuthPage from './components/AuthPage'
import { UserProvider } from './hooks/useUser'
import ErrorPage from './components/ErrorPage'
import AdminRoute from './components/AdminRoute'
import AdminDashboard from './pages/AdminDashboard'
import MentorPick from './pages/MentorPick'
import RootHandler from './components/RootHandler'
import ErrorPage from './components/ErrorPage'
import EditSurvey from './pages/EditSurvey'
import Survey from './pages/Survey'

// Authentication Handlers
import RootHandler from './components/auth/RootHandler'
import UnAuthPage from './components/auth/UnAuthPage'
import AuthPage from './components/auth/AuthPage'
import AdminRoute from './components/auth/AdminRoute'

function App() {
return (
Expand Down Expand Up @@ -56,6 +62,7 @@ function App() {
element={<ErrorPage>Couldn't find this form!</ErrorPage>}
/>
<Route path="/mentor-pick" element={<MentorPick />} />
<Route path="/survey/:id" element={<Survey />} />
<Route
element={
<AdminRoute>
Expand All @@ -64,6 +71,7 @@ function App() {
}
>
<Route path="/admin" element={<AdminDashboard />} />
<Route path="/survey/edit/:id" element={<EditSurvey />} />
</Route>
</Route>
</Routes>
Expand Down
2 changes: 1 addition & 1 deletion mentor-match-app/src/api/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export const getFormAnswers = async (userId) => {
if (!mentorData && !menteeData) {
return null
}
console.log('getFormData', { mentorData, menteeData })
// console.log('getFormData', { mentorData, menteeData })
return { ok: true, mentorData, menteeData }
} catch (err) {
console.error('Error fetching form answers:', err)
Expand Down
217 changes: 217 additions & 0 deletions mentor-match-app/src/api/surveys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import {
doc,
setDoc,
collection,
deleteDoc,
getDoc,
getDocs,
addDoc,
serverTimestamp,
onSnapshot,
query,
orderBy,
updateDoc,
increment
} from 'firebase/firestore'
import { db } from './firebaseConfig'
import { getUserById } from './users'

export const createEmptySurvey = async (userId) => {
try {
const surveyRef = doc(collection(db, 'surveys'))
const user = await getUserById(userId)
if (!user) {
throw new Error('User not found')
}
const author = { id: userId, name: user.displayName, email: user.email }
await setDoc(surveyRef, {
author,
title: 'Untitled Survey',
description: '',
createdAt: serverTimestamp(),
publishedAt: null,
responses: 0,
questionsCount: 0,
enabledFor: { mentors: false, mentees: false },
status: 'draft'
})

return surveyRef.id
} catch (error) {
console.error('Error creating survey:', error)
throw error
}
}

export const deleteSurvey = async (surveyId) => {
try {
const surveyRef = doc(db, 'surveys', surveyId)
await deleteDoc(surveyRef)
return { ok: true }
} catch (error) {
console.error('Error deleting survey:', error)
return { ok: false, error: error.message }
}
}

export const getSurveyById = async (id) => {
if (!id) {
throw new Error('getSurveyById: missing survey id')
}
try {
// Ensure we never pass undefined into doc()
const ref = doc(collection(db, 'surveys'), String(id))
const snap = await getDoc(ref)
if (!snap.exists()) {
throw new Error('Survey not found')
}

// Fetch questions ordered by "order"
const questionsRef = collection(db, 'surveys', id, 'questions')
const questionsSnap = await getDocs(query(questionsRef, orderBy('order', 'asc')))
const questions = questionsSnap.docs.map((d) => ({ id: d.id, ...d.data() }))

return { id: snap.id, ...snap.data(), questions }
} catch (error) {
console.error('Error fetching survey by ID:', error)
throw error
}
}

export const getAllSurveys = async () => {
try {
const surveysCol = collection(db, 'surveys')
const surveySnapshot = await getDocs(surveysCol)
const surveys = surveySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
// console.log('Fetched surveys:', surveys)
return surveys
} catch (error) {
console.error('Error fetching surveys:', error)
throw error
}
}

export const getSurveysByStatusAndUserRole = async (status, role) => {
const surveys = await getAllSurveys()

// normalize role to 'mentors' | 'mentees'
const normalizeRole = (r) => {
if (!r) return null
const v = String(r).toLowerCase()
if (v === 'mentor') return 'mentors'
if (v === 'mentee') return 'mentees'
return null
}
const roleKey = normalizeRole(role)

const filteredSurveys = surveys.filter((survey) => {
const matchesStatus = survey.status === status
const matchesRole = roleKey ? !!(survey.enabledFor && survey.enabledFor[roleKey]) : true
return matchesStatus && matchesRole
})

return filteredSurveys
}

// Adds a new document in subcollection surveys/{surveyId}/questions
// questionData: { title, type, options?, required?, description?, order? }
export const addQuestionToSurvey = async (surveyId, questionType, questionData = {}) => {
try {
const surveyRef = doc(db, 'surveys', surveyId)
const questionsCol = collection(surveyRef, 'questions')

const emptyDefault = {
title: 'New Question',
type: questionType,
isRequired: false,
options: [],
description: '',
order: Date.now()
}

const newQuestion = {
...emptyDefault,
...(questionData || {})
}

const qRef = await addDoc(questionsCol, newQuestion)
// increment questionsCount
await updateDoc(surveyRef, {
questionsCount: increment(1),
updatedAt: serverTimestamp()
})

return qRef.id
} catch (error) {
console.error('Error adding question to survey:', error)
throw error
}
}

// Subscribe to survey meta changes
export const subscribeToSurvey = (surveyId, callback) => {
const surveyRef = doc(db, 'surveys', surveyId)
return onSnapshot(surveyRef, callback)
}

// Subscribe to questions (ordered)
export const subscribeToQuestions = (surveyId, callback) => {
const qRef = collection(db, 'surveys', surveyId, 'questions')
const q = query(qRef, orderBy('order', 'asc'))
return onSnapshot(q, (snap) => {
const list = snap.docs.map((d) => ({ id: d.id, ...d.data() }))
callback(list)
})
}

// Update survey metadata (debounced from UI)
export const updateSurveyMeta = async (surveyId, patch) => {
const surveyRef = doc(db, 'surveys', surveyId)
try {
await updateDoc(surveyRef, { ...patch, updatedAt: serverTimestamp() })
return { ok: true }
} catch (error) {
console.error('Error updating survey meta:', error)
return { ok: false, error: error.message }
}
}

// Upsert a question (debounced from UI)
export const upsertQuestion = async (surveyId, questionId, patch) => {
const qRef = doc(db, 'surveys', surveyId, 'questions', questionId)
await setDoc(qRef, { ...patch }, { merge: true })
}

// Delete a question and decrement count
export const deleteQuestionFromSurvey = async (surveyId, questionId) => {
const surveyRef = doc(db, 'surveys', surveyId)
const qRef = doc(db, 'surveys', surveyId, 'questions', questionId)
await deleteDoc(qRef)
await updateDoc(surveyRef, {
questionsCount: increment(-1),
updatedAt: serverTimestamp()
})
}

export const submitSurveyResponse = async (surveyId, responses, userId) => {
try {
const responsesCol = collection(db, 'surveys', surveyId, 'responses')
const docRef = await addDoc(responsesCol, { answers: responses, submittedAt: serverTimestamp(), userId })
return { ok: true, id: docRef.id }
} catch (error) {
console.error('Error submitting survey response:', error)
return { ok: false, error: error.message }
}
}

export const getSurveyResponses = async (surveyId) => {
try {
const responsesCol = collection(db, 'surveys', surveyId, 'responses')
const responsesSnap = await getDocs(responsesCol)
const responses = responsesSnap.docs.map(doc => ({ id: doc.id, ...doc.data() }))
return { ok: true, responses }
} catch (error) {
console.error('Error fetching survey responses:', error)
return { ok: false, error: error.message }
}
}
4 changes: 2 additions & 2 deletions mentor-match-app/src/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const getUserArrayByIds = async (userIds) => {
}
try {
const users = await Promise.all(userIds.map((id) => getUserById(id)))
console.log('Fetched users:', users)
// console.log('Fetched users:', users)
return users
} catch (err) {
console.error('Error fetching user list by IDs:', err)
Expand All @@ -96,7 +96,7 @@ export const getAdmins = async () => {
const querySnapshot = await getDocs(collection(db, 'admins'))
const adminIds = querySnapshot.docs.map((d) => (d.id))
const admins = await getUserArrayByIds(adminIds)
console.log('Fetched admins:', admins)
// console.log('Fetched admins:', admins)
return admins
} catch (err) {
console.error('Error fetching admins:', err)
Expand Down
8 changes: 7 additions & 1 deletion mentor-match-app/src/components/FormCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ChevronLeft } from '@mui/icons-material'
const FormCard = ({
children,
title,
description,
type,
props,
enableInfo = true,
Expand Down Expand Up @@ -78,10 +79,15 @@ const FormCard = ({
<Typography
alignSelf={'flex-start'}
variant={'h5'}
sx={{ pb: 2 }}
sx={{ pb: description ? 1 : 2 }}
>
{title}
</Typography>
{description && (
<Typography alignSelf={'flex-start'} sx={{ pb: 2 }}>
{description}
</Typography>
)}
</Stack>
{showInfo ? (
<Box>
Expand Down
Loading