This commit is contained in:
2025-12-10 12:38:55 +01:00
parent 0f7fe67d3c
commit fbbcf414a4
87 changed files with 11972 additions and 390 deletions

View File

@@ -22,6 +22,8 @@ import { LeagueStructureSection } from './LeagueStructureSection';
import { LeagueScoringSection } from './LeagueScoringSection';
import { LeagueDropSection } from './LeagueDropSection';
import { LeagueTimingsSection } from './LeagueTimingsSection';
import { LeagueSponsorshipsSection } from './LeagueSponsorshipsSection';
import { LeagueMembershipFeesSection } from './LeagueMembershipFeesSection';
import { useEffectiveDriverId } from '@/lib/currentDriver';
import type { MembershipRole } from '@/lib/leagueMembership';
import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO';
@@ -29,7 +31,7 @@ import { EntityMappers } from '@gridpilot/racing/application/mappers/EntityMappe
import DriverSummaryPill from '@/components/profile/DriverSummaryPill';
import DriverIdentity from '@/components/drivers/DriverIdentity';
import Modal from '@/components/ui/Modal';
import { AlertTriangle, CheckCircle, Clock, XCircle, Flag, Calendar, User } from 'lucide-react';
import { AlertTriangle, CheckCircle, Clock, XCircle, Flag, Calendar, User, DollarSign, Wallet, Paintbrush, Trophy, Download, Car, Upload } from 'lucide-react';
import type { Protest } from '@gridpilot/racing/domain/entities/Protest';
import type { Race } from '@gridpilot/racing/domain/entities/Race';
@@ -56,7 +58,8 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
const [ownerDriver, setOwnerDriver] = useState<DriverDTO | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<'members' | 'requests' | 'races' | 'settings' | 'protests'>('members');
const [activeTab, setActiveTab] = useState<'members' | 'requests' | 'races' | 'settings' | 'protests' | 'sponsorships' | 'fees' | 'wallet' | 'prizes' | 'liveries'>('members');
const [downloadingLiveryPack, setDownloadingLiveryPack] = useState(false);
const [rejectReason, setRejectReason] = useState('');
const [configForm, setConfigForm] = useState<LeagueConfigFormModel | null>(null);
const [configLoading, setConfigLoading] = useState(false);
@@ -369,26 +372,26 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
{/* Admin Tabs */}
<div className="mb-6 border-b border-charcoal-outline">
<div className="flex gap-4">
<div className="flex gap-4 overflow-x-auto">
<button
onClick={() => setActiveTab('members')}
className={`pb-3 px-1 font-medium transition-colors ${
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'members'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
}`}
>
Manage Members
Members
</button>
<button
onClick={() => setActiveTab('requests')}
className={`pb-3 px-1 font-medium transition-colors flex items-center gap-2 ${
className={`pb-3 px-1 font-medium transition-colors flex items-center gap-2 whitespace-nowrap ${
activeTab === 'requests'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
}`}
>
Join Requests
Requests
{joinRequests.length > 0 && (
<span className="px-2 py-0.5 text-xs bg-primary-blue text-white rounded-full">
{joinRequests.length}
@@ -397,17 +400,67 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
</button>
<button
onClick={() => setActiveTab('races')}
className={`pb-3 px-1 font-medium transition-colors ${
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'races'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
}`}
>
Create Race
Races
</button>
<button
onClick={() => setActiveTab('sponsorships')}
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'sponsorships'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
}`}
>
Sponsors
</button>
<button
onClick={() => setActiveTab('fees')}
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'fees'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
}`}
>
Fees
</button>
<button
onClick={() => setActiveTab('wallet')}
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'wallet'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
}`}
>
Wallet
</button>
<button
onClick={() => setActiveTab('prizes')}
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'prizes'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
}`}
>
Prizes
</button>
<button
onClick={() => setActiveTab('liveries')}
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'liveries'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
}`}
>
Liveries
</button>
<button
onClick={() => setActiveTab('protests')}
className={`pb-3 px-1 font-medium transition-colors ${
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'protests'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
@@ -417,7 +470,7 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
</button>
<button
onClick={() => setActiveTab('settings')}
className={`pb-3 px-1 font-medium transition-colors ${
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
activeTab === 'settings'
? 'text-primary-blue border-b-2 border-primary-blue'
: 'text-gray-400 hover:text-white'
@@ -681,6 +734,266 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
</Card>
)}
{activeTab === 'sponsorships' && (
<Card>
<LeagueSponsorshipsSection leagueId={league.id} />
</Card>
)}
{activeTab === 'fees' && (
<Card>
<LeagueMembershipFeesSection leagueId={league.id} />
</Card>
)}
{activeTab === 'wallet' && (
<Card>
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-xl font-semibold text-white">League Wallet</h2>
<p className="text-sm text-gray-400 mt-1">
Track revenue from sponsorships and membership fees
</p>
</div>
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-warning-amber/10 border border-warning-amber/30">
<AlertTriangle className="w-4 h-4 text-warning-amber" />
<span className="text-xs font-medium text-warning-amber">Alpha Preview</span>
</div>
</div>
<div className="grid grid-cols-3 gap-4 mb-6">
<div className="rounded-lg bg-iron-gray/50 border border-charcoal-outline p-4">
<div className="flex items-center gap-2 text-performance-green mb-1">
<Wallet className="w-4 h-4" />
<span className="text-xs font-medium uppercase">Balance</span>
</div>
<div className="text-2xl font-bold text-white">$0.00</div>
</div>
<div className="rounded-lg bg-iron-gray/50 border border-charcoal-outline p-4">
<div className="flex items-center gap-2 text-primary-blue mb-1">
<DollarSign className="w-4 h-4" />
<span className="text-xs font-medium uppercase">Total Revenue</span>
</div>
<div className="text-2xl font-bold text-white">$0.00</div>
</div>
<div className="rounded-lg bg-iron-gray/50 border border-charcoal-outline p-4">
<div className="flex items-center gap-2 text-warning-amber mb-1">
<DollarSign className="w-4 h-4" />
<span className="text-xs font-medium uppercase">Platform Fees</span>
</div>
<div className="text-2xl font-bold text-white">$0.00</div>
</div>
</div>
<div className="text-center py-12">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-iron-gray/50 flex items-center justify-center">
<Wallet className="w-8 h-8 text-gray-500" />
</div>
<h3 className="text-lg font-medium text-white mb-2">No Transactions</h3>
<p className="text-sm text-gray-400 max-w-md mx-auto">
Revenue from sponsorships and membership fees will appear here.
</p>
</div>
<div className="mt-6 p-4 rounded-lg bg-iron-gray/30 border border-charcoal-outline/50">
<p className="text-xs text-gray-500">
<strong className="text-gray-400">Withdrawal Note:</strong> Funds can only be withdrawn after the season is completed.
A 10% platform fee applies to all revenue.
</p>
</div>
</Card>
)}
{activeTab === 'prizes' && (
<Card>
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-xl font-semibold text-white">Season Prizes</h2>
<p className="text-sm text-gray-400 mt-1">
Define prizes for championship positions
</p>
</div>
<Button variant="primary">
Add Prize
</Button>
</div>
<div className="text-center py-12">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-iron-gray/50 flex items-center justify-center">
<Trophy className="w-8 h-8 text-gray-500" />
</div>
<h3 className="text-lg font-medium text-white mb-2">No Prizes Defined</h3>
<p className="text-sm text-gray-400 max-w-md mx-auto">
Add prizes to be awarded to drivers at the end of the season based on final standings.
</p>
</div>
<div className="mt-6 p-4 rounded-lg bg-warning-amber/10 border border-warning-amber/30">
<p className="text-xs text-gray-400">
<strong className="text-warning-amber">Alpha Note:</strong> Prize management is demonstration-only.
In production, prizes are paid from the league wallet after season completion.
</p>
</div>
</Card>
)}
{activeTab === 'liveries' && (
<Card>
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-xl font-semibold text-white">Livery Management</h2>
<p className="text-sm text-gray-400 mt-1">
Upload templates and download composited livery packs
</p>
</div>
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-warning-amber/10 border border-warning-amber/30">
<AlertTriangle className="w-4 h-4 text-warning-amber" />
<span className="text-xs font-medium text-warning-amber">Alpha Preview</span>
</div>
</div>
{/* Livery Templates Section */}
<div className="mb-8">
<h3 className="text-lg font-semibold text-white mb-4">Livery Templates</h3>
<p className="text-sm text-gray-400 mb-4">
Upload base liveries for each car allowed in the league. Position sponsor decals on these templates.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Example car templates */}
{[
{ id: 'car-1', name: 'Porsche 911 GT3 R', hasTemplate: false },
{ id: 'car-2', name: 'Ferrari 488 GT3', hasTemplate: false },
].map((car) => (
<div
key={car.id}
className="rounded-lg border border-charcoal-outline bg-deep-graphite/70 p-4"
>
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-iron-gray/50">
<Car className="w-6 h-6 text-gray-400" />
</div>
<div>
<h4 className="text-sm font-semibold text-white">{car.name}</h4>
<p className="text-xs text-gray-500 mt-0.5">
{car.hasTemplate ? 'Template uploaded' : 'No template'}
</p>
</div>
</div>
<Button variant="secondary" className="px-3 py-1.5">
<Upload className="w-4 h-4 mr-1" />
Upload
</Button>
</div>
</div>
))}
</div>
</div>
{/* Download Livery Pack Section */}
<div className="mb-8">
<h3 className="text-lg font-semibold text-white mb-4">Download Livery Pack</h3>
<p className="text-sm text-gray-400 mb-4">
Generate a .zip file containing all driver liveries with sponsor decals burned in.
Members and admins can use this pack in-game.
</p>
<div className="rounded-lg border border-charcoal-outline bg-iron-gray/30 p-6">
<div className="grid grid-cols-3 gap-4 mb-6">
<div className="rounded-lg bg-iron-gray/50 border border-charcoal-outline p-4">
<div className="flex items-center gap-2 text-primary-blue mb-1">
<User className="w-4 h-4" />
<span className="text-xs font-medium uppercase">Drivers</span>
</div>
<div className="text-2xl font-bold text-white">0</div>
<div className="text-xs text-gray-500">with uploaded liveries</div>
</div>
<div className="rounded-lg bg-iron-gray/50 border border-charcoal-outline p-4">
<div className="flex items-center gap-2 text-performance-green mb-1">
<Paintbrush className="w-4 h-4" />
<span className="text-xs font-medium uppercase">Templates</span>
</div>
<div className="text-2xl font-bold text-white">0</div>
<div className="text-xs text-gray-500">cars configured</div>
</div>
<div className="rounded-lg bg-iron-gray/50 border border-charcoal-outline p-4">
<div className="flex items-center gap-2 text-warning-amber mb-1">
<DollarSign className="w-4 h-4" />
<span className="text-xs font-medium uppercase">Sponsors</span>
</div>
<div className="text-2xl font-bold text-white">0</div>
<div className="text-xs text-gray-500">active this season</div>
</div>
</div>
<div className="flex items-center gap-4">
<Button
variant="primary"
onClick={async () => {
setDownloadingLiveryPack(true);
try {
// Alpha: Simulate pack generation
await new Promise(resolve => setTimeout(resolve, 1500));
alert('Livery pack generation is demonstration-only in alpha.');
} finally {
setDownloadingLiveryPack(false);
}
}}
disabled={downloadingLiveryPack}
className="px-6"
>
<Download className="w-4 h-4 mr-2" />
{downloadingLiveryPack ? 'Generating...' : 'Download Livery Pack'}
</Button>
<p className="text-xs text-gray-500">
Estimated size: ~50MB Includes all drivers
</p>
</div>
</div>
</div>
{/* Decal Placement Info */}
<div className="mb-6">
<h3 className="text-lg font-semibold text-white mb-4">How It Works</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="rounded-lg border border-charcoal-outline bg-iron-gray/30 p-4">
<h4 className="text-sm font-semibold text-white mb-2">1. Template Setup</h4>
<p className="text-xs text-gray-400">
Upload base liveries for each car. Position where sponsor logos will appear.
</p>
</div>
<div className="rounded-lg border border-charcoal-outline bg-iron-gray/30 p-4">
<h4 className="text-sm font-semibold text-white mb-2">2. Driver Liveries</h4>
<p className="text-xs text-gray-400">
Drivers upload their personal liveries. Must be clean (no logos/text).
</p>
</div>
<div className="rounded-lg border border-charcoal-outline bg-iron-gray/30 p-4">
<h4 className="text-sm font-semibold text-white mb-2">3. Sponsor Decals</h4>
<p className="text-xs text-gray-400">
Sponsor logos are automatically placed based on your template positions.
</p>
</div>
<div className="rounded-lg border border-charcoal-outline bg-iron-gray/30 p-4">
<h4 className="text-sm font-semibold text-white mb-2">4. Pack Generation</h4>
<p className="text-xs text-gray-400">
Download .zip with all liveries composited. Ready for in-game use.
</p>
</div>
</div>
</div>
<div className="p-4 rounded-lg bg-warning-amber/10 border border-warning-amber/30">
<p className="text-xs text-gray-400">
<strong className="text-warning-amber">Alpha Note:</strong> Livery compositing and pack generation are demonstration-only.
In production, the system automatically validates liveries, places sponsor decals, and generates downloadable packs.
The companion app will also auto-install packs.
</p>
</div>
</Card>
)}
{activeTab === 'settings' && (
<Card>
<h2 className="text-xl font-semibold text-white mb-4">League Settings</h2>