extract components from website
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import Card from '@/components/ui/Card';
|
||||
import PointsTable from '@/components/leagues/PointsTable';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { LeagueDetailPageViewModel } from '@/lib/view-models/LeagueDetailPageViewModel';
|
||||
|
||||
@@ -124,49 +125,7 @@ export default function LeagueRulebookPage() {
|
||||
</div>
|
||||
|
||||
{/* Points Table */}
|
||||
<Card>
|
||||
<h2 className="text-lg font-semibold text-white mb-4">Points Distribution</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-charcoal-outline">
|
||||
<th className="text-left py-3 px-4 font-medium text-gray-400">Position</th>
|
||||
<th className="text-right py-3 px-4 font-medium text-gray-400">Points</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{positionPoints.map(({ position, points }) => (
|
||||
<tr
|
||||
key={position}
|
||||
className={`border-b border-charcoal-outline/50 transition-colors hover:bg-iron-gray/30 ${
|
||||
position <= 3 ? 'bg-iron-gray/20' : ''
|
||||
}`}
|
||||
>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold ${
|
||||
position === 1 ? 'bg-yellow-500 text-black' :
|
||||
position === 2 ? 'bg-gray-400 text-black' :
|
||||
position === 3 ? 'bg-amber-600 text-white' :
|
||||
'bg-charcoal-outline text-white'
|
||||
}`}>
|
||||
{position}
|
||||
</div>
|
||||
<span className="text-white font-medium">
|
||||
{position === 1 ? '1st' : position === 2 ? '2nd' : position === 3 ? '3rd' : `${position}th`}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-right">
|
||||
<span className="text-white font-semibold tabular-nums">{points}</span>
|
||||
<span className="text-gray-500 ml-1">pts</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
<PointsTable points={positionPoints} />
|
||||
|
||||
{/* Bonus Points */}
|
||||
{primaryChampionship?.bonusSummary && primaryChampionship.bonusSummary.length > 0 && (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { ReadonlyLeagueInfo } from '@/components/leagues/ReadonlyLeagueInfo';
|
||||
import DriverSummaryPill from '@/components/profile/DriverSummaryPill';
|
||||
import Button from '@/components/ui/Button';
|
||||
import LeagueOwnershipTransfer from '@/components/leagues/LeagueOwnershipTransfer';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles';
|
||||
@@ -22,9 +21,6 @@ export default function LeagueSettingsPage() {
|
||||
const [settings, setSettings] = useState<LeagueSettingsViewModel | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [showTransferDialog, setShowTransferDialog] = useState(false);
|
||||
const [selectedNewOwner, setSelectedNewOwner] = useState<string>('');
|
||||
const [transferring, setTransferring] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -58,20 +54,12 @@ export default function LeagueSettingsPage() {
|
||||
|
||||
const ownerSummary = settings?.owner || null;
|
||||
|
||||
const handleTransferOwnership = async () => {
|
||||
if (!selectedNewOwner || !settings) return;
|
||||
|
||||
setTransferring(true);
|
||||
const handleTransferOwnership = async (newOwnerId: string) => {
|
||||
try {
|
||||
await leagueSettingsService.transferOwnership(leagueId, currentDriverId, selectedNewOwner);
|
||||
|
||||
setShowTransferDialog(false);
|
||||
await leagueSettingsService.transferOwnership(leagueId, currentDriverId, newOwnerId);
|
||||
router.refresh();
|
||||
} catch (err) {
|
||||
console.error('Failed to transfer ownership:', err);
|
||||
alert(err instanceof Error ? err.message : 'Failed to transfer ownership');
|
||||
} finally {
|
||||
setTransferring(false);
|
||||
throw err; // Let the component handle the error
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,76 +116,11 @@ export default function LeagueSettingsPage() {
|
||||
<div className="space-y-4">
|
||||
<ReadonlyLeagueInfo league={settings.league} configForm={settings.config} />
|
||||
|
||||
{/* League Owner - Compact */}
|
||||
<div className="rounded-xl border border-charcoal-outline bg-gradient-to-br from-iron-gray/40 to-iron-gray/20 p-5">
|
||||
<h3 className="text-sm font-semibold text-gray-400 mb-3">League Owner</h3>
|
||||
{ownerSummary ? (
|
||||
<DriverSummaryPill
|
||||
driver={ownerSummary.driver}
|
||||
rating={ownerSummary.rating}
|
||||
rank={ownerSummary.rank}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">Loading owner details...</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Transfer Ownership - Owner Only */}
|
||||
{settings.league.ownerId === currentDriverId && settings.members.length > 0 && (
|
||||
<div className="rounded-xl border border-charcoal-outline bg-gradient-to-br from-iron-gray/40 to-iron-gray/20 p-5">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<UserCog className="w-4 h-4 text-gray-400" />
|
||||
<h3 className="text-sm font-semibold text-gray-400">Transfer Ownership</h3>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mb-4">
|
||||
Transfer league ownership to another active member. You will become an admin.
|
||||
</p>
|
||||
|
||||
{!showTransferDialog ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setShowTransferDialog(true)}
|
||||
>
|
||||
Transfer Ownership
|
||||
</Button>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<select
|
||||
value={selectedNewOwner}
|
||||
onChange={(e) => setSelectedNewOwner(e.target.value)}
|
||||
className="w-full rounded-lg border border-charcoal-outline bg-charcoal-card px-3 py-2 text-sm text-white focus:border-primary-blue focus:outline-none"
|
||||
>
|
||||
<option value="">Select new owner...</option>
|
||||
{settings.members.map((member) => (
|
||||
<option key={member.driver.id} value={member.driver.id}>
|
||||
{member.driver.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleTransferOwnership}
|
||||
disabled={!selectedNewOwner || transferring}
|
||||
>
|
||||
{transferring ? 'Transferring...' : 'Confirm Transfer'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowTransferDialog(false);
|
||||
setSelectedNewOwner('');
|
||||
}}
|
||||
disabled={transferring}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<LeagueOwnershipTransfer
|
||||
settings={settings}
|
||||
currentDriverId={currentDriverId}
|
||||
onTransferOwnership={handleTransferOwnership}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import StandingsTable from '@/components/leagues/StandingsTable';
|
||||
import LeagueChampionshipStats from '@/components/leagues/LeagueChampionshipStats';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import type { LeagueMembership, MembershipRole } from '@/lib/types';
|
||||
@@ -86,54 +87,10 @@ export default function LeagueStandingsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const leader = standings[0];
|
||||
const totalRaces = Math.max(...standings.map(s => s.races), 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Championship Stats */}
|
||||
{standings.length > 0 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-full bg-yellow-500/10 flex items-center justify-center">
|
||||
<span className="text-2xl">🏆</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-400 mb-1">Championship Leader</p>
|
||||
<p className="font-bold text-white">{drivers.find(d => d.id === leader?.driverId)?.name || 'N/A'}</p>
|
||||
<p className="text-sm text-yellow-400 font-medium">{leader?.points || 0} points</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-full bg-primary-blue/10 flex items-center justify-center">
|
||||
<span className="text-2xl">🏁</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-400 mb-1">Races Completed</p>
|
||||
<p className="text-2xl font-bold text-white">{totalRaces}</p>
|
||||
<p className="text-sm text-gray-400">Season in progress</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-full bg-performance-green/10 flex items-center justify-center">
|
||||
<span className="text-2xl">👥</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-400 mb-1">Active Drivers</p>
|
||||
<p className="text-2xl font-bold text-white">{standings.length}</p>
|
||||
<p className="text-sm text-gray-400">Competing for points</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
<LeagueChampionshipStats standings={standings} drivers={drivers} />
|
||||
|
||||
<Card>
|
||||
<h2 className="text-xl font-semibold text-white mb-4">Championship Standings</h2>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import PenaltyFAB from '@/components/leagues/PenaltyFAB';
|
||||
import QuickPenaltyModal from '@/components/leagues/QuickPenaltyModal';
|
||||
import { ReviewProtestModal } from '@/components/leagues/ReviewProtestModal';
|
||||
import StewardingStats from '@/components/leagues/StewardingStats';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
@@ -183,29 +184,11 @@ export default function LeagueStewardingPage() {
|
||||
|
||||
{/* Stats summary */}
|
||||
{!loading && stewardingData && (
|
||||
<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-warning-amber mb-1">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span className="text-xs font-medium uppercase">Pending Review</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-white">{stewardingData.totalPending}</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">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
<span className="text-xs font-medium uppercase">Resolved</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-white">{stewardingData.totalResolved}</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-iron-gray/50 border border-charcoal-outline p-4">
|
||||
<div className="flex items-center gap-2 text-red-400 mb-1">
|
||||
<Gavel className="w-4 h-4" />
|
||||
<span className="text-xs font-medium uppercase">Penalties</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-white">{stewardingData.totalPenalties}</div>
|
||||
</div>
|
||||
</div>
|
||||
<StewardingStats
|
||||
totalPending={stewardingData.totalPending}
|
||||
totalResolved={stewardingData.totalResolved}
|
||||
totalPenalties={stewardingData.totalPenalties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Tab navigation */}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import TransactionRow from '@/components/leagues/TransactionRow';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { LeagueWalletViewModel } from '@/lib/view-models/LeagueWalletViewModel';
|
||||
import {
|
||||
@@ -21,69 +22,6 @@ import {
|
||||
Calendar
|
||||
} from 'lucide-react';
|
||||
|
||||
function TransactionRow({ transaction }: { transaction: any }) {
|
||||
const isIncoming = transaction.amount > 0;
|
||||
|
||||
const typeIcons = {
|
||||
sponsorship: DollarSign,
|
||||
membership: CreditCard,
|
||||
withdrawal: ArrowUpRight,
|
||||
prize: TrendingUp,
|
||||
};
|
||||
const TypeIcon = typeIcons[transaction.type];
|
||||
|
||||
const statusConfig = {
|
||||
completed: { color: 'text-performance-green', bg: 'bg-performance-green/10', icon: CheckCircle },
|
||||
pending: { color: 'text-warning-amber', bg: 'bg-warning-amber/10', icon: Clock },
|
||||
failed: { color: 'text-racing-red', bg: 'bg-racing-red/10', icon: XCircle },
|
||||
};
|
||||
const status = statusConfig[transaction.status];
|
||||
const StatusIcon = status.icon;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between p-4 border-b border-charcoal-outline last:border-b-0 hover:bg-iron-gray/30 transition-colors">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`flex h-10 w-10 items-center justify-center rounded-lg ${isIncoming ? 'bg-performance-green/10' : 'bg-iron-gray/50'}`}>
|
||||
{isIncoming ? (
|
||||
<ArrowDownLeft className="w-5 h-5 text-performance-green" />
|
||||
) : (
|
||||
<ArrowUpRight className="w-5 h-5 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-white">{transaction.description}</span>
|
||||
<span className={`px-2 py-0.5 rounded text-xs ${status.bg} ${status.color}`}>
|
||||
{transaction.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500 mt-1">
|
||||
<TypeIcon className="w-3 h-3" />
|
||||
<span className="capitalize">{transaction.type}</span>
|
||||
{transaction.reference && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<span>{transaction.reference}</span>
|
||||
</>
|
||||
)}
|
||||
<span>•</span>
|
||||
<span>{transaction.formattedDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className={`font-semibold ${isIncoming ? 'text-performance-green' : 'text-white'}`}>
|
||||
{transaction.formattedAmount}
|
||||
</div>
|
||||
{transaction.fee > 0 && (
|
||||
<div className="text-xs text-gray-500">
|
||||
Fee: ${transaction.fee.toFixed(2)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LeagueWalletPage() {
|
||||
const params = useParams();
|
||||
|
||||
Reference in New Issue
Block a user