diff --git a/src/app/api/airdrop/leaderboard/route.ts b/src/app/api/airdrop/leaderboard/route.ts index 298493a..66d7412 100644 --- a/src/app/api/airdrop/leaderboard/route.ts +++ b/src/app/api/airdrop/leaderboard/route.ts @@ -13,6 +13,8 @@ export async function GET(req: NextRequest) { } const userAddress = req.nextUrl.searchParams.get("address")?.toLowerCase(); + const page = Math.max(1, parseInt(req.nextUrl.searchParams.get("page") ?? "1", 10) || 1); + const limit = Math.min(50, Math.max(1, parseInt(req.nextUrl.searchParams.get("limit") ?? "20", 10) || 20)); // Aggregate points per address const { data: allPoints } = await supabase @@ -20,7 +22,7 @@ export async function GET(req: NextRequest) { .select("address, points"); if (!allPoints || allPoints.length === 0) { - return NextResponse.json({ entries: [], userRank: null, totalParticipants: 0 }); + return NextResponse.json({ entries: [], userRank: null, totalParticipants: 0, page: 1, totalPages: 0, limit }); } // Sum points by address @@ -36,34 +38,40 @@ export async function GET(req: NextRequest) { const sorted = [...pointsByAddress.entries()] .sort((a, b) => b[1] - a[1]); - // Look up usernames for top 50 - const top50Addresses = sorted.slice(0, 50).map(([addr]) => addr); + // Paginate + const totalParticipants = pointsByAddress.size; + const totalPages = Math.ceil(totalParticipants / limit); + const start = (page - 1) * limit; + const pageSlice = sorted.slice(start, start + limit); + + // Look up usernames for current page + const pageAddresses = pageSlice.map(([addr]) => addr); const { data: users } = await supabase .from("pl_referral_codes") .select("address, code, is_farcaster_username") - .in("address", top50Addresses); + .in("address", pageAddresses); const usernameMap = new Map( (users ?? []).map((u) => [u.address.toLowerCase(), u.is_farcaster_username ? u.code : null]), ); - const entries = sorted.slice(0, 50).map(([addr, pts], i) => ({ - rank: i + 1, + const entries = pageSlice.map(([addr, pts], i) => ({ + rank: start + i + 1, address: addr, username: usernameMap.get(addr) ?? null, totalPoints: Math.round(pts * 100) / 100, sharePercent: globalTotal > 0 ? Math.round((pts / globalTotal) * 10000) / 100 : 0, })); - // Find user's rank if requested and not in top 50 + // Find user's rank if requested let userRank: number | null = null; if (userAddress) { const idx = sorted.findIndex(([addr]) => addr === userAddress); userRank = idx >= 0 ? idx + 1 : null; } - return NextResponse.json({ entries, userRank, totalParticipants: pointsByAddress.size }, { + return NextResponse.json({ entries, userRank, totalParticipants, page, totalPages, limit }, { headers: { "Cache-Control": "public, s-maxage=30, stale-while-revalidate=15" }, }); } diff --git a/src/components/airdrop/Leaderboard.tsx b/src/components/airdrop/Leaderboard.tsx index 902e5b7..5d40303 100644 --- a/src/components/airdrop/Leaderboard.tsx +++ b/src/components/airdrop/Leaderboard.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState } from "react"; import { useAccount } from "wagmi"; import { useQuery } from "@tanstack/react-query"; @@ -15,6 +16,9 @@ interface LeaderboardData { entries: LeaderboardEntry[]; userRank: number | null; totalParticipants: number; + page: number; + totalPages: number; + limit: number; } function truncateAddress(addr: string) { @@ -23,12 +27,14 @@ function truncateAddress(addr: string) { export function Leaderboard() { const { address, isConnected } = useAccount(); + const [page, setPage] = useState(1); const { data, isLoading } = useQuery({ - queryKey: ["airdrop-leaderboard", address], + queryKey: ["airdrop-leaderboard", address, page], queryFn: async () => { - const params = address ? `?address=${address.toLowerCase()}` : ""; - const res = await fetch(`/api/airdrop/leaderboard${params}`); + const params = new URLSearchParams({ page: String(page), limit: "20" }); + if (address) params.set("address", address.toLowerCase()); + const res = await fetch(`/api/airdrop/leaderboard?${params}`); if (!res.ok) throw new Error("Failed to fetch leaderboard"); return res.json(); }, @@ -44,7 +50,7 @@ export function Leaderboard() { ); } - if (data.entries.length === 0) { + if (data.entries.length === 0 && data.totalParticipants === 0) { return (

Leaderboard

@@ -54,7 +60,7 @@ export function Leaderboard() { } const userAddr = address?.toLowerCase(); - const inTop50 = userAddr && data.entries.some((e) => e.address.toLowerCase() === userAddr); + const onCurrentPage = userAddr && data.entries.some((e) => e.address.toLowerCase() === userAddr); return (
@@ -99,8 +105,31 @@ export function Leaderboard() {
- {/* User's rank if outside top 50 */} - {isConnected && !inTop50 && data.userRank && ( + {/* Pagination */} + {data.totalPages > 1 && ( +
+ + + {data.page}/{data.totalPages} + + +
+ )} + + {/* User's rank if outside current page */} + {isConnected && !onCurrentPage && data.userRank && (
Your rank: #{data.userRank}