'use client';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter, useParams } from 'next/navigation';
import SponsorInsightsCard, { useSponsorMode, MetricBuilders, SlotTemplates } from '@/components/sponsors/SponsorInsightsCard';
import {
User,
Trophy,
Star,
Calendar,
Users,
Flag,
Award,
TrendingUp,
UserPlus,
ExternalLink,
Target,
Zap,
Clock,
Medal,
Crown,
ChevronRight,
Globe,
Twitter,
Youtube,
Twitch,
MessageCircle,
ArrowLeft,
BarChart3,
History,
Shield,
Percent,
Activity,
Megaphone,
DollarSign,
} from 'lucide-react';
import {
getGetProfileOverviewUseCase,
getGetDriverTeamUseCase,
getImageService,
getGetAllTeamsUseCase,
getGetTeamMembersUseCase,
} from '@/lib/di-container';
import { AllTeamsPresenter } from '@/lib/presenters/AllTeamsPresenter';
import { TeamMembersPresenter } from '@/lib/presenters/TeamMembersPresenter';
import { Driver, EntityMappers, type Team } from '@gridpilot/racing';
import type { DriverDTO } from '@gridpilot/racing';
import type { ProfileOverviewViewModel } from '@gridpilot/racing/application/presenters/IProfileOverviewPresenter';
import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import Breadcrumbs from '@/components/layout/Breadcrumbs';
import type { GetDriverTeamQueryResultDTO } from '@gridpilot/racing/application/dto/TeamCommandAndQueryDTO';
import type { TeamMemberViewModel } from '@gridpilot/racing/application/presenters/ITeamMembersPresenter';
// ============================================================================
// TYPES
// ============================================================================
type ProfileTab = 'overview' | 'stats';
interface SocialHandle {
platform: 'twitter' | 'youtube' | 'twitch' | 'discord';
handle: string;
url: string;
}
interface Achievement {
id: string;
title: string;
description: string;
icon: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap';
rarity: 'common' | 'rare' | 'epic' | 'legendary';
earnedAt: Date;
}
interface DriverExtendedProfile {
socialHandles: SocialHandle[];
achievements: Achievement[];
racingStyle: string;
favoriteTrack: string;
favoriteCar: string;
timezone: string;
availableHours: string;
lookingForTeam: boolean;
openToRequests: boolean;
}
interface TeamMembershipInfo {
team: Team;
role: string;
joinedAt: Date;
}
// ============================================================================
// DEMO DATA
// ============================================================================
function getDemoExtendedProfile(driverId: string): DriverExtendedProfile {
const hash = driverId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
const socialOptions: SocialHandle[][] = [
[
{ platform: 'twitter', handle: '@speedracer', url: 'https://twitter.com/speedracer' },
{ platform: 'youtube', handle: 'SpeedRacer Racing', url: 'https://youtube.com/@speedracer' },
{ platform: 'twitch', handle: 'speedracer_live', url: 'https://twitch.tv/speedracer_live' },
],
[
{ platform: 'twitter', handle: '@racingpro', url: 'https://twitter.com/racingpro' },
{ platform: 'discord', handle: 'RacingPro#1234', url: '#' },
],
[
{ platform: 'twitch', handle: 'simracer_elite', url: 'https://twitch.tv/simracer_elite' },
{ platform: 'youtube', handle: 'SimRacer Elite', url: 'https://youtube.com/@simracerelite' },
],
];
const achievementSets: Achievement[][] = [
[
{ id: '1', title: 'First Victory', description: 'Win your first race', icon: 'trophy', rarity: 'common', earnedAt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000) },
{ id: '2', title: 'Clean Racer', description: '10 races without incidents', icon: 'star', rarity: 'rare', earnedAt: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000) },
{ id: '3', title: 'Podium Streak', description: '5 consecutive podium finishes', icon: 'medal', rarity: 'epic', earnedAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) },
],
[
{ id: '1', title: 'Rookie No More', description: 'Complete 25 races', icon: 'target', rarity: 'common', earnedAt: new Date(Date.now() - 120 * 24 * 60 * 60 * 1000) },
{ id: '2', title: 'Consistent Performer', description: 'Maintain 80%+ consistency rating', icon: 'zap', rarity: 'rare', earnedAt: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000) },
],
[
{ id: '1', title: 'Welcome Racer', description: 'Join GridPilot', icon: 'star', rarity: 'common', earnedAt: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000) },
{ id: '2', title: 'Team Player', description: 'Join a racing team', icon: 'medal', rarity: 'rare', earnedAt: new Date(Date.now() - 80 * 24 * 60 * 60 * 1000) },
],
];
const tracks = ['Spa-Francorchamps', 'Nürburgring Nordschleife', 'Suzuka', 'Monza', 'Interlagos', 'Silverstone'];
const cars = ['Porsche 911 GT3 R', 'Ferrari 488 GT3', 'Mercedes-AMG GT3', 'BMW M4 GT3', 'Audi R8 LMS'];
const styles = ['Aggressive Overtaker', 'Consistent Pacer', 'Strategic Calculator', 'Late Braker', 'Smooth Operator'];
const timezones = ['EST (UTC-5)', 'CET (UTC+1)', 'PST (UTC-8)', 'GMT (UTC+0)', 'JST (UTC+9)'];
const hours = ['Evenings (18:00-23:00)', 'Weekends only', 'Late nights (22:00-02:00)', 'Flexible schedule'];
const socialHandles = socialOptions[hash % socialOptions.length] ?? [];
const achievements = achievementSets[hash % achievementSets.length] ?? [];
const racingStyle = styles[hash % styles.length] ?? 'Consistent Pacer';
const favoriteTrack = tracks[hash % tracks.length] ?? 'Unknown Track';
const favoriteCar = cars[hash % cars.length] ?? 'Unknown Car';
const timezone = timezones[hash % timezones.length] ?? 'UTC';
const availableHours = hours[hash % hours.length] ?? 'Flexible schedule';
return {
socialHandles,
achievements,
racingStyle,
favoriteTrack,
favoriteCar,
timezone,
availableHours,
lookingForTeam: hash % 3 === 0,
openToRequests: hash % 2 === 0,
};
}
// ============================================================================
// HELPERS
// ============================================================================
function getCountryFlag(countryCode: string): string {
const code = countryCode.toUpperCase();
if (code.length === 2) {
const codePoints = [...code].map(char => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
return '🏁';
}
function getRarityColor(rarity: Achievement['rarity']) {
switch (rarity) {
case 'common':
return 'text-gray-400 bg-gray-400/10 border-gray-400/30';
case 'rare':
return 'text-primary-blue bg-primary-blue/10 border-primary-blue/30';
case 'epic':
return 'text-purple-400 bg-purple-400/10 border-purple-400/30';
case 'legendary':
return 'text-yellow-400 bg-yellow-400/10 border-yellow-400/30';
}
}
function getAchievementIcon(icon: Achievement['icon']) {
switch (icon) {
case 'trophy':
return Trophy;
case 'medal':
return Medal;
case 'star':
return Star;
case 'crown':
return Crown;
case 'target':
return Target;
case 'zap':
return Zap;
}
}
function getSocialIcon(platform: SocialHandle['platform']) {
switch (platform) {
case 'twitter':
return Twitter;
case 'youtube':
return Youtube;
case 'twitch':
return Twitch;
case 'discord':
return MessageCircle;
}
}
function getSocialColor(platform: SocialHandle['platform']) {
switch (platform) {
case 'twitter':
return 'hover:text-sky-400 hover:bg-sky-400/10';
case 'youtube':
return 'hover:text-red-500 hover:bg-red-500/10';
case 'twitch':
return 'hover:text-purple-400 hover:bg-purple-400/10';
case 'discord':
return 'hover:text-indigo-400 hover:bg-indigo-400/10';
}
}
// ============================================================================
// STAT DIAGRAM COMPONENTS
// ============================================================================
interface CircularProgressProps {
value: number;
max: number;
label: string;
color: string;
size?: number;
}
function CircularProgress({ value, max, label, color, size = 80 }: CircularProgressProps) {
const percentage = Math.min((value / max) * 100, 100);
const strokeWidth = 6;
const radius = (size - strokeWidth) / 2;
const circumference = radius * 2 * Math.PI;
const strokeDashoffset = circumference - (percentage / 100) * circumference;
return (
);
}
interface BarChartProps {
data: { label: string; value: number; color: string }[];
maxValue: number;
}
function HorizontalBarChart({ data, maxValue }: BarChartProps) {
return (
{data.map((item) => (
{item.label}
{item.value}
))}
);
}
// ============================================================================
// MAIN PAGE
// ============================================================================
interface DriverProfileStatsViewModel {
rating: number;
wins: number;
podiums: number;
dnfs: number;
totalRaces: number;
avgFinish: number;
bestFinish: number;
worstFinish: number;
consistency: number;
percentile: number;
}
interface DriverProfileFriendViewModel {
id: string;
name: string;
country: string;
}
interface DriverProfileExtendedViewModel extends DriverExtendedProfile {}
interface DriverProfileViewModel {
currentDriver?: {
id: string;
name: string;
iracingId?: string | null;
country: string;
bio?: string | null;
joinedAt: string | Date;
globalRank?: number;
totalDrivers?: number;
};
stats?: DriverProfileStatsViewModel;
extendedProfile?: DriverProfileExtendedViewModel;
socialSummary?: {
friends: DriverProfileFriendViewModel[];
};
}
export default function DriverDetailPage() {
const router = useRouter();
const params = useParams();
const driverId = params.id as string;
const [driver, setDriver] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [activeTab, setActiveTab] = useState('overview');
const [teamData, setTeamData] = useState(null);
const [allTeamMemberships, setAllTeamMemberships] = useState([]);
const [friends, setFriends] = useState([]);
const [friendRequestSent, setFriendRequestSent] = useState(false);
const [profileData, setProfileData] = useState(null);
const search =
typeof window !== 'undefined'
? new URLSearchParams(window.location.search)
: undefined;
const from = search?.get('from') ?? undefined;
const leagueId = search?.get('leagueId') ?? undefined;
const raceId = search?.get('raceId') ?? undefined;
let backLink: string | null = null;
if (from === 'league-standings' && leagueId) {
backLink = `/leagues/${leagueId}/standings`;
} else if (from === 'league' && leagueId) {
backLink = `/leagues/${leagueId}`;
} else if (from === 'league-members' && leagueId) {
backLink = `/leagues/${leagueId}`;
} else if (from === 'league-race' && raceId) {
backLink = `/races/${raceId}`;
} else {
backLink = null;
}
const isSponsorMode = useSponsorMode();
useEffect(() => {
loadDriver();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [driverId]);
const loadDriver = async () => {
try {
// Use GetProfileOverviewUseCase to load all profile data
const profileUseCase = getGetProfileOverviewUseCase();
const profileViewModel = await profileUseCase.execute({ driverId });
if (!profileViewModel || !profileViewModel.currentDriver) {
setError('Driver not found');
setLoading(false);
return;
}
// Set driver from ViewModel
const driverData: DriverDTO = {
id: profileViewModel.currentDriver.id,
name: profileViewModel.currentDriver.name,
iracingId: profileViewModel.currentDriver.iracingId ?? '',
country: profileViewModel.currentDriver.country,
bio: profileViewModel.currentDriver.bio || '',
joinedAt: profileViewModel.currentDriver.joinedAt,
};
setDriver(driverData);
setProfileData(profileViewModel);
// Load ALL team memberships using caller-owned presenters
const allTeamsUseCase = getGetAllTeamsUseCase();
const allTeamsPresenter = new AllTeamsPresenter();
await allTeamsUseCase.execute(undefined as void, allTeamsPresenter);
const allTeamsViewModel = allTeamsPresenter.getViewModel();
const allTeams = allTeamsViewModel?.teams ?? [];
const membershipsUseCase = getGetTeamMembersUseCase();
const memberships: TeamMembershipInfo[] = [];
for (const team of allTeams) {
const teamMembersPresenter = new TeamMembersPresenter();
await membershipsUseCase.execute({ teamId: team.id }, teamMembersPresenter);
const membersResult = teamMembersPresenter.getViewModel();
const members = membersResult?.members ?? [];
const membership = members.find(
(member: TeamMemberViewModel) => member.driverId === driverId,
);
if (membership) {
memberships.push({
team: {
id: team.id,
name: team.name,
tag: team.tag,
description: team.description,
ownerId: '',
leagues: team.leagues,
createdAt: new Date(),
} as Team,
role: membership.role,
joinedAt: new Date(membership.joinedAt),
});
}
}
setAllTeamMemberships(memberships);
// Set friends from ViewModel
const friendsList = profileViewModel.socialSummary?.friends.map(f => {
return {
id: f.id,
name: f.name,
country: f.country,
} as Driver;
}) || [];
setFriends(friendsList);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load driver');
} finally {
setLoading(false);
}
};
const handleAddFriend = () => {
setFriendRequestSent(true);
};
if (loading) {
return (
Loading driver profile...
);
}
if (error || !driver) {
return (
{error || 'Driver not found'}
);
}
const demoExtended = getDemoExtendedProfile(driver.id);
const extendedProfile: DriverExtendedProfile = {
socialHandles: profileData?.extendedProfile?.socialHandles ?? demoExtended.socialHandles,
achievements:
profileData?.extendedProfile?.achievements
? profileData.extendedProfile.achievements.map((achievement) => ({
id: achievement.id,
title: achievement.title,
description: achievement.description,
icon: achievement.icon,
rarity: achievement.rarity,
earnedAt: new Date(achievement.earnedAt),
}))
: demoExtended.achievements,
racingStyle: profileData?.extendedProfile?.racingStyle ?? demoExtended.racingStyle,
favoriteTrack: profileData?.extendedProfile?.favoriteTrack ?? demoExtended.favoriteTrack,
favoriteCar: profileData?.extendedProfile?.favoriteCar ?? demoExtended.favoriteCar,
timezone: profileData?.extendedProfile?.timezone ?? demoExtended.timezone,
availableHours: profileData?.extendedProfile?.availableHours ?? demoExtended.availableHours,
lookingForTeam:
profileData?.extendedProfile?.lookingForTeam ?? demoExtended.lookingForTeam,
openToRequests:
profileData?.extendedProfile?.openToRequests ?? demoExtended.openToRequests,
};
const stats = profileData?.stats || null;
const globalRank = profileData?.currentDriver?.globalRank || 1;
// Build sponsor insights for driver
const driverMetrics = [
MetricBuilders.rating(stats?.rating ?? 0, 'Driver Rating'),
MetricBuilders.views((friends.length * 8) + 50),
MetricBuilders.engagement(stats?.consistency ?? 75),
MetricBuilders.reach((friends.length * 12) + 100),
];
return (
{/* Back Navigation */}
{backLink ? (
Back to league
) : (
)}
{/* Breadcrumb */}
{/* Sponsor Insights Card - Consistent placement at top */}
{isSponsorMode && driver && (
)}
{/* Hero Header Section */}
{/* Background Pattern */}
{/* Avatar */}
{/* Driver Info */}
{driver.name}
{getCountryFlag(driver.country)}
{teamData?.team.tag && (
[{teamData.team.tag}]
)}
{/* Rating and Rank */}
{stats && (
<>
{stats.rating}
Rating
#{globalRank}
Global
>
)}
{teamData && (
{teamData.team.name}
)}
{/* Meta info */}
iRacing: {driver.iracingId}
Joined{' '}
{new Date(driver.joinedAt).toLocaleDateString('en-US', {
month: 'short',
year: 'numeric',
})}
{extendedProfile.timezone}
{/* Action Buttons */}
{/* Social Handles */}
{extendedProfile.socialHandles.length > 0 && (
Connect:
{extendedProfile.socialHandles.map((social: SocialHandle) => {
const Icon = getSocialIcon(social.platform);
return (
{social.handle}
);
})}
)}
{/* Bio Section */}
{driver.bio && (
About
{driver.bio}
)}
{/* Team Memberships */}
{allTeamMemberships.length > 0 && (
Team Memberships
({allTeamMemberships.length})
{allTeamMemberships.map((membership) => (
{membership.team.name}
{membership.role}
Since {membership.joinedAt.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
))}
)}
{/* Performance Overview with Diagrams */}
{stats && (
Performance Overview
{/* Circular Progress Charts */}
{/* Bar chart and key metrics */}
Results Breakdown
Best Finish
P{stats.bestFinish}
Avg Finish
P{(stats.avgFinish ?? 0).toFixed(1)}
)}
{/* Tab Navigation */}
{/* Tab Content */}
{activeTab === 'overview' && (
<>
{/* Stats and Profile Grid */}
{/* Career Stats */}
Career Statistics
{stats ? (
{stats.consistency}%
Consistency
) : (
No race statistics available yet.
)}
{/* Racing Preferences */}
Racing Profile
Racing Style
{extendedProfile.racingStyle}
Favorite Track
{extendedProfile.favoriteTrack}
Favorite Car
{extendedProfile.favoriteCar}
Available
{extendedProfile.availableHours}
{/* Status badges */}
{extendedProfile.lookingForTeam && (
Looking for Team
)}
{extendedProfile.openToRequests && (
Open to Friend Requests
)}
{/* Achievements */}
Achievements
{extendedProfile.achievements.length} earned
{extendedProfile.achievements.map((achievement: Achievement) => {
const Icon = getAchievementIcon(achievement.icon);
const rarityClasses = getRarityColor(achievement.rarity);
return (
{achievement.title}
{achievement.description}
{achievement.earnedAt.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
);
})}
{/* Friends Preview */}
{friends.length > 0 && (
Friends
({friends.length})
{friends.slice(0, 8).map((friend) => (
{friend.name}
{getCountryFlag(friend.country)}
))}
{friends.length > 8 && (
+{friends.length - 8} more
)}
)}
>
)}
{activeTab === 'stats' && stats && (
{/* Detailed Performance Metrics */}
Detailed Performance Metrics
{/* Performance Bars */}
Results Breakdown
{/* Key Metrics */}
{((stats.wins / stats.totalRaces) * 100).toFixed(1)}%
{((stats.podiums / stats.totalRaces) * 100).toFixed(1)}%
Finish Rate
{(((stats.totalRaces - stats.dnfs) / stats.totalRaces) * 100).toFixed(1)}%
{/* Position Statistics */}
Position Statistics
P{stats.bestFinish}
Best Finish
P{(stats.avgFinish ?? 0).toFixed(1)}
Avg Finish
P{stats.worstFinish}
Worst Finish
{/* Global Rankings */}
Global Rankings
#{globalRank}
Global Rank
Top {stats.percentile}%
Percentile
)}
{activeTab === 'stats' && !stats && (
No statistics available yet
This driver hasn't completed any races yet
)}
);
}