website refactor
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import Card from '@/components/ui/Card';
|
||||
import Card from '@/ui/Card';
|
||||
|
||||
interface BonusPointsCardProps {
|
||||
bonusSummary: string[];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Card from '@/components/ui/Card';
|
||||
import Card from '@/ui/Card';
|
||||
import type { LeagueScoringChampionshipViewModel } from '@/lib/view-models/LeagueScoringChampionshipViewModel';
|
||||
|
||||
type PointsPreviewRow = {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import LeagueReviewSummary from '@/components/leagues/LeagueReviewSummary';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Heading from '@/components/ui/Heading';
|
||||
import Input from '@/components/ui/Input';
|
||||
import Button from '@/ui/Button';
|
||||
import Card from '@/ui/Card';
|
||||
import Heading from '@/ui/Heading';
|
||||
import Input from '@/ui/Input';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import {
|
||||
AlertCircle,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Card from '@/components/ui/Card';
|
||||
import Card from '@/ui/Card';
|
||||
|
||||
interface DropRulesExplanationProps {
|
||||
dropPolicyDescription: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trophy, Sparkles, Search } from 'lucide-react';
|
||||
import Heading from '@/components/ui/Heading';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Heading from '@/ui/Heading';
|
||||
import Button from '@/ui/Button';
|
||||
import Card from '@/ui/Card';
|
||||
|
||||
interface EmptyStateProps {
|
||||
title: string;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { AlertTriangle, TestTube, CheckCircle2 } from 'lucide-react';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Button from '@/ui/Button';
|
||||
|
||||
interface EndRaceModalProps {
|
||||
raceId: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trophy, Plus } from 'lucide-react';
|
||||
import Heading from '@/components/ui/Heading';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Heading from '@/ui/Heading';
|
||||
import Button from '@/ui/Button';
|
||||
|
||||
interface StatItem {
|
||||
value: number;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { FileText, Gamepad2, AlertCircle, Check } from 'lucide-react';
|
||||
import Input from '@/components/ui/Input';
|
||||
import Input from '@/ui/Input';
|
||||
import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
|
||||
|
||||
interface LeagueBasicsSectionProps {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
|
||||
import { getLeagueCoverClasses } from '@/lib/leagueCovers';
|
||||
import PlaceholderImage from '@/components/ui/PlaceholderImage';
|
||||
import PlaceholderImage from '@/ui/PlaceholderImage';
|
||||
import { getMediaUrl } from '@/lib/utilities/media';
|
||||
|
||||
interface LeagueCardProps {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
interface LeagueChampionshipStatsProps {
|
||||
standings: Array<{
|
||||
@@ -21,45 +28,45 @@ export function LeagueChampionshipStats({ standings, drivers }: LeagueChampionsh
|
||||
const totalRaces = Math.max(...standings.map(s => s.racesFinished), 0);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<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?.totalPoints || 0} points</p>
|
||||
</div>
|
||||
</div>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Surface variant="muted" rounded="full" padding={3} style={{ backgroundColor: 'rgba(250, 204, 21, 0.1)' }}>
|
||||
<Text size="2xl">🏆</Text>
|
||||
</Surface>
|
||||
<Box>
|
||||
<Text size="xs" color="text-gray-400" block mb={1}>Championship Leader</Text>
|
||||
<Text weight="bold" color="text-white" block>{drivers.find(d => d.id === leader?.driverId)?.name || 'N/A'}</Text>
|
||||
<Text size="sm" color="text-warning-amber" weight="medium">{leader?.totalPoints || 0} points</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</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>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Surface variant="muted" rounded="full" padding={3} style={{ backgroundColor: 'rgba(59, 130, 246, 0.1)' }}>
|
||||
<Text size="2xl">🏁</Text>
|
||||
</Surface>
|
||||
<Box>
|
||||
<Text size="xs" color="text-gray-400" block mb={1}>Races Completed</Text>
|
||||
<Text size="2xl" weight="bold" color="text-white" block>{totalRaces}</Text>
|
||||
<Text size="sm" color="text-gray-400">Season in progress</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</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>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Surface variant="muted" rounded="full" padding={3} style={{ backgroundColor: 'rgba(16, 185, 129, 0.1)' }}>
|
||||
<Text size="2xl">👥</Text>
|
||||
</Surface>
|
||||
<Box>
|
||||
<Text size="xs" color="text-gray-400" block mb={1}>Active Drivers</Text>
|
||||
<Text size="2xl" weight="bold" color="text-white" block>{standings.length}</Text>
|
||||
<Text size="sm" color="text-gray-400">Competing for points</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Card>
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Card from '@/ui/Card';
|
||||
import Button from '@/ui/Button';
|
||||
import {
|
||||
Move,
|
||||
RotateCw,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import DriverSummaryPill from '@/components/profile/DriverSummaryPill';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Button from '@/ui/Button';
|
||||
import { UserCog } from 'lucide-react';
|
||||
import { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel';
|
||||
import { DriverViewModel } from '@/lib/view-models/DriverViewModel';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { User, Users2, Info, Check, HelpCircle, X } from 'lucide-react';
|
||||
import { useState, useRef, useEffect, useMemo } from 'react';
|
||||
import type * as React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import Input from '@/components/ui/Input';
|
||||
import Input from '@/ui/Input';
|
||||
import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
|
||||
|
||||
// ============================================================================
|
||||
|
||||
70
apps/website/components/leagues/LeagueSummaryCard.tsx
Normal file
70
apps/website/components/leagues/LeagueSummaryCard.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
|
||||
interface LeagueSummaryCardProps {
|
||||
league: {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
settings: {
|
||||
maxDrivers: number;
|
||||
qualifyingFormat: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function LeagueSummaryCard({ league }: LeagueSummaryCardProps) {
|
||||
return (
|
||||
<Card p={0} style={{ overflow: 'hidden' }}>
|
||||
<Box p={4}>
|
||||
<Stack direction="row" align="center" gap={4} mb={4}>
|
||||
<Box style={{ width: '3.5rem', height: '3.5rem', borderRadius: '0.75rem', overflow: 'hidden', backgroundColor: '#262626', flexShrink: 0 }}>
|
||||
<Image src={`/media/league-logo/${league.id}`} alt={league.name} width={56} height={56} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
</Box>
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="xs" color="text-gray-500" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }} block mb={0.5}>League</Text>
|
||||
<Heading level={3} style={{ fontSize: '1rem' }}>{league.name}</Heading>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{league.description && (
|
||||
<Text size="sm" color="text-gray-400" block mb={4} style={{ display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{league.description}</Text>
|
||||
)}
|
||||
|
||||
<Box mb={4}>
|
||||
<Grid cols={2} gap={3}>
|
||||
<Surface variant="dark" rounded="lg" padding={3}>
|
||||
<Text size="xs" color="text-gray-500" block mb={1}>Max Drivers</Text>
|
||||
<Text weight="medium" color="text-white">{league.settings.maxDrivers}</Text>
|
||||
</Surface>
|
||||
<Surface variant="dark" rounded="lg" padding={3}>
|
||||
<Text size="xs" color="text-gray-500" block mb={1}>Format</Text>
|
||||
<Text weight="medium" color="text-white" style={{ textTransform: 'capitalize' }}>{league.settings.qualifyingFormat}</Text>
|
||||
</Surface>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Link href={`/leagues/${league.id}`} variant="ghost">
|
||||
<Button variant="secondary" fullWidth icon={<Icon icon={ArrowRight} size={4} />}>
|
||||
View League
|
||||
</Button>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
36
apps/website/components/leagues/LeagueTabs.tsx
Normal file
36
apps/website/components/leagues/LeagueTabs.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Link } from '@/ui/Link';
|
||||
|
||||
interface Tab {
|
||||
label: string;
|
||||
href: string;
|
||||
exact?: boolean;
|
||||
}
|
||||
|
||||
interface LeagueTabsProps {
|
||||
tabs: Tab[];
|
||||
}
|
||||
|
||||
export function LeagueTabs({ tabs }: LeagueTabsProps) {
|
||||
return (
|
||||
<Box style={{ borderBottom: '1px solid #262626' }}>
|
||||
<Stack direction="row" gap={6} style={{ overflowX: 'auto' }}>
|
||||
{tabs.map((tab) => (
|
||||
<Link
|
||||
key={tab.href}
|
||||
href={tab.href}
|
||||
variant="ghost"
|
||||
>
|
||||
<Box pb={3} px={1}>
|
||||
<span style={{ fontWeight: 500, whiteSpace: 'nowrap' }}>{tab.label}</span>
|
||||
</Box>
|
||||
</Link>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
} from 'lucide-react';
|
||||
import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
|
||||
import type { Weekday } from '@/lib/types/Weekday';
|
||||
import Input from '@/components/ui/Input';
|
||||
import RangeField from '@/components/ui/RangeField';
|
||||
import Input from '@/ui/Input';
|
||||
import RangeField from '@/ui/RangeField';
|
||||
|
||||
// Common time zones for racing leagues
|
||||
const TIME_ZONES = [
|
||||
|
||||
@@ -19,10 +19,10 @@ import {
|
||||
Timer,
|
||||
} from 'lucide-react';
|
||||
import LeagueCard from '@/components/leagues/LeagueCard';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Input from '@/components/ui/Input';
|
||||
import Heading from '@/components/ui/Heading';
|
||||
import Button from '@/ui/Button';
|
||||
import Card from '@/ui/Card';
|
||||
import Input from '@/ui/Input';
|
||||
import Heading from '@/ui/Heading';
|
||||
import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
|
||||
import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Search } from 'lucide-react';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/ui/Button';
|
||||
import Card from '@/ui/Card';
|
||||
|
||||
interface NoResultsStateProps {
|
||||
icon?: React.ElementType;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Card from '@/components/ui/Card';
|
||||
import Card from '@/ui/Card';
|
||||
|
||||
interface PointsBreakdownTableProps {
|
||||
positionPoints: Array<{ position: number; points: number }>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Card from '@/ui/Card';
|
||||
|
||||
interface PointsTableProps {
|
||||
title?: string;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Button from '@/ui/Button';
|
||||
import { usePenaltyMutation } from "@/lib/hooks/league/usePenaltyMutation";
|
||||
import { AlertTriangle, Clock, Flag, Zap } from 'lucide-react';
|
||||
|
||||
|
||||
40
apps/website/components/leagues/RulebookTabs.tsx
Normal file
40
apps/website/components/leagues/RulebookTabs.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
export type RulebookSection = 'scoring' | 'conduct' | 'protests' | 'penalties';
|
||||
|
||||
interface RulebookTabsProps {
|
||||
activeSection: RulebookSection;
|
||||
onSectionChange: (section: RulebookSection) => void;
|
||||
}
|
||||
|
||||
export function RulebookTabs({ activeSection, onSectionChange }: RulebookTabsProps) {
|
||||
const sections: { id: RulebookSection; label: string }[] = [
|
||||
{ id: 'scoring', label: 'Scoring' },
|
||||
{ id: 'conduct', label: 'Conduct' },
|
||||
{ id: 'protests', label: 'Protests' },
|
||||
{ id: 'penalties', label: 'Penalties' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Surface variant="muted" rounded="lg" padding={1} style={{ backgroundColor: '#0f1115', border: '1px solid #262626' }}>
|
||||
<Box style={{ display: 'flex', gap: '0.25rem' }}>
|
||||
{sections.map((section) => (
|
||||
<Button
|
||||
key={section.id}
|
||||
variant={activeSection === section.id ? 'secondary' : 'ghost'}
|
||||
onClick={() => onSectionChange(section.id)}
|
||||
fullWidth
|
||||
size="sm"
|
||||
>
|
||||
{section.label}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
77
apps/website/components/leagues/ScheduleRaceCard.tsx
Normal file
77
apps/website/components/leagues/ScheduleRaceCard.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Calendar, Clock, MapPin, Car, Trophy } from 'lucide-react';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
interface Race {
|
||||
id: string;
|
||||
name: string;
|
||||
track?: string;
|
||||
car?: string;
|
||||
scheduledAt: string;
|
||||
status: string;
|
||||
sessionType?: string;
|
||||
isPast?: boolean;
|
||||
}
|
||||
|
||||
interface ScheduleRaceCardProps {
|
||||
race: Race;
|
||||
}
|
||||
|
||||
export function ScheduleRaceCard({ race }: ScheduleRaceCardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<Stack gap={4}>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Box style={{ width: '0.75rem', height: '0.75rem', borderRadius: '9999px', backgroundColor: race.isPast ? '#10b981' : '#3b82f6' }} />
|
||||
<Heading level={3} style={{ fontSize: '1.125rem' }}>{race.name}</Heading>
|
||||
<Badge variant={race.status === 'completed' ? 'success' : 'primary'}>
|
||||
{race.status === 'completed' ? 'Completed' : 'Scheduled'}
|
||||
</Badge>
|
||||
</Stack>
|
||||
|
||||
<Grid cols={4} gap={4}>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Calendar} size={4} color="#9ca3af" />
|
||||
<Text size="sm" color="text-gray-300">{new Date(race.scheduledAt).toLocaleDateString()}</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Clock} size={4} color="#9ca3af" />
|
||||
<Text size="sm" color="text-gray-300">{new Date(race.scheduledAt).toLocaleTimeString()}</Text>
|
||||
</Stack>
|
||||
|
||||
{race.track && (
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={MapPin} size={4} color="#9ca3af" />
|
||||
<Text size="sm" color="text-gray-300" truncate>{race.track}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{race.car && (
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Car} size={4} color="#9ca3af" />
|
||||
<Text size="sm" color="text-gray-300" truncate>{race.car}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{race.sessionType && (
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Trophy} size={4} color="#9ca3af" />
|
||||
<Text size="sm" color="text-gray-400">{race.sessionType} Session</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import Card from '@/components/ui/Card';
|
||||
import Card from '@/ui/Card';
|
||||
|
||||
interface ScoringOverviewCardProps {
|
||||
gameName: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Search, Filter } from 'lucide-react';
|
||||
import Input from '@/components/ui/Input';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/ui/Input';
|
||||
import Button from '@/ui/Button';
|
||||
|
||||
interface Category {
|
||||
id: string;
|
||||
|
||||
75
apps/website/components/leagues/SponsorshipRequestCard.tsx
Normal file
75
apps/website/components/leagues/SponsorshipRequestCard.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { CheckCircle, XCircle, AlertCircle } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
interface SponsorshipRequest {
|
||||
id: string;
|
||||
sponsorName: string;
|
||||
status: 'pending' | 'approved' | 'rejected';
|
||||
requestedAt: string;
|
||||
slotName: string;
|
||||
}
|
||||
|
||||
interface SponsorshipRequestCardProps {
|
||||
request: SponsorshipRequest;
|
||||
}
|
||||
|
||||
export function SponsorshipRequestCard({ request }: SponsorshipRequestCardProps) {
|
||||
const statusVariant = {
|
||||
pending: 'warning' as const,
|
||||
approved: 'success' as const,
|
||||
rejected: 'danger' as const,
|
||||
}[request.status];
|
||||
|
||||
const statusIcon = {
|
||||
pending: AlertCircle,
|
||||
approved: CheckCircle,
|
||||
rejected: XCircle,
|
||||
}[request.status];
|
||||
|
||||
const statusColor = {
|
||||
pending: '#f59e0b',
|
||||
approved: '#10b981',
|
||||
rejected: '#ef4444',
|
||||
}[request.status];
|
||||
|
||||
return (
|
||||
<Surface
|
||||
variant="muted"
|
||||
rounded="lg"
|
||||
border
|
||||
padding={4}
|
||||
style={{
|
||||
backgroundColor: `${statusColor}0D`,
|
||||
borderColor: statusColor
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" align="start" justify="between">
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Stack direction="row" align="center" gap={3} mb={2}>
|
||||
<Icon icon={statusIcon} size={5} color={statusColor} />
|
||||
<Text weight="semibold" color="text-white">{request.sponsorName}</Text>
|
||||
<Badge variant={statusVariant}>
|
||||
{request.status}
|
||||
</Badge>
|
||||
</Stack>
|
||||
|
||||
<Text size="sm" color="text-gray-300" block mb={2}>
|
||||
Requested: {request.slotName}
|
||||
</Text>
|
||||
|
||||
<Text size="xs" color="text-gray-400" block>
|
||||
{new Date(request.requestedAt).toLocaleDateString()}
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
67
apps/website/components/leagues/SponsorshipSlotCard.tsx
Normal file
67
apps/website/components/leagues/SponsorshipSlotCard.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { DollarSign } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
interface SponsorshipSlot {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
price: number;
|
||||
currency: string;
|
||||
isAvailable: boolean;
|
||||
sponsoredBy?: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SponsorshipSlotCardProps {
|
||||
slot: SponsorshipSlot;
|
||||
}
|
||||
|
||||
export function SponsorshipSlotCard({ slot }: SponsorshipSlotCardProps) {
|
||||
return (
|
||||
<Surface
|
||||
variant="muted"
|
||||
rounded="lg"
|
||||
border
|
||||
padding={4}
|
||||
style={{
|
||||
backgroundColor: slot.isAvailable ? 'rgba(16, 185, 129, 0.05)' : 'rgba(38, 38, 38, 0.3)',
|
||||
borderColor: slot.isAvailable ? '#10b981' : '#262626'
|
||||
}}
|
||||
>
|
||||
<Stack gap={3}>
|
||||
<Stack direction="row" align="start" justify="between">
|
||||
<Heading level={4}>{slot.name}</Heading>
|
||||
<Badge variant={slot.isAvailable ? 'success' : 'default'}>
|
||||
{slot.isAvailable ? 'Available' : 'Taken'}
|
||||
</Badge>
|
||||
</Stack>
|
||||
|
||||
<Text size="sm" color="text-gray-300" block>{slot.description}</Text>
|
||||
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={DollarSign} size={4} color="#9ca3af" />
|
||||
<Text weight="semibold" color="text-white">
|
||||
{slot.price} {slot.currency}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
{!slot.isAvailable && slot.sponsoredBy && (
|
||||
<Box pt={3} style={{ borderTop: '1px solid #262626' }}>
|
||||
<Text size="xs" color="text-gray-400" block mb={1}>Sponsored by</Text>
|
||||
<Text size="sm" weight="medium" color="text-white">{slot.sponsoredBy.name}</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import CountryFlag from '@/components/ui/CountryFlag';
|
||||
import PlaceholderImage from '@/components/ui/PlaceholderImage';
|
||||
import CountryFlag from '@/ui/CountryFlag';
|
||||
import PlaceholderImage from '@/ui/PlaceholderImage';
|
||||
|
||||
// League role display data
|
||||
const leagueRoleDisplay = {
|
||||
|
||||
@@ -1,91 +1,77 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ArrowDownLeft,
|
||||
ArrowUpRight,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
CreditCard,
|
||||
DollarSign,
|
||||
TrendingUp,
|
||||
XCircle
|
||||
} from 'lucide-react';
|
||||
import { ArrowUpRight, ArrowDownRight, DollarSign, TrendingUp, LucideIcon } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
interface Transaction {
|
||||
id: string;
|
||||
amount: number;
|
||||
type: 'sponsorship' | 'membership' | 'withdrawal' | 'prize';
|
||||
status: 'completed' | 'pending' | 'failed';
|
||||
type: string;
|
||||
description: string;
|
||||
reference?: string;
|
||||
formattedDate: string;
|
||||
formattedAmount: string;
|
||||
fee: number;
|
||||
typeColor: string;
|
||||
status: string;
|
||||
statusColor: string;
|
||||
amountColor: string;
|
||||
}
|
||||
|
||||
interface TransactionRowProps {
|
||||
transaction: Transaction;
|
||||
}
|
||||
|
||||
export default function TransactionRow({ transaction }: TransactionRowProps) {
|
||||
const isIncoming = transaction.amount > 0;
|
||||
|
||||
const typeIcons = {
|
||||
sponsorship: DollarSign,
|
||||
membership: CreditCard,
|
||||
withdrawal: ArrowUpRight,
|
||||
prize: TrendingUp,
|
||||
export function TransactionRow({ transaction }: TransactionRowProps) {
|
||||
const getTransactionIcon = (type: string): LucideIcon => {
|
||||
switch (type) {
|
||||
case 'deposit': return ArrowUpRight;
|
||||
case 'withdrawal': return ArrowDownRight;
|
||||
case 'sponsorship': return DollarSign;
|
||||
case 'prize': return TrendingUp;
|
||||
default: return DollarSign;
|
||||
}
|
||||
};
|
||||
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>
|
||||
<Surface
|
||||
variant="muted"
|
||||
rounded="lg"
|
||||
border
|
||||
padding={4}
|
||||
style={{ backgroundColor: 'rgba(38, 38, 38, 0.3)', borderColor: '#262626' }}
|
||||
>
|
||||
<Stack direction="row" align="center" justify="between">
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Box style={{ flexShrink: 0 }}>
|
||||
<Icon icon={getTransactionIcon(transaction.type)} size={4} color={transaction.typeColor} />
|
||||
</Box>
|
||||
<Box style={{ minWidth: 0, flex: 1 }}>
|
||||
<Text size="sm" weight="medium" color="text-white" block truncate>
|
||||
{transaction.description}
|
||||
</Text>
|
||||
<Stack direction="row" align="center" gap={2} mt={1}>
|
||||
<Text size="xs" color="text-gray-500">{transaction.formattedDate}</Text>
|
||||
<Text size="xs" color="text-gray-500">•</Text>
|
||||
<Text size="xs" style={{ color: transaction.typeColor, textTransform: 'capitalize' }}>
|
||||
{transaction.type}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500">•</Text>
|
||||
<Text size="xs" style={{ color: transaction.statusColor, textTransform: 'capitalize' }}>
|
||||
{transaction.status}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Box style={{ textAlign: 'right' }}>
|
||||
<Text size="lg" weight="semibold" style={{ color: transaction.amountColor }}>
|
||||
{transaction.formattedAmount}
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user