Skip to content
Draft
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
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"test:unit": "tsc --noEmit && NODE_OPTIONS='--experimental-vm-modules --no-warnings=DEP0040' jest"
},
"dependencies": {
"@apollo/client": "^3.14.0",
"@apollo/client": "^4.0.0",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
Expand Down Expand Up @@ -63,6 +63,7 @@
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
"react-router-dom": "^7.8.2",
"rxjs": "^7.8.2",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7"
},
Expand Down
71 changes: 13 additions & 58 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 21 additions & 12 deletions frontend/src/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import { useQuery } from '@apollo/client'
import { useQuery } from '@apollo/client/react'
import {
faCircleCheck,
faClock,
Expand All @@ -19,11 +19,16 @@ import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper'
import { ErrorDisplay, handleAppError } from 'app/global-error'
import { GET_PROJECT_METADATA, GET_TOP_CONTRIBUTORS } from 'server/queries/projectQueries'
import { GET_LEADER_DATA } from 'server/queries/userQueries'
import type { Contributor } from 'types/contributor'
import type { Project } from 'types/project'
import type { User } from 'types/user'
import {
GetProjectMetadataDocument,
GetProjectMetadataQuery,
GetTopContributorsDocument,
GetTopContributorsQuery,
} from 'types/__generated__/projectQueries.generated'
import {
GetLeaderDataDocument,
GetLeaderDataQuery,
} from 'types/__generated__/userQueries.generated'
import { aboutText, technologies } from 'utils/aboutData'
import AnchorTitle from 'components/AnchorTitle'
import AnimatedCounter from 'components/AnimatedCounter'
Expand All @@ -42,14 +47,14 @@ const projectKey = 'nest'

const About = () => {
const { data: projectMetadataResponse, error: projectMetadataRequestError } = useQuery(
GET_PROJECT_METADATA,
GetProjectMetadataDocument,
{
variables: { key: projectKey },
}
)

Comment on lines 49 to 55
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix: spinner can hang forever on errors — gate loading off hook loading flags, not data presence.

Using !data to infer loading causes an indefinite spinner when a request errors (no data ever arrives). Read loading from each useQuery and derive isLoading from those flags.

Apply:

-  const { data: projectMetadataResponse, error: projectMetadataRequestError } = useQuery(
+  const {
+    data: projectMetadataResponse,
+    error: projectMetadataRequestError,
+    loading: projectMetadataLoading,
+  } = useQuery(
     GetProjectMetadataDocument,
     {
       variables: { key: projectKey },
     }
   )
...
-  const { data: topContributorsResponse, error: topContributorsRequestError } = useQuery(
+  const {
+    data: topContributorsResponse,
+    error: topContributorsRequestError,
+    loading: topContributorsLoading,
+  } = useQuery(
     GetTopContributorsDocument,
     {
       variables: {
         excludedUsernames: Object.keys(leaders),
         hasFullName: true,
         key: projectKey,
         limit: 24,
       },
     }
   )
...
-  const isLoading =
-    !projectMetadataResponse ||
-    !topContributorsResponse ||
-    (projectMetadataRequestError && !projectMetadata) ||
-    (topContributorsRequestError && !topContributors)
+  const isLoading = projectMetadataLoading || topContributorsLoading

Also applies to: 56-66, 95-103

🤖 Prompt for AI Agents
In frontend/src/app/about/page.tsx around lines 49-55 (and similarly for 56-66
and 95-103), the component currently infers loading state by checking for
absence of data, which causes the spinner to hang when a query errors; update
each useQuery call to also destructure its loading flag (e.g., { data, error,
loading }) and compute a combined isLoading by OR-ing those loading flags, then
gate the spinner/rendering off that isLoading value instead of !data so errors
don't leave the spinner stuck; ensure error handling still renders error UI when
an error exists.

const { data: topContributorsResponse, error: topContributorsRequestError } = useQuery(
GET_TOP_CONTRIBUTORS,
GetTopContributorsDocument,
{
variables: {
excludedUsernames: Object.keys(leaders),
Expand All @@ -60,8 +65,12 @@ const About = () => {
}
)

const [projectMetadata, setProjectMetadata] = useState<Project | null>(null)
const [topContributors, setTopContributors] = useState<Contributor[]>([])
const [projectMetadata, setProjectMetadata] = useState<GetProjectMetadataQuery['project'] | null>(
null
)
const [topContributors, setTopContributors] = useState<
GetTopContributorsQuery['topContributors']
>([])

useEffect(() => {
if (projectMetadataResponse?.project) {
Expand Down Expand Up @@ -247,7 +256,7 @@ const About = () => {
}

const LeaderData = ({ username }: { username: string }) => {
const { data, loading, error } = useQuery(GET_LEADER_DATA, {
const { data, loading, error } = useQuery(GetLeaderDataDocument, {
variables: { key: username },
})
const router = useRouter()
Expand All @@ -261,7 +270,7 @@ const LeaderData = ({ username }: { username: string }) => {
return <p>No data available for {username}</p>
}

const handleButtonClick = (user: User) => {
const handleButtonClick = (user: GetLeaderDataQuery['user']) => {
router.push(`/members/${user.login}`)
}

Expand Down
18 changes: 11 additions & 7 deletions frontend/src/app/chapters/[chapterKey]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
'use client'
import { useQuery } from '@apollo/client'
import { useQuery } from '@apollo/client/react'
import Link from 'next/link'
import { useParams } from 'next/navigation'
import { useState, useEffect } from 'react'
import { handleAppError, ErrorDisplay } from 'app/global-error'
import { GET_CHAPTER_DATA } from 'server/queries/chapterQueries'
import type { Chapter } from 'types/chapter'
import {
GetChapterDataDocument,
GetChapterDataQuery,
} from 'types/__generated__/chapterQueries.generated'
import type { Contributor } from 'types/contributor'
import { formatDate } from 'utils/dateFormatter'
import DetailsCard from 'components/CardDetailsPage'
import LoadingSpinner from 'components/LoadingSpinner'

export default function ChapterDetailsPage() {
const { chapterKey } = useParams()
const [chapter, setChapter] = useState<Chapter>({} as Chapter)
const { chapterKey } = useParams<{ chapterKey: string }>()
const [chapter, setChapter] = useState<GetChapterDataQuery['chapter']>(
{} as GetChapterDataQuery['chapter']
)
Comment on lines +18 to +20
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid using type assertion for initial state.

Using {} as GetChapterDataQuery['chapter'] for initial state is unsafe as it creates an object that doesn't satisfy the type contract.

-  const [chapter, setChapter] = useState<GetChapterDataQuery['chapter']>(
-    {} as GetChapterDataQuery['chapter']
-  )
+  const [chapter, setChapter] = useState<GetChapterDataQuery['chapter']>(null)

This change requires the state to explicitly handle null, which is already handled in your conditional rendering (Line 44).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [chapter, setChapter] = useState<GetChapterDataQuery['chapter']>(
{} as GetChapterDataQuery['chapter']
)
const [chapter, setChapter] = useState<GetChapterDataQuery['chapter']>(null)
🤖 Prompt for AI Agents
In frontend/src/app/chapters/[chapterKey]/page.tsx around lines 18 to 20, the
initial state uses an unsafe type assertion ({} as
GetChapterDataQuery['chapter']); change the state to explicitly allow null by
typing it as GetChapterDataQuery['chapter'] | null and initialize it to null,
then keep the existing conditional rendering that handles null (no other runtime
changes required).

const [topContributors, setTopContributors] = useState<Contributor[]>([])
const [isLoading, setIsLoading] = useState<boolean>(true)

const { data, error: graphQLRequestError } = useQuery(GET_CHAPTER_DATA, {
const { data, error: graphQLRequestError } = useQuery(GetChapterDataDocument, {
variables: { key: chapterKey },
})

Expand Down Expand Up @@ -63,7 +67,7 @@ export default function ChapterDetailsPage() {
<DetailsCard
details={details}
entityKey={chapter.key}
geolocationData={[chapter]}
geolocationData={[chapter]} // TODO: change the type of component
isActive={chapter.isActive}
socialLinks={chapter.relatedUrls}
summary={chapter.summary}
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/app/committees/[committeeKey]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import { useQuery } from '@apollo/client'
import { useQuery } from '@apollo/client/react'
import {
faCode,
faCodeFork,
Expand All @@ -11,20 +11,22 @@ import Link from 'next/link'
import { useParams } from 'next/navigation'
import { useState, useEffect } from 'react'
import { ErrorDisplay, handleAppError } from 'app/global-error'
import { GET_COMMITTEE_DATA } from 'server/queries/committeeQueries'
import type { Committee } from 'types/committee'
import {
GetCommitteeDataDocument,
GetCommitteeDataQuery,
} from 'types/__generated__/committeeQueries.generated'
import type { Contributor } from 'types/contributor'
import { formatDate } from 'utils/dateFormatter'
import DetailsCard from 'components/CardDetailsPage'
import LoadingSpinner from 'components/LoadingSpinner'

export default function CommitteeDetailsPage() {
const { committeeKey } = useParams<{ committeeKey: string }>()
const [committee, setCommittee] = useState<Committee | null>(null)
const [committee, setCommittee] = useState<GetCommitteeDataQuery['committee'] | null>(null)
const [topContributors, setTopContributors] = useState<Contributor[]>([])
const [isLoading, setIsLoading] = useState<boolean>(true)

const { data, error: graphQLRequestError } = useQuery(GET_COMMITTEE_DATA, {
const { data, error: graphQLRequestError } = useQuery(GetCommitteeDataDocument, {
variables: { key: committeeKey },
})

Expand Down
28 changes: 12 additions & 16 deletions frontend/src/app/members/[memberKey]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import { useQuery } from '@apollo/client'
import { useQuery } from '@apollo/client/react'
import {
faCodeMerge,
faFolderOpen,
Expand All @@ -12,26 +12,22 @@ import { useParams } from 'next/navigation'
import { useTheme } from 'next-themes'
import React, { useState, useEffect, useRef } from 'react'
import { handleAppError, ErrorDisplay } from 'app/global-error'
import { GET_USER_DATA } from 'server/queries/userQueries'
import type { Issue } from 'types/issue'
import type { Milestone } from 'types/milestone'
import type { RepositoryCardProps } from 'types/project'
import type { PullRequest } from 'types/pullRequest'
import type { Release } from 'types/release'
import type { UserDetails } from 'types/user'
import { GetUserDataDocument, GetUserDataQuery } from 'types/__generated__/userQueries.generated'
import { formatDate } from 'utils/dateFormatter'
import { drawContributions, fetchHeatmapData, HeatmapData } from 'utils/helpers/githubHeatmap'
import DetailsCard from 'components/CardDetailsPage'
import LoadingSpinner from 'components/LoadingSpinner'

const UserDetailsPage: React.FC = () => {
const { memberKey } = useParams()
const [user, setUser] = useState<UserDetails | null>()
const [issues, setIssues] = useState<Issue[]>([])
const [topRepositories, setTopRepositories] = useState<RepositoryCardProps[]>([])
const [milestones, setMilestones] = useState<Milestone[]>([])
const [pullRequests, setPullRequests] = useState<PullRequest[]>([])
const [releases, setReleases] = useState<Release[]>([])
const { memberKey } = useParams<{ memberKey: string }>()
const [user, setUser] = useState<GetUserDataQuery['user'] | null>()
const [issues, setIssues] = useState<GetUserDataQuery['recentIssues']>([])
const [topRepositories, setTopRepositories] = useState<
GetUserDataQuery['topContributedRepositories']
>([])
const [milestones, setMilestones] = useState<GetUserDataQuery['recentMilestones']>([])
const [pullRequests, setPullRequests] = useState<GetUserDataQuery['recentPullRequests']>([])
const [releases, setReleases] = useState<GetUserDataQuery['recentReleases']>([])
const [data, setData] = useState<HeatmapData>({} as HeatmapData)
const [isLoading, setIsLoading] = useState<boolean>(true)
const [username, setUsername] = useState('')
Expand All @@ -40,7 +36,7 @@ const UserDetailsPage: React.FC = () => {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const theme = 'blue'
Comment on lines 36 to 37
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Heatmap never draws when data arrives; dead canvasRef and stale effect.

Top-level canvasRef isn’t attached to any element; the draw effect depends on it and never runs. The inner Heatmap effect doesn’t react to data/username updates.

Apply this diff to remove dead code and fix dependencies:

-  const canvasRef = useRef<HTMLCanvasElement | null>(null)
-  const theme = 'blue'
@@
-  useEffect(() => {
-    if (canvasRef.current && data && data.years && data.years.length > 0) {
-      drawContributions(canvasRef.current, { data, username, theme })
-      const imageURL = canvasRef.current.toDataURL()
-      setImageLink(imageURL)
-    }
-  }, [username, data])
+  // Removed: drawing happens inside Heatmap component tied to its canvas

And inside Heatmap:

-    useEffect(() => {
+    useEffect(() => {
       if (canvasRef.current && data?.years?.length) {
         drawContributions(canvasRef.current, {
           data,
           username,
           themeName: isDarkMode ? 'dark' : 'light',
         })
         const imageURL = canvasRef.current.toDataURL()
         setImageLink(imageURL)
       }
-    }, [isDarkMode])
+    }, [isDarkMode, data, username])

Also applies to: 74-81, 139-155


const { data: graphQLData, error: graphQLRequestError } = useQuery(GET_USER_DATA, {
const { data: graphQLData, error: graphQLRequestError } = useQuery(GetUserDataDocument, {
variables: { key: memberKey },
})

Expand Down
Loading
Loading