website refactor

This commit is contained in:
2026-01-14 23:46:04 +01:00
parent c1a86348d7
commit 4a2d7d15a5
294 changed files with 5637 additions and 3418 deletions

View File

@@ -1,4 +1,4 @@
import Card from '@/components/ui/Card';
import Card from '@/ui/Card';
interface BonusPointsCardProps {
bonusSummary: string[];

View File

@@ -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 = {

View File

@@ -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,

View File

@@ -1,4 +1,4 @@
import Card from '@/components/ui/Card';
import Card from '@/ui/Card';
interface DropRulesExplanationProps {
dropPolicyDescription: string;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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>
);
}

View File

@@ -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,

View File

@@ -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';

View File

@@ -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';
// ============================================================================

View 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>
);
}

View 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>
);
}

View File

@@ -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 = [

View File

@@ -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';

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
import Card from '@/components/ui/Card';
import Card from '@/ui/Card';
interface PointsBreakdownTableProps {
positionPoints: Array<{ position: number; points: number }>;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import Card from '@/components/ui/Card';
import Card from '@/ui/Card';
interface PointsTableProps {
title?: string;

View File

@@ -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';

View 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>
);
}

View 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>
);
}

View File

@@ -1,4 +1,4 @@
import Card from '@/components/ui/Card';
import Card from '@/ui/Card';
interface ScoringOverviewCardProps {
gameName: string;

View File

@@ -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;

View 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>
);
}

View 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>
);
}

View File

@@ -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 = {

View File

@@ -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>
);
}
}