website cleanup
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user