From 41a2c0cbb535ad435e31093c2f47a6277137e867 Mon Sep 17 00:00:00 2001 From: Joshua Zhou Date: Sat, 22 Nov 2025 16:55:50 -0500 Subject: [PATCH 1/3] link to other team members in hacker view --- src/config/team.ts | 1 + .../SingleHacker/SingleHackerView.tsx | 103 ++++++++++++++++++ src/pages/Application/View/[id].tsx | 2 +- 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/src/config/team.ts b/src/config/team.ts index 7d7a6343d..5fe24f61f 100644 --- a/src/config/team.ts +++ b/src/config/team.ts @@ -6,6 +6,7 @@ export interface ITeam { } export interface IMemberName { + id: string; firstName: string; lastName: string; school?: string; diff --git a/src/features/SingleHacker/SingleHackerView.tsx b/src/features/SingleHacker/SingleHackerView.tsx index c2e8ce65a..0d248161f 100644 --- a/src/features/SingleHacker/SingleHackerView.tsx +++ b/src/features/SingleHacker/SingleHackerView.tsx @@ -1,19 +1,24 @@ import React, { useEffect, useState } from 'react'; import Helmet from 'react-helmet'; import { Input } from '../../shared/Form'; +import { Link } from 'react-router-dom'; import { Box, Flex } from '@rebass/grid'; import { toast } from 'react-toastify'; import { Hacker } from '../../api'; +import Team from '../../api/team'; import { + FrontendRoute, HACKATHON_NAME, HackerStatus, HackerReviewerStatus, IAccount, IHacker, + IMemberName, UserType, } from '../../config'; +import { ITeamResponse } from '../../config/teamGETResponse'; import { Button, ButtonVariant, @@ -50,6 +55,8 @@ const SingleHackerView: React.FC = (props) => { const [reviewerComments2, setReviewerComments2] = useState(props.hacker.reviewerComments2); const [isAdmin, setIsAdmin] = useState(true); const [isLoading, setIsLoading] = useState(false); + const [teamMembers, setTeamMembers] = useState([]); + const [isLoadingTeam, setIsLoadingTeam] = useState(false); useEffect(() => { setStatus(props.hacker.status); @@ -66,6 +73,44 @@ const SingleHackerView: React.FC = (props) => { setReviewerStatus2(props.hacker.reviewerStatus2); }, [props]); + // Fetch team members + useEffect(() => { + const fetchTeamMembers = async () => { + // only if hacker has a teamId + if (props.hacker.teamId) { + setIsLoadingTeam(true); + // teamId might be an object (populated) or a string/ObjectId + // extract the ID if it's an object, otherwise use it as-is + let teamId: string; + if (typeof props.hacker.teamId === 'object' && props.hacker.teamId !== null) { + teamId = String((props.hacker.teamId as any)._id || (props.hacker.teamId as any).id); + } else { + teamId = String(props.hacker.teamId); + } + try { + const teamResponse: ITeamResponse = (await Team.get(teamId)).data.data; + + // filter out the current hacker from the team members list + // convert both IDs to strings for comparison to handle ObjectId vs string mismatches + const currentHackerId = String(props.hacker.id); + const otherMembers = teamResponse.members.filter( + (member) => String(member.id) !== currentHackerId + ); + + setTeamMembers(otherMembers); + } catch (e: any) { + setTeamMembers([]); + } finally { + setIsLoadingTeam(false); + } + } else { + setTeamMembers([]); + } + }; + + fetchTeamMembers(); + }, [props.hacker.teamId, props.hacker.id]); + const submit = async () => { if (!isStaffMember && !isHackboardMember) { return; @@ -389,6 +434,64 @@ const SingleHackerView: React.FC = (props) => { link={hackerDetails.application.general.URL.dribbble} /> +
+ {/* Team Members Section */} + {props.hacker.teamId && ( + <> +

Team Members

+ {isLoadingTeam ? ( + Loading team members... + ) : teamMembers.length > 0 ? ( + + {teamMembers.map((member: IMemberName) => { + const hackerPage = FrontendRoute.VIEW_HACKER_PAGE.replace( + ':id', + member.id + ); + return ( + + + { + e.currentTarget.style.backgroundColor = + theme.colors.purpleLight; + }} + onMouseLeave={(e: any) => { + e.currentTarget.style.backgroundColor = 'transparent'; + }} + > + + {member.firstName} {member.lastName} + + {member.school && ( + + {member.school} + + )} + + + + ); + })} + + ) : ( + No other team members found. + )} +
+ + )} {/* Only tier1 sponsors and admin have access to user resumes */} {props.userType === UserType.SPONSOR_T1 || canViewAdminSection ? ( diff --git a/src/pages/Application/View/[id].tsx b/src/pages/Application/View/[id].tsx index 0322df727..944b052c6 100644 --- a/src/pages/Application/View/[id].tsx +++ b/src/pages/Application/View/[id].tsx @@ -32,7 +32,7 @@ const SingleHackerPage: React.FC = () => { (async () => { try { const viewer = (await Account.getSelf()).data.data; - console.log(viewer, viewer.accountType); + // console.log(viewer, viewer.accountType); setUserType(viewer.accountType); const newHacker = (await Hacker.get(id)).data.data; const account = (await Account.get(newHacker.accountId as string)) From 417879db53e2f165c7434b7e88e8e561743d97df Mon Sep 17 00:00:00 2001 From: Joshua Zhou Date: Sat, 22 Nov 2025 17:12:29 -0500 Subject: [PATCH 2/3] added option to filter by grouped teams in search (exclude hackers with no team) --- src/features/Search/Filters.tsx | 11 ++++++++-- src/features/Search/Search.tsx | 39 ++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/features/Search/Filters.tsx b/src/features/Search/Filters.tsx index f9731517f..9ab4985e1 100644 --- a/src/features/Search/Filters.tsx +++ b/src/features/Search/Filters.tsx @@ -20,7 +20,7 @@ import { getOptionsFromEnum } from '../../util'; interface IFilterProps { initFilters: ISearchParameter[]; - onChange: (newFilters: ISearchParameter[], reviewStatus?: number[], reviewScore?: number[]) => void; + onChange: (newFilters: ISearchParameter[], reviewStatus?: number[], reviewScore?: number[], groupTeams?: boolean) => void; onResetForm: () => void; loading: boolean; } @@ -65,6 +65,7 @@ class FilterComponent extends React.Component { ), reviewer1: this.searchParam2List('reviewerName', initFilters), reviewer2: this.searchParam2List('reviewerName2', initFilters), + groupTeams: false, }; return initVals; } @@ -181,6 +182,12 @@ class FilterComponent extends React.Component { options={getOptionsFromEnum(reviewers)} value={fp.values.reviewer2} /> +