website refactor
This commit is contained in:
@@ -4,7 +4,7 @@ import { Text } from '@/ui/Text';
|
||||
|
||||
interface JoinRequestItemProps {
|
||||
driverId: string;
|
||||
requestedAt: string | Date;
|
||||
formattedRequestedAt: string;
|
||||
onApprove: () => void;
|
||||
onReject: () => void;
|
||||
isApproving?: boolean;
|
||||
@@ -13,7 +13,7 @@ interface JoinRequestItemProps {
|
||||
|
||||
export function JoinRequestItem({
|
||||
driverId,
|
||||
requestedAt,
|
||||
formattedRequestedAt,
|
||||
onApprove,
|
||||
onReject,
|
||||
isApproving,
|
||||
@@ -47,7 +47,7 @@ export function JoinRequestItem({
|
||||
<Stack flexGrow={1}>
|
||||
<Text color="text-white" weight="medium" block>{driverId}</Text>
|
||||
<Text size="sm" color="text-gray-400" block>
|
||||
Requested {new Date(requestedAt).toLocaleDateString()}
|
||||
Requested {formattedRequestedAt}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ActivityFeedItem } from '@/components/feed/ActivityFeedItem';
|
||||
import { useLeagueRaces } from "@/hooks/league/useLeagueRaces";
|
||||
import { LeagueActivityService } from '@/lib/services/league/LeagueActivityService';
|
||||
import { RelativeTimeDisplay } from '@/lib/display-objects/RelativeTimeDisplay';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
@@ -20,19 +21,6 @@ interface LeagueActivityFeedProps {
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
function timeAgo(timestamp: Date): string {
|
||||
const diffMs = Date.now() - timestamp.getTime();
|
||||
const diffMinutes = Math.floor(diffMs / 60000);
|
||||
if (diffMinutes < 1) return 'Just now';
|
||||
if (diffMinutes < 60) return `${diffMinutes} min ago`;
|
||||
const diffHours = Math.floor(diffMinutes / 60);
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
if (diffDays === 1) return 'Yesterday';
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
return timestamp.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
export function LeagueActivityFeed({ leagueId, limit = 10 }: LeagueActivityFeedProps) {
|
||||
const { data: raceList = [], isLoading } = useLeagueRaces(leagueId);
|
||||
|
||||
@@ -140,7 +128,7 @@ function ActivityItem({ activity }: { activity: LeagueActivity }) {
|
||||
<ActivityFeedItem
|
||||
icon={getIcon()}
|
||||
content={getContent()}
|
||||
timestamp={timeAgo(activity.timestamp)}
|
||||
timestamp={RelativeTimeDisplay.format(activity.timestamp, new Date())}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ import { Icon } from '@/ui/Icon';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { DurationDisplay } from '@/lib/display-objects/DurationDisplay';
|
||||
|
||||
interface LeagueReviewSummaryProps {
|
||||
form: LeagueConfigFormModel;
|
||||
presets: LeagueScoringPresetViewModel[];
|
||||
@@ -139,11 +142,7 @@ export function LeagueReviewSummary({ form, presets }: LeagueReviewSummaryProps)
|
||||
|
||||
const seasonStartLabel =
|
||||
timings.seasonStartDate
|
||||
? new Date(timings.seasonStartDate).toLocaleDateString(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})
|
||||
? DateDisplay.formatShort(timings.seasonStartDate)
|
||||
: null;
|
||||
|
||||
const stewardingLabel = (() => {
|
||||
|
||||
@@ -194,17 +194,10 @@ export function LeagueSchedule({ leagueId, onRaceClick }: LeagueScheduleProps) {
|
||||
<Stack display="flex" alignItems="center" gap={3}>
|
||||
<Stack textAlign="right">
|
||||
<Text color="text-white" weight="medium" block>
|
||||
{race.scheduledAt.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
{race.formattedDate}
|
||||
</Text>
|
||||
<Text size="sm" color="text-gray-400" block>
|
||||
{race.scheduledAt.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
{race.formattedTime}
|
||||
</Text>
|
||||
{isPast && race.status === 'completed' && (
|
||||
<Text size="xs" color="text-primary-blue" mt={1} block>View Results →</Text>
|
||||
|
||||
@@ -286,7 +286,7 @@ export function ReviewProtestModal({
|
||||
<Stack direction="row" align="center" justify="between">
|
||||
<Text size="sm" color="text-gray-400">Filed Date</Text>
|
||||
<Text size="sm" color="text-white" weight="medium">
|
||||
{new Date(protest.filedAt || protest.submittedAt).toLocaleString()}
|
||||
{protest.formattedSubmittedAt}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack direction="row" align="center" justify="between">
|
||||
@@ -299,7 +299,7 @@ export function ReviewProtestModal({
|
||||
<Text size="sm" color="text-gray-400">Status</Text>
|
||||
<Stack as="span" px={2} py={1} rounded="sm" bg="bg-orange-500/20">
|
||||
<Text size="xs" weight="medium" color="text-orange-400">
|
||||
{protest.status}
|
||||
{protest.statusDisplay}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -15,8 +15,10 @@ interface Race {
|
||||
name: string;
|
||||
track?: string;
|
||||
car?: string;
|
||||
scheduledAt: string;
|
||||
formattedDate: string;
|
||||
formattedTime: string;
|
||||
status: string;
|
||||
statusLabel: string;
|
||||
sessionType?: string;
|
||||
isPast?: boolean;
|
||||
}
|
||||
@@ -33,19 +35,19 @@ export function ScheduleRaceCard({ race }: ScheduleRaceCardProps) {
|
||||
<Stack w="3" h="3" rounded="full" bg={race.isPast ? 'bg-performance-green' : 'bg-primary-blue'} />
|
||||
<Heading level={3} fontSize="lg">{race.name}</Heading>
|
||||
<Badge variant={race.status === 'completed' ? 'success' : 'primary'}>
|
||||
{race.status === 'completed' ? 'Completed' : 'Scheduled'}
|
||||
{race.statusLabel}
|
||||
</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>
|
||||
<Text size="sm" color="text-gray-300">{race.formattedDate}</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>
|
||||
<Text size="sm" color="text-gray-300">{race.formattedTime}</Text>
|
||||
</Stack>
|
||||
|
||||
{race.track && (
|
||||
|
||||
@@ -11,7 +11,8 @@ interface SponsorshipRequest {
|
||||
id: string;
|
||||
sponsorName: string;
|
||||
status: 'pending' | 'approved' | 'rejected';
|
||||
requestedAt: string;
|
||||
statusLabel: string;
|
||||
formattedRequestedAt: string;
|
||||
slotName: string;
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ export function SponsorshipRequestCard({ request }: SponsorshipRequestCardProps)
|
||||
<Icon icon={statusIcon} size={5} color={statusColor} />
|
||||
<Text weight="semibold" color="text-white">{request.sponsorName}</Text>
|
||||
<Badge variant={statusVariant}>
|
||||
{request.status}
|
||||
{request.statusLabel}
|
||||
</Badge>
|
||||
</Stack>
|
||||
|
||||
@@ -66,7 +67,7 @@ export function SponsorshipRequestCard({ request }: SponsorshipRequestCardProps)
|
||||
</Text>
|
||||
|
||||
<Text size="xs" color="text-gray-400" block>
|
||||
{new Date(request.requestedAt).toLocaleDateString()}
|
||||
{request.formattedRequestedAt}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -55,12 +55,12 @@ interface StandingsTableProps {
|
||||
standings: Array<{
|
||||
driverId: string;
|
||||
position: number;
|
||||
totalPoints: number;
|
||||
racesFinished: number;
|
||||
racesStarted: number;
|
||||
avgFinish: number | null;
|
||||
penaltyPoints: number;
|
||||
bonusPoints: number;
|
||||
positionLabel: string;
|
||||
totalPointsLabel: string;
|
||||
racesLabel: string;
|
||||
avgFinishLabel: string;
|
||||
penaltyPointsLabel: string;
|
||||
bonusPointsLabel: string;
|
||||
teamName?: string;
|
||||
}>;
|
||||
drivers: Array<{
|
||||
@@ -508,7 +508,7 @@ export function StandingsTable({
|
||||
'text-white'
|
||||
}
|
||||
>
|
||||
{row.position}
|
||||
{row.positionLabel}
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
@@ -625,7 +625,7 @@ export function StandingsTable({
|
||||
{/* Total Points with Hover Action */}
|
||||
<TableCell textAlign="right" position="relative">
|
||||
<Stack display="flex" alignItems="center" justifyContent="end" gap={2}>
|
||||
<Text color="text-white" weight="bold" size="lg">{row.totalPoints}</Text>
|
||||
<Text color="text-white" weight="bold" size="lg">{row.totalPointsLabel}</Text>
|
||||
{isAdmin && canModify && (
|
||||
<Stack
|
||||
as="button"
|
||||
@@ -650,28 +650,27 @@ export function StandingsTable({
|
||||
|
||||
{/* Races (Finished/Started) */}
|
||||
<TableCell textAlign="center">
|
||||
<Text color="text-white">{row.racesFinished}</Text>
|
||||
<Text color="text-gray-500">/{row.racesStarted}</Text>
|
||||
<Text color="text-white">{row.racesLabel}</Text>
|
||||
</TableCell>
|
||||
|
||||
{/* Avg Finish */}
|
||||
<TableCell textAlign="right">
|
||||
<Text color="text-gray-300">
|
||||
{row.avgFinish !== null ? row.avgFinish.toFixed(1) : '—'}
|
||||
{row.avgFinishLabel}
|
||||
</Text>
|
||||
</TableCell>
|
||||
|
||||
{/* Penalty */}
|
||||
<TableCell textAlign="right">
|
||||
<Text color={row.penaltyPoints > 0 ? 'text-red-400' : 'text-gray-500'} weight={row.penaltyPoints > 0 ? 'medium' : 'normal'}>
|
||||
{row.penaltyPoints > 0 ? `-${row.penaltyPoints}` : '—'}
|
||||
<Text color="text-gray-500">
|
||||
{row.penaltyPointsLabel}
|
||||
</Text>
|
||||
</TableCell>
|
||||
|
||||
{/* Bonus */}
|
||||
<TableCell textAlign="right">
|
||||
<Text color={row.bonusPoints !== 0 ? 'text-green-400' : 'text-gray-500'} weight={row.bonusPoints !== 0 ? 'medium' : 'normal'}>
|
||||
{row.bonusPoints !== 0 ? `+${row.bonusPoints}` : '—'}
|
||||
<Text color="text-gray-500">
|
||||
{row.bonusPointsLabel}
|
||||
</Text>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -13,7 +13,7 @@ interface Protest {
|
||||
protestingDriver: string;
|
||||
accusedDriver: string;
|
||||
description: string;
|
||||
submittedAt: string;
|
||||
formattedSubmittedAt: string;
|
||||
status: 'pending' | 'under_review' | 'resolved' | 'rejected';
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export function StewardingQueuePanel({ protests, onReview }: StewardingQueuePane
|
||||
<Stack direction="row" align="center" gap={1.5}>
|
||||
<Icon icon={Clock} size={3} color="text-gray-600" />
|
||||
<Text size="xs" color="text-gray-500">
|
||||
{new Date(protest.submittedAt).toLocaleString()}
|
||||
{protest.formattedSubmittedAt}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -9,21 +9,21 @@ import { ArrowDownLeft, ArrowUpRight, History, Wallet } from 'lucide-react';
|
||||
|
||||
interface Transaction {
|
||||
id: string;
|
||||
type: 'credit' | 'debit';
|
||||
amount: number;
|
||||
type: 'deposit' | 'withdrawal' | 'sponsorship' | 'prize';
|
||||
formattedAmount: string;
|
||||
description: string;
|
||||
date: string;
|
||||
formattedDate: string;
|
||||
}
|
||||
|
||||
interface WalletSummaryPanelProps {
|
||||
balance: number;
|
||||
formattedBalance: string;
|
||||
currency: string;
|
||||
transactions: Transaction[];
|
||||
onDeposit: () => void;
|
||||
onWithdraw: () => void;
|
||||
}
|
||||
|
||||
export function WalletSummaryPanel({ balance, currency, transactions, onDeposit, onWithdraw }: WalletSummaryPanelProps) {
|
||||
export function WalletSummaryPanel({ formattedBalance, currency, transactions, onDeposit, onWithdraw }: WalletSummaryPanelProps) {
|
||||
return (
|
||||
<Stack gap={6}>
|
||||
<Surface variant="dark" border rounded="lg" padding={8} position="relative" overflow="hidden">
|
||||
@@ -48,7 +48,7 @@ export function WalletSummaryPanel({ balance, currency, transactions, onDeposit,
|
||||
</Stack>
|
||||
<Stack direction="row" align="baseline" gap={2}>
|
||||
<Text size="4xl" weight="bold" color="text-white">
|
||||
{balance.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
{formattedBalance}
|
||||
</Text>
|
||||
<Text size="xl" weight="medium" color="text-gray-500">{currency}</Text>
|
||||
</Stack>
|
||||
@@ -87,37 +87,40 @@ export function WalletSummaryPanel({ balance, currency, transactions, onDeposit,
|
||||
<Text color="text-gray-500">No recent transactions.</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
transactions.map((tx) => (
|
||||
<Stack key={tx.id} p={4} borderBottom borderColor="border-charcoal-outline" hoverBg="bg-white/5" transition>
|
||||
<Stack direction="row" justify="between" align="center">
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Stack
|
||||
center
|
||||
w={10}
|
||||
h={10}
|
||||
rounded="full"
|
||||
bg={tx.type === 'credit' ? 'bg-performance-green/10' : 'bg-error-red/10'}
|
||||
transactions.map((tx) => {
|
||||
const isCredit = tx.type === 'deposit' || tx.type === 'sponsorship';
|
||||
return (
|
||||
<Stack key={tx.id} p={4} borderBottom borderColor="border-charcoal-outline" hoverBg="bg-white/5" transition>
|
||||
<Stack direction="row" justify="between" align="center">
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Stack
|
||||
center
|
||||
w={10}
|
||||
h={10}
|
||||
rounded="full"
|
||||
bg={isCredit ? 'bg-performance-green/10' : 'bg-error-red/10'}
|
||||
>
|
||||
<Icon
|
||||
icon={isCredit ? ArrowDownLeft : ArrowUpRight}
|
||||
size={4}
|
||||
color={isCredit ? 'text-performance-green' : 'text-error-red'}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack gap={0.5}>
|
||||
<Text weight="medium" color="text-white">{tx.description}</Text>
|
||||
<Text size="xs" color="text-gray-500">{tx.formattedDate}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Text
|
||||
weight="bold"
|
||||
color={isCredit ? 'text-performance-green' : 'text-white'}
|
||||
>
|
||||
<Icon
|
||||
icon={tx.type === 'credit' ? ArrowDownLeft : ArrowUpRight}
|
||||
size={4}
|
||||
color={tx.type === 'credit' ? 'text-performance-green' : 'text-error-red'}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack gap={0.5}>
|
||||
<Text weight="medium" color="text-white">{tx.description}</Text>
|
||||
<Text size="xs" color="text-gray-500">{new Date(tx.date).toLocaleDateString()}</Text>
|
||||
</Stack>
|
||||
{tx.formattedAmount}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
weight="bold"
|
||||
color={tx.type === 'credit' ? 'text-performance-green' : 'text-white'}
|
||||
>
|
||||
{tx.type === 'credit' ? '+' : '-'}{tx.amount.toFixed(2)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
))
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Stack>
|
||||
</Surface>
|
||||
|
||||
Reference in New Issue
Block a user