wip
This commit is contained in:
@@ -52,29 +52,10 @@ interface DriverListItem {
|
||||
// ============================================================================
|
||||
// DEMO DATA
|
||||
// ============================================================================
|
||||
|
||||
const DEMO_DRIVERS: DriverListItem[] = [
|
||||
{ id: 'demo-1', name: 'Max Verstappen', rating: 4250, skillLevel: 'pro', nationality: 'NL', racesCompleted: 156, wins: 47, podiums: 89, isActive: true, rank: 1 },
|
||||
{ id: 'demo-2', name: 'Lewis Hamilton', rating: 4180, skillLevel: 'pro', nationality: 'GB', racesCompleted: 198, wins: 52, podiums: 112, isActive: true, rank: 2 },
|
||||
{ id: 'demo-3', name: 'Charles Leclerc', rating: 3950, skillLevel: 'pro', nationality: 'MC', racesCompleted: 134, wins: 28, podiums: 67, isActive: true, rank: 3 },
|
||||
{ id: 'demo-4', name: 'Lando Norris', rating: 3820, skillLevel: 'advanced', nationality: 'GB', racesCompleted: 112, wins: 18, podiums: 45, isActive: true, rank: 4 },
|
||||
{ id: 'demo-5', name: 'Carlos Sainz', rating: 3750, skillLevel: 'advanced', nationality: 'ES', racesCompleted: 145, wins: 15, podiums: 52, isActive: true, rank: 5 },
|
||||
{ id: 'demo-6', name: 'Oscar Piastri', rating: 3680, skillLevel: 'advanced', nationality: 'AU', racesCompleted: 78, wins: 8, podiums: 24, isActive: true, rank: 6 },
|
||||
{ id: 'demo-7', name: 'George Russell', rating: 3620, skillLevel: 'advanced', nationality: 'GB', racesCompleted: 98, wins: 6, podiums: 31, isActive: true, rank: 7 },
|
||||
{ id: 'demo-8', name: 'Fernando Alonso', rating: 3580, skillLevel: 'advanced', nationality: 'ES', racesCompleted: 256, wins: 32, podiums: 98, isActive: true, rank: 8 },
|
||||
{ id: 'demo-9', name: 'Nico Hülkenberg', rating: 3420, skillLevel: 'advanced', nationality: 'DE', racesCompleted: 167, wins: 2, podiums: 18, isActive: true, rank: 9 },
|
||||
{ id: 'demo-10', name: 'Yuki Tsunoda', rating: 3250, skillLevel: 'intermediate', nationality: 'JP', racesCompleted: 89, wins: 1, podiums: 8, isActive: true, rank: 10 },
|
||||
{ id: 'demo-11', name: 'Alex Albon', rating: 3180, skillLevel: 'intermediate', nationality: 'TH', racesCompleted: 102, wins: 0, podiums: 4, isActive: true, rank: 11 },
|
||||
{ id: 'demo-12', name: 'Kevin Magnussen', rating: 3050, skillLevel: 'intermediate', nationality: 'DK', racesCompleted: 145, wins: 0, podiums: 2, isActive: true, rank: 12 },
|
||||
{ id: 'demo-13', name: 'Pierre Gasly', rating: 2980, skillLevel: 'intermediate', nationality: 'FR', racesCompleted: 124, wins: 1, podiums: 5, isActive: true, rank: 13 },
|
||||
{ id: 'demo-14', name: 'Esteban Ocon', rating: 2920, skillLevel: 'intermediate', nationality: 'FR', racesCompleted: 118, wins: 1, podiums: 4, isActive: true, rank: 14 },
|
||||
{ id: 'demo-15', name: 'Lance Stroll', rating: 2850, skillLevel: 'intermediate', nationality: 'CA', racesCompleted: 134, wins: 0, podiums: 3, isActive: true, rank: 15 },
|
||||
{ id: 'demo-16', name: 'Zhou Guanyu', rating: 2650, skillLevel: 'intermediate', nationality: 'CN', racesCompleted: 67, wins: 0, podiums: 0, isActive: true, rank: 16 },
|
||||
{ id: 'demo-17', name: 'Daniel Ricciardo', rating: 2500, skillLevel: 'intermediate', nationality: 'AU', racesCompleted: 189, wins: 8, podiums: 32, isActive: false, rank: 17 },
|
||||
{ id: 'demo-18', name: 'Valtteri Bottas', rating: 2450, skillLevel: 'intermediate', nationality: 'FI', racesCompleted: 212, wins: 10, podiums: 67, isActive: false, rank: 18 },
|
||||
{ id: 'demo-19', name: 'Logan Sargeant', rating: 1850, skillLevel: 'beginner', nationality: 'US', racesCompleted: 34, wins: 0, podiums: 0, isActive: false, rank: 19 },
|
||||
{ id: 'demo-20', name: 'Nyck de Vries', rating: 1750, skillLevel: 'beginner', nationality: 'NL', racesCompleted: 12, wins: 0, podiums: 0, isActive: false, rank: 20 },
|
||||
];
|
||||
//
|
||||
// In alpha, all driver listings come from the in-memory repositories wired
|
||||
// through the DI container. We intentionally avoid hardcoded fallback driver
|
||||
// lists here so that the demo data stays consistent across pages.
|
||||
|
||||
// ============================================================================
|
||||
// SKILL LEVEL CONFIG
|
||||
@@ -464,7 +445,7 @@ export default function DriversPage() {
|
||||
return rankA - rankB || b.rating - a.rating;
|
||||
});
|
||||
|
||||
setDrivers(items.length > 0 ? items : DEMO_DRIVERS);
|
||||
setDrivers(items);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@@ -472,7 +453,6 @@ export default function DriversPage() {
|
||||
}, []);
|
||||
|
||||
const handleDriverClick = (driverId: string) => {
|
||||
if (driverId.startsWith('demo-')) return;
|
||||
router.push(`/drivers/${driverId}`);
|
||||
};
|
||||
|
||||
|
||||
@@ -41,28 +41,6 @@ interface DriverListItem {
|
||||
rank: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DEMO DATA
|
||||
// ============================================================================
|
||||
|
||||
const DEMO_DRIVERS: DriverListItem[] = [
|
||||
{ id: 'demo-1', name: 'Max Verstappen', rating: 4250, skillLevel: 'pro', nationality: 'NL', racesCompleted: 156, wins: 47, podiums: 89, rank: 1 },
|
||||
{ id: 'demo-2', name: 'Lewis Hamilton', rating: 4180, skillLevel: 'pro', nationality: 'GB', racesCompleted: 198, wins: 52, podiums: 112, rank: 2 },
|
||||
{ id: 'demo-3', name: 'Charles Leclerc', rating: 3950, skillLevel: 'pro', nationality: 'MC', racesCompleted: 134, wins: 28, podiums: 67, rank: 3 },
|
||||
{ id: 'demo-4', name: 'Lando Norris', rating: 3820, skillLevel: 'advanced', nationality: 'GB', racesCompleted: 112, wins: 18, podiums: 45, rank: 4 },
|
||||
{ id: 'demo-5', name: 'Carlos Sainz', rating: 3750, skillLevel: 'advanced', nationality: 'ES', racesCompleted: 145, wins: 15, podiums: 52, rank: 5 },
|
||||
{ id: 'demo-6', name: 'Oscar Piastri', rating: 3680, skillLevel: 'advanced', nationality: 'AU', racesCompleted: 78, wins: 8, podiums: 24, rank: 6 },
|
||||
{ id: 'demo-7', name: 'George Russell', rating: 3620, skillLevel: 'advanced', nationality: 'GB', racesCompleted: 98, wins: 6, podiums: 31, rank: 7 },
|
||||
{ id: 'demo-8', name: 'Fernando Alonso', rating: 3580, skillLevel: 'advanced', nationality: 'ES', racesCompleted: 256, wins: 32, podiums: 98, rank: 8 },
|
||||
{ id: 'demo-9', name: 'Nico Hülkenberg', rating: 3420, skillLevel: 'advanced', nationality: 'DE', racesCompleted: 167, wins: 2, podiums: 18, rank: 9 },
|
||||
{ id: 'demo-10', name: 'Yuki Tsunoda', rating: 3250, skillLevel: 'intermediate', nationality: 'JP', racesCompleted: 89, wins: 1, podiums: 8, rank: 10 },
|
||||
{ id: 'demo-11', name: 'Alex Albon', rating: 3180, skillLevel: 'intermediate', nationality: 'TH', racesCompleted: 102, wins: 0, podiums: 4, rank: 11 },
|
||||
{ id: 'demo-12', name: 'Kevin Magnussen', rating: 3050, skillLevel: 'intermediate', nationality: 'DK', racesCompleted: 145, wins: 0, podiums: 2, rank: 12 },
|
||||
{ id: 'demo-13', name: 'Pierre Gasly', rating: 2980, skillLevel: 'intermediate', nationality: 'FR', racesCompleted: 124, wins: 1, podiums: 5, rank: 13 },
|
||||
{ id: 'demo-14', name: 'Esteban Ocon', rating: 2920, skillLevel: 'intermediate', nationality: 'FR', racesCompleted: 118, wins: 1, podiums: 4, rank: 14 },
|
||||
{ id: 'demo-15', name: 'Lance Stroll', rating: 2850, skillLevel: 'intermediate', nationality: 'CA', racesCompleted: 134, wins: 0, podiums: 3, rank: 15 },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// SKILL LEVEL CONFIG
|
||||
// ============================================================================
|
||||
@@ -247,7 +225,7 @@ export default function DriverLeaderboardPage() {
|
||||
};
|
||||
});
|
||||
|
||||
setDrivers(items.length > 0 ? items : DEMO_DRIVERS);
|
||||
setDrivers(items);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -51,31 +51,6 @@ interface TeamDisplayData {
|
||||
performanceLevel: SkillLevel;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DEMO DATA
|
||||
// ============================================================================
|
||||
|
||||
const DEMO_DRIVERS: DriverListItem[] = [
|
||||
{ id: 'demo-1', name: 'Max Verstappen', rating: 4250, skillLevel: 'pro', nationality: 'NL', wins: 47, podiums: 89, rank: 1 },
|
||||
{ id: 'demo-2', name: 'Lewis Hamilton', rating: 4180, skillLevel: 'pro', nationality: 'GB', wins: 52, podiums: 112, rank: 2 },
|
||||
{ id: 'demo-3', name: 'Charles Leclerc', rating: 3950, skillLevel: 'pro', nationality: 'MC', wins: 28, podiums: 67, rank: 3 },
|
||||
{ id: 'demo-4', name: 'Lando Norris', rating: 3820, skillLevel: 'advanced', nationality: 'GB', wins: 18, podiums: 45, rank: 4 },
|
||||
{ id: 'demo-5', name: 'Carlos Sainz', rating: 3750, skillLevel: 'advanced', nationality: 'ES', wins: 15, podiums: 52, rank: 5 },
|
||||
{ id: 'demo-6', name: 'Oscar Piastri', rating: 3680, skillLevel: 'advanced', nationality: 'AU', wins: 8, podiums: 24, rank: 6 },
|
||||
{ id: 'demo-7', name: 'George Russell', rating: 3620, skillLevel: 'advanced', nationality: 'GB', wins: 6, podiums: 31, rank: 7 },
|
||||
{ id: 'demo-8', name: 'Fernando Alonso', rating: 3580, skillLevel: 'advanced', nationality: 'ES', wins: 32, podiums: 98, rank: 8 },
|
||||
{ id: 'demo-9', name: 'Nico Hülkenberg', rating: 3420, skillLevel: 'advanced', nationality: 'DE', wins: 2, podiums: 18, rank: 9 },
|
||||
{ id: 'demo-10', name: 'Yuki Tsunoda', rating: 3250, skillLevel: 'intermediate', nationality: 'JP', wins: 1, podiums: 8, rank: 10 },
|
||||
];
|
||||
|
||||
const DEMO_TEAMS: TeamDisplayData[] = [
|
||||
{ id: 'demo-team-1', name: 'Apex Predators Racing', memberCount: 8, rating: 4850, totalWins: 47, totalRaces: 156, performanceLevel: 'pro' },
|
||||
{ id: 'demo-team-2', name: 'Velocity Esports', memberCount: 12, rating: 5200, totalWins: 63, totalRaces: 198, performanceLevel: 'pro' },
|
||||
{ id: 'demo-team-3', name: 'Nitro Motorsport', memberCount: 6, rating: 4720, totalWins: 38, totalRaces: 112, performanceLevel: 'pro' },
|
||||
{ id: 'demo-team-4', name: 'Horizon Racing Collective', memberCount: 10, rating: 3800, totalWins: 24, totalRaces: 89, performanceLevel: 'advanced' },
|
||||
{ id: 'demo-team-5', name: 'Phoenix Rising eSports', memberCount: 7, rating: 3650, totalWins: 19, totalRaces: 76, performanceLevel: 'advanced' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// SKILL LEVEL CONFIG
|
||||
// ============================================================================
|
||||
@@ -420,12 +395,12 @@ export default function LeaderboardsPage() {
|
||||
}),
|
||||
);
|
||||
|
||||
setDrivers(driverItems.length > 0 ? driverItems : DEMO_DRIVERS);
|
||||
setTeams(teamData.length > 0 ? teamData : DEMO_TEAMS);
|
||||
setDrivers(driverItems);
|
||||
setTeams(teamData);
|
||||
} catch (error) {
|
||||
console.error('Failed to load leaderboard data:', error);
|
||||
setDrivers(DEMO_DRIVERS);
|
||||
setTeams(DEMO_TEAMS);
|
||||
setDrivers([]);
|
||||
setTeams([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -435,12 +410,10 @@ export default function LeaderboardsPage() {
|
||||
}, []);
|
||||
|
||||
const handleDriverClick = (driverId: string) => {
|
||||
if (driverId.startsWith('demo-')) return;
|
||||
router.push(`/drivers/${driverId}`);
|
||||
};
|
||||
|
||||
const handleTeamClick = (teamId: string) => {
|
||||
if (teamId.startsWith('demo-team-')) return;
|
||||
router.push(`/teams/${teamId}`);
|
||||
};
|
||||
|
||||
|
||||
@@ -48,223 +48,6 @@ interface TeamDisplayData {
|
||||
languages?: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DEMO TEAMS DATA
|
||||
// ============================================================================
|
||||
|
||||
const DEMO_TEAMS: TeamDisplayData[] = [
|
||||
{
|
||||
id: 'demo-team-1',
|
||||
name: 'Apex Predators Racing',
|
||||
description: 'Elite GT3 team competing at the highest level.',
|
||||
memberCount: 8,
|
||||
rating: 4850,
|
||||
totalWins: 47,
|
||||
totalRaces: 156,
|
||||
performanceLevel: 'pro',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇺🇸 North America',
|
||||
languages: ['English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-2',
|
||||
name: 'Velocity Esports',
|
||||
description: 'Professional sim racing team with sponsors.',
|
||||
memberCount: 12,
|
||||
rating: 5200,
|
||||
totalWins: 63,
|
||||
totalRaces: 198,
|
||||
performanceLevel: 'pro',
|
||||
isRecruiting: false,
|
||||
createdAt: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'endurance',
|
||||
region: '🇬🇧 Europe',
|
||||
languages: ['English', 'German'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-3',
|
||||
name: 'Nitro Motorsport',
|
||||
description: 'Championship-winning sprint specialists.',
|
||||
memberCount: 6,
|
||||
rating: 4720,
|
||||
totalWins: 38,
|
||||
totalRaces: 112,
|
||||
performanceLevel: 'pro',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇩🇪 Germany',
|
||||
languages: ['German', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-4',
|
||||
name: 'Horizon Racing Collective',
|
||||
description: 'Ambitious team on the rise.',
|
||||
memberCount: 10,
|
||||
rating: 3800,
|
||||
totalWins: 24,
|
||||
totalRaces: 89,
|
||||
performanceLevel: 'advanced',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇳🇱 Netherlands',
|
||||
languages: ['Dutch', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-5',
|
||||
name: 'Phoenix Rising eSports',
|
||||
description: 'From the ashes to the podium.',
|
||||
memberCount: 7,
|
||||
rating: 3650,
|
||||
totalWins: 19,
|
||||
totalRaces: 76,
|
||||
performanceLevel: 'advanced',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'endurance',
|
||||
region: '🇫🇷 France',
|
||||
languages: ['French', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-6',
|
||||
name: 'Thunderbolt Racing',
|
||||
description: 'Fast and furious sprint racing.',
|
||||
memberCount: 5,
|
||||
rating: 3420,
|
||||
totalWins: 15,
|
||||
totalRaces: 54,
|
||||
performanceLevel: 'advanced',
|
||||
isRecruiting: false,
|
||||
createdAt: new Date(Date.now() - 120 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇮🇹 Italy',
|
||||
languages: ['Italian', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-7',
|
||||
name: 'Grid Starters',
|
||||
description: 'Growing together as racers.',
|
||||
memberCount: 9,
|
||||
rating: 2800,
|
||||
totalWins: 11,
|
||||
totalRaces: 67,
|
||||
performanceLevel: 'intermediate',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇪🇸 Spain',
|
||||
languages: ['Spanish', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-8',
|
||||
name: 'Midnight Racers',
|
||||
description: 'Night owls who love endurance racing.',
|
||||
memberCount: 6,
|
||||
rating: 2650,
|
||||
totalWins: 8,
|
||||
totalRaces: 42,
|
||||
performanceLevel: 'intermediate',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'endurance',
|
||||
region: '🌍 International',
|
||||
languages: ['English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-9',
|
||||
name: 'Casual Speedsters',
|
||||
description: 'Racing for fun, improving together.',
|
||||
memberCount: 4,
|
||||
rating: 2400,
|
||||
totalWins: 5,
|
||||
totalRaces: 31,
|
||||
performanceLevel: 'intermediate',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇵🇱 Poland',
|
||||
languages: ['Polish', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-10',
|
||||
name: 'Fresh Rubber Racing',
|
||||
description: 'New team for new racers!',
|
||||
memberCount: 3,
|
||||
rating: 1800,
|
||||
totalWins: 2,
|
||||
totalRaces: 18,
|
||||
performanceLevel: 'beginner',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇧🇷 Brazil',
|
||||
languages: ['Portuguese', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-11',
|
||||
name: 'Rookie Revolution',
|
||||
description: 'First time racers welcome!',
|
||||
memberCount: 5,
|
||||
rating: 1650,
|
||||
totalWins: 1,
|
||||
totalRaces: 12,
|
||||
performanceLevel: 'beginner',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇦🇺 Australia',
|
||||
languages: ['English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-12',
|
||||
name: 'Pit Lane Pioneers',
|
||||
description: 'Learning endurance racing from scratch.',
|
||||
memberCount: 4,
|
||||
rating: 1500,
|
||||
totalWins: 0,
|
||||
totalRaces: 8,
|
||||
performanceLevel: 'beginner',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'endurance',
|
||||
region: '🇯🇵 Japan',
|
||||
languages: ['Japanese', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-13',
|
||||
name: 'Shadow Squadron',
|
||||
description: 'Elite drivers emerging from the shadows.',
|
||||
memberCount: 6,
|
||||
rating: 4100,
|
||||
totalWins: 12,
|
||||
totalRaces: 34,
|
||||
performanceLevel: 'advanced',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇸🇪 Scandinavia',
|
||||
languages: ['Swedish', 'Norwegian', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-14',
|
||||
name: 'Turbo Collective',
|
||||
description: 'Fast, furious, and friendly.',
|
||||
memberCount: 4,
|
||||
rating: 3200,
|
||||
totalWins: 7,
|
||||
totalRaces: 28,
|
||||
performanceLevel: 'intermediate',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 12 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇨🇦 Canada',
|
||||
languages: ['English', 'French'],
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// SKILL LEVEL CONFIG
|
||||
// ============================================================================
|
||||
@@ -537,7 +320,7 @@ export default function TeamLeaderboardPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const teams = [...realTeams, ...DEMO_TEAMS];
|
||||
const teams = realTeams;
|
||||
|
||||
const handleTeamClick = (teamId: string) => {
|
||||
if (teamId.startsWith('demo-team-')) {
|
||||
|
||||
@@ -53,228 +53,6 @@ interface TeamDisplayData {
|
||||
languages?: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DEMO TEAMS DATA
|
||||
// ============================================================================
|
||||
|
||||
const DEMO_TEAMS: TeamDisplayData[] = [
|
||||
// Pro Teams
|
||||
{
|
||||
id: 'demo-team-1',
|
||||
name: 'Apex Predators Racing',
|
||||
description: 'Elite GT3 team competing at the highest level. Multiple championship winners seeking consistent drivers.',
|
||||
memberCount: 8,
|
||||
rating: 4850,
|
||||
totalWins: 47,
|
||||
totalRaces: 156,
|
||||
performanceLevel: 'pro',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇺🇸 North America',
|
||||
languages: ['English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-2',
|
||||
name: 'Velocity Esports',
|
||||
description: 'Professional sim racing team with sponsors. Competing in major endurance events worldwide.',
|
||||
memberCount: 12,
|
||||
rating: 5200,
|
||||
totalWins: 63,
|
||||
totalRaces: 198,
|
||||
performanceLevel: 'pro',
|
||||
isRecruiting: false,
|
||||
createdAt: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'endurance',
|
||||
region: '🇬🇧 Europe',
|
||||
languages: ['English', 'German'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-3',
|
||||
name: 'Nitro Motorsport',
|
||||
description: 'Championship-winning sprint specialists. Fast, consistent, and always fighting for podiums.',
|
||||
memberCount: 6,
|
||||
rating: 4720,
|
||||
totalWins: 38,
|
||||
totalRaces: 112,
|
||||
performanceLevel: 'pro',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇩🇪 Germany',
|
||||
languages: ['German', 'English'],
|
||||
},
|
||||
// Advanced Teams
|
||||
{
|
||||
id: 'demo-team-4',
|
||||
name: 'Horizon Racing Collective',
|
||||
description: 'Ambitious team on the rise. Building towards professional competition with dedicated drivers.',
|
||||
memberCount: 10,
|
||||
rating: 3800,
|
||||
totalWins: 24,
|
||||
totalRaces: 89,
|
||||
performanceLevel: 'advanced',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇳🇱 Netherlands',
|
||||
languages: ['Dutch', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-5',
|
||||
name: 'Phoenix Rising eSports',
|
||||
description: 'From the ashes to the podium. A team built on improvement and teamwork.',
|
||||
memberCount: 7,
|
||||
rating: 3650,
|
||||
totalWins: 19,
|
||||
totalRaces: 76,
|
||||
performanceLevel: 'advanced',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'endurance',
|
||||
region: '🇫🇷 France',
|
||||
languages: ['French', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-6',
|
||||
name: 'Thunderbolt Racing',
|
||||
description: 'Fast and furious sprint racing. We live for wheel-to-wheel battles.',
|
||||
memberCount: 5,
|
||||
rating: 3420,
|
||||
totalWins: 15,
|
||||
totalRaces: 54,
|
||||
performanceLevel: 'advanced',
|
||||
isRecruiting: false,
|
||||
createdAt: new Date(Date.now() - 120 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇮🇹 Italy',
|
||||
languages: ['Italian', 'English'],
|
||||
},
|
||||
// Intermediate Teams
|
||||
{
|
||||
id: 'demo-team-7',
|
||||
name: 'Grid Starters',
|
||||
description: 'Growing together as racers. Friendly competition with a focus on learning and fun.',
|
||||
memberCount: 9,
|
||||
rating: 2800,
|
||||
totalWins: 11,
|
||||
totalRaces: 67,
|
||||
performanceLevel: 'intermediate',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇪🇸 Spain',
|
||||
languages: ['Spanish', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-8',
|
||||
name: 'Midnight Racers',
|
||||
description: 'Night owls who love endurance racing. Join us for late-night stints and good vibes.',
|
||||
memberCount: 6,
|
||||
rating: 2650,
|
||||
totalWins: 8,
|
||||
totalRaces: 42,
|
||||
performanceLevel: 'intermediate',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'endurance',
|
||||
region: '🌍 International',
|
||||
languages: ['English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-9',
|
||||
name: 'Casual Speedsters',
|
||||
description: 'Racing for fun, improving together. No pressure, just clean racing.',
|
||||
memberCount: 4,
|
||||
rating: 2400,
|
||||
totalWins: 5,
|
||||
totalRaces: 31,
|
||||
performanceLevel: 'intermediate',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇵🇱 Poland',
|
||||
languages: ['Polish', 'English'],
|
||||
},
|
||||
// Beginner Teams
|
||||
{
|
||||
id: 'demo-team-10',
|
||||
name: 'Fresh Rubber Racing',
|
||||
description: 'New team for new racers! Learn the basics together in a supportive environment.',
|
||||
memberCount: 3,
|
||||
rating: 1800,
|
||||
totalWins: 2,
|
||||
totalRaces: 18,
|
||||
performanceLevel: 'beginner',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇧🇷 Brazil',
|
||||
languages: ['Portuguese', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-11',
|
||||
name: 'Rookie Revolution',
|
||||
description: 'First time racers welcome! We all start somewhere.',
|
||||
memberCount: 5,
|
||||
rating: 1650,
|
||||
totalWins: 1,
|
||||
totalRaces: 12,
|
||||
performanceLevel: 'beginner',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇦🇺 Australia',
|
||||
languages: ['English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-12',
|
||||
name: 'Pit Lane Pioneers',
|
||||
description: 'Learning endurance racing from scratch. Long races, longer friendships.',
|
||||
memberCount: 4,
|
||||
rating: 1500,
|
||||
totalWins: 0,
|
||||
totalRaces: 8,
|
||||
performanceLevel: 'beginner',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'endurance',
|
||||
region: '🇯🇵 Japan',
|
||||
languages: ['Japanese', 'English'],
|
||||
},
|
||||
// Recently Added
|
||||
{
|
||||
id: 'demo-team-13',
|
||||
name: 'Shadow Squadron',
|
||||
description: 'Elite drivers emerging from the shadows. Watch out for us this season.',
|
||||
memberCount: 6,
|
||||
rating: 4100,
|
||||
totalWins: 12,
|
||||
totalRaces: 34,
|
||||
performanceLevel: 'advanced',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000),
|
||||
specialization: 'mixed',
|
||||
region: '🇸🇪 Scandinavia',
|
||||
languages: ['Swedish', 'Norwegian', 'English'],
|
||||
},
|
||||
{
|
||||
id: 'demo-team-14',
|
||||
name: 'Turbo Collective',
|
||||
description: 'Fast, furious, and friendly. Sprint racing specialists looking for quick racers.',
|
||||
memberCount: 4,
|
||||
rating: 3200,
|
||||
totalWins: 7,
|
||||
totalRaces: 28,
|
||||
performanceLevel: 'intermediate',
|
||||
isRecruiting: true,
|
||||
createdAt: new Date(Date.now() - 12 * 60 * 60 * 1000),
|
||||
specialization: 'sprint',
|
||||
region: '🇨🇦 Canada',
|
||||
languages: ['English', 'French'],
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// SKILL LEVEL CONFIG
|
||||
// ============================================================================
|
||||
@@ -745,7 +523,7 @@ export default function TeamsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const teams = [...realTeams, ...DEMO_TEAMS];
|
||||
const teams = realTeams;
|
||||
|
||||
const handleTeamClick = (teamId: string) => {
|
||||
if (teamId.startsWith('demo-team-')) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffectiveDriverId } from '@/lib/currentDriver';
|
||||
import { getSendNotificationUseCase } from '@/lib/di-container';
|
||||
import { getSendNotificationUseCase, getRaceRepository, getLeagueRepository } from '@/lib/di-container';
|
||||
import type { NotificationUrgency } from '@gridpilot/notifications/application';
|
||||
import {
|
||||
Bell,
|
||||
@@ -170,38 +170,64 @@ export default function DevToolbar() {
|
||||
setSending(true);
|
||||
try {
|
||||
const sendNotification = getSendNotificationUseCase();
|
||||
|
||||
|
||||
const raceRepository = getRaceRepository();
|
||||
const leagueRepository = getLeagueRepository();
|
||||
|
||||
const [allRaces, allLeagues] = await Promise.all([
|
||||
raceRepository.findAll(),
|
||||
leagueRepository.findAll(),
|
||||
]);
|
||||
|
||||
const completedRaces = allRaces.filter((race: any) => race.status === 'completed');
|
||||
const scheduledRaces = allRaces.filter((race: any) => race.status === 'scheduled');
|
||||
|
||||
const primaryRace = completedRaces[0] ?? allRaces[0];
|
||||
const secondaryRace = scheduledRaces[0] ?? allRaces[1] ?? primaryRace;
|
||||
const primaryLeague = allLeagues[0];
|
||||
|
||||
let title: string;
|
||||
let body: string;
|
||||
let notificationType: 'protest_filed' | 'protest_defense_requested' | 'protest_vote_required';
|
||||
let actionUrl: string;
|
||||
|
||||
switch (selectedType) {
|
||||
case 'protest_filed':
|
||||
case 'protest_filed': {
|
||||
const raceId = primaryRace?.id;
|
||||
title = '🚨 Protest Filed Against You';
|
||||
body = 'Max Verstappen has filed a protest against you for unsafe rejoining at Turn 3, Lap 12 during the Spa-Francorchamps race.';
|
||||
body =
|
||||
'A protest has been filed against you for unsafe rejoining during a recent race. Please review the incident details.';
|
||||
notificationType = 'protest_filed';
|
||||
actionUrl = '/races/race-1/stewarding';
|
||||
actionUrl = raceId ? `/races/${raceId}/stewarding` : '/races';
|
||||
break;
|
||||
case 'defense_requested':
|
||||
}
|
||||
case 'defense_requested': {
|
||||
const raceId = secondaryRace?.id ?? primaryRace?.id;
|
||||
title = '⚖️ Defense Requested';
|
||||
body = 'A steward has requested your defense regarding the incident at Turn 1 in the Monza race. Please provide your side of the story within 48 hours.';
|
||||
body =
|
||||
'A steward has requested your defense regarding a recent incident. Please provide your side of the story within 48 hours.';
|
||||
notificationType = 'protest_defense_requested';
|
||||
actionUrl = '/races/race-2/stewarding';
|
||||
actionUrl = raceId ? `/races/${raceId}/stewarding` : '/races';
|
||||
break;
|
||||
case 'vote_required':
|
||||
}
|
||||
case 'vote_required': {
|
||||
const leagueId = primaryLeague?.id;
|
||||
title = '🗳️ Your Vote Required';
|
||||
body = 'As a league steward, you are required to vote on the protest: Driver A vs Driver B - Causing a collision at Eau Rouge.';
|
||||
body =
|
||||
'As a league steward, you are required to vote on an open protest. Please review the case and cast your vote.';
|
||||
notificationType = 'protest_vote_required';
|
||||
actionUrl = '/leagues/league-1/stewarding';
|
||||
actionUrl = leagueId ? `/leagues/${leagueId}/stewarding` : '/leagues';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// For modal urgency, add actions
|
||||
const actions = selectedUrgency === 'modal' ? [
|
||||
{ label: 'View Protest', type: 'primary' as const, href: actionUrl, actionId: 'view' },
|
||||
{ label: 'Dismiss', type: 'secondary' as const, actionId: 'dismiss' },
|
||||
] : undefined;
|
||||
const actions =
|
||||
selectedUrgency === 'modal'
|
||||
? [
|
||||
{ label: 'View Protest', type: 'primary' as const, href: actionUrl, actionId: 'view' },
|
||||
{ label: 'Dismiss', type: 'secondary' as const, actionId: 'dismiss' },
|
||||
]
|
||||
: undefined;
|
||||
|
||||
await sendNotification.execute({
|
||||
recipientId: currentDriverId,
|
||||
@@ -214,9 +240,12 @@ export default function DevToolbar() {
|
||||
actions,
|
||||
data: {
|
||||
protestId: `demo-protest-${Date.now()}`,
|
||||
raceId: 'race-1',
|
||||
leagueId: 'league-1',
|
||||
deadline: selectedUrgency === 'modal' ? new Date(Date.now() + 48 * 60 * 60 * 1000) : undefined,
|
||||
raceId: primaryRace?.id,
|
||||
leagueId: primaryLeague?.id,
|
||||
deadline:
|
||||
selectedUrgency === 'modal'
|
||||
? new Date(Date.now() + 48 * 60 * 60 * 1000)
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import DriverRankings from './DriverRankings';
|
||||
import PerformanceMetrics from './PerformanceMetrics';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getDriverStats, getLeagueRankings, getGetDriverTeamQuery, getAllDriverRankings } from '@/lib/di-container';
|
||||
import { getPrimaryLeagueIdForDriver } from '@/lib/leagueMembership';
|
||||
import type { GetDriverTeamQueryResultDTO } from '@gridpilot/racing/application/dto/TeamCommandAndQueryDTO';
|
||||
|
||||
interface DriverProfileProps {
|
||||
@@ -19,7 +20,10 @@ interface DriverProfileProps {
|
||||
|
||||
export default function DriverProfile({ driver, isOwnProfile = false, onEditClick }: DriverProfileProps) {
|
||||
const driverStats = getDriverStats(driver.id);
|
||||
const leagueRank = getLeagueRankings(driver.id, 'league-1');
|
||||
const primaryLeagueId = getPrimaryLeagueIdForDriver(driver.id);
|
||||
const leagueRank = primaryLeagueId
|
||||
? getLeagueRankings(driver.id, primaryLeagueId)
|
||||
: { rank: 0, totalDrivers: 0, percentile: 0 };
|
||||
const allRankings = getAllDriverRankings();
|
||||
const [teamData, setTeamData] = useState<GetDriverTeamQueryResultDTO | null>(null);
|
||||
|
||||
@@ -53,7 +57,7 @@ export default function DriverProfile({ driver, isOwnProfile = false, onEditClic
|
||||
},
|
||||
{
|
||||
type: 'league' as const,
|
||||
name: 'European GT Championship',
|
||||
name: 'Primary League',
|
||||
rank: leagueRank.rank,
|
||||
totalDrivers: leagueRank.totalDrivers,
|
||||
percentile: leagueRank.percentile,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Card from '../ui/Card';
|
||||
import RankBadge from './RankBadge';
|
||||
import { getDriverStats, getAllDriverRankings, getLeagueRankings } from '@/lib/di-container';
|
||||
import { getPrimaryLeagueIdForDriver } from '@/lib/leagueMembership';
|
||||
|
||||
interface ProfileStatsProps {
|
||||
driverId?: string;
|
||||
@@ -19,7 +20,9 @@ interface ProfileStatsProps {
|
||||
export default function ProfileStats({ driverId, stats }: ProfileStatsProps) {
|
||||
const driverStats = driverId ? getDriverStats(driverId) : null;
|
||||
const allRankings = getAllDriverRankings();
|
||||
const leagueRank = driverId ? getLeagueRankings(driverId, 'league-1') : null;
|
||||
const primaryLeagueId = driverId ? getPrimaryLeagueIdForDriver(driverId) : null;
|
||||
const leagueRank =
|
||||
driverId && primaryLeagueId ? getLeagueRankings(driverId, primaryLeagueId) : null;
|
||||
|
||||
const defaultStats = stats || (driverStats
|
||||
? {
|
||||
@@ -115,7 +118,7 @@ export default function ProfileStats({ driverId, stats }: ProfileStatsProps) {
|
||||
<div className="flex items-center gap-3">
|
||||
<RankBadge rank={leagueRank.rank} size="md" />
|
||||
<div>
|
||||
<div className="text-white font-medium">European GT Championship</div>
|
||||
<div className="text-white font-medium">Primary League</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
{leagueRank.rank} of {leagueRank.totalDrivers} drivers
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Image from 'next/image';
|
||||
import type { FeedItem } from '@gridpilot/social/domain/entities/FeedItem';
|
||||
import { friends } from '@gridpilot/testing-support';
|
||||
import { getDriverRepository, getImageService, getSocialRepository } from '@/lib/di-container';
|
||||
|
||||
function timeAgo(timestamp: Date): string {
|
||||
const diffMs = Date.now() - timestamp.getTime();
|
||||
@@ -15,16 +15,39 @@ function timeAgo(timestamp: Date): string {
|
||||
return `${diffDays} d ago`;
|
||||
}
|
||||
|
||||
function getActor(item: FeedItem) {
|
||||
async function resolveActor(item: FeedItem) {
|
||||
const driverRepo = getDriverRepository();
|
||||
const imageService = getImageService();
|
||||
const socialRepo = getSocialRepository();
|
||||
|
||||
if (item.actorFriendId) {
|
||||
const friend = friends.find(f => f.driverId === item.actorFriendId);
|
||||
if (friend) {
|
||||
return {
|
||||
name: friend.displayName,
|
||||
avatarUrl: friend.avatarUrl
|
||||
};
|
||||
// Try social graph first (friend display name/avatar)
|
||||
try {
|
||||
const friend = await socialRepo.getFriendByDriverId?.(item.actorFriendId);
|
||||
if (friend) {
|
||||
return {
|
||||
name: friend.displayName ?? friend.driverName ?? `Driver ${item.actorFriendId}`,
|
||||
avatarUrl: friend.avatarUrl ?? imageService.getDriverAvatar(item.actorFriendId),
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// fall through to driver lookup
|
||||
}
|
||||
|
||||
// Fallback to driver entity + image service
|
||||
try {
|
||||
const driver = await driverRepo.findById(item.actorFriendId);
|
||||
if (driver) {
|
||||
return {
|
||||
name: driver.name,
|
||||
avatarUrl: imageService.getDriverAvatar(driver.id),
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// ignore and return null below
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -33,7 +56,22 @@ interface FeedItemCardProps {
|
||||
}
|
||||
|
||||
export default function FeedItemCard({ item }: FeedItemCardProps) {
|
||||
const actor = getActor(item);
|
||||
const [actor, setActor] = useState<{ name: string; avatarUrl: string } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
void (async () => {
|
||||
const resolved = await resolveActor(item);
|
||||
if (!cancelled) {
|
||||
setActor(resolved);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [item]);
|
||||
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
@@ -68,7 +106,7 @@ export default function FeedItemCard({ item }: FeedItemCardProps) {
|
||||
{timeAgo(item.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
{(item.ctaHref && item.ctaLabel) && (
|
||||
{item.ctaHref && item.ctaLabel && (
|
||||
<div className="mt-3">
|
||||
<Button
|
||||
as="a"
|
||||
|
||||
@@ -16,5 +16,25 @@ export function useEffectiveDriverId(): string {
|
||||
}
|
||||
| undefined;
|
||||
|
||||
return user?.primaryDriverId ?? 'driver-1';
|
||||
// In alpha mode, if the user has no bound driver yet, fall back to the
|
||||
// first seeded driver from the in-memory repository instead of a hardcoded ID.
|
||||
if (user?.primaryDriverId) {
|
||||
return user.primaryDriverId;
|
||||
}
|
||||
|
||||
try {
|
||||
// Lazy-load to avoid importing DI facade at module evaluation time
|
||||
const { getDriverRepository } = require('./di-container') as typeof import('./di-container');
|
||||
const repo = getDriverRepository();
|
||||
// In-memory repository is synchronous for findAll in the demo implementation
|
||||
const allDrivers = repo.findAllSync?.() as Array<{ id: string }> | undefined;
|
||||
if (allDrivers && allDrivers.length > 0) {
|
||||
return allDrivers[0].id;
|
||||
}
|
||||
} catch {
|
||||
// Ignore and fall back to legacy default below
|
||||
}
|
||||
|
||||
// Legacy fallback: preserved only as a last resort for demo
|
||||
return '';
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export function configureDIContainer(): void {
|
||||
|
||||
// Create seed data
|
||||
const seedData = createStaticRacingSeed(42);
|
||||
const primaryDriverId = seedData.drivers[0]?.id ?? 'driver-1';
|
||||
const primaryDriverId = seedData.drivers[0]!.id;
|
||||
|
||||
// Create driver statistics from seed data
|
||||
const driverStats = createDemoDriverStats(seedData.drivers);
|
||||
@@ -556,7 +556,7 @@ export function configureDIContainer(): void {
|
||||
name: t.name,
|
||||
tag: t.tag,
|
||||
description: t.description,
|
||||
ownerId: seedData.drivers[0]?.id ?? 'driver-1',
|
||||
ownerId: seedData.drivers[0]!.id,
|
||||
leagues: [t.primaryLeagueId],
|
||||
createdAt: new Date(),
|
||||
}))
|
||||
@@ -640,13 +640,8 @@ export function configureDIContainer(): void {
|
||||
);
|
||||
|
||||
const sponsorshipPricingRepo = new InMemorySponsorshipPricingRepository();
|
||||
// Seed sponsorship pricings from demo data
|
||||
seedData.sponsorshipPricings?.forEach(pricing => {
|
||||
(sponsorshipPricingRepo as any).pricings.set(
|
||||
`${pricing.entityType}-${pricing.entityId}`,
|
||||
pricing
|
||||
);
|
||||
});
|
||||
// Seed sponsorship pricings from demo data using domain SponsorshipPricing
|
||||
sponsorshipPricingRepo.seed(seedData.sponsorshipPricings ?? []);
|
||||
container.registerInstance<ISponsorshipPricingRepository>(
|
||||
DI_TOKENS.SponsorshipPricingRepository,
|
||||
sponsorshipPricingRepo
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
MembershipRole,
|
||||
MembershipStatus,
|
||||
} from '@gridpilot/racing/domain/entities/LeagueMembership';
|
||||
import { leagues, memberships as seedMemberships, drivers } from '@gridpilot/testing-support';
|
||||
|
||||
/**
|
||||
* Lightweight league membership model mirroring the domain type but with
|
||||
@@ -18,112 +17,49 @@ export interface LeagueMembership extends Omit<DomainLeagueMembership, 'joinedAt
|
||||
const leagueMemberships = new Map<string, LeagueMembership[]>();
|
||||
|
||||
/**
|
||||
* Initialize league memberships once from static seed data.
|
||||
* Initialize league memberships once from the in-memory league membership repository
|
||||
* that is seeded via the static racing seed in the DI container.
|
||||
*
|
||||
* - All seeded memberships become active members.
|
||||
* - League owners are guaranteed to have an owner membership.
|
||||
* This avoids depending on raw testing-support seed exports and keeps all demo
|
||||
* membership data flowing through the same in-memory repositories used elsewhere.
|
||||
*/
|
||||
(function initializeLeagueMembershipsFromSeed() {
|
||||
(async function initializeLeagueMembershipsFromRepository() {
|
||||
if (leagueMemberships.size > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const byLeague = new Map<string, LeagueMembership[]>();
|
||||
try {
|
||||
const { getLeagueRepository, getLeagueMembershipRepository } = await import('./di-container');
|
||||
const leagueRepo = getLeagueRepository();
|
||||
const membershipRepo = getLeagueMembershipRepository();
|
||||
|
||||
for (const membership of seedMemberships) {
|
||||
const list = byLeague.get(membership.leagueId) ?? [];
|
||||
const joinedAt = new Date().toISOString();
|
||||
const allLeagues = await leagueRepo.findAll();
|
||||
const byLeague = new Map<string, LeagueMembership[]>();
|
||||
|
||||
list.push({
|
||||
leagueId: membership.leagueId,
|
||||
driverId: membership.driverId,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
joinedAt,
|
||||
});
|
||||
for (const league of allLeagues) {
|
||||
const memberships = await membershipRepo.getLeagueMembers(league.id);
|
||||
|
||||
byLeague.set(membership.leagueId, list);
|
||||
}
|
||||
const mapped: LeagueMembership[] = memberships.map((membership) => ({
|
||||
leagueId: membership.leagueId,
|
||||
driverId: membership.driverId,
|
||||
role: membership.role,
|
||||
status: membership.status,
|
||||
joinedAt:
|
||||
membership.joinedAt instanceof Date
|
||||
? membership.joinedAt.toISOString()
|
||||
: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
for (const league of leagues) {
|
||||
const list = byLeague.get(league.id) ?? [];
|
||||
const existingOwner = list.find((m) => m.driverId === league.ownerId);
|
||||
|
||||
if (existingOwner) {
|
||||
existingOwner.role = 'owner';
|
||||
} else {
|
||||
const joinedAt = new Date().toISOString();
|
||||
list.unshift({
|
||||
leagueId: league.id,
|
||||
driverId: league.ownerId,
|
||||
role: 'owner',
|
||||
status: 'active',
|
||||
joinedAt,
|
||||
});
|
||||
byLeague.set(league.id, mapped);
|
||||
}
|
||||
|
||||
byLeague.set(league.id, list);
|
||||
}
|
||||
|
||||
// Seed sample league admins for the primary driver's league (alpha demo)
|
||||
const primaryDriverId = drivers[0]?.id ?? 'driver-1';
|
||||
const primaryLeagueForAdmins = leagues.find((l) => l.ownerId === primaryDriverId) ?? leagues[0];
|
||||
|
||||
if (primaryLeagueForAdmins) {
|
||||
const adminCandidates = drivers
|
||||
.filter((d) => d.id !== primaryLeagueForAdmins.ownerId)
|
||||
.slice(0, 2);
|
||||
|
||||
adminCandidates.forEach((driver) => {
|
||||
const list = byLeague.get(primaryLeagueForAdmins.id) ?? [];
|
||||
const existing = list.find((m) => m.driverId === driver.id);
|
||||
if (existing) {
|
||||
if (existing.role !== 'owner') {
|
||||
existing.role = 'admin';
|
||||
}
|
||||
} else {
|
||||
const joinedAt = new Date().toISOString();
|
||||
list.push({
|
||||
leagueId: primaryLeagueForAdmins.id,
|
||||
driverId: driver.id,
|
||||
role: 'admin',
|
||||
status: 'active',
|
||||
joinedAt,
|
||||
});
|
||||
}
|
||||
byLeague.set(primaryLeagueForAdmins.id, list);
|
||||
});
|
||||
}
|
||||
|
||||
// Seed sample league stewards for the primary driver's league (alpha demo)
|
||||
if (primaryLeagueForAdmins) {
|
||||
const stewardCandidates = drivers
|
||||
.filter((d) => d.id !== primaryLeagueForAdmins.ownerId)
|
||||
.slice(2, 5);
|
||||
|
||||
stewardCandidates.forEach((driver) => {
|
||||
const list = byLeague.get(primaryLeagueForAdmins.id) ?? [];
|
||||
const existing = list.find((m) => m.driverId === driver.id);
|
||||
if (existing) {
|
||||
if (existing.role !== 'owner' && existing.role !== 'admin') {
|
||||
existing.role = 'steward';
|
||||
}
|
||||
} else {
|
||||
const joinedAt = new Date().toISOString();
|
||||
list.push({
|
||||
leagueId: primaryLeagueForAdmins.id,
|
||||
driverId: driver.id,
|
||||
role: 'steward',
|
||||
status: 'active',
|
||||
joinedAt,
|
||||
});
|
||||
}
|
||||
byLeague.set(primaryLeagueForAdmins.id, list);
|
||||
});
|
||||
}
|
||||
|
||||
for (const [leagueId, list] of byLeague.entries()) {
|
||||
leagueMemberships.set(leagueId, list);
|
||||
for (const [leagueId, list] of byLeague.entries()) {
|
||||
leagueMemberships.set(leagueId, list);
|
||||
}
|
||||
} catch (error) {
|
||||
// In alpha/demo mode we tolerate failures here; callers will see empty memberships.
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to initialize league memberships from repository', error);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -137,6 +73,19 @@ export function getLeagueMembers(leagueId: string): LeagueMembership[] {
|
||||
return [...(leagueMemberships.get(leagueId) ?? [])];
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a driver's primary league from in-memory league memberships.
|
||||
* Prefers any active membership and returns the first matching league.
|
||||
*/
|
||||
export function getPrimaryLeagueIdForDriver(driverId: string): string | null {
|
||||
for (const [leagueId, members] of leagueMemberships.entries()) {
|
||||
if (members.some((m) => m.driverId === driverId && m.status === 'active')) {
|
||||
return leagueId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isOwnerOrAdmin(leagueId: string, driverId: string): boolean {
|
||||
const membership = getMembership(leagueId, driverId);
|
||||
if (!membership) return false;
|
||||
|
||||
Reference in New Issue
Block a user