From 48184021979095db1025ad4ea1e1d3af83c92907 Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Wed, 11 Feb 2026 20:38:38 +0530 Subject: [PATCH 1/4] Removed Nested componentes --- .../src/app/board/[year]/candidates/page.tsx | 1045 +++++++++-------- frontend/src/app/global-error.tsx | 14 +- frontend/src/app/members/[memberKey]/page.tsx | 124 +- frontend/src/components/SkeletonsBase.tsx | 55 +- 4 files changed, 640 insertions(+), 598 deletions(-) diff --git a/frontend/src/app/board/[year]/candidates/page.tsx b/frontend/src/app/board/[year]/candidates/page.tsx index 2820c5933e..b28e944cb7 100644 --- a/frontend/src/app/board/[year]/candidates/page.tsx +++ b/frontend/src/app/board/[year]/candidates/page.tsx @@ -85,602 +85,605 @@ type CandidateWithSnapshot = Candidate & { snapshot?: MemberSnapshot } -const BoardCandidatesPage = () => { - const { year } = useParams<{ year: string }>() - const [candidates, setCandidates] = useState([]) +interface CandidateCardProps { + candidate: CandidateWithSnapshot + year: string +} - const { - data: graphQLData, - error: graphQLRequestError, - loading, - } = useQuery(GetBoardCandidatesDocument, { - variables: { year: Number.parseInt(year) }, - }) +const CandidateCard = ({ candidate, year }: CandidateCardProps) => { + const client = useApolloClient() + const [snapshot, setSnapshot] = useState(null) + const [ledChapters, setLedChapters] = useState([]) + const [ledProjects, setLedProjects] = useState([]) - useEffect(() => { - if (graphQLData?.boardOfDirectors) { - setCandidates(graphQLData.boardOfDirectors.candidates || []) - } - if (graphQLRequestError) { - handleAppError(graphQLRequestError) - } - }, [graphQLData, graphQLRequestError]) - - const CandidateCard = ({ candidate }: { candidate: CandidateWithSnapshot }) => { - const client = useApolloClient() - const [snapshot, setSnapshot] = useState(null) - const [ledChapters, setLedChapters] = useState([]) - const [ledProjects, setLedProjects] = useState([]) + const sortByName = (items: T[]): T[] => { + return [...items].sort((a, b) => a.name.localeCompare(b.name)) + } - const sortByName = (items: T[]): T[] => { - return [...items].sort((a, b) => a.name.localeCompare(b.name)) - } + const sortByContributionCount = (entries: Array<[string, number]>): Array<[string, number]> => { + return [...entries].sort(([, a], [, b]) => b - a) + } - const sortByContributionCount = (entries: Array<[string, number]>): Array<[string, number]> => { - return [...entries].sort(([, a], [, b]) => b - a) - } + const sortChannelsByMessageCount = ( + entries: Array<[string, number]> + ): Array<[string, number]> => { + return [...entries].sort(([, a], [, b]) => b - a) + } - const sortChannelsByMessageCount = ( - entries: Array<[string, number]> - ): Array<[string, number]> => { - return [...entries].sort(([, a], [, b]) => b - a) - } + // Render a single repository link item + const renderChannelLink = (channelName: string, messageCount: string | number) => ( + e.stopPropagation()} + > + #{channelName} + + {Number(messageCount)} messages + + + ) - // Render a single repository link item - const renderChannelLink = (channelName: string, messageCount: string | number) => ( + // Render a single repository link item + const renderRepositoryLink = (repoName: string, count: number) => { + const commitCount = Number(count) + return ( e.stopPropagation()} > - #{channelName} + {repoName} - {Number(messageCount)} messages + {commitCount} commits ) + } - // Render a single repository link item - const renderRepositoryLink = (repoName: string, count: number) => { - const commitCount = Number(count) + const renderTopActiveChannels = () => { + if (!snapshot) return null + + if ( + !snapshot.channelCommunications || + Object.keys(snapshot.channelCommunications).length <= 0 + ) { return ( - e.stopPropagation()} - > - {repoName} - - {commitCount} commits - - +
+

+ Top 5 Active Channels +

+
+ No Engagement +
+
) } - const renderTopActiveChannels = () => { - if (!snapshot) return null - - if ( - !snapshot.channelCommunications || - Object.keys(snapshot.channelCommunications).length <= 0 - ) { - return ( -
-

- Top 5 Active Channels -

-
- No Engagement -
-
- ) - } - - const sortedChannels = sortChannelsByMessageCount( - Object.entries(snapshot.channelCommunications) - ) + const sortedChannels = sortChannelsByMessageCount( + Object.entries(snapshot.channelCommunications) + ) - if (sortedChannels.length === 0) return null + if (sortedChannels.length === 0) return null - const topChannel = sortedChannels[0] - const [topChannelName, topChannelCount] = topChannel + const topChannel = sortedChannels[0] + const [topChannelName, topChannelCount] = topChannel - return ( -
- - {sortedChannels.length > 1 && ( -
-
- {sortedChannels - .slice(1) - .map(([channelName, messageCount]) => - renderChannelLink(channelName, messageCount) - )} -
-
- )} + return ( +
+ - ) - } + {sortedChannels.length > 1 && ( +
+
+ {sortedChannels + .slice(1) + .map(([channelName, messageCount]) => renderChannelLink(channelName, messageCount))} +
+
+ )} +
+ ) + } - const { data: snapshotData } = useQuery(GetMemberSnapshotDocument, { - variables: { - userLogin: candidate.member?.login || '', - }, - skip: !candidate.member?.login, - }) + const { data: snapshotData } = useQuery(GetMemberSnapshotDocument, { + variables: { + userLogin: candidate.member?.login || '', + }, + skip: !candidate.member?.login, + }) - useEffect(() => { - if (snapshotData?.memberSnapshot) { - setSnapshot(snapshotData.memberSnapshot) - } - }, [snapshotData]) + useEffect(() => { + if (snapshotData?.memberSnapshot) { + setSnapshot(snapshotData.memberSnapshot) + } + }, [snapshotData]) - // Fetch chapters based on chapterContributions keys - useEffect(() => { - const fetchChapters = async () => { - if (!snapshot?.chapterContributions) return + // Fetch chapters based on chapterContributions keys + useEffect(() => { + const fetchChapters = async () => { + if (!snapshot?.chapterContributions) return - const chapterKeys = Object.keys(snapshot.chapterContributions) - const chapters: Chapter[] = [] + const chapterKeys = Object.keys(snapshot.chapterContributions) + const chapters: Chapter[] = [] - for (const key of chapterKeys) { - try { - const { data } = await client.query({ - query: GetChapterByKeyDocument, - variables: { key: key.replace('www-chapter-', '') }, - }) + for (const key of chapterKeys) { + try { + const { data } = await client.query({ + query: GetChapterByKeyDocument, + variables: { key: key.replace('www-chapter-', '') }, + }) - if (data?.chapter) { - chapters.push(data.chapter) - } - } catch { - // Silently skip chapters that fail to fetch + if (data?.chapter) { + chapters.push(data.chapter) } + } catch { + // Silently skip chapters that fail to fetch } - - setLedChapters(sortByName(chapters)) } - fetchChapters() - }, [client, snapshot?.chapterContributions]) + setLedChapters(sortByName(chapters)) + } - // Fetch projects based on projectContributions keys - useEffect(() => { - const fetchProjects = async () => { - if (!snapshot?.projectContributions) return + fetchChapters() + }, [client, snapshot?.chapterContributions]) - const projectKeys = Object.keys(snapshot.projectContributions) - const projects: Project[] = [] + // Fetch projects based on projectContributions keys + useEffect(() => { + const fetchProjects = async () => { + if (!snapshot?.projectContributions) return - for (const key of projectKeys) { - try { - const { data } = await client.query({ - query: GetProjectByKeyDocument, - variables: { key: key.replace('www-project-', '') }, - }) + const projectKeys = Object.keys(snapshot.projectContributions) + const projects: Project[] = [] - if (data?.project) { - projects.push(data.project) - } - } catch { - // Silently skip projects that fail to fetch + for (const key of projectKeys) { + try { + const { data } = await client.query({ + query: GetProjectByKeyDocument, + variables: { key: key.replace('www-project-', '') }, + }) + + if (data?.project) { + projects.push(data.project) } + } catch { + // Silently skip projects that fail to fetch } - - setLedProjects(sortByName(projects)) } - fetchProjects() - }, [client, snapshot?.projectContributions]) - - const handleCardClick = () => { - // Convert name to slug format. - const nameSlug = candidate.memberName.toLowerCase().replaceAll(/\s+/g, '_') - const candidateUrl = `https://owasp.org/www-board-candidates/${year}/${nameSlug}.html` - window.open(candidateUrl, '_blank', 'noopener,noreferrer') + setLedProjects(sortByName(projects)) } - // Check if candidate leads any flagship level projects - const leadsFlagshipProject = ledProjects.some((project) => project.level === 'flagship') + fetchProjects() + }, [client, snapshot?.projectContributions]) - return ( -
- {candidate.description && ( -

- {candidate.description} -

+
+ {candidate.member?.bio && ( +
+ {candidate.member.bio.replaceAll(/\n+/g, ' ').replaceAll(/\s+/g, ' ').trim()} +
)} +
- {snapshot && ( -
-

- OWASP Ecosystem Contributions -

-
-
- -
-

Commits

-

- {millify(snapshot.commitsCount, { precision: 1 })} -

-
+ {candidate.description && ( +

+ {candidate.description} +

+ )} + + {snapshot && ( +
+

+ OWASP Ecosystem Contributions +

+
+
+ +
+

Commits

+

+ {millify(snapshot.commitsCount, { precision: 1 })} +

-
- -
-

PRs

-

- {millify(snapshot.pullRequestsCount, { precision: 1 })} -

-
+
+
+ +
+

PRs

+

+ {millify(snapshot.pullRequestsCount, { precision: 1 })} +

-
- -
-

Issues

-

- {millify(snapshot.issuesCount, { precision: 1 })} -

-
+
+
+ +
+

Issues

+

+ {millify(snapshot.issuesCount, { precision: 1 })} +

-
- -
-

Total

-

- {millify(snapshot.totalContributions, { precision: 1 })} -

-
+
+
+ +
+

Total

+

+ {millify(snapshot.totalContributions, { precision: 1 })} +

- {snapshot.contributionHeatmapData && - Object.keys(snapshot.contributionHeatmapData).length > 0 && ( -
- -
+
+ {snapshot.contributionHeatmapData && + Object.keys(snapshot.contributionHeatmapData).length > 0 && ( +
+ +
+ )} +
+

+ Leadership + {(ledChapters.length > 0 || ledProjects.length > 0) && ( + + {' '} + ( + {ledChapters.length > 0 && ( + <> + {ledChapters.length} chapter{ledChapters.length === 1 ? '' : 's'} + + )} + {ledChapters.length > 0 && ledProjects.length > 0 && ', '} + {ledProjects.length > 0 && ( + <> + {ledProjects.length} project{ledProjects.length === 1 ? '' : 's'} + + )} + ) + )} -
-

- Leadership - {(ledChapters.length > 0 || ledProjects.length > 0) && ( - - {' '} - ( - {ledChapters.length > 0 && ( - <> - {ledChapters.length} chapter{ledChapters.length === 1 ? '' : 's'} - - )} - {ledChapters.length > 0 && ledProjects.length > 0 && ', '} - {ledProjects.length > 0 && ( - <> - {ledProjects.length} project{ledProjects.length === 1 ? '' : 's'} - - )} - ) - - )} -

- {ledChapters.length === 0 && ledProjects.length === 0 ? ( -
- No chapters or projects are lead by this candidate -
- ) : ( -
- {ledChapters.map((chapter) => { - // Strip prefix if present to get bare key - const bareKey = chapter.key.startsWith('www-chapter-') - ? chapter.key.replace('www-chapter-', '') - : chapter.key - // Check both key formats for compatibility - const directKey = snapshot?.chapterContributions?.[bareKey] - const withPrefix = snapshot?.chapterContributions?.[`www-chapter-${bareKey}`] - const contributionCount = directKey || withPrefix || 0 - - return ( - + {ledChapters.length === 0 && ledProjects.length === 0 ? ( +
+ No chapters or projects are lead by this candidate +
+ ) : ( +
+ {ledChapters.map((chapter) => { + // Strip prefix if present to get bare key + const bareKey = chapter.key.startsWith('www-chapter-') + ? chapter.key.replace('www-chapter-', '') + : chapter.key + // Check both key formats for compatibility + const directKey = snapshot?.chapterContributions?.[bareKey] + const withPrefix = snapshot?.chapterContributions?.[`www-chapter-${bareKey}`] + const contributionCount = directKey || withPrefix || 0 + + return ( + e.stopPropagation()} + > + {chapter.name} + e.stopPropagation()} > - {chapter.name} - - {contributionCount === 0 - ? 'no contributions' - : `${contributionCount} contributions`} - - - ) - })} - {ledProjects.map((project) => { - // Strip prefix if present to get bare key - const bareKey = project.key.startsWith('www-project-') - ? project.key.replace('www-project-', '') - : project.key - // Check both key formats for compatibility - const directKey = snapshot?.projectContributions?.[bareKey] - const withPrefix = snapshot?.projectContributions?.[`www-project-${bareKey}`] - const contributionCount = directKey || withPrefix || 0 - return ( - + + ) + })} + {ledProjects.map((project) => { + // Strip prefix if present to get bare key + const bareKey = project.key.startsWith('www-project-') + ? project.key.replace('www-project-', '') + : project.key + // Check both key formats for compatibility + const directKey = snapshot?.projectContributions?.[bareKey] + const withPrefix = snapshot?.projectContributions?.[`www-project-${bareKey}`] + const contributionCount = directKey || withPrefix || 0 + return ( + e.stopPropagation()} + > + {project.name} + e.stopPropagation()} - > - {project.name} - - {contributionCount === 0 - ? 'no contributions' - : `${contributionCount} contributions`} - - - ) - })} -
- )} -
- {snapshot.repositoryContributions && - Object.keys(snapshot.repositoryContributions).length > 0 && - (() => { - const sortedRepos = sortByContributionCount( - Object.entries(snapshot.repositoryContributions) - ) - const topRepo = sortedRepos[0] - const [topRepoName, topRepoCount] = topRepo - - return ( -
- - {sortedRepos.length > 1 && ( -
-
- {sortedRepos - .slice(1) - .map(([repoName, count]) => renderRepositoryLink(repoName, count))} -
-
- )} + {contributionCount === 0 + ? 'no contributions' + : `${contributionCount} contributions`} + + + ) + })} +
+ )} +
+ {snapshot.repositoryContributions && + Object.keys(snapshot.repositoryContributions).length > 0 && + (() => { + const sortedRepos = sortByContributionCount( + Object.entries(snapshot.repositoryContributions) + ) + const topRepo = sortedRepos[0] + const [topRepoName, topRepoCount] = topRepo + + return ( +
+ - ) - })()} + {sortedRepos.length > 1 && ( +
+
+ {sortedRepos + .slice(1) + .map(([repoName, count]) => renderRepositoryLink(repoName, count))} +
+
+ )} +
+ ) + })()} +

+ )} + + {/* Slack Communications Heatmap */} + {snapshot?.communicationHeatmapData && + Object.keys(snapshot.communicationHeatmapData).length > 0 && ( +
+
)} - {/* Slack Communications Heatmap */} - {snapshot?.communicationHeatmapData && - Object.keys(snapshot.communicationHeatmapData).length > 0 && ( -
- + {renderTopActiveChannels()} + + {/* Additional Information */} + {(candidate.member?.isOwaspBoardMember || + candidate.member?.isFormerOwaspStaff || + candidate.member?.isGsocMentor || + leadsFlagshipProject) && ( +
+

+ Additional Information +

+
+ {candidate.member?.isOwaspBoardMember && ( + + OWASP Board of Directors Member + + )} + {candidate.member?.isFormerOwaspStaff && ( + + Former OWASP Staff Member + + )} + {candidate.member?.isGsocMentor && ( + + Google Summer of Code Mentor + + )} +
+ {leadsFlagshipProject && ( +
+ This candidate may have additional community engagement in other Slack workspaces + based on the flagship level project(s) they are leading.
)} +
+ )} + + ) +} - {renderTopActiveChannels()} +const BoardCandidatesPage = () => { + const { year } = useParams<{ year: string }>() + const [candidates, setCandidates] = useState([]) - {/* Additional Information */} - {(candidate.member?.isOwaspBoardMember || - candidate.member?.isFormerOwaspStaff || - candidate.member?.isGsocMentor || - leadsFlagshipProject) && ( -
-

- Additional Information -

-
- {candidate.member?.isOwaspBoardMember && ( - - OWASP Board of Directors Member - - )} - {candidate.member?.isFormerOwaspStaff && ( - - Former OWASP Staff Member - - )} - {candidate.member?.isGsocMentor && ( - - Google Summer of Code Mentor - - )} -
- {leadsFlagshipProject && ( -
- This candidate may have additional community engagement in other Slack workspaces - based on the flagship level project(s) they are leading. -
- )} -
- )} - - ) - } + const { + data: graphQLData, + error: graphQLRequestError, + loading, + } = useQuery(GetBoardCandidatesDocument, { + variables: { year: Number.parseInt(year) }, + }) + + useEffect(() => { + if (graphQLData?.boardOfDirectors) { + setCandidates(graphQLData.boardOfDirectors.candidates || []) + } + if (graphQLRequestError) { + handleAppError(graphQLRequestError) + } + }, [graphQLData, graphQLRequestError]) if (loading) { return @@ -752,7 +755,7 @@ const BoardCandidatesPage = () => { ) : (
{candidates.map((candidate) => ( - + ))}
)} diff --git a/frontend/src/app/global-error.tsx b/frontend/src/app/global-error.tsx index 6b0c5442b9..f3213ea415 100644 --- a/frontend/src/app/global-error.tsx +++ b/frontend/src/app/global-error.tsx @@ -91,17 +91,21 @@ export const handleAppError = (error: unknown) => { export const ErrorWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { return ( { - Sentry.captureException(error) - const errorConfig = ERROR_CONFIGS['500'] - return - }} + fallback={(props) => } > {children} ) } +export const SentryErrorFallback: React.FC<{ + error: unknown + errorConfig?: ErrorDisplayProps +}> = ({ error, errorConfig = ERROR_CONFIGS['500'] }) => { + Sentry.captureException(error instanceof Error ? error : new Error(String(error))) + return +} + export default function GlobalError({ error }: Readonly<{ error: Error }>) { Sentry.captureException(error) const errorConfig = ERROR_CONFIGS['500'] diff --git a/frontend/src/app/members/[memberKey]/page.tsx b/frontend/src/app/members/[memberKey]/page.tsx index 962008aed4..dabcbeca37 100644 --- a/frontend/src/app/members/[memberKey]/page.tsx +++ b/frontend/src/app/members/[memberKey]/page.tsx @@ -9,12 +9,76 @@ import { handleAppError, ErrorDisplay } from 'app/global-error' import { GetUserDataDocument } from 'types/__generated__/userQueries.generated' import { Badge } from 'types/badge' +import { User } from 'types/user' import { formatDate } from 'utils/dateFormatter' import Badges from 'components/Badges' import DetailsCard from 'components/CardDetailsPage' import ContributionHeatmap from 'components/ContributionHeatmap' import MemberDetailsPageSkeleton from 'components/skeletons/MemberDetailsPageSkeleton' +type DateRange = { startDate: string; endDate: string } + +interface UserSummaryProps { + user: User | null + contributionData: Record + dateRange: DateRange + hasContributionData: boolean + formattedBio: React.ReactNode +} + +export const UserSummary: React.FC = ({ + user, + contributionData, + dateRange, + hasContributionData, + formattedBio, +}) => ( +
+ {user?.name +
+
+
+ + @{user?.login} + + {user?.badges && user.badges.length > 0 && ( +
+ {user.badges.slice().map((badge: Badge) => ( + + + + ))} +
+ )} +
+

{formattedBio}

+
+ {hasContributionData && dateRange.startDate && dateRange.endDate && ( +
+
+ +
+
+ )} +
+
+) + const UserDetailsPage: React.FC = () => { const { memberKey } = useParams<{ memberKey: string }>() @@ -129,56 +193,6 @@ const UserDetailsPage: React.FC = () => { { icon: FaCodeMerge, value: user?.contributionsCount || 0, unit: 'Contribution' }, ] - const UserSummary = () => ( -
- {user?.name -
-
-
- - @{user?.login} - - {user?.badges && user.badges.length > 0 && ( -
- {user.badges.slice().map((badge: Badge) => ( - - - - ))} -
- )} -
-

{formattedBio}

-
- {hasContributionData && dateRange.startDate && dateRange.endDate && ( -
-
- -
-
- )} -
-
- ) - return ( { stats={userStats} title={user?.name || user?.login} type="user" - userSummary={} + userSummary={ + + } /> ) } diff --git a/frontend/src/components/SkeletonsBase.tsx b/frontend/src/components/SkeletonsBase.tsx index 96577819bd..fd30b4aa3a 100644 --- a/frontend/src/components/SkeletonsBase.tsx +++ b/frontend/src/components/SkeletonsBase.tsx @@ -1,4 +1,5 @@ import { Skeleton } from '@heroui/skeleton' +import React from 'react' import LoadingSpinner from 'components/LoadingSpinner' import AboutSkeleton from 'components/skeletons/AboutSkeleton' import CardSkeleton from 'components/skeletons/Card' @@ -6,6 +7,19 @@ import MemberDetailsPageSkeleton from 'components/skeletons/MemberDetailsPageSke import OrganizationDetailsPageSkeleton from 'components/skeletons/OrganizationDetailsPageSkeleton' import SnapshotSkeleton from 'components/skeletons/SnapshotSkeleton' import UserCardSkeleton from 'components/skeletons/UserCard' + +type CardSkeletonComponentProps = { + showLevel?: boolean + showIcons?: boolean + showLink?: boolean + numIcons?: number + showContributors?: boolean + showSocial?: boolean +} + +export const CardSkeletonComponent: React.FC = (props) => ( + +) function userCardRender() { const cardCount = 12 return ( @@ -35,31 +49,30 @@ const SkeletonBase = ({ indexName: string loadingImageUrl: string }) => { - let Component + let Component: React.FC = CardSkeletonComponent + let componentProps: CardSkeletonComponentProps = {} switch (indexName) { case 'chapters': - Component = () => + Component = CardSkeletonComponent + componentProps = { showLevel: false, showIcons: false, showLink: false } break case 'issues': - Component = () => ( - - ) + Component = CardSkeletonComponent + componentProps = { + showLevel: false, + showIcons: true, + numIcons: 2, + showContributors: false, + showSocial: false, + } break case 'projects': - Component = () => ( - - ) + Component = CardSkeletonComponent + componentProps = { showLink: false, showSocial: false, showIcons: true, numIcons: 3 } break case 'committees': - Component = () => ( - - ) + Component = CardSkeletonComponent + componentProps = { showLink: false, showLevel: false, showIcons: true, numIcons: 1 } break case 'users': return userCardRender() @@ -82,11 +95,11 @@ const SkeletonBase = ({ {indexName == 'chapters' ? ( ) : ( - + )} - - - + + +
) } From dd402750c11a15ded8c135d3c486169190e27c32 Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Wed, 11 Feb 2026 22:18:32 +0530 Subject: [PATCH 2/4] fixed code rabbit review --- .../src/app/board/[year]/candidates/page.tsx | 22 ++++++---------- frontend/src/app/global-error.tsx | 10 +++++-- frontend/src/components/SkeletonsBase.tsx | 26 +++++++------------ 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/frontend/src/app/board/[year]/candidates/page.tsx b/frontend/src/app/board/[year]/candidates/page.tsx index b28e944cb7..f29d0f61ac 100644 --- a/frontend/src/app/board/[year]/candidates/page.tsx +++ b/frontend/src/app/board/[year]/candidates/page.tsx @@ -92,7 +92,6 @@ interface CandidateCardProps { const CandidateCard = ({ candidate, year }: CandidateCardProps) => { const client = useApolloClient() - const [snapshot, setSnapshot] = useState(null) const [ledChapters, setLedChapters] = useState([]) const [ledProjects, setLedProjects] = useState([]) @@ -110,7 +109,7 @@ const CandidateCard = ({ candidate, year }: CandidateCardProps) => { return [...entries].sort(([, a], [, b]) => b - a) } - // Render a single repository link item + // Render a single channel link item const renderChannelLink = (channelName: string, messageCount: string | number) => ( { }, skip: !candidate.member?.login, }) - - useEffect(() => { - if (snapshotData?.memberSnapshot) { - setSnapshot(snapshotData.memberSnapshot) - } - }, [snapshotData]) + // Derive snapshot directly from the query result to avoid an extra render + const snapshot: MemberSnapshot | null = snapshotData?.memberSnapshot ?? null // Fetch chapters based on chapterContributions keys useEffect(() => { @@ -666,8 +661,6 @@ const CandidateCard = ({ candidate, year }: CandidateCardProps) => { const BoardCandidatesPage = () => { const { year } = useParams<{ year: string }>() - const [candidates, setCandidates] = useState([]) - const { data: graphQLData, error: graphQLRequestError, @@ -676,14 +669,15 @@ const BoardCandidatesPage = () => { variables: { year: Number.parseInt(year) }, }) + // Derive candidates directly from GraphQL data to avoid an extra render + const candidates: CandidateWithSnapshot[] = graphQLData?.boardOfDirectors?.candidates ?? [] + + // Keep reporting errors as a side-effect only useEffect(() => { - if (graphQLData?.boardOfDirectors) { - setCandidates(graphQLData.boardOfDirectors.candidates || []) - } if (graphQLRequestError) { handleAppError(graphQLRequestError) } - }, [graphQLData, graphQLRequestError]) + }, [graphQLRequestError]) if (loading) { return diff --git a/frontend/src/app/global-error.tsx b/frontend/src/app/global-error.tsx index f3213ea415..9532023d39 100644 --- a/frontend/src/app/global-error.tsx +++ b/frontend/src/app/global-error.tsx @@ -102,12 +102,18 @@ export const SentryErrorFallback: React.FC<{ error: unknown errorConfig?: ErrorDisplayProps }> = ({ error, errorConfig = ERROR_CONFIGS['500'] }) => { - Sentry.captureException(error instanceof Error ? error : new Error(String(error))) + React.useEffect(() => { + Sentry.captureException(error instanceof Error ? error : new Error(String(error))) + }, [error]) + return } export default function GlobalError({ error }: Readonly<{ error: Error }>) { - Sentry.captureException(error) + React.useEffect(() => { + Sentry.captureException(error) + }, [error]) + const errorConfig = ERROR_CONFIGS['500'] return } diff --git a/frontend/src/components/SkeletonsBase.tsx b/frontend/src/components/SkeletonsBase.tsx index fd30b4aa3a..cc2312248d 100644 --- a/frontend/src/components/SkeletonsBase.tsx +++ b/frontend/src/components/SkeletonsBase.tsx @@ -1,5 +1,6 @@ import { Skeleton } from '@heroui/skeleton' import React from 'react' +import type { CardSkeletonProps } from 'types/skeleton' import LoadingSpinner from 'components/LoadingSpinner' import AboutSkeleton from 'components/skeletons/AboutSkeleton' import CardSkeleton from 'components/skeletons/Card' @@ -8,16 +9,7 @@ import OrganizationDetailsPageSkeleton from 'components/skeletons/OrganizationDe import SnapshotSkeleton from 'components/skeletons/SnapshotSkeleton' import UserCardSkeleton from 'components/skeletons/UserCard' -type CardSkeletonComponentProps = { - showLevel?: boolean - showIcons?: boolean - showLink?: boolean - numIcons?: number - showContributors?: boolean - showSocial?: boolean -} - -export const CardSkeletonComponent: React.FC = (props) => ( +export const CardSkeletonComponent: React.FC = (props) => ( ) function userCardRender() { @@ -49,8 +41,8 @@ const SkeletonBase = ({ indexName: string loadingImageUrl: string }) => { - let Component: React.FC = CardSkeletonComponent - let componentProps: CardSkeletonComponentProps = {} + let Component: React.FC + let componentProps: CardSkeletonProps = {} switch (indexName) { case 'chapters': Component = CardSkeletonComponent @@ -90,16 +82,18 @@ const SkeletonBase = ({ default: return } + const ResolvedComponent: React.FC = Component ?? CardSkeletonComponent + return (
{indexName == 'chapters' ? ( ) : ( - + )} - - - + + +
) } From 3d2b51d65116d9b0a80d7195efaf0ae37d097bbd Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Wed, 11 Feb 2026 22:50:02 +0530 Subject: [PATCH 3/4] update code --- frontend/src/app/board/[year]/candidates/page.tsx | 4 ++-- frontend/src/components/SkeletonsBase.tsx | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/board/[year]/candidates/page.tsx b/frontend/src/app/board/[year]/candidates/page.tsx index f29d0f61ac..f00c0a9190 100644 --- a/frontend/src/app/board/[year]/candidates/page.tsx +++ b/frontend/src/app/board/[year]/candidates/page.tsx @@ -472,7 +472,7 @@ const CandidateCard = ({ candidate, year }: CandidateCardProps) => { {ledChapters.length === 0 && ledProjects.length === 0 ? (
- No chapters or projects are lead by this candidate + No chapters or projects are led by this candidate
) : (
@@ -586,7 +586,7 @@ const CandidateCard = ({ candidate, year }: CandidateCardProps) => {
{sortedRepos - .slice(1) + .slice(1, 5) .map(([repoName, count]) => renderRepositoryLink(repoName, count))}
diff --git a/frontend/src/components/SkeletonsBase.tsx b/frontend/src/components/SkeletonsBase.tsx index cc2312248d..a651be8d8f 100644 --- a/frontend/src/components/SkeletonsBase.tsx +++ b/frontend/src/components/SkeletonsBase.tsx @@ -82,18 +82,16 @@ const SkeletonBase = ({ default: return } - const ResolvedComponent: React.FC = Component ?? CardSkeletonComponent - return (
{indexName == 'chapters' ? ( ) : ( - + )} - - - + + +
) } From 70ec4809f4dc082526af586038fa289040717c31 Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Wed, 11 Feb 2026 23:09:15 +0530 Subject: [PATCH 4/4] fixed coderabbit review --- .../src/app/board/[year]/candidates/page.tsx | 28 ++++++------------- frontend/src/components/SkeletonsBase.tsx | 19 ++++--------- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/frontend/src/app/board/[year]/candidates/page.tsx b/frontend/src/app/board/[year]/candidates/page.tsx index f00c0a9190..6217c04361 100644 --- a/frontend/src/app/board/[year]/candidates/page.tsx +++ b/frontend/src/app/board/[year]/candidates/page.tsx @@ -24,6 +24,12 @@ import LoadingSpinner from 'components/LoadingSpinner' dayjs.extend(relativeTime) +const sortByName = (items: T[]): T[] => + [...items].sort((a, b) => a.name.localeCompare(b.name)) + +const sortByCount = (entries: Array<[string, number]>): Array<[string, number]> => + [...entries].sort(([, a], [, b]) => b - a) + type Candidate = { id: string memberName: string @@ -95,20 +101,6 @@ const CandidateCard = ({ candidate, year }: CandidateCardProps) => { const [ledChapters, setLedChapters] = useState([]) const [ledProjects, setLedProjects] = useState([]) - const sortByName = (items: T[]): T[] => { - return [...items].sort((a, b) => a.name.localeCompare(b.name)) - } - - const sortByContributionCount = (entries: Array<[string, number]>): Array<[string, number]> => { - return [...entries].sort(([, a], [, b]) => b - a) - } - - const sortChannelsByMessageCount = ( - entries: Array<[string, number]> - ): Array<[string, number]> => { - return [...entries].sort(([, a], [, b]) => b - a) - } - // Render a single channel link item const renderChannelLink = (channelName: string, messageCount: string | number) => (
{ ) } - const sortedChannels = sortChannelsByMessageCount( - Object.entries(snapshot.channelCommunications) - ) + const sortedChannels = sortByCount(Object.entries(snapshot.channelCommunications)) if (sortedChannels.length === 0) return null @@ -557,9 +547,7 @@ const CandidateCard = ({ candidate, year }: CandidateCardProps) => { {snapshot.repositoryContributions && Object.keys(snapshot.repositoryContributions).length > 0 && (() => { - const sortedRepos = sortByContributionCount( - Object.entries(snapshot.repositoryContributions) - ) + const sortedRepos = sortByCount(Object.entries(snapshot.repositoryContributions)) const topRepo = sortedRepos[0] const [topRepoName, topRepoCount] = topRepo diff --git a/frontend/src/components/SkeletonsBase.tsx b/frontend/src/components/SkeletonsBase.tsx index a651be8d8f..1519b5943f 100644 --- a/frontend/src/components/SkeletonsBase.tsx +++ b/frontend/src/components/SkeletonsBase.tsx @@ -1,5 +1,4 @@ import { Skeleton } from '@heroui/skeleton' -import React from 'react' import type { CardSkeletonProps } from 'types/skeleton' import LoadingSpinner from 'components/LoadingSpinner' import AboutSkeleton from 'components/skeletons/AboutSkeleton' @@ -9,9 +8,7 @@ import OrganizationDetailsPageSkeleton from 'components/skeletons/OrganizationDe import SnapshotSkeleton from 'components/skeletons/SnapshotSkeleton' import UserCardSkeleton from 'components/skeletons/UserCard' -export const CardSkeletonComponent: React.FC = (props) => ( - -) +// Use CardSkeleton directly; wrapper removed function userCardRender() { const cardCount = 12 return ( @@ -41,15 +38,13 @@ const SkeletonBase = ({ indexName: string loadingImageUrl: string }) => { - let Component: React.FC let componentProps: CardSkeletonProps = {} + switch (indexName) { case 'chapters': - Component = CardSkeletonComponent componentProps = { showLevel: false, showIcons: false, showLink: false } break case 'issues': - Component = CardSkeletonComponent componentProps = { showLevel: false, showIcons: true, @@ -59,11 +54,9 @@ const SkeletonBase = ({ } break case 'projects': - Component = CardSkeletonComponent componentProps = { showLink: false, showSocial: false, showIcons: true, numIcons: 3 } break case 'committees': - Component = CardSkeletonComponent componentProps = { showLink: false, showLevel: false, showIcons: true, numIcons: 1 } break case 'users': @@ -87,11 +80,11 @@ const SkeletonBase = ({ {indexName == 'chapters' ? ( ) : ( - + )} - - - + + +
) }