'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 (
);
}
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 (
);
}
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 */}
{/* 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.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}
{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 */}
{((stats.wins / stats.totalRaces) * 100).toFixed(1)}%
{((stats.podiums / stats.totalRaces) * 100).toFixed(1)}%
{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
{/* 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
)}
);
}