Problem
The leaderboard API hardcodes `sorted.slice(0, 50)` and the component shows all entries at once with no pagination. As participants grow, this won't scale.
Solution
API: Add `page` and `limit` query params
File: `src/app/api/airdrop/leaderboard/route.ts`
```
GET /api/airdrop/leaderboard?page=1&limit=20&address=0x...
```
- `page`: 1-indexed page number (default: 1)
- `limit`: entries per page (default: 20, max: 50)
- Keep `address` param for user rank lookup
Response adds pagination metadata:
```json
{
"entries": [...],
"userRank": 67,
"totalParticipants": 150,
"page": 1,
"totalPages": 8,
"limit": 20
}
```
Note: the API currently fetches ALL `pl_points` rows and aggregates in-memory. Pagination only affects the returned slice — the full sort still happens server-side. This is fine for now but may need a materialized view or DB-level aggregation at scale.
Component: Prev/Next pagination
File: `src/components/airdrop/Leaderboard.tsx`
```
┌────────────────────────────────┐
│ Leaderboard │
│ 150 participants │
│ │
│ # User PL Share │
│ 1 alice.eth 1,247 12.5% │
│ 2 0xAB...CD 980 9.8% │
│ ... │
│ 20 0xEF...12 120 1.2% │
│ │
│ ← Prev 1/8 Next → │
│ │
│ Your rank: #67 │
└────────────────────────────────┘
```
- Default 20 entries per page
- Prev/Next buttons with current page / total pages
- User rank always shown at bottom (regardless of page)
- React Query key includes page number for caching
Acceptance Criteria
Problem
The leaderboard API hardcodes `sorted.slice(0, 50)` and the component shows all entries at once with no pagination. As participants grow, this won't scale.
Solution
API: Add `page` and `limit` query params
File: `src/app/api/airdrop/leaderboard/route.ts`
```
GET /api/airdrop/leaderboard?page=1&limit=20&address=0x...
```
Response adds pagination metadata:
```json
{
"entries": [...],
"userRank": 67,
"totalParticipants": 150,
"page": 1,
"totalPages": 8,
"limit": 20
}
```
Note: the API currently fetches ALL `pl_points` rows and aggregates in-memory. Pagination only affects the returned slice — the full sort still happens server-side. This is fine for now but may need a materialized view or DB-level aggregation at scale.
Component: Prev/Next pagination
File: `src/components/airdrop/Leaderboard.tsx`
```
┌────────────────────────────────┐
│ Leaderboard │
│ 150 participants │
│ │
│ # User PL Share │
│ 1 alice.eth 1,247 12.5% │
│ 2 0xAB...CD 980 9.8% │
│ ... │
│ 20 0xEF...12 120 1.2% │
│ │
│ ← Prev 1/8 Next → │
│ │
│ Your rank: #67 │
└────────────────────────────────┘
```
Acceptance Criteria