'use client'; import { useState, useEffect } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; 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 { getGetProfileOverviewUseCase, getImageService, getUpdateDriverProfileUseCase, } from '@/lib/di-container'; import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO'; import type { ProfileOverviewViewModel, ProfileOverviewDriverSummaryViewModel, ProfileOverviewStatsViewModel, ProfileOverviewTeamMembershipViewModel, ProfileOverviewSocialSummaryViewModel, ProfileOverviewExtendedProfileViewModel, ProfileOverviewAchievementViewModel, ProfileOverviewSocialHandleViewModel, } from '@gridpilot/racing/application/presenters/IProfileOverviewPresenter'; 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'; // ============================================================================ // TYPES // ============================================================================ type ProfileTab = 'overview' | 'history' | 'stats'; // ============================================================================ // 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: ProfileOverviewAchievementViewModel['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: ProfileOverviewAchievementViewModel['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: ProfileOverviewSocialHandleViewModel['platform']) { switch (platform) { case 'twitter': return Twitter; case 'youtube': return Youtube; case 'twitch': return Twitch; case 'discord': return MessageCircle; } } function getSocialColor(platform: ProfileOverviewSocialHandleViewModel['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 router = useRouter(); const searchParams = useSearchParams(); const tabParam = searchParams.get('tab') as ProfileTab | null; const [driver, setDriver] = useState(null); const [loading, setLoading] = useState(true); const [editMode, setEditMode] = useState(false); const [activeTab, setActiveTab] = useState(tabParam || 'overview'); const [profileData, setProfileData] = useState(null); const [friendRequestSent, setFriendRequestSent] = useState(false); const effectiveDriverId = useEffectiveDriverId(); const isOwnProfile = true; // This page is always your own profile useEffect(() => { const loadData = async () => { try { const currentDriverId = effectiveDriverId; // Use GetProfileOverviewUseCase to load all profile data const profileUseCase = getGetProfileOverviewUseCase(); const profileViewModel = await profileUseCase.execute({ driverId: currentDriverId }); if (profileViewModel && profileViewModel.currentDriver) { // Set driver from ViewModel instead of direct repository access 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); } } catch (error) { console.error('Failed to load profile:', error); } finally { setLoading(false); } }; void loadData(); }, [effectiveDriverId]); // Update URL when tab changes useEffect(() => { if (tabParam !== activeTab) { const params = new URLSearchParams(searchParams.toString()); if (activeTab === 'overview') { params.delete('tab'); } else { params.set('tab', activeTab); } const query = params.toString(); router.replace(`/profile${query ? `?${query}` : ''}`, { scroll: false }); } }, [activeTab, tabParam, searchParams, router]); // Sync tab from URL on mount and param change useEffect(() => { if (tabParam && tabParam !== activeTab) { setActiveTab(tabParam); } }, [tabParam]); const handleSaveSettings = async (updates: Partial) => { if (!driver) return; try { const updateProfileUseCase = getUpdateDriverProfileUseCase(); const input: { driverId: string; bio?: string; country?: string } = { driverId: driver.id }; if (typeof updates.bio === 'string') { input.bio = updates.bio; } if (typeof updates.country === 'string') { input.country = updates.country; } const updatedDto = await updateProfileUseCase.execute(input); if (updatedDto) { setDriver(updatedDto); setEditMode(false); } } catch (error) { console.error('Failed to update profile:', error); } }; 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.

); } // Extract data from profileData ViewModel const currentDriver = profileData?.currentDriver || null; const stats = profileData?.stats || null; const finishDistribution = profileData?.finishDistribution || null; const teamMemberships = profileData?.teamMemberships || []; const socialSummary = profileData?.socialSummary || { friendsCount: 0, friends: [] }; const extendedProfile = profileData?.extendedProfile; const globalRank = currentDriver?.globalRank || null; // 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)} {teamMemberships.length > 0 && teamMemberships[0] && ( [{teamMemberships[0].teamTag || 'TEAM'}] )}
{/* Rating and Rank */}
{stats && ( <>
{stats.rating ?? 0} Rating
#{globalRank} Global
)} {teamMemberships.length > 0 && teamMemberships[0] && ( {teamMemberships[0].teamName} )}
{/* Meta info */}
iRacing: {driver.iracingId} Joined {new Date(driver.joinedAt).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} {extendedProfile && ( {extendedProfile.timezone} )}
{/* Action Buttons */}
{isOwnProfile ? ( ) : ( <> )}
{/* Social Handles */} {extendedProfile && 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 */} {teamMemberships.length > 0 && (

Team Memberships ({teamMemberships.length})

{teamMemberships.map((membership) => (

{membership.teamName}

{membership.role} Since {new Date(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 ?? 0).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 ?? 0}%
Consistency
) : (

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

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

Racing Profile

{extendedProfile && ( <> {/* 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 */} {extendedProfile && extendedProfile.achievements.length > 0 && (

Achievements {extendedProfile.achievements.length} earned

{extendedProfile.achievements.map((achievement) => { const Icon = getAchievementIcon(achievement.icon); const rarityClasses = getRarityColor(achievement.rarity); return (

{achievement.title}

{achievement.description}

{new Date(achievement.earnedAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', })}

); })}
)} {/* Friends Preview */} {socialSummary && socialSummary.friends.length > 0 && (

Friends ({socialSummary.friendsCount})

{socialSummary.friends.slice(0, 8).map((friend) => (
{friend.name}
{friend.name} {getCountryFlag(friend.country)} ))} {socialSummary.friendsCount > 8 && (
+{socialSummary.friendsCount - 8} more
)}
)} )} {activeTab === 'history' && driver && (

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 ?? 0}%

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
{stats.dnfs}
DNFs
{/* Global Rankings */}

Global Rankings

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

No statistics available yet

Join a league and complete races to see detailed stats

)}
); }