'use client'; import { useState, useEffect } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { User, Trophy, Star, Calendar, Users, Flag, Award, TrendingUp, Settings, UserPlus, ExternalLink, Target, Zap, Clock, Medal, Crown, ChevronRight, Edit3, Globe, Twitter, Youtube, Twitch, MessageCircle, BarChart3, History, Shield, Percent, Activity, } from 'lucide-react'; import { getDriverRepository, getDriverStats, getAllDriverRankings, getGetDriverTeamQuery, getSocialRepository, getImageService, getGetAllTeamsQuery, getGetTeamMembersQuery, } from '@/lib/di-container'; import { Driver, EntityMappers, type DriverDTO, type Team } from '@gridpilot/racing'; import CreateDriverForm from '@/components/drivers/CreateDriverForm'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import Heading from '@/components/ui/Heading'; import ProfileRaceHistory from '@/components/drivers/ProfileRaceHistory'; import ProfileSettings from '@/components/drivers/ProfileSettings'; import { useEffectiveDriverId } from '@/lib/currentDriver'; import type { GetDriverTeamQueryResultDTO } from '@gridpilot/racing/application/dto/TeamCommandAndQueryDTO'; // ============================================================================ // TYPES // ============================================================================ type ProfileTab = 'overview' | 'history' | '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 (Extended profile info not in domain yet) // ============================================================================ function getDemoExtendedProfile(driverId: string): DriverExtendedProfile { // Demo social handles based on driver id hash 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: '4', title: 'Championship Glory', description: 'Win a league championship', icon: 'crown', rarity: 'legendary', earnedAt: new Date(Date.now() - 7 * 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: '3', title: 'Endurance Master', description: 'Complete a 24-hour race', icon: 'star', rarity: 'epic', earnedAt: new Date(Date.now() - 15 * 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']; return { socialHandles: socialOptions[hash % socialOptions.length], achievements: achievementSets[hash % achievementSets.length], racingStyle: styles[hash % styles.length], favoriteTrack: tracks[hash % tracks.length], favoriteCar: cars[hash % cars.length], timezone: timezones[hash % timezones.length], availableHours: hours[hash % hours.length], lookingForTeam: hash % 3 === 0, openToRequests: hash % 2 === 0, }; } // ============================================================================ // HELPER COMPONENTS // ============================================================================ 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 (
{percentage.toFixed(0)}%
{label}
); } interface BarChartProps { data: { label: string; value: number; color: string }[]; maxValue: number; } function HorizontalBarChart({ data, maxValue }: BarChartProps) { return (
{data.map((item) => (
{item.label} {item.value}
))}
); } interface FinishDistributionProps { wins: number; podiums: number; topTen: number; total: number; } function FinishDistributionChart({ wins, podiums, topTen, total }: FinishDistributionProps) { const outsideTopTen = total - topTen; const podiumsNotWins = podiums - wins; const topTenNotPodium = topTen - podiums; const segments = [ { label: 'Wins', value: wins, color: 'bg-performance-green', textColor: 'text-performance-green' }, { label: 'Podiums', value: podiumsNotWins, color: 'bg-warning-amber', textColor: 'text-warning-amber' }, { label: 'Top 10', value: topTenNotPodium, color: 'bg-primary-blue', textColor: 'text-primary-blue' }, { label: 'Other', value: outsideTopTen, color: 'bg-gray-600', textColor: 'text-gray-400' }, ].filter(s => s.value > 0); return (
{segments.map((segment, index) => (
))}
{segments.map((segment) => (
{segment.label}: {segment.value} ({((segment.value / total) * 100).toFixed(0)}%)
))}
); } // ============================================================================ // MAIN PAGE COMPONENT // ============================================================================ export default function ProfilePage() { const [driver, setDriver] = useState(null); const [loading, setLoading] = useState(true); const [editMode, setEditMode] = useState(false); const [activeTab, setActiveTab] = useState('overview'); const [teamData, setTeamData] = useState(null); const [allTeamMemberships, setAllTeamMemberships] = useState([]); const [friends, setFriends] = useState([]); const [friendRequestSent, setFriendRequestSent] = useState(false); const effectiveDriverId = useEffectiveDriverId(); const isOwnProfile = true; // This page is always your own profile useEffect(() => { const loadData = async () => { try { const driverRepo = getDriverRepository(); const currentDriverId = effectiveDriverId; const currentDriver = await driverRepo.findById(currentDriverId); if (currentDriver) { const driverData = EntityMappers.toDriverDTO(currentDriver); setDriver(driverData); // Load primary team data const teamQuery = getGetDriverTeamQuery(); const teamResult = await teamQuery.execute({ driverId: currentDriverId }); setTeamData(teamResult); // Load ALL team memberships const allTeamsQuery = getGetAllTeamsQuery(); const allTeams = await allTeamsQuery.execute(); const membershipsQuery = getGetTeamMembersQuery(); const memberships: TeamMembershipInfo[] = []; for (const team of allTeams) { const members = await membershipsQuery.execute({ teamId: team.id }); const membership = members.find((m) => m.driverId === currentDriverId); if (membership) { memberships.push({ team, role: membership.role, joinedAt: membership.joinedAt, }); } } setAllTeamMemberships(memberships); // Load friends const socialRepo = getSocialRepository(); const friendsList = await socialRepo.getFriends(currentDriverId); setFriends(friendsList); } } catch (error) { console.error('Failed to load profile:', error); } finally { setLoading(false); } }; void loadData(); }, [effectiveDriverId]); const handleSaveSettings = async (updates: Partial) => { if (!driver) return; const driverRepo = getDriverRepository(); const currentDriver = await driverRepo.findById(driver.id); if (currentDriver) { const updatedDriver: Driver = currentDriver.update({ bio: updates.bio ?? currentDriver.bio, country: updates.country ?? currentDriver.country, }); const persistedDriver = await driverRepo.update(updatedDriver); const updatedDto = EntityMappers.toDriverDTO(persistedDriver); setDriver(updatedDto); setEditMode(false); } }; const handleAddFriend = () => { setFriendRequestSent(true); // In production, this would call a use case }; if (loading) { return (

Loading profile...

); } if (!driver) { return (
Create Your Driver Profile

Join the GridPilot community and start your racing journey

Get Started

Create your driver profile to join leagues, compete in races, and connect with other drivers.

); } // Get extended profile data const extendedProfile = getDemoExtendedProfile(driver.id); const stats = getDriverStats(driver.id); const allRankings = getAllDriverRankings(); const globalRank = stats?.overallRank ?? allRankings.findIndex(r => r.driverId === driver.id) + 1; // Show edit mode if (editMode) { return (
Edit Profile
); } return (
{/* Hero Header Section */}
{/* Background Pattern */}
{/* Avatar */}
{driver.name}
{/* Online status indicator */}
{/* 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 */}
{isOwnProfile ? ( ) : ( <> )}
{/* Social Handles */} {extendedProfile.socialHandles.length > 0 && (
Connect: {extendedProfile.socialHandles.map((social) => { 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' })}
))}
)} {/* Key Stats Overview with Diagrams */} {stats && (

Performance Overview

{/* Circular Progress Charts */}
{/* Finish Distribution */}

Finish Distribution

Best Finish

P{stats.bestFinish}

Avg Finish

P{stats.avgFinish.toFixed(1)}

)} {/* Tab Navigation */}
{/* Tab Content */} {activeTab === 'overview' && ( <> {/* Racing Profile & Quick Stats */}
{/* Career Stats Summary */}

Career Statistics

{stats ? (
{stats.totalRaces}
Races
{stats.wins}
Wins
{stats.podiums}
Podiums
{stats.consistency}%
Consistency
) : (

No race statistics available yet. Join a league and compete to start building your record!

)}
{/* Racing Preferences */} {/* Background accent */}

Racing Profile

{/* Racing Style - Featured */}
Racing Style

{extendedProfile.racingStyle}

{/* Track & Car Grid */}
Track

{extendedProfile.favoriteTrack}

Car

{extendedProfile.favoriteCar}

{/* Availability */}
Available

{extendedProfile.availableHours}

{/* Status badges */}
{extendedProfile.lookingForTeam && (
Looking for Team Open to recruitment offers
)} {extendedProfile.openToRequests && (
Open to Requests Accepting friend invites
)}
{/* Achievements */}

Achievements {extendedProfile.achievements.length} earned

{extendedProfile.achievements.map((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}
{friend.name} {getCountryFlag(friend.country)} ))} {friends.length > 8 && (
+{friends.length - 8} more
)}
)} )} {activeTab === 'history' && (

Race History

)} {activeTab === 'stats' && stats && (
{/* Detailed Performance Metrics */}

Detailed Performance Metrics

{/* Performance Bars */}

Results Breakdown

{/* Key Metrics */}
Win Rate

{((stats.wins / stats.totalRaces) * 100).toFixed(1)}%

Podium Rate

{((stats.podiums / stats.totalRaces) * 100).toFixed(1)}%

Consistency

{stats.consistency}%

Finish Rate

{(((stats.totalRaces - stats.dnfs) / stats.totalRaces) * 100).toFixed(1)}%

{/* Position Statistics */}

Position Statistics

P{stats.bestFinish}
Best Finish
P{stats.avgFinish.toFixed(1)}
Avg Finish
P{stats.worstFinish}
Worst Finish
{stats.dnfs}
DNFs
{/* Global Rankings */}

Global Rankings

#{globalRank}
Global Rank
{stats.rating}
Rating
Top {stats.percentile}%
Percentile
)} {activeTab === 'stats' && !stats && (

No statistics available yet

Join a league and complete races to see detailed stats

)}
); }