wip
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, use } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
@@ -43,12 +43,16 @@ import {
|
||||
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
|
||||
@@ -134,14 +138,22 @@ function getDemoExtendedProfile(driverId: string): DriverExtendedProfile {
|
||||
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: 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],
|
||||
socialHandles,
|
||||
achievements,
|
||||
racingStyle,
|
||||
favoriteTrack,
|
||||
favoriteCar,
|
||||
timezone,
|
||||
availableHours,
|
||||
lookingForTeam: hash % 3 === 0,
|
||||
openToRequests: hash % 2 === 0,
|
||||
};
|
||||
@@ -301,11 +313,46 @@ function HorizontalBarChart({ data, maxValue }: BarChartProps) {
|
||||
// MAIN PAGE
|
||||
// ============================================================================
|
||||
|
||||
export default function DriverDetailPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: any;
|
||||
}) {
|
||||
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;
|
||||
@@ -318,24 +365,16 @@ export default function DriverDetailPage({
|
||||
const [allTeamMemberships, setAllTeamMemberships] = useState<TeamMembershipInfo[]>([]);
|
||||
const [friends, setFriends] = useState<Driver[]>([]);
|
||||
const [friendRequestSent, setFriendRequestSent] = useState(false);
|
||||
const [profileData, setProfileData] = useState<any>(null);
|
||||
const [profileData, setProfileData] = useState<ProfileOverviewViewModel | null>(null);
|
||||
|
||||
const unwrappedSearchParams = use(searchParams) as URLSearchParams | undefined;
|
||||
|
||||
const from =
|
||||
typeof unwrappedSearchParams?.get === 'function'
|
||||
? unwrappedSearchParams.get('from') ?? undefined
|
||||
const search =
|
||||
typeof window !== 'undefined'
|
||||
? new URLSearchParams(window.location.search)
|
||||
: undefined;
|
||||
|
||||
const leagueId =
|
||||
typeof unwrappedSearchParams?.get === 'function'
|
||||
? unwrappedSearchParams.get('leagueId') ?? undefined
|
||||
: undefined;
|
||||
|
||||
const raceId =
|
||||
typeof unwrappedSearchParams?.get === 'function'
|
||||
? unwrappedSearchParams.get('raceId') ?? undefined
|
||||
: undefined;
|
||||
const from = search?.get('from') ?? undefined;
|
||||
const leagueId = search?.get('leagueId') ?? undefined;
|
||||
const raceId = search?.get('raceId') ?? undefined;
|
||||
|
||||
let backLink: string | null = null;
|
||||
|
||||
@@ -362,8 +401,7 @@ export default function DriverDetailPage({
|
||||
try {
|
||||
// Use GetProfileOverviewUseCase to load all profile data
|
||||
const profileUseCase = getGetProfileOverviewUseCase();
|
||||
await profileUseCase.execute({ driverId });
|
||||
const profileViewModel = profileUseCase.presenter.getViewModel();
|
||||
const profileViewModel = await profileUseCase.execute({ driverId });
|
||||
|
||||
if (!profileViewModel || !profileViewModel.currentDriver) {
|
||||
setError('Driver not found');
|
||||
@@ -375,7 +413,7 @@ export default function DriverDetailPage({
|
||||
const driverData: DriverDTO = {
|
||||
id: profileViewModel.currentDriver.id,
|
||||
name: profileViewModel.currentDriver.name,
|
||||
iracingId: profileViewModel.currentDriver.iracingId,
|
||||
iracingId: profileViewModel.currentDriver.iracingId ?? '',
|
||||
country: profileViewModel.currentDriver.country,
|
||||
bio: profileViewModel.currentDriver.bio || '',
|
||||
joinedAt: profileViewModel.currentDriver.joinedAt,
|
||||
@@ -383,30 +421,37 @@ export default function DriverDetailPage({
|
||||
setDriver(driverData);
|
||||
setProfileData(profileViewModel);
|
||||
|
||||
// Load team data
|
||||
const teamUseCase = getGetDriverTeamUseCase();
|
||||
await teamUseCase.execute({ driverId });
|
||||
const teamViewModel = teamUseCase.presenter.getViewModel();
|
||||
setTeamData(teamViewModel.result);
|
||||
|
||||
// Load ALL team memberships
|
||||
// Load ALL team memberships using caller-owned presenters
|
||||
const allTeamsUseCase = getGetAllTeamsUseCase();
|
||||
await allTeamsUseCase.execute();
|
||||
const allTeamsViewModel = allTeamsUseCase.presenter.getViewModel();
|
||||
const allTeams = allTeamsViewModel.teams;
|
||||
const membershipsUseCase = getGetTeamMembersUseCase();
|
||||
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) {
|
||||
await membershipsUseCase.execute({ teamId: team.id });
|
||||
const membersViewModel = membershipsUseCase.presenter.getViewModel();
|
||||
const members = membersViewModel.members;
|
||||
const membership = members.find((m) => m.driverId === driverId);
|
||||
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,
|
||||
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: membership.joinedAt,
|
||||
joinedAt: new Date(membership.joinedAt),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -459,7 +504,30 @@ export default function DriverDetailPage({
|
||||
);
|
||||
}
|
||||
|
||||
const extendedProfile = profileData?.extendedProfile || getDemoExtendedProfile(driver.id);
|
||||
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;
|
||||
|
||||
@@ -627,7 +695,7 @@ export default function DriverDetailPage({
|
||||
<div className="mt-6 pt-6 border-t border-charcoal-outline/50">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-sm text-gray-500 mr-2">Connect:</span>
|
||||
{extendedProfile.socialHandles.map((social) => {
|
||||
{extendedProfile.socialHandles.map((social: SocialHandle) => {
|
||||
const Icon = getSocialIcon(social.platform);
|
||||
return (
|
||||
<a
|
||||
@@ -724,7 +792,7 @@ export default function DriverDetailPage({
|
||||
</div>
|
||||
<div className="flex gap-6">
|
||||
<CircularProgress
|
||||
value={stats.consistency}
|
||||
value={stats.consistency ?? 0}
|
||||
max={100}
|
||||
label="Consistency"
|
||||
color="text-primary-blue"
|
||||
@@ -766,7 +834,9 @@ export default function DriverDetailPage({
|
||||
<Target className="w-4 h-4 text-primary-blue" />
|
||||
<span className="text-xs text-gray-500 uppercase">Avg Finish</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-primary-blue">P{stats.avgFinish.toFixed(1)}</p>
|
||||
<p className="text-2xl font-bold text-primary-blue">
|
||||
P{(stats.avgFinish ?? 0).toFixed(1)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -888,7 +958,7 @@ export default function DriverDetailPage({
|
||||
<span className="ml-auto text-sm text-gray-500">{extendedProfile.achievements.length} earned</span>
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{extendedProfile.achievements.map((achievement) => {
|
||||
{extendedProfile.achievements.map((achievement: Achievement) => {
|
||||
const Icon = getAchievementIcon(achievement.icon);
|
||||
const rarityClasses = getRarityColor(achievement.rarity);
|
||||
return (
|
||||
@@ -1033,7 +1103,9 @@ export default function DriverDetailPage({
|
||||
<div className="text-xs text-gray-400 uppercase">Best Finish</div>
|
||||
</div>
|
||||
<div className="p-4 rounded-xl bg-gradient-to-br from-primary-blue/20 to-primary-blue/5 border border-primary-blue/30 text-center">
|
||||
<div className="text-4xl font-bold text-primary-blue mb-1">P{stats.avgFinish.toFixed(1)}</div>
|
||||
<div className="text-4xl font-bold text-primary-blue mb-1">
|
||||
P{(stats.avgFinish ?? 0).toFixed(1)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 uppercase">Avg Finish</div>
|
||||
</div>
|
||||
<div className="p-4 rounded-xl bg-gradient-to-br from-warning-amber/20 to-warning-amber/5 border border-warning-amber/30 text-center">
|
||||
|
||||
Reference in New Issue
Block a user