The Top Students Leaderboard feature displays top-performing students based on course completion rates, quiz scores, or combined metrics. It includes pagination, user highlighting, and responsive design.
- ✅ Dynamic Data Fetching - API endpoint with filtering and sorting
- ✅ Multiple Metrics - Course completion, quiz scores, or combined scoring
- ✅ User Avatar & Name - Beautiful display with fallback initials
- ✅ Score Display - Clear percentage-based scoring
- ✅ Pagination Support - Navigate through large datasets
- ✅ User Highlighting - Highlights logged-in user's position
- ✅ Responsive Design - Mobile-friendly and adaptive layouts
- ✅ Real-time Updates - Optional auto-refresh capability
- ✅ Trend Indicators - Shows ranking changes (up/down/stable/new)
src/
├── types/
│ └── leaderboard.ts # TypeScript type definitions
├── app/
│ ├── api/
│ │ └── leaderboard/
│ │ └── students/
│ │ ├── route.ts # API endpoint
│ │ └── __tests__/
│ │ └── route.test.ts # API tests
│ └── leaderboard-demo/
│ └── page.tsx # Demo page
├── components/
│ └── leaderboard/
│ ├── TopStudentsLeaderboard.tsx # Main component
│ └── __tests__/
│ └── leaderboard.test.ts # Component tests
import { TopStudentsLeaderboard } from '@/components/leaderboard/TopStudentsLeaderboard';
export default function MyPage() {
return (
<TopStudentsLeaderboard
metricType="completion"
limit={20}
highlightUserId={currentUser.id}
showPagination={true}
/>
);
}<TopStudentsLeaderboard
metricType="combined" // 'completion' | 'quiz_score' | 'combined'
courseId="blockchain-101" // Filter by specific course
limit={25} // Items per page
showPagination={true} // Enable pagination controls
highlightUserId="student-123" // Highlight current user
showTrends={true} // Show ranking trend indicators
enableRealTime={false} // Enable auto-refresh
className="shadow-xl" // Custom CSS classes
onStudentClick={(student) => { // Click handler
console.log('Selected:', student);
}}
/>Fetches leaderboard data with optional filtering.
| Parameter | Type | Default | Description |
|---|---|---|---|
metricType |
string | 'completion' |
Metric to rank by: completion, quiz_score, or combined |
courseId |
string | - | Filter by specific course ID |
limit |
number | 20 |
Number of entries per page |
offset |
number | 0 |
Pagination offset |
userId |
string | - | User ID to highlight (shows their position) |
interface LeaderboardResponse {
entries: LeaderboardEntry[];
totalCount: number;
currentUserEntry?: LeaderboardEntry;
hasMore: boolean;
filters: LeaderboardFilters;
lastUpdatedAt: string;
}{
"entries": [
{
"rank": 1,
"studentId": "student-1",
"studentName": "Alice Johnson",
"avatarUrl": "/avatars/alice.johnson.jpg",
"courseId": "blockchain-101",
"courseName": "Blockchain Fundamentals",
"cohort": "Cohort A",
"score": 98,
"metricType": "completion",
"coursesCompleted": 4,
"quizzesTaken": 12,
"averageQuizScore": 95,
"completionRate": 98
}
],
"totalCount": 150,
"currentUserEntry": {
"rank": 42,
"studentId": "student-42",
"studentName": "John Doe",
"score": 75
},
"hasMore": true,
"filters": {
"metricType": "completion",
"limit": 20,
"offset": 0
},
"lastUpdatedAt": "2025-03-27T10:30:00.000Z"
}| Prop | Type | Default | Description |
|---|---|---|---|
metricType |
LeaderboardMetricType |
'completion' |
Ranking metric to use |
courseId |
string |
- | Filter by course |
limit |
number |
20 |
Entries per page |
showPagination |
boolean |
true |
Show pagination controls |
highlightUserId |
string |
- | User ID to highlight |
showTrends |
boolean |
false |
Show trend indicators |
enableRealTime |
boolean |
false |
Enable auto-refresh |
className |
string |
- | Additional CSS classes |
onStudentClick |
(entry) => void |
- | Callback when student is clicked |
score = currentProgress (0-100)score = quizAverageScore (0-100)score = (currentProgress × 0.6) + (quizAverageScore × 0.4)All requirements have been met:
- ✅ Fetches data dynamically - Uses fetch API with configurable parameters
- ✅ User avatar, name, score - Displays complete student profile
- ✅ Supports pagination or scrolling - Implements traditional pagination
- ✅ Highlights logged-in user - Special styling for current user
- ✅ Responsive & mobile-friendly - Adaptive design with Tailwind breakpoints
pnpm test src/components/leaderboard/__tests__/leaderboard.test.ts
pnpm test src/app/api/leaderboard/students/__tests__/route.test.tsTests cover:
- Score calculation logic
- Pagination functionality
- User highlighting
- Trend detection
- API filtering and sorting
- Edge cases and error handling
The component uses Tailwind CSS and Shadcn UI components. Customize by:
- Override with className prop
- Modify theme colors in tailwind.config.cjs
- Replace Shadcn components with custom variants
<TopStudentsLeaderboard
className="dark bg-gradient-to-r from-gray-900 to-gray-800 text-white"
/>Add to your dashboard tabs:
<TabsContent value="leaderboard">
<TopStudentsLeaderboard
metricType="combined"
highlightUserId={session.user.id}
/>
</TabsContent><Link href="/leaderboard-demo">
<Button variant="ghost">
<Trophy className="h-4 w-4 mr-2" />
Leaderboard
</Button>
</Link>Potential improvements:
- WebSocket Integration - Real-time ranking updates
- Historical Trends - Graph showing rank over time
- Social Sharing - Share achievements on social media
- Achievement Badges - Display medals and awards
- Filter Sidebar - Advanced filtering options
- Export Functionality - Download as PDF/CSV
- Gamification - Streaks, levels, and rewards
- Virtual scrolling recommended for >100 items
- Implement caching (SWR/React Query)
- Debounce filter changes
- Ensure FERPA/GDPR compliance
- Allow users to opt-out
- Anonymize data if needed
Current implementation maintains original order for equal scores. Consider adding:
- Secondary sort by quiz average
- Timestamp-based tie-breaking
- Alphabetical ordering
<div className="grid md:grid-cols-2 gap-6">
<TopStudentsLeaderboard
metricType="completion"
title="By Completion"
/>
<TopStudentsLeaderboard
metricType="quiz_score"
title="By Quiz Scores"
/>
</div>const [filters, setFilters] = useState({
metricType: 'completion',
courseId: 'all',
});
<Select
value={filters.metricType}
onValueChange={(v) => setFilters({ ...filters, metricType: v })}
>
<SelectItem value="completion">Completion</SelectItem>
<SelectItem value="quiz_score">Quiz Scores</SelectItem>
<SelectItem value="combined">Combined</SelectItem>
</Select>Solution: Check that avatar paths exist in /public/avatars/ or fallback will be used.
Solution: Ensure showPagination={true} and check limit/offset calculations.
Solution: Verify highlightUserId matches a valid studentId in the dataset.
For issues or questions:
- Check this documentation
- Review test files for usage examples
- Inspect browser console for errors
- Verify API response format
Built with ❤️ using Next.js, TypeScript, and Shadcn UI