wip
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user