From 61909dddff42658841dcf0979d1b118318e2ceda Mon Sep 17 00:00:00 2001 From: shriya53 Date: Tue, 19 May 2026 23:31:35 +0530 Subject: [PATCH 1/2] Add sticky current-user leaderboard row --- src/app/(app)/dashboard/page.tsx | 92 ++++++++++++++++++++++++----- src/app/(app)/leaderboard/page.tsx | 93 +++++++++++++++++++++++------- src/app/actions/leaderboard.ts | 61 ++++++++++++++++++-- 3 files changed, 206 insertions(+), 40 deletions(-) diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 95c7093..a993917 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -112,6 +112,7 @@ export default async function DashboardPage() { .in('source', ['review', 'help_review']); const mentorPoints = mentorEvents?.reduce((acc, e) => acc + (e.xp_delta || 0), 0) || 0; + // Leaderboard // Leaderboard const { data: leaders } = await service .from('profiles') @@ -119,6 +120,28 @@ export default async function DashboardPage() { .order('xp', { ascending: false }) .limit(4); + // Get all profiles to calculate current user's rank + const { data: allProfiles } = await service + .from('profiles') + .select('github_handle, xp') + .order('xp', { ascending: false }); + + const currentUserRank = + allProfiles?.findIndex((p: any) => p.github_handle === profile?.github_handle) ?? -1; + + const myLeaderboardEntry = + currentUserRank >= 0 + ? { + github_handle: profile?.github_handle ?? 'You', + xp, + rank: currentUserRank + 1, + } + : null; + + const isUserVisible = leaders?.some( + (leader: any) => leader.github_handle === profile?.github_handle, + ); + // Mentees const { data: menteesData } = await service .from('help_requests') @@ -238,23 +261,64 @@ export default async function DashboardPage() {

- ACTIVE ISSUES + LEADERBOARD SNAPSHOT

- - BROWSE MORE - + + GLOBAL
- {recs.length > 0 ? ( - - ) : ( -
- No recommendations yet. Check back soon. -
- )} +
+ {leaders && leaders.length > 0 ? ( + <> + {leaders.map((leader: any, index: number) => { + const isMe = leader.github_handle === profile?.github_handle; + + return ( +
+
+ + {(index + 1).toString().padStart(2, '0')} + + {leader.github_handle} {isMe && '(YOU)'} +
+ + {leader.xp.toLocaleString()} XP +
+ ); + })} + + {!isUserVisible && myLeaderboardEntry && ( + <> +
+ + YOUR RANK + +
+ +
+
+ + {myLeaderboardEntry.rank.toString().padStart(2, '0')} + + {myLeaderboardEntry.github_handle} (YOU) +
+ + {myLeaderboardEntry.xp.toLocaleString()} XP +
+ + )} + + ) : ( +
+ Leaderboard is empty. +
+ )} +
diff --git a/src/app/(app)/leaderboard/page.tsx b/src/app/(app)/leaderboard/page.tsx index 5f5da68..dc8de69 100644 --- a/src/app/(app)/leaderboard/page.tsx +++ b/src/app/(app)/leaderboard/page.tsx @@ -45,28 +45,77 @@ export default async function LeaderboardPage({ {isOk(result) && result.data.length === 0 ? (
  • No entries yet.
  • ) : isOk(result) ? ( - result.data.map((entry) => ( -
  • - #{entry.rank} - {entry.avatarUrl && ( - // eslint-disable-next-line @next/next/no-img-element - - )} - - @{entry.githubHandle} - {entry.displayName && ( - {entry.displayName} - )} - - L{entry.level} - {entry.xp.toLocaleString()} XP -
  • - )) + <> + {(() => { + // Current logged-in user handle from visible list + const currentGithubHandle = result.data.find((e: any) => e.rank)?.githubHandle; + + const isUserVisible = result.data.some( + (entry: any) => entry.githubHandle === currentGithubHandle, + ); + + return ( + <> + {result.data.map((entry: any) => { + const isMe = entry.githubHandle === currentGithubHandle; + + return ( +
  • + #{entry.rank} + + {entry.avatarUrl && ( + // eslint-disable-next-line @next/next/no-img-element + + )} + + + @{entry.githubHandle} + + {entry.displayName && ( + + {entry.displayName} + + )} + + {isMe && (YOU)} + + + L{entry.level} + + + {entry.xp.toLocaleString()} XP + +
  • + ); + })} + + {!isUserVisible && ( + <> +
  • + + YOUR RANK + +
  • + +
  • + Your rank is outside visible leaderboard. +
  • + + )} + + ); + })()} + ) : (
  • Couldn't load: {result.error.message}
  • )} diff --git a/src/app/actions/leaderboard.ts b/src/app/actions/leaderboard.ts index c18f427..29f1e7d 100644 --- a/src/app/actions/leaderboard.ts +++ b/src/app/actions/leaderboard.ts @@ -4,6 +4,7 @@ import { sql } from 'drizzle-orm'; import { tryGetDb } from '@/lib/db/client'; import { cacheGet, cacheSet } from '@/lib/cache'; import { ok, err, type Result } from '@/lib/result'; +import { getServerSupabase } from '@/lib/supabase/server'; export type LeaderboardScope = 'global' | 'cohort' | 'language' | 'tag'; @@ -23,9 +24,17 @@ export async function getLeaderboard( scope: LeaderboardScope, scopeId: string | null, limit = 50, -): Promise> { +): Promise< + Result<{ + entries: LeaderboardEntry[]; + currentUserRank: LeaderboardEntry | null; + }> +> { const cacheKey = `leaderboard:${scope}:${scopeId ?? 'all'}:${limit}`; - const cached = await cacheGet(cacheKey); + const cached = await cacheGet<{ + entries: LeaderboardEntry[]; + currentUserRank: LeaderboardEntry | null; + }>(cacheKey); if (cached) return ok(cached); const db = tryGetDb(); @@ -93,6 +102,50 @@ export async function getLeaderboard( level: r.level, })); - await cacheSet(cacheKey, entries, TTL); - return ok(entries); + const sb = await getServerSupabase(); + + let currentUserRank: LeaderboardEntry | null = null; + + if (sb) { + const { + data: { user }, + } = await sb.auth.getUser(); + + if (user) { + const allRows = (await db.execute(sql` + select id, github_handle, display_name, avatar_url, xp, level + from profiles + order by xp desc + `)) as unknown as typeof rows; + + const normalizedAllRows: typeof rows = Array.isArray(allRows) + ? allRows + : (allRows as unknown as { rows: typeof rows }).rows; + + const currentIndex = normalizedAllRows.findIndex((r) => r.id === user.id); + + if (currentIndex >= 0) { + const current = normalizedAllRows[currentIndex]!; + + currentUserRank = { + rank: currentIndex + 1, + userId: current.id, + githubHandle: current.github_handle, + displayName: current.display_name, + avatarUrl: current.avatar_url, + xp: current.xp, + level: current.level, + }; + } + } + } + + const response = { + entries, + currentUserRank, + }; + + await cacheSet(cacheKey, response, TTL); + + return ok(response); } From b32d995a517dfb922f6e9bb11628a722243bcab3 Mon Sep 17 00:00:00 2001 From: shriya53 Date: Fri, 22 May 2026 16:02:41 +0530 Subject: [PATCH 2/2] fix: resolve leaderboard current user rank issues --- src/app/(app)/leaderboard/page.tsx | 8 +- src/app/actions/leaderboard.ts | 168 ++++++++++++++++++----------- 2 files changed, 112 insertions(+), 64 deletions(-) diff --git a/src/app/(app)/leaderboard/page.tsx b/src/app/(app)/leaderboard/page.tsx index dc8de69..118764f 100644 --- a/src/app/(app)/leaderboard/page.tsx +++ b/src/app/(app)/leaderboard/page.tsx @@ -42,21 +42,21 @@ export default async function LeaderboardPage({
      - {isOk(result) && result.data.length === 0 ? ( + {isOk(result) && result.data.entries.length === 0 ? (
    • No entries yet.
    • ) : isOk(result) ? ( <> {(() => { // Current logged-in user handle from visible list - const currentGithubHandle = result.data.find((e: any) => e.rank)?.githubHandle; + const currentGithubHandle = result.data.entries.find((e: any) => e.rank)?.githubHandle; - const isUserVisible = result.data.some( + const isUserVisible = result.data.entries.some( (entry: any) => entry.githubHandle === currentGithubHandle, ); return ( <> - {result.data.map((entry: any) => { + {result.data.entries.map((entry: any) => { const isMe = entry.githubHandle === currentGithubHandle; return ( diff --git a/src/app/actions/leaderboard.ts b/src/app/actions/leaderboard.ts index 29f1e7d..91a0fbf 100644 --- a/src/app/actions/leaderboard.ts +++ b/src/app/actions/leaderboard.ts @@ -31,11 +31,8 @@ export async function getLeaderboard( }> > { const cacheKey = `leaderboard:${scope}:${scopeId ?? 'all'}:${limit}`; - const cached = await cacheGet<{ - entries: LeaderboardEntry[]; - currentUserRank: LeaderboardEntry | null; - }>(cacheKey); - if (cached) return ok(cached); + const cached = await cacheGet(cacheKey); + let entries: LeaderboardEntry[] = cached ?? []; const db = tryGetDb(); if (!db) return err('not_configured', 'database not configured'); @@ -49,15 +46,16 @@ export async function getLeaderboard( level: number; }[] = []; - if (scope === 'global') { - rows = (await db.execute(sql` + if (!cached) { + if (scope === 'global') { + rows = (await db.execute(sql` select id, github_handle, display_name, avatar_url, xp, level from profiles order by xp desc limit ${limit} `)) as unknown as typeof rows; - } else if (scope === 'cohort' && scopeId) { - rows = (await db.execute(sql` + } else if (scope === 'cohort' && scopeId) { + rows = (await db.execute(sql` select p.id, p.github_handle, p.display_name, p.avatar_url, p.xp, p.level from profiles p join cohort_members cm on cm.user_id = p.id @@ -66,16 +64,16 @@ export async function getLeaderboard( order by p.xp desc limit ${limit} `)) as unknown as typeof rows; - } else if (scope === 'language' && scopeId) { - rows = (await db.execute(sql` + } else if (scope === 'language' && scopeId) { + rows = (await db.execute(sql` select id, github_handle, display_name, avatar_url, xp, level from profiles where primary_language = ${scopeId} order by xp desc limit ${limit} `)) as unknown as typeof rows; - } else if (scope === 'tag' && scopeId) { - rows = (await db.execute(sql` + } else if (scope === 'tag' && scopeId) { + rows = (await db.execute(sql` select p.id, p.github_handle, p.display_name, p.avatar_url, p.xp, p.level from profiles p join profile_tags pt on pt.user_id = p.id @@ -83,26 +81,28 @@ export async function getLeaderboard( order by p.xp desc limit ${limit} `)) as unknown as typeof rows; - } else { - return err('invalid_scope', `scope ${scope} requires a scopeId`); - } - - // drizzle execute returns { rows: [...] } in some versions; normalize - const list: typeof rows = Array.isArray(rows) - ? rows - : (rows as unknown as { rows: typeof rows }).rows; + } else { + return err('invalid_scope', `scope ${scope} requires a scopeId`); + } - const entries: LeaderboardEntry[] = list.map((r, i) => ({ - rank: i + 1, - userId: r.id, - githubHandle: r.github_handle, - displayName: r.display_name, - avatarUrl: r.avatar_url, - xp: r.xp, - level: r.level, - })); + const list: typeof rows = Array.isArray(rows) + ? rows + : (rows as unknown as { rows: typeof rows }).rows; + + entries = list.map((r, i) => ({ + rank: i + 1, + userId: r.id, + githubHandle: r.github_handle, + displayName: r.display_name, + avatarUrl: r.avatar_url, + xp: r.xp, + level: r.level, + })); + + await cacheSet(cacheKey, entries, TTL); + } - const sb = await getServerSupabase(); + const sb = getServerSupabase(); let currentUserRank: LeaderboardEntry | null = null; @@ -112,40 +112,88 @@ export async function getLeaderboard( } = await sb.auth.getUser(); if (user) { - const allRows = (await db.execute(sql` - select id, github_handle, display_name, avatar_url, xp, level + let rankQuery: ReturnType | null; + + if (scope === 'global') { + rankQuery = sql` + select count(*) + 1 as rank + from profiles + where xp > ( + select xp from profiles where id = ${user.id} + ) + `; + } else if (scope === 'language' && scopeId) { + rankQuery = sql` + select count(*) + 1 as rank from profiles - order by xp desc - `)) as unknown as typeof rows; - - const normalizedAllRows: typeof rows = Array.isArray(allRows) - ? allRows - : (allRows as unknown as { rows: typeof rows }).rows; - - const currentIndex = normalizedAllRows.findIndex((r) => r.id === user.id); - - if (currentIndex >= 0) { - const current = normalizedAllRows[currentIndex]!; - - currentUserRank = { - rank: currentIndex + 1, - userId: current.id, - githubHandle: current.github_handle, - displayName: current.display_name, - avatarUrl: current.avatar_url, - xp: current.xp, - level: current.level, - }; + where primary_language = ${scopeId} + and xp > ( + select xp +from profiles +where id = ${user.id} + and primary_language = ${scopeId} + ) + `; + } else { + rankQuery = null; + } + + if (rankQuery) { + const rankResult = (await db.execute(rankQuery)) as unknown as { + rank: number; + }[]; + + let userQuery: ReturnType | null; + + if (scope === 'global') { + userQuery = sql` + select id, github_handle, display_name, avatar_url, xp, level + from profiles + where id = ${user.id} + limit 1 + `; + } else if (scope === 'language' && scopeId) { + userQuery = sql` + select id, github_handle, display_name, avatar_url, xp, level + from profiles + where id = ${user.id} + and primary_language = ${scopeId} + limit 1 + `; + } else { + userQuery = null; + } + + if (userQuery) { + const userRows = (await db.execute(userQuery)) as unknown as { + id: string; + github_handle: string; + display_name: string | null; + avatar_url: string | null; + xp: number; + level: number; + }[]; + + const current = userRows[0]; + + if (current && rankResult[0]) { + currentUserRank = { + rank: Number(rankResult[0].rank), + userId: current.id, + githubHandle: current.github_handle, + displayName: current.display_name, + avatarUrl: current.avatar_url, + xp: current.xp, + level: current.level, + }; + } + } } } } - const response = { + return ok({ entries, currentUserRank, - }; - - await cacheSet(cacheKey, response, TTL); - - return ok(response); + }); }