website cleanup

This commit is contained in:
2025-12-24 21:44:58 +01:00
parent 9b683a59d3
commit d78854a4c6
277 changed files with 6141 additions and 2693 deletions

View File

@@ -8,9 +8,11 @@ import type { LeagueMembership } from '@/lib/types/LeagueMembership';
import type { MembershipRoleDTO } from '@/lib/types/generated/MembershipRoleDTO';
import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility';
import { useServices } from '@/lib/services/ServiceProvider';
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
import { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { LeagueStandingsViewModel } from '@/lib/view-models/LeagueStandingsViewModel';
import { StandingEntryViewModel } from '@/lib/view-models/StandingEntryViewModel';
import type { LeagueStandingDTO } from '@/lib/types/generated/LeagueStandingDTO';
import { useParams } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react';
@@ -21,7 +23,8 @@ export default function LeagueStandingsPage() {
const { leagueService } = useServices();
const [standings, setStandings] = useState<StandingEntryViewModel[]>([]);
const [drivers, setDrivers] = useState<DriverViewModel[]>([]);
const [drivers, setDrivers] = useState<DriverDTO[]>([]);
const [driverVms, setDriverVms] = useState<DriverViewModel[]>([]);
const [memberships, setMemberships] = useState<LeagueMembership[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -31,7 +34,8 @@ export default function LeagueStandingsPage() {
try {
const vm = await leagueService.getLeagueStandings(leagueId, currentDriverId);
setStandings(vm.standings);
setDrivers(vm.drivers.map(d => new DriverViewModel(d)));
setDrivers(vm.drivers as unknown as DriverDTO[]);
setDriverVms(vm.drivers.map((d) => new DriverViewModel(d)));
setMemberships(vm.memberships);
// Check if current user is admin
@@ -89,12 +93,33 @@ export default function LeagueStandingsPage() {
return (
<div className="space-y-6">
{/* Championship Stats */}
<LeagueChampionshipStats standings={standings} drivers={drivers} />
<LeagueChampionshipStats standings={standings} drivers={driverVms} />
<Card>
<h2 className="text-xl font-semibold text-white mb-4">Championship Standings</h2>
<StandingsTable
standings={standings}
standings={standings.map((s) => ({
leagueId,
driverId: s.driverId,
position: s.position,
totalPoints: s.points,
racesFinished: s.races,
racesStarted: s.races,
avgFinish: null,
penaltyPoints: 0,
bonusPoints: 0,
}) satisfies {
leagueId: string;
driverId: string;
position: number;
totalPoints: number;
racesFinished: number;
racesStarted: number;
avgFinish: number | null;
penaltyPoints: number;
bonusPoints: number;
teamName?: string;
})}
drivers={drivers}
leagueId={leagueId}
memberships={memberships}
@@ -106,4 +131,4 @@ export default function LeagueStandingsPage() {
</Card>
</div>
);
}
}

View File

@@ -14,27 +14,13 @@ import SimPlatformMockup from '@/components/mockups/SimPlatformMockup';
import MockupStack from '@/components/ui/MockupStack';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { LandingService } from '@/lib/services/landing/LandingService';
import { SessionService } from '@/lib/services/auth/SessionService';
import { AuthApiClient } from '@/lib/api/auth/AuthApiClient';
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { ServiceFactory } from '@/lib/services/ServiceFactory';
export default async function HomePage() {
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
const authApiClient = new AuthApiClient(baseUrl, errorReporter, logger);
const racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger);
const leaguesApiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
const teamsApiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
const sessionService = new SessionService(authApiClient);
const landingService = new LandingService(racesApiClient, leaguesApiClient, teamsApiClient);
const serviceFactory = new ServiceFactory(baseUrl);
const sessionService = serviceFactory.createSessionService();
const landingService = serviceFactory.createLandingService();
const session = await sessionService.getSession();
if (session) {
@@ -368,4 +354,4 @@ export default async function HomePage() {
<Footer />
</main>
);
}
}

View File

@@ -14,7 +14,7 @@ import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility';
import type { RaceResultsDetailViewModel } from '@/lib/view-models/RaceResultsDetailViewModel';
import { ArrowLeft, Calendar, Trophy, Users, Zap } from 'lucide-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useState } from 'react';
export default function RaceResultsPage() {
const router = useRouter();
@@ -93,19 +93,6 @@ export default function RaceResultsPage() {
Back to Races
</Button>
</Card>
{showQuickPenaltyModal && (
<QuickPenaltyModal
{...({
raceId,
drivers: raceData?.drivers as any,
onClose: handleCloseQuickPenaltyModal,
preSelectedDriver: preSelectedDriver as any,
adminId: currentDriverId,
races: undefined,
} as any)}
/>
)}
</div>
</div>
);
@@ -190,4 +177,4 @@ export default function RaceResultsPage() {
</div>
</div>
);
}
}

View File

@@ -8,8 +8,7 @@ import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { siteConfig } from '@/lib/siteConfig';
import { LeagueDetailViewModel } from '@/lib/view-models/LeagueDetailViewModel';
import { SponsorService } from '@/lib/services/sponsors/SponsorService';
import { ServiceFactory } from '@/lib/services/ServiceFactory';
import { useServices } from '@/lib/services/ServiceProvider';
import {
Trophy,
Users,
@@ -40,6 +39,8 @@ export default function SponsorLeagueDetailPage() {
const searchParams = useSearchParams();
const shouldReduceMotion = useReducedMotion();
const { sponsorService } = useServices();
const showSponsorAction = searchParams.get('action') === 'sponsor';
const [activeTab, setActiveTab] = useState<TabType>(showSponsorAction ? 'sponsor' : 'overview');
const [selectedTier, setSelectedTier] = useState<'main' | 'secondary'>('main');
@@ -52,7 +53,6 @@ export default function SponsorLeagueDetailPage() {
useEffect(() => {
const loadLeagueDetail = async () => {
try {
const sponsorService = ServiceFactory.getSponsorService();
const leagueData = await sponsorService.getLeagueDetail(leagueId);
setData(new LeagueDetailViewModel(leagueData));
} catch (err) {
@@ -66,7 +66,7 @@ export default function SponsorLeagueDetailPage() {
if (leagueId) {
loadLeagueDetail();
}
}, [leagueId]);
}, [leagueId, sponsorService]);
if (loading) {
return (
@@ -385,7 +385,7 @@ export default function SponsorLeagueDetailPage() {
}`} />
<div>
<div className="font-medium text-white">{race.name}</div>
<div className="text-sm text-gray-500">{race.date}</div>
<div className="text-sm text-gray-500">{race.formattedDate}</div>
</div>
</div>
<div className="flex items-center gap-4">
@@ -560,4 +560,4 @@ export default function SponsorLeagueDetailPage() {
)}
</div>
);
}
}

View File

@@ -7,8 +7,7 @@ import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { siteConfig } from '@/lib/siteConfig';
import { AvailableLeaguesViewModel } from '@/lib/view-models/AvailableLeaguesViewModel';
import { SponsorService } from '@/lib/services/sponsors/SponsorService';
import { ServiceFactory } from '@/lib/services/ServiceFactory';
import { useServices } from '@/lib/services/ServiceProvider';
import {
Trophy,
Users,
@@ -194,6 +193,7 @@ function LeagueCard({ league, index }: { league: any; index: number }) {
export default function SponsorLeaguesPage() {
const shouldReduceMotion = useReducedMotion();
const { sponsorService } = useServices();
const [searchQuery, setSearchQuery] = useState('');
const [tierFilter, setTierFilter] = useState<TierFilter>('all');
const [availabilityFilter, setAvailabilityFilter] = useState<AvailabilityFilter>('all');
@@ -205,7 +205,6 @@ export default function SponsorLeaguesPage() {
useEffect(() => {
const loadLeagues = async () => {
try {
const sponsorService = ServiceFactory.getSponsorService();
const leaguesData = await sponsorService.getAvailableLeagues();
setData(new AvailableLeaguesViewModel(leaguesData));
} catch (err) {
@@ -217,7 +216,7 @@ export default function SponsorLeaguesPage() {
};
loadLeagues();
}, []);
}, [sponsorService]);
if (loading) {
return (
@@ -458,4 +457,4 @@ export default function SponsorLeaguesPage() {
</div>
</div>
);
}
}

View File

@@ -19,13 +19,7 @@ import { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
type TeamRole = 'owner' | 'manager' | 'driver';
interface TeamMembership {
driverId: string;
role: TeamRole;
joinedAt: Date;
}
type TeamRole = 'owner' | 'admin' | 'member';
type Tab = 'overview' | 'roster' | 'standings' | 'admin';
@@ -57,7 +51,7 @@ export default function TeamDetailPage() {
const teamMembers = await teamService.getTeamMembers(teamId, currentDriverId, teamDetails.ownerId);
const adminStatus = teamDetails.isOwner ||
teamMembers.some(m => m.driverId === currentDriverId && (m.role === 'manager' || m.role === 'owner'));
teamMembers.some((m) => m.driverId === currentDriverId && (m.role === 'admin' || m.role === 'owner'));
setTeam(teamDetails);
setMemberships(teamMembers);
@@ -82,8 +76,8 @@ export default function TeamDetailPage() {
try {
const performer = await teamService.getMembership(teamId, currentDriverId);
if (!performer || (performer.role !== 'owner' && performer.role !== 'manager')) {
throw new Error('Only owners or managers can remove members');
if (!performer || (performer.role !== 'owner' && performer.role !== 'admin')) {
throw new Error('Only owners or admins can remove members');
}
const membership = await teamService.getMembership(teamId, driverId);
@@ -104,8 +98,8 @@ export default function TeamDetailPage() {
const handleChangeRole = async (driverId: string, newRole: TeamRole) => {
try {
const performer = await teamService.getMembership(teamId, currentDriverId);
if (!performer || (performer.role !== 'owner' && performer.role !== 'manager')) {
throw new Error('Only owners or managers can update roles');
if (!performer || (performer.role !== 'owner' && performer.role !== 'admin')) {
throw new Error('Only owners or admins can update roles');
}
const membership = await teamService.getMembership(teamId, driverId);

View File

@@ -37,8 +37,8 @@ type SortBy = 'rating' | 'wins' | 'winRate' | 'races';
type TeamDisplayData = TeamSummaryViewModel;
const getSafeRating = (team: TeamDisplayData): number => {
const value = typeof team.rating === 'number' ? team.rating : 0;
return Number.isFinite(value) ? value : 0;
void team;
return 0;
};
const getSafeTotalWins = (team: TeamDisplayData): number => {
@@ -497,4 +497,4 @@ export default function TeamLeaderboardPage() {
</div>
</div>
);
}
}

View File

@@ -41,6 +41,8 @@ import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewMode
type TeamDisplayData = TeamSummaryViewModel;
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
// ============================================================================
// SKILL LEVEL CONFIG
// ============================================================================
@@ -126,9 +128,9 @@ export default function TeamsPage() {
// Select top teams by rating for the preview section
const topTeams = useMemo(() => {
const sortedByRating = [...teams].sort((a, b) => {
const aRating = typeof a.rating === 'number' && Number.isFinite(a.rating) ? a.rating : 0;
const bRating = typeof b.rating === 'number' && Number.isFinite(b.rating) ? b.rating : 0;
return bRating - aRating;
// Rating is not currently part of TeamSummaryViewModel in this build.
// Keep deterministic ordering by name until a rating field is exposed.
return a.name.localeCompare(b.name);
});
return sortedByRating.slice(0, 5);
}, [teams]);
@@ -172,7 +174,7 @@ export default function TeamsPage() {
intermediate: [],
advanced: [],
pro: [],
} as Record<string, typeof teams>,
} as Record<string, TeamSummaryViewModel[]>,
);
}, [groupsBySkillLevel, filteredTeams]);
@@ -373,7 +375,7 @@ export default function TeamsPage() {
<div key={level.id} id={`level-${level.id}`} className="scroll-mt-8">
<SkillLevelSection
level={level}
teams={teamsByLevel[level.id]}
teams={teamsByLevel[level.id] ?? []}
onTeamClick={handleTeamClick}
defaultExpanded={index === 0}
/>
@@ -383,4 +385,4 @@ export default function TeamsPage() {
)}
</div>
);
}
}