Skip to content

feat: migrate leaderboard from REST to GraphQL#121

Merged
0xdevcollins merged 3 commits intoboundlessfi:mainfrom
1sraeliteX:main
Feb 25, 2026
Merged

feat: migrate leaderboard from REST to GraphQL#121
0xdevcollins merged 3 commits intoboundlessfi:mainfrom
1sraeliteX:main

Conversation

@1sraeliteX
Copy link
Contributor

@1sraeliteX 1sraeliteX commented Feb 25, 2026

Closes #120


  • Update types/leaderboard.ts to re-export generated GraphQL types
  • Add GraphQL queries for leaderboard, user rank, and top contributors
  • Refactor hooks/use-leaderboard.ts to use GraphQL client and generated documents
  • Remove lib/api/leaderboard.ts and all REST API references
  • Update components to use proper GraphQL enum values
  • Regenerate GraphQL types with new leaderboard schema
  • Ensure filtering and pagination work with GraphQL
  • Add GraphQL schema types for leaderboard functionality

All leaderboard functionality now uses GraphQL with full type safety and feature parity with the previous REST implementation.

Summary by CodeRabbit

  • New Features

    • Leaderboard now powered by GraphQL with richer contributor stats, top-contributors list, and per-user rank lookup.
    • Adds timeframe (all-time, monthly, weekly) and contributor-tier filtering.
  • Improvements

    • Faster, more reliable leaderboard loading and pagination.
    • More consistent data handling and stronger typing for safer leaderboard interactions.

- Update types/leaderboard.ts to re-export generated GraphQL types
- Add GraphQL queries for leaderboard, user rank, and top contributors
- Refactor hooks/use-leaderboard.ts to use GraphQL client and generated documents
- Remove lib/api/leaderboard.ts and all REST API references
- Update components to use proper GraphQL enum values
- Regenerate GraphQL types with new leaderboard schema
- Ensure filtering and pagination work with GraphQL
- Add GraphQL schema types for leaderboard functionality

All leaderboard functionality now uses GraphQL with full type safety
and feature parity with the previous REST implementation.
@vercel
Copy link

vercel bot commented Feb 25, 2026

Someone is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

Migrates leaderboard from REST to GraphQL: removes REST API, adds GraphQL schema/operations and generated types/hooks, updates hooks and UI to use generated enums/types and fetchers, and adapts mock data and exported types to the generated outputs.

Changes

Cohort / File(s) Summary
GraphQL schema & operations
lib/graphql/schema.graphql, lib/graphql/operations/leaderboard-queries.graphql
Added leaderboard schema, inputs/enums (LeaderboardTimeframe, ReputationTier), and three queries (leaderboard, userLeaderboardRank, topContributors).
Generated GraphQL types & hooks
lib/graphql/generated.ts
Added generated types, enums, query Documents, and React Query hooks (LeaderboardQuery, UserLeaderboardRankQuery, TopContributorsQuery) and getKey helpers.
Frontend hooks & keys
hooks/use-leaderboard.ts
Rewrote leaderboard hooks to use GraphQL fetcher/documents and generated types; added LEADERBOARD_KEYS.lists() and .list(filters); typed pagination and query functions.
REST API removal
lib/api/leaderboard.ts
Removed REST leaderboard API surface (leaderboardApi, fetchLeaderboard, fetchUserRank, fetchTopContributors, LEADERBOARD_ENDPOINT).
UI components & filters
app/leaderboard/page.tsx, components/leaderboard/leaderboard-filters.tsx, components/leaderboard/mini-leaderboard.tsx
Replaced string literals with generated enums (LeaderboardTimeframe., ReputationTier.); adjusted default timeframe, URL sync logic, and added explicit contributor typing in mini-leaderboard.
Types re-export & mocks
types/leaderboard.ts, lib/mock-leaderboard.ts
Removed local leaderboard type declarations; re-exported types from generated GraphQL output and updated mock data to use generated enums.
Dev tooling
package.json
Bumped GraphQL codegen devDependency patch versions.
Minor UI change
components/bounty-detail/bounty-detail-submissions-card.tsx
Replaced reviewStatus state with a constant ("APPROVED").

Sequence Diagram(s)

sequenceDiagram
    participant Page as "Leaderboard Page"
    participant Hook as "useLeaderboard Hook"
    participant Fetcher as "GraphQL Fetcher"
    participant API as "GraphQL Server"

    Page->>Hook: request leaderboard (filters, pagination)
    Hook->>Fetcher: execute LeaderboardDocument with variables
    Fetcher->>API: GraphQL query (Leaderboard)
    API-->>Fetcher: GraphQL response (entries, totalCount, userRank)
    Fetcher-->>Hook: parsed data
    Hook-->>Page: data / cache
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Benjtalkshow

Poem

"I'm a rabbit with a query and a hop,
REST trails fade as GraphQL pops,
Enums in my pocket, types all aligned,
Pages fetch cleanly, results well-defined,
Hooray — the leaderboard leaps to the top! 🐰"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning One out-of-scope change detected: components/bounty-detail/bounty-detail-submissions-card.tsx has an unrelated modification replacing reviewStatus state with a constant, which is not part of the leaderboard migration objectives. Remove the reviewStatus state-to-constant change from bounty-detail-submissions-card.tsx as it is unrelated to the leaderboard migration scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: migrate leaderboard from REST to GraphQL' accurately summarizes the main change - migrating leaderboard functionality from REST API to GraphQL.
Linked Issues check ✅ Passed The pull request fully addresses all coding requirements from issue #120: types re-exported from GraphQL generated files, GraphQL queries added, hooks refactored to use GraphQL with generated documents, REST API code removed, filtering and pagination preserved, and all leaderboard logic uses generated types.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Replace require() calls with proper imports in hooks/use-leaderboard.ts
- Remove unused setReviewStatus variable in bounty-detail-submissions-card.tsx
- Fix all TypeScript ESLint errors and warnings
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
lib/graphql/operations/leaderboard-queries.graphql (1)

1-87: Extract a shared fragment for the contributor selection set.

The contributor field selection (Lines 7–28, 39–60, 66–86) is duplicated across all three queries. A GraphQL fragment would reduce drift risk when fields change.

♻️ Proposed fragment extraction
+fragment ContributorFields on LeaderboardContributor {
+  id
+  userId
+  walletAddress
+  displayName
+  avatarUrl
+  totalScore
+  tier
+  stats {
+    totalCompleted
+    totalEarnings
+    earningsCurrency
+    completionRate
+    averageCompletionTime
+    currentStreak
+    longestStreak
+    nextTierThreshold
+    currentTierPoints
+  }
+  topTags
+  lastActiveAt
+}
+
 query Leaderboard($filters: LeaderboardFilters!, $pagination: LeaderboardPagination!) {
   leaderboard(filters: $filters, pagination: $pagination) {
     entries {
       rank
       previousRank
       rankChange
       contributor {
-        id
-        userId
-        walletAddress
-        displayName
-        avatarUrl
-        totalScore
-        tier
-        stats {
-          totalCompleted
-          totalEarnings
-          earningsCurrency
-          completionRate
-          averageCompletionTime
-          currentStreak
-          longestStreak
-          nextTierThreshold
-          currentTierPoints
-        }
-        topTags
-        lastActiveAt
+        ...ContributorFields
       }
     }
     totalCount
     currentUserRank
     lastUpdatedAt
   }
 }

Apply the same pattern to UserLeaderboardRank and TopContributors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/graphql/operations/leaderboard-queries.graphql` around lines 1 - 87,
Extract the duplicated contributor selection into a GraphQL fragment (e.g.,
fragment ContributorFields on Contributor) that includes id, userId,
walletAddress, displayName, avatarUrl, totalScore, tier, stats { ... }, topTags,
and lastActiveAt, then replace the inline contributor selection in the
Leaderboard, UserLeaderboardRank, and TopContributors queries with the fragment
spread (e.g., ...ContributorFields) and ensure the fragment is declared in the
same file so all three queries reference the single source of truth.
components/leaderboard/mini-leaderboard.tsx (1)

12-12: Consider importing from the re-export barrel (@/types/leaderboard) for consistency.

types/leaderboard.ts already re-exports LeaderboardContributor. Other files (e.g., leaderboard-filters.tsx) use a mix of direct and re-exported imports. Picking one convention avoids confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/leaderboard/mini-leaderboard.tsx` at line 12, Change the import of
LeaderboardContributor in mini-leaderboard.tsx to use the project's re-export
barrel to stay consistent: replace the direct import from
"@/lib/graphql/generated" with the re-export from "@/types/leaderboard" so the
symbol LeaderboardContributor (used in the MiniLeaderboard component) is
imported from the unified types barrel.
hooks/use-leaderboard.ts (1)

30-36: Inconsistent getNextPageParam logic between useLeaderboard and usePrefetchLeaderboardPage.

useLeaderboard (Line 32) uses the optimized allPages.length * limit to estimate loaded entries, while usePrefetchLeaderboardPage (Line 83) uses allPages.flatMap(p => p.entries).length for an exact count. Both compute "has more pages" but could diverge if a page returns fewer than limit entries (e.g., server-side filtering). Pick one strategy for consistency — the flatMap approach is more accurate.

Also applies to: 82-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-leaderboard.ts` around lines 30 - 36, The getNextPageParam logic in
useLeaderboard should be made consistent with usePrefetchLeaderboardPage by
switching from the optimistic calculation (allPages.length * limit) to the exact
entry count using allPages.flatMap(p => p.entries).length; update the
getNextPageParam implementation in both useLeaderboard and
usePrefetchLeaderboardPage to compute loaded = allPages.flatMap(p =>
p.entries).length and then return allPages.length + 1 if loaded <
lastPage.totalCount, otherwise undefined, referencing the existing
getNextPageParam functions, limit, lastPage.totalCount, and allPages.flatMap
usage.
lib/mock-leaderboard.ts (1)

1-2: Prefer the leaderboard type barrel import for cohesion.

Consider importing ReputationTier from @/types/leaderboard (same as LeaderboardContributor) so app code stays decoupled from generated-module path churn.

Suggested diff
-import { LeaderboardContributor } from "@/types/leaderboard";
-import { ReputationTier } from "@/lib/graphql/generated";
+import { LeaderboardContributor, ReputationTier } from "@/types/leaderboard";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mock-leaderboard.ts` around lines 1 - 2, Replace the direct import of
ReputationTier from the generated GraphQL module by importing it from the
leaderboard type barrel alongside LeaderboardContributor; update the import
statement that currently brings in LeaderboardContributor and ReputationTier so
ReputationTier is imported from "@/types/leaderboard" (same symbol name) to keep
the module decoupled from generated paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/use-leaderboard.ts`:
- Around line 24-29: Replace the runtime require() usages that fetch GraphQL
document constants inside the queryFn (and other queryFn handlers) with static
ES module imports: add imports for LeaderboardDocument,
UserLeaderboardRankDocument, and TopContributorsDocument from
'@/lib/graphql/generated' at the top of the file alongside the existing type
imports, then update the fetcher calls (e.g., in queryFn:
fetcher(...LeaderboardDocument, ...), and the other places referencing
UserLeaderboardRankDocument and TopContributorsDocument) to use the imported
symbols instead of require(...).Document so the build no longer violates
`@typescript-eslint/no-require-imports`.

In `@types/leaderboard.ts`:
- Around line 1-12: The barrel is re-exporting enums as types which strips their
runtime values; update types/leaderboard.ts to re-export the actual enum values
for ReputationTier and LeaderboardTimeframe (so consumers can access enum
members at runtime) while keeping the rest as type-only re-exports (leave
ContributorStats, LeaderboardContributor, LeaderboardEntry, LeaderboardResponse,
LeaderboardFilters, LeaderboardPagination, UserLeaderboardRankResponse exported
as types). Locate the file and replace the type-only export of ReputationTier
and LeaderboardTimeframe with a value export for those two symbols and keep the
remaining exports as type exports.

---

Nitpick comments:
In `@components/leaderboard/mini-leaderboard.tsx`:
- Line 12: Change the import of LeaderboardContributor in mini-leaderboard.tsx
to use the project's re-export barrel to stay consistent: replace the direct
import from "@/lib/graphql/generated" with the re-export from
"@/types/leaderboard" so the symbol LeaderboardContributor (used in the
MiniLeaderboard component) is imported from the unified types barrel.

In `@hooks/use-leaderboard.ts`:
- Around line 30-36: The getNextPageParam logic in useLeaderboard should be made
consistent with usePrefetchLeaderboardPage by switching from the optimistic
calculation (allPages.length * limit) to the exact entry count using
allPages.flatMap(p => p.entries).length; update the getNextPageParam
implementation in both useLeaderboard and usePrefetchLeaderboardPage to compute
loaded = allPages.flatMap(p => p.entries).length and then return allPages.length
+ 1 if loaded < lastPage.totalCount, otherwise undefined, referencing the
existing getNextPageParam functions, limit, lastPage.totalCount, and
allPages.flatMap usage.

In `@lib/graphql/operations/leaderboard-queries.graphql`:
- Around line 1-87: Extract the duplicated contributor selection into a GraphQL
fragment (e.g., fragment ContributorFields on Contributor) that includes id,
userId, walletAddress, displayName, avatarUrl, totalScore, tier, stats { ... },
topTags, and lastActiveAt, then replace the inline contributor selection in the
Leaderboard, UserLeaderboardRank, and TopContributors queries with the fragment
spread (e.g., ...ContributorFields) and ensure the fragment is declared in the
same file so all three queries reference the single source of truth.

In `@lib/mock-leaderboard.ts`:
- Around line 1-2: Replace the direct import of ReputationTier from the
generated GraphQL module by importing it from the leaderboard type barrel
alongside LeaderboardContributor; update the import statement that currently
brings in LeaderboardContributor and ReputationTier so ReputationTier is
imported from "@/types/leaderboard" (same symbol name) to keep the module
decoupled from generated paths.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8a12149 and 5b6570b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • app/leaderboard/page.tsx
  • components/leaderboard/leaderboard-filters.tsx
  • components/leaderboard/mini-leaderboard.tsx
  • hooks/use-leaderboard.ts
  • lib/api/leaderboard.ts
  • lib/graphql/generated.ts
  • lib/graphql/operations/leaderboard-queries.graphql
  • lib/graphql/schema.graphql
  • lib/mock-leaderboard.ts
  • package.json
  • types/leaderboard.ts
💤 Files with no reviewable changes (1)
  • lib/api/leaderboard.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
hooks/use-leaderboard.ts (2)

33-38: ⚠️ Potential issue | 🟡 Minor

getNextPageParam math optimisation may terminate pagination early on partial pages.

allPages.length * limit overestimates the number of loaded entries. If the server ever returns fewer than limit items on a page that is not the last (e.g., under filtering), allPages.length * limit can exceed totalCount while actualLoadedEntries < totalCount, causing hasNextPage to be false prematurely.

Additionally, this logic is inconsistent with usePrefetchLeaderboardPage (Line 86), which correctly counts allPages.flatMap(p => p.entries).length. Aligning both removes the edge-case risk.

♻️ Proposed fix
-        getNextPageParam: (lastPage: LeaderboardResponse, allPages: LeaderboardResponse[]) => {
-            // Optimization: Use simple math instead of iterating all entries
-            if (allPages.length * limit < lastPage.totalCount) {
-                return allPages.length + 1;
-            }
-            return undefined;
-        },
+        getNextPageParam: (lastPage: LeaderboardResponse, allPages: LeaderboardResponse[]) => {
+            const loadedCount = allPages.flatMap((p) => p.entries).length;
+            if (loadedCount < lastPage.totalCount) {
+                return allPages.length + 1;
+            }
+            return undefined;
+        },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-leaderboard.ts` around lines 33 - 38, The getNextPageParam logic in
getNextPageParam currently multiplies allPages.length * limit which can
overestimate loaded entries and prematurely stop pagination; update
getNextPageParam to compute actualLoadedEntries by summing entries across pages
(e.g., allPages.flatMap(p => p.entries).length or reduce) and return
allPages.length + 1 only if actualLoadedEntries < lastPage.totalCount, matching
the approach used in usePrefetchLeaderboardPage to avoid the partial-page edge
case.

75-93: ⚠️ Potential issue | 🟡 Minor

Prefetch Promise is not returned — errors are silently swallowed.

queryClient.prefetchInfiniteQuery returns a Promise<void>. The inner closure discards it, so callers have no way to await completion or catch failures. If the prefetch request errors, it is silently ignored.

The method works the same as fetchQuery except that it will not throw or return any data. However, the returned Promise does resolve/reject based on network success, and not returning it prevents callers from chaining or awaiting the warm-up.

♻️ Proposed fix
     return (filters: LeaderboardFilters, page: number, limit: number) => {
-        queryClient.prefetchInfiniteQuery({
+        return queryClient.prefetchInfiniteQuery({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-leaderboard.ts` around lines 75 - 93, The prefetch Promise from
queryClient.prefetchInfiniteQuery is being discarded, so update the function
that currently calls queryClient.prefetchInfiniteQuery (inside the exported
callback in hooks/use-leaderboard.ts) to return that Promise instead of ignoring
it; specifically return the result of queryClient.prefetchInfiniteQuery({...})
from the outer function so callers can await or catch errors from the prefetch.
Ensure the signature still matches (returns Promise<void> or the appropriate
Promise type) and keep the existing queryKey, queryFn, getNextPageParam,
initialPageParam and pages options unchanged.
🧹 Nitpick comments (2)
components/bounty-detail/bounty-detail-submissions-card.tsx (1)

60-60: Replace unnecessary state with a constant.

Line 60 uses useState for a value that never changes. A constant is clearer and avoids an extra hook.

♻️ Proposed fix
-  const [reviewStatus] = useState("APPROVED");
+  const reviewStatus = "APPROVED" as const;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty-detail/bounty-detail-submissions-card.tsx` at line 60,
Replace the unnecessary React state with a plain constant: change the
declaration const [reviewStatus] = useState("APPROVED"); to const reviewStatus =
"APPROVED"; and remove the useState import if it becomes unused to avoid an
unused import warning.
hooks/use-leaderboard.ts (1)

78-82: pageParam is typed as optional, causing an unnecessary as number cast.

Since initialPageParam: 1 is required by TanStack Query v5 and the framework always supplies a number, typing pageParam as number | undefined is incorrect and forces the unsafe cast on Line 81. Removing the ? aligns the type with TQ v5's guarantee.

♻️ Proposed fix
-            queryFn: ({ pageParam }: { pageParam?: number }) => {
+            queryFn: ({ pageParam }: { pageParam: number }) => {
                 return fetcher<LeaderboardQuery, LeaderboardQueryVariables>(
                     LeaderboardDocument,
-                    { filters, pagination: { page: pageParam as number, limit } }
+                    { filters, pagination: { page: pageParam, limit } }
                 )().then(data => data.leaderboard);
             },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-leaderboard.ts` around lines 78 - 82, The queryFn's parameter types
incorrectly allow pageParam to be undefined, forcing an unsafe cast; update the
queryFn signature to use ({ pageParam }: { pageParam: number }) so pageParam is
a number (remove the ?), then remove the "as number" cast in the fetcher call
(keep existing references to fetcher, LeaderboardQuery,
LeaderboardQueryVariables, and LeaderboardDocument) to rely on TanStack Query
v5's guaranteed initialPageParam.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@hooks/use-leaderboard.ts`:
- Around line 33-38: The getNextPageParam logic in getNextPageParam currently
multiplies allPages.length * limit which can overestimate loaded entries and
prematurely stop pagination; update getNextPageParam to compute
actualLoadedEntries by summing entries across pages (e.g., allPages.flatMap(p =>
p.entries).length or reduce) and return allPages.length + 1 only if
actualLoadedEntries < lastPage.totalCount, matching the approach used in
usePrefetchLeaderboardPage to avoid the partial-page edge case.
- Around line 75-93: The prefetch Promise from queryClient.prefetchInfiniteQuery
is being discarded, so update the function that currently calls
queryClient.prefetchInfiniteQuery (inside the exported callback in
hooks/use-leaderboard.ts) to return that Promise instead of ignoring it;
specifically return the result of queryClient.prefetchInfiniteQuery({...}) from
the outer function so callers can await or catch errors from the prefetch.
Ensure the signature still matches (returns Promise<void> or the appropriate
Promise type) and keep the existing queryKey, queryFn, getNextPageParam,
initialPageParam and pages options unchanged.

---

Nitpick comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Line 60: Replace the unnecessary React state with a plain constant: change the
declaration const [reviewStatus] = useState("APPROVED"); to const reviewStatus =
"APPROVED"; and remove the useState import if it becomes unused to avoid an
unused import warning.

In `@hooks/use-leaderboard.ts`:
- Around line 78-82: The queryFn's parameter types incorrectly allow pageParam
to be undefined, forcing an unsafe cast; update the queryFn signature to use ({
pageParam }: { pageParam: number }) so pageParam is a number (remove the ?),
then remove the "as number" cast in the fetcher call (keep existing references
to fetcher, LeaderboardQuery, LeaderboardQueryVariables, and
LeaderboardDocument) to rely on TanStack Query v5's guaranteed initialPageParam.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6570b and 2cbc240.

📒 Files selected for processing (2)
  • components/bounty-detail/bounty-detail-submissions-card.tsx
  • hooks/use-leaderboard.ts

@1sraeliteX 1sraeliteX closed this Feb 25, 2026
@1sraeliteX 1sraeliteX reopened this Feb 25, 2026
- Re-export enums as values in types/leaderboard.ts for runtime access
- Fix getNextPageParam logic to use actual loaded entries count
- Return prefetch promise from usePrefetchLeaderboardPage
- Remove unsafe cast in queryFn parameter types
- Replace unnecessary React state with constant in bounty-detail-submissions-card.tsx
- Import LeaderboardContributor from project type barrel in mini-leaderboard.tsx
- Import ReputationTier from project type barrel in mock-leaderboard.tsx
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
hooks/use-leaderboard.ts (2)

33-40: Duplicate getNextPageParam logic — consider extracting a shared helper.

The getNextPageParam implementation is identical in both useLeaderboard (Lines 33–40) and usePrefetchLeaderboardPage (Lines 86–92). A divergence here would silently cause inconsistent pagination behavior between the live query and the prefetch.

♻️ Suggested refactor: extract shared helper
+const getLeaderboardNextPage = (
+    lastPage: LeaderboardResponse,
+    allPages: LeaderboardResponse[]
+): number | undefined => {
+    const loadedCount = allPages.flatMap(p => p.entries).length;
+    return loadedCount < lastPage.totalCount ? allPages.length + 1 : undefined;
+};

 export const useLeaderboard = (filters: LeaderboardFilters, limit: number = 20) => {
     return useInfiniteQuery({
         ...
-        getNextPageParam: (lastPage: LeaderboardResponse, allPages: LeaderboardResponse[]) => {
-            const loadedCount = allPages.flatMap(p => p.entries).length;
-            if (loadedCount < lastPage.totalCount) {
-                return allPages.length + 1;
-            }
-            return undefined;
-        },
+        getNextPageParam: getLeaderboardNextPage,
         ...
     });
 };

 export const usePrefetchLeaderboardPage = () => {
     ...
-        getNextPageParam: (lastPage: LeaderboardResponse, allPages: LeaderboardResponse[]) => {
-            const loadedCount = allPages.flatMap((p: LeaderboardResponse) => p.entries).length;
-            if (loadedCount < lastPage.totalCount) {
-                return allPages.length + 1;
-            }
-            return undefined;
-        },
+        getNextPageParam: getLeaderboardNextPage,
         ...
 };

Also applies to: 86-92

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-leaderboard.ts` around lines 33 - 40, Extract the duplicated
pagination logic into a shared helper (e.g., a function
getNextPageParamFromPages) and replace the inline implementations in
useLeaderboard's getNextPageParam and usePrefetchLeaderboardPage's
getNextPageParam with calls to that helper; the helper should accept (lastPage:
LeaderboardResponse, allPages: LeaderboardResponse[]) and return the next page
number (or undefined) by computing loadedCount = allPages.flatMap(p =>
p.entries).length and comparing to lastPage.totalCount, ensuring both
useLeaderboard and usePrefetchLeaderboardPage reference the same function to
keep pagination behavior consistent.

27-31: Inconsistent pageParam typing between the two queryFn definitions.

Line 27 uses a JavaScript default ({ pageParam = 1 }) while Line 79 uses an explicit type annotation ({ pageParam }: { pageParam: number }). The default value on Line 27 is also redundant because initialPageParam: 1 guarantees pageParam is always set. Prefer the explicit form used in the prefetch query for clarity and consistency.

♻️ Suggested fix
-        queryFn: ({ pageParam = 1 }) => {
+        queryFn: ({ pageParam }: { pageParam: number }) => {
             return fetcher<LeaderboardQuery, LeaderboardQueryVariables>(
                 LeaderboardDocument,
                 { filters, pagination: { page: pageParam as number, limit } }

Also applies to: 79-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-leaderboard.ts` around lines 27 - 31, The two queryFn definitions
use inconsistent pageParam handling; update the primary queryFn signature to
match the prefetch version by removing the default and adding an explicit type:
change "queryFn: ({ pageParam = 1 }) => { ... }" to "queryFn: ({ pageParam }: {
pageParam: number }) => { ... }" (the initialPageParam: 1 already guarantees a
value), and ensure the fetcher call still uses pageParam as a number; reference
the queryFn and initialPageParam symbols to locate both places to make the same
change.
components/leaderboard/mini-leaderboard.tsx (1)

65-65: Redundant inline type annotations on .map() callback parameters.

TypeScript infers both contributor (as LeaderboardContributor) and index (as number) from the contributors array type returned by useTopContributors. The explicit annotations are not harmful but add noise.

♻️ Suggested simplification
-                        {contributors?.map((contributor: LeaderboardContributor, index: number) => (
+                        {contributors?.map((contributor, index) => (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/leaderboard/mini-leaderboard.tsx` at line 65, Remove the redundant
inline type annotations on the .map callback in the MiniLeaderboard render:
locate the contributors?.map((contributor: LeaderboardContributor, index:
number) => ...) usage in the mini-leaderboard component and change the callback
to use inferred types (contributors?.map((contributor, index) => ...)) so
TypeScript relies on the array's element type from useTopContributors instead of
explicit annotations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@components/leaderboard/mini-leaderboard.tsx`:
- Line 65: Remove the redundant inline type annotations on the .map callback in
the MiniLeaderboard render: locate the contributors?.map((contributor:
LeaderboardContributor, index: number) => ...) usage in the mini-leaderboard
component and change the callback to use inferred types
(contributors?.map((contributor, index) => ...)) so TypeScript relies on the
array's element type from useTopContributors instead of explicit annotations.

In `@hooks/use-leaderboard.ts`:
- Around line 33-40: Extract the duplicated pagination logic into a shared
helper (e.g., a function getNextPageParamFromPages) and replace the inline
implementations in useLeaderboard's getNextPageParam and
usePrefetchLeaderboardPage's getNextPageParam with calls to that helper; the
helper should accept (lastPage: LeaderboardResponse, allPages:
LeaderboardResponse[]) and return the next page number (or undefined) by
computing loadedCount = allPages.flatMap(p => p.entries).length and comparing to
lastPage.totalCount, ensuring both useLeaderboard and usePrefetchLeaderboardPage
reference the same function to keep pagination behavior consistent.
- Around line 27-31: The two queryFn definitions use inconsistent pageParam
handling; update the primary queryFn signature to match the prefetch version by
removing the default and adding an explicit type: change "queryFn: ({ pageParam
= 1 }) => { ... }" to "queryFn: ({ pageParam }: { pageParam: number }) => { ...
}" (the initialPageParam: 1 already guarantees a value), and ensure the fetcher
call still uses pageParam as a number; reference the queryFn and
initialPageParam symbols to locate both places to make the same change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2cbc240 and 65e8d6d.

📒 Files selected for processing (5)
  • components/bounty-detail/bounty-detail-submissions-card.tsx
  • components/leaderboard/mini-leaderboard.tsx
  • hooks/use-leaderboard.ts
  • lib/mock-leaderboard.ts
  • types/leaderboard.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/bounty-detail/bounty-detail-submissions-card.tsx

@0xdevcollins 0xdevcollins merged commit 1c735c5 into boundlessfi:main Feb 25, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Leaderboard Page from REST to GraphQL

2 participants