'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 {
getDriverRepository,
getDriverStats,
getAllDriverRankings,
getGetDriverTeamUseCase,
getSocialRepository,
getImageService,
getGetAllTeamsUseCase,
getGetTeamMembersUseCase,
} 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 (
);
}
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 [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 teamUseCase = getGetDriverTeamUseCase();
await teamUseCase.execute({ driverId: currentDriverId });
const teamViewModel = teamUseCase.presenter.getViewModel();
setTeamData(teamViewModel.result);
// Load ALL team memberships
const allTeamsUseCase = getGetAllTeamsUseCase();
await allTeamsUseCase.execute();
const allTeamsViewModel = allTeamsUseCase.presenter.getViewModel();
const allTeams = allTeamsViewModel.teams;
const membershipsUseCase = getGetTeamMembersUseCase();
const memberships: TeamMembershipInfo[] = [];
for (const team of allTeams) {
await membershipsUseCase.execute({ teamId: team.id });
const membersViewModel = membershipsUseCase.presenter.getViewModel();
const members = membersViewModel.members;
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]);
// 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;
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 (
);
}
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 */}
{/* 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.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}
{getCountryFlag(friend.country)}
))}
{friends.length > 8 && (
+{friends.length - 8} more
)}
)}
>
)}
{activeTab === 'history' && driver && (
Race History
)}
{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.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
Join a league and complete races to see detailed stats
)}
);
}