diff --git a/src/app/airdrop/page.tsx b/src/app/airdrop/page.tsx index 54649432..3d4ea7d5 100644 --- a/src/app/airdrop/page.tsx +++ b/src/app/airdrop/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { CampaignHero } from "../../components/airdrop/CampaignHero"; import { UserPoints } from "../../components/airdrop/UserPoints"; +import { Leaderboard } from "../../components/airdrop/Leaderboard"; import { MilestoneTrack } from "../../components/airdrop/MilestoneTrack"; export const metadata: Metadata = { @@ -13,6 +14,7 @@ export default function AirdropPage() {
+
); diff --git a/src/components/airdrop/Leaderboard.tsx b/src/components/airdrop/Leaderboard.tsx new file mode 100644 index 00000000..03d04d89 --- /dev/null +++ b/src/components/airdrop/Leaderboard.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useAccount } from "wagmi"; +import { useQuery } from "@tanstack/react-query"; + +interface LeaderboardEntry { + rank: number; + address: string; + username: string | null; + totalPoints: number; + sharePercent: number; +} + +interface LeaderboardData { + entries: LeaderboardEntry[]; + userRank: number | null; +} + +function truncateAddress(addr: string) { + return `${addr.slice(0, 6)}...${addr.slice(-4)}`; +} + +export function Leaderboard() { + const { address, isConnected } = useAccount(); + + const { data, isLoading } = useQuery({ + queryKey: ["airdrop-leaderboard", address], + queryFn: async () => { + const params = address ? `?address=${address.toLowerCase()}` : ""; + const res = await fetch(`/api/airdrop/leaderboard${params}`); + if (!res.ok) throw new Error("Failed to fetch leaderboard"); + return res.json(); + }, + staleTime: 60_000, + refetchInterval: 60_000, + }); + + if (isLoading || !data) { + return ( +
+
Loading leaderboard...
+
+ ); + } + + if (data.entries.length === 0) { + return ( +
+

Leaderboard

+
No participants yet.
+
+ ); + } + + const userAddr = address?.toLowerCase(); + const inTop50 = userAddr && data.entries.some((e) => e.address.toLowerCase() === userAddr); + + return ( +
+

Leaderboard

+ +
+ + + + + + + + + + + {data.entries.map((entry) => { + const isUser = isConnected && userAddr === entry.address.toLowerCase(); + return ( + + + + + + + ); + })} + +
#UserPLShare
{entry.rank} + {entry.username ?? truncateAddress(entry.address)} + {isUser && (you)} + + {entry.totalPoints.toLocaleString()} + + {entry.sharePercent}% +
+
+ + {/* User's rank if outside top 50 */} + {isConnected && !inTop50 && data.userRank && ( +
+ Your rank: + #{data.userRank} +
+ )} +
+ ); +}