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

@@ -4,7 +4,7 @@ import { useState, FormEvent } from 'react';
import { useRouter } from 'next/navigation';
import Input from '../ui/Input';
import Button from '../ui/Button';
import { Driver } from '@core/racing';
import { useServices } from '@/lib/services/ServiceProvider';
interface FormErrors {
name?: string;
@@ -16,12 +16,12 @@ interface FormErrors {
export default function CreateDriverForm() {
const router = useRouter();
const { driverService } = useServices();
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<FormErrors>({});
const [formData, setFormData] = useState({
name: '',
iracingId: '',
country: '',
bio: ''
});
@@ -33,16 +33,6 @@ export default function CreateDriverForm() {
newErrors.name = 'Name is required';
}
if (!formData.iracingId.trim()) {
newErrors.iracingId = 'iRacing ID is required';
} else {
const driverRepo = getDriverRepository();
const exists = await driverRepo.existsByIRacingId(formData.iracingId);
if (exists) {
newErrors.iracingId = 'This iRacing ID is already registered';
}
}
if (!formData.country.trim()) {
newErrors.country = 'Country is required';
} else if (!/^[A-Z]{2,3}$/i.test(formData.country)) {
@@ -68,18 +58,21 @@ export default function CreateDriverForm() {
setLoading(true);
try {
const driverRepo = getDriverRepository();
const bio = formData.bio.trim();
const driver = Driver.create({
id: crypto.randomUUID(),
iracingId: formData.iracingId.trim(),
name: formData.name.trim(),
const displayName = formData.name.trim();
const parts = displayName.split(' ').filter(Boolean);
const firstName = parts[0] ?? displayName;
const lastName = parts.slice(1).join(' ') || 'Driver';
await driverService.completeDriverOnboarding({
firstName,
lastName,
displayName,
country: formData.country.trim().toUpperCase(),
...(bio ? { bio } : {}),
});
await driverRepo.create(driver);
router.push('/profile');
router.refresh();
} catch (error) {
@@ -111,16 +104,16 @@ export default function CreateDriverForm() {
<div>
<label htmlFor="iracingId" className="block text-sm font-medium text-gray-300 mb-2">
iRacing ID *
Display Name *
</label>
<Input
id="iracingId"
id="name"
type="text"
value={formData.iracingId}
onChange={(e) => setFormData({ ...formData, iracingId: e.target.value })}
error={!!errors.iracingId}
errorMessage={errors.iracingId}
placeholder="123456"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
error={!!errors.name}
errorMessage={errors.name}
placeholder="Alex Vermeer"
disabled={loading}
/>
</div>
@@ -182,4 +175,4 @@ export default function CreateDriverForm() {
</form>
</>
);
}
}

View File

@@ -3,8 +3,6 @@
import { useState, useEffect } from 'react';
import Card from '../ui/Card';
import Button from '../ui/Button';
import RaceResultCard from '../races/RaceResultCard';
import { useServices } from '@/lib/services/ServiceProvider';
interface RaceHistoryProps {
driverId: string;
@@ -13,35 +11,14 @@ interface RaceHistoryProps {
export default function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
const [filter, setFilter] = useState<'all' | 'wins' | 'podiums'>('all');
const [page, setPage] = useState(1);
const [races, setRaces] = useState<Race[]>([]);
const [results, setResults] = useState<Result[]>([]);
const [leagues, setLeagues] = useState<Map<string, League>>(new Map());
const [loading, setLoading] = useState(true);
const resultsPerPage = 10;
useEffect(() => {
async function loadRaceHistory() {
try {
const resultRepo = getResultRepository();
const raceRepo = getRaceRepository();
const leagueRepo = getLeagueRepository();
const driverResults = await resultRepo.findByDriverId(driverId);
const allRaces = await raceRepo.findAll();
const allLeagues = await leagueRepo.findAll();
// Filter races to only those where driver has results
const raceIds = new Set(driverResults.map(r => r.raceId));
const driverRaces = allRaces
.filter(race => raceIds.has(race.id) && race.status === 'completed')
.sort((a, b) => b.scheduledAt.getTime() - a.scheduledAt.getTime());
const leagueMap = new Map<string, League>();
allLeagues.forEach(league => leagueMap.set(league.id, league));
setRaces(driverRaces);
setResults(driverResults);
setLeagues(leagueMap);
// Driver race history is not exposed via API yet.
// Keep as placeholder until an endpoint exists.
} catch (err) {
console.error('Failed to load race history:', err);
} finally {
@@ -52,22 +29,7 @@ export default function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
loadRaceHistory();
}, [driverId]);
const raceHistory = races.map(race => {
const result = results.find(r => r.raceId === race.id);
const league = leagues.get(race.leagueId);
return {
race,
result,
league,
};
}).filter(item => item.result);
const filteredResults = raceHistory.filter(item => {
if (!item.result) return false;
if (filter === 'wins') return item.result.position === 1;
if (filter === 'podiums') return item.result.position <= 3;
return true;
});
const filteredResults: Array<unknown> = [];
const totalPages = Math.ceil(filteredResults.length / resultsPerPage);
const paginatedResults = filteredResults.slice(
@@ -94,7 +56,7 @@ export default function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
);
}
if (raceHistory.length === 0) {
if (filteredResults.length === 0) {
return (
<Card className="text-center py-12">
<p className="text-gray-400 mb-2">No race history yet</p>
@@ -131,19 +93,7 @@ export default function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
<Card>
<div className="space-y-2">
{paginatedResults.map(({ race, result, league }) => {
if (!result || !league) return null;
return (
<RaceResultCard
key={race.id}
race={race}
result={result}
league={league}
showLeague={true}
/>
);
})}
{/* No results until API provides driver results */}
</div>
{totalPages > 1 && (
@@ -172,4 +122,4 @@ export default function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
</Card>
</div>
);
}
}

View File

@@ -2,8 +2,8 @@
import Card from '../ui/Card';
import RankBadge from './RankBadge';
import { useState, useEffect } from 'react';
import { getPrimaryLeagueIdForDriver } from '@/lib/leagueMembership';
import { useMemo } from 'react';
import { useDriverProfile } from '@/hooks/useDriverService';
interface ProfileStatsProps {
driverId?: string;
@@ -17,43 +17,36 @@ interface ProfileStatsProps {
};
}
type DriverProfileOverviewViewModel = ProfileOverviewOutputPort | null;
export default function ProfileStats({ driverId, stats }: ProfileStatsProps) {
const [profileData, setProfileData] = useState<DriverProfileOverviewViewModel>(null);
useEffect(() => {
if (driverId) {
const load = async () => {
const profileUseCase = getGetProfileOverviewUseCase();
const vm = await profileUseCase.execute({ driverId });
setProfileData(vm);
};
void load();
const { data: profileData } = useDriverProfile(driverId ?? '');
const driverStats = profileData?.stats ?? null;
const totalDrivers = profileData?.currentDriver?.totalDrivers ?? 0;
// League rank widget needs a dedicated API contract; keep it disabled until provided.
// (Leaving UI block out avoids `never` typing issues.)
const defaultStats = useMemo(() => {
if (stats) {
return stats;
}
}, [driverId]);
const driverStats = profileData?.stats || null;
const totalDrivers = profileData?.driver?.totalDrivers ?? 0;
const primaryLeagueId = driverId ? getPrimaryLeagueIdForDriver(driverId) : null;
const leagueRank =
driverId && primaryLeagueId ? getLeagueRankings(driverId, primaryLeagueId) : null;
if (!driverStats) {
return null;
}
const defaultStats =
stats ||
(driverStats
? {
totalRaces: driverStats.totalRaces,
wins: driverStats.wins,
podiums: driverStats.podiums,
dnfs: driverStats.dnfs,
avgFinish: driverStats.avgFinish ?? 0,
completionRate:
driverStats.totalRaces > 0
? ((driverStats.totalRaces - driverStats.dnfs) / driverStats.totalRaces) * 100
: 0,
}
: null);
return {
totalRaces: driverStats.totalRaces,
wins: driverStats.wins,
podiums: driverStats.podiums,
dnfs: driverStats.dnfs,
avgFinish: driverStats.avgFinish ?? 0,
completionRate:
driverStats.totalRaces > 0
? ((driverStats.totalRaces - driverStats.dnfs) / driverStats.totalRaces) * 100
: 0,
};
}, [stats, driverStats]);
const winRate =
defaultStats && defaultStats.totalRaces > 0
@@ -134,27 +127,7 @@ export default function ProfileStats({ driverId, stats }: ProfileStatsProps) {
</div>
</div>
{leagueRank && leagueRank.totalDrivers > 0 && (
<div className="p-4 rounded-lg bg-deep-graphite border border-charcoal-outline">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<RankBadge rank={leagueRank.rank} size="md" />
<div>
<div className="text-white font-medium">Primary League</div>
<div className="text-sm text-gray-400">
{leagueRank.rank} of {leagueRank.totalDrivers} drivers
</div>
</div>
</div>
<div className="text-right">
<div className={`text-sm font-medium ${getPercentileColor(leagueRank.percentile)}`}>
{getPercentileLabel(leagueRank.percentile)}
</div>
<div className="text-xs text-gray-500">League Percentile</div>
</div>
</div>
</div>
)}
{/* Primary-league ranking removed until we have a dedicated API + view model for league ranks. */}
</div>
</Card>
)}
@@ -264,4 +237,4 @@ function PerformanceRow({ label, races, wins, podiums, avgFinish }: {
</div>
</div>
);
}
}