'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import {
Users,
Trophy,
Search,
Crown,
Star,
TrendingUp,
Shield,
Target,
Award,
ArrowLeft,
Medal,
Percent,
Hash,
Globe,
Languages,
} from 'lucide-react';
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import Heading from '@/components/ui/Heading';
import { getGetTeamsLeaderboardUseCase } from '@/lib/di-container';
import { TeamsLeaderboardPresenter } from '@/lib/presenters/TeamsLeaderboardPresenter';
import type {
TeamLeaderboardItemViewModel,
SkillLevel,
} from '@gridpilot/racing/application/presenters/ITeamsLeaderboardPresenter';
// ============================================================================
// TYPES
// ============================================================================
type SortBy = 'rating' | 'wins' | 'winRate' | 'races';
type TeamDisplayData = TeamLeaderboardItemViewModel;
const getSafeRating = (team: TeamDisplayData): number => {
const value = typeof team.rating === 'number' ? team.rating : 0;
return Number.isFinite(value) ? value : 0;
};
const getSafeTotalWins = (team: TeamDisplayData): number => {
const raw = team.totalWins;
const value = typeof raw === 'number' ? raw : 0;
return Number.isFinite(value) ? value : 0;
};
const getSafeTotalRaces = (team: TeamDisplayData): number => {
const raw = team.totalRaces;
const value = typeof raw === 'number' ? raw : 0;
return Number.isFinite(value) ? value : 0;
};
// ============================================================================
// SKILL LEVEL CONFIG
// ============================================================================
const SKILL_LEVELS: {
id: SkillLevel;
label: string;
icon: React.ElementType;
color: string;
bgColor: string;
borderColor: string;
}[] = [
{
id: 'pro',
label: 'Pro',
icon: Crown,
color: 'text-yellow-400',
bgColor: 'bg-yellow-400/10',
borderColor: 'border-yellow-400/30',
},
{
id: 'advanced',
label: 'Advanced',
icon: Star,
color: 'text-purple-400',
bgColor: 'bg-purple-400/10',
borderColor: 'border-purple-400/30',
},
{
id: 'intermediate',
label: 'Intermediate',
icon: TrendingUp,
color: 'text-primary-blue',
bgColor: 'bg-primary-blue/10',
borderColor: 'border-primary-blue/30',
},
{
id: 'beginner',
label: 'Beginner',
icon: Shield,
color: 'text-green-400',
bgColor: 'bg-green-400/10',
borderColor: 'border-green-400/30',
},
];
// ============================================================================
// SORT OPTIONS
// ============================================================================
const SORT_OPTIONS: { id: SortBy; label: string; icon: React.ElementType }[] = [
{ id: 'rating', label: 'Rating', icon: Star },
{ id: 'wins', label: 'Total Wins', icon: Trophy },
{ id: 'winRate', label: 'Win Rate', icon: Percent },
{ id: 'races', label: 'Races', icon: Hash },
];
// ============================================================================
// TOP THREE PODIUM COMPONENT
// ============================================================================
interface TopThreePodiumProps {
teams: TeamDisplayData[];
onTeamClick: (teamId: string) => void;
}
function TopThreePodium({ teams, onTeamClick }: TopThreePodiumProps) {
const top3 = teams.slice(0, 3) as [TeamDisplayData, TeamDisplayData, TeamDisplayData];
if (teams.length < 3) return null;
// Display order: 2nd, 1st, 3rd
const podiumOrder: [TeamDisplayData, TeamDisplayData, TeamDisplayData] = [
top3[1],
top3[0],
top3[2],
];
const podiumHeights = ['h-28', 'h-36', 'h-20'];
const podiumPositions = [2, 1, 3];
const getPositionColor = (position: number) => {
switch (position) {
case 1:
return 'text-yellow-400';
case 2:
return 'text-gray-300';
case 3:
return 'text-amber-600';
default:
return 'text-gray-500';
}
};
const getGradient = (position: number) => {
switch (position) {
case 1:
return 'from-yellow-400/30 via-yellow-500/20 to-yellow-600/10';
case 2:
return 'from-gray-300/30 via-gray-400/20 to-gray-500/10';
case 3:
return 'from-amber-500/30 via-amber-600/20 to-amber-700/10';
default:
return 'from-gray-600/30 to-gray-700/10';
}
};
const getBorderColor = (position: number) => {
switch (position) {
case 1:
return 'border-yellow-400/50';
case 2:
return 'border-gray-300/50';
case 3:
return 'border-amber-600/50';
default:
return 'border-charcoal-outline';
}
};
return (
Top 3 Teams
{podiumOrder.map((team, index) => {
const position = podiumPositions[index] ?? 0;
const levelConfig = SKILL_LEVELS.find((l) => l.id === team.performanceLevel);
const LevelIcon = levelConfig?.icon || Shield;
return (
onTeamClick(team.id)}
className="flex flex-col items-center group"
>
{/* Team card */}
{/* Crown for 1st place */}
{position === 1 && (
)}
{/* Team icon */}
{/* Team name */}
{team.name}
{/* Rating */}
{getSafeRating(team).toLocaleString()}
{/* Stats row */}
{getSafeTotalWins(team)}
{team.memberCount}
{/* Podium stand */}
{position}
);
})}
);
}
// ============================================================================
// MAIN PAGE COMPONENT
// ============================================================================
export default function TeamLeaderboardPage() {
const router = useRouter();
const [teams, setTeams] = useState([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [sortBy, setSortBy] = useState('rating');
const [filterLevel, setFilterLevel] = useState('all');
useEffect(() => {
const loadTeams = async () => {
try {
const useCase = getGetTeamsLeaderboardUseCase();
const presenter = new TeamsLeaderboardPresenter();
await useCase.execute(undefined as void, presenter);
const viewModel = presenter.getViewModel();
if (viewModel) {
setTeams(viewModel.teams);
}
} catch (error) {
console.error('Failed to load teams:', error);
} finally {
setLoading(false);
}
};
void loadTeams();
}, []);
const handleTeamClick = (teamId: string) => {
if (teamId.startsWith('demo-team-')) {
return;
}
router.push(`/teams/${teamId}`);
};
// Filter and sort teams
const filteredAndSortedTeams = teams
.filter((team) => {
// Search filter
if (searchQuery) {
const query = searchQuery.toLowerCase();
if (!team.name.toLowerCase().includes(query) && !(team.description ?? '').toLowerCase().includes(query)) {
return false;
}
}
// Level filter
if (filterLevel !== 'all' && team.performanceLevel !== filterLevel) {
return false;
}
// Must have rating for leaderboard
return team.rating !== null;
})
.sort((a, b) => {
switch (sortBy) {
case 'rating': {
const aRating = getSafeRating(a);
const bRating = getSafeRating(b);
return bRating - aRating;
}
case 'wins': {
const aWinsSort = getSafeTotalWins(a);
const bWinsSort = getSafeTotalWins(b);
return bWinsSort - aWinsSort;
}
case 'winRate': {
const aRaces = getSafeTotalRaces(a);
const bRaces = getSafeTotalRaces(b);
const aWins = getSafeTotalWins(a);
const bWins = getSafeTotalWins(b);
const aRate = aRaces > 0 ? aWins / aRaces : 0;
const bRate = bRaces > 0 ? bWins / bRaces : 0;
return bRate - aRate;
}
case 'races': {
const aRacesSort = getSafeTotalRaces(a);
const bRacesSort = getSafeTotalRaces(b);
return bRacesSort - aRacesSort;
}
default:
return 0;
}
});
const getMedalColor = (position: number) => {
switch (position) {
case 0:
return 'text-yellow-400';
case 1:
return 'text-gray-300';
case 2:
return 'text-amber-600';
default:
return 'text-gray-500';
}
};
const getMedalBg = (position: number) => {
switch (position) {
case 0:
return 'bg-gradient-to-br from-yellow-400/20 to-yellow-600/10 border-yellow-400/40';
case 1:
return 'bg-gradient-to-br from-gray-300/20 to-gray-400/10 border-gray-300/40';
case 2:
return 'bg-gradient-to-br from-amber-600/20 to-amber-700/10 border-amber-600/40';
default:
return 'bg-iron-gray/50 border-charcoal-outline';
}
};
if (loading) {
return (
);
}
return (
{/* Header */}
router.push('/teams')}
className="flex items-center gap-2 mb-6"
>
Back to Teams
Team Leaderboard
Rankings of all teams by performance metrics
{/* Filters and Search */}
{/* Search and Level Filter Row */}
setSearchQuery(e.target.value)}
className="pl-11"
/>
{/* Level Filter */}
setFilterLevel('all')}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-all ${
filterLevel === 'all'
? 'bg-purple-600 text-white'
: 'bg-iron-gray/50 text-gray-400 border border-charcoal-outline hover:text-white'
}`}
>
All Levels
{SKILL_LEVELS.map((level) => {
const LevelIcon = level.icon;
return (
setFilterLevel(level.id)}
className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-all ${
filterLevel === level.id
? `${level.bgColor} ${level.color} border ${level.borderColor}`
: 'bg-iron-gray/50 text-gray-400 border border-charcoal-outline hover:text-white'
}`}
>
{level.label}
);
})}
{/* Sort Options */}
Sort by:
{SORT_OPTIONS.map((option) => (
setSortBy(option.id)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all ${
sortBy === option.id
? 'bg-purple-600 text-white'
: 'text-gray-400 hover:text-white hover:bg-iron-gray'
}`}
>
{option.label}
))}
{/* Podium for Top 3 - only show when viewing by rating without filters */}
{sortBy === 'rating' && filterLevel === 'all' && !searchQuery && filteredAndSortedTeams.length >= 3 && (
)}
{/* Stats Summary */}
Total Teams
{filteredAndSortedTeams.length}
Pro Teams
{filteredAndSortedTeams.filter((t) => t.performanceLevel === 'pro').length}
Total Wins
{filteredAndSortedTeams.reduce(
(sum, t) => sum + getSafeTotalWins(t),
0,
)}
Total Races
{filteredAndSortedTeams.reduce(
(sum, t) => sum + getSafeTotalRaces(t),
0,
)}
{/* Leaderboard Table */}
{/* Table Header */}
Rank
Team
Members
Rating
Wins
Win Rate
{/* Table Body */}
{filteredAndSortedTeams.map((team, index) => {
const levelConfig = SKILL_LEVELS.find((l) => l.id === team.performanceLevel);
const LevelIcon = levelConfig?.icon || Shield;
const totalRaces = getSafeTotalRaces(team);
const totalWins = getSafeTotalWins(team);
const winRate =
totalRaces > 0 ? ((totalWins / totalRaces) * 100).toFixed(1) : '0.0';
return (
handleTeamClick(team.id)}
className="grid grid-cols-12 gap-4 px-4 py-4 w-full text-left hover:bg-iron-gray/30 transition-colors group"
>
{/* Position */}
{index < 3 ? (
) : (
index + 1
)}
{/* Team Info */}
{team.name}
{levelConfig?.label}
{team.region && (
{team.region}
)}
{team.languages && team.languages.length > 0 && (
{team.languages.slice(0, 2).join(', ')}
{team.languages.length > 2 && ` +${team.languages.length - 2}`}
)}
{team.isRecruiting && (
Recruiting
)}
{/* Members */}
{team.memberCount}
{/* Rating */}
{getSafeRating(team).toLocaleString()}
{/* Wins */}
{getSafeTotalWins(team)}
{/* Win Rate */}
{winRate}%
);
})}
{/* Empty State */}
{filteredAndSortedTeams.length === 0 && (
No teams found
Try adjusting your filters or search query
{
setSearchQuery('');
setFilterLevel('all');
}}
className="mt-4"
>
Clear Filters
)}
);
}