mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 09:11:12 -05:00
Add contributor leaderboard
Add types, hook, UI components, and integration for leaderboard showing top users with badges
This commit is contained in:
172
src/components/contributors/ContributorLeaderboard.tsx
Normal file
172
src/components/contributors/ContributorLeaderboard.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { useContributorLeaderboard } from '@/hooks/useContributorLeaderboard';
|
||||
import { LeaderboardEntry } from './LeaderboardEntry';
|
||||
import { TimePeriod } from '@/types/contributor';
|
||||
import { Trophy, TrendingUp, Users, AlertCircle } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
export function ContributorLeaderboard() {
|
||||
const [timePeriod, setTimePeriod] = useState<TimePeriod>('all_time');
|
||||
const [limit, setLimit] = useState(50);
|
||||
|
||||
const { data, isLoading, error } = useContributorLeaderboard(limit, timePeriod);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Failed to load contributor leaderboard. Please try again later.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-2xl">
|
||||
<Trophy className="w-6 h-6 text-yellow-500" />
|
||||
Contributor Leaderboard
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Celebrating our amazing contributors who make ThrillWiki possible
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Badge variant="secondary" className="text-lg px-4 py-2">
|
||||
<Users className="w-4 h-4 mr-2" />
|
||||
{data?.total_contributors.toLocaleString() || 0} Contributors
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
{/* Time Period Filter */}
|
||||
<div className="flex-1">
|
||||
<label className="text-sm font-medium mb-2 block">Time Period</label>
|
||||
<Select value={timePeriod} onValueChange={(value) => setTimePeriod(value as TimePeriod)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all_time">
|
||||
<div className="flex items-center gap-2">
|
||||
<Trophy className="w-4 h-4" />
|
||||
All Time
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="month">
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
This Month
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="week">
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
This Week
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Limit Filter */}
|
||||
<div className="flex-1">
|
||||
<label className="text-sm font-medium mb-2 block">Show Top</label>
|
||||
<Select value={limit.toString()} onValueChange={(value) => setLimit(parseInt(value))}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="10">Top 10</SelectItem>
|
||||
<SelectItem value="25">Top 25</SelectItem>
|
||||
<SelectItem value="50">Top 50</SelectItem>
|
||||
<SelectItem value="100">Top 100</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Achievement Legend */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Achievement Levels</CardTitle>
|
||||
<CardDescription>
|
||||
Contribution points are calculated based on approved submissions: Parks (10 pts), Rides (8 pts), Companies (5 pts), Models (5 pts), Reviews (3 pts), Photos (2 pts), Edits (1 pt)
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<AchievementInfo level="Legend" points="5000+" color="bg-gradient-to-r from-purple-500 to-pink-500" />
|
||||
<AchievementInfo level="Platinum" points="1000+" color="bg-gradient-to-r from-slate-300 to-slate-400" />
|
||||
<AchievementInfo level="Gold" points="500+" color="bg-gradient-to-r from-yellow-400 to-yellow-500" />
|
||||
<AchievementInfo level="Silver" points="100+" color="bg-gradient-to-r from-gray-300 to-gray-400" />
|
||||
<AchievementInfo level="Bronze" points="10+" color="bg-gradient-to-r from-orange-400 to-orange-500" />
|
||||
<AchievementInfo level="Newcomer" points="0-9" color="bg-muted" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Leaderboard */}
|
||||
{isLoading ? (
|
||||
<div className="space-y-4">
|
||||
{[...Array(10)].map((_, i) => (
|
||||
<Card key={i} className="p-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<Skeleton className="w-[60px] h-[60px] rounded-lg" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-6 w-1/3" />
|
||||
<Skeleton className="h-4 w-1/4" />
|
||||
<Skeleton className="h-20 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : data?.contributors && data.contributors.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{data.contributors.map((contributor) => (
|
||||
<LeaderboardEntry
|
||||
key={contributor.user_id}
|
||||
contributor={contributor}
|
||||
showPeriodStats={timePeriod !== 'all_time'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="py-12 text-center">
|
||||
<Trophy className="w-12 h-12 mx-auto mb-4 text-muted-foreground" />
|
||||
<h3 className="text-lg font-semibold mb-2">No Contributors Yet</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Be the first to contribute to ThrillWiki!
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AchievementInfo({ level, points, color }: { level: string; points: string; color: string }) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className={`${color} rounded-lg p-3 mb-2`}>
|
||||
<Trophy className="w-6 h-6 mx-auto" />
|
||||
</div>
|
||||
<div className="text-sm font-semibold">{level}</div>
|
||||
<div className="text-xs text-muted-foreground">{points} pts</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user