website refactor

This commit is contained in:
2026-01-17 15:46:55 +01:00
parent 4d5ce9bfd6
commit 72a626ce71
346 changed files with 19308 additions and 8605 deletions

View File

@@ -0,0 +1,83 @@
'use client';
import React from 'react';
import { Users, ChevronRight } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Image } from '@/ui/Image';
interface TeamCardProps {
id: string;
name: string;
logoUrl?: string;
memberCount: number;
isRecruiting?: boolean;
onClick?: () => void;
}
export function TeamCard({ name, logoUrl, memberCount, isRecruiting, onClick }: TeamCardProps) {
return (
<Box
as="article"
bg="surface-charcoal"
border
borderColor="outline-steel"
p={5}
cursor="pointer"
hover={{ borderColor: 'primary-accent', bg: 'surface-charcoal/80' }}
transition="smooth"
onClick={onClick}
position="relative"
overflow="hidden"
>
{/* Accent line */}
<Box
position="absolute"
top="0"
left="0"
w="1"
h="full"
bg={isRecruiting ? 'telemetry-aqua' : 'outline-steel'}
/>
<Stack direction="row" align="center" gap={4}>
<Box
w="12"
h="12"
bg="base-black"
border
borderColor="outline-steel"
display="flex"
center
overflow="hidden"
>
{logoUrl ? (
<Image src={logoUrl} alt={name} width={48} height={48} />
) : (
<Text size="xs" weight="bold" color="text-gray-600">{name.substring(0, 2).toUpperCase()}</Text>
)}
</Box>
<Box flex="1">
<Heading level={4} weight="bold">{name}</Heading>
<Stack direction="row" align="center" gap={3} mt={1}>
<Stack direction="row" align="center" gap={1}>
<Icon icon={Users} size={3} color="text-gray-500" />
<Text size="xs" color="text-gray-500" font="mono">{memberCount}</Text>
</Stack>
{isRecruiting && (
<Box px={1.5} py={0.5} bg="telemetry-aqua/10" border borderColor="telemetry-aqua/20">
<Text size="xs" color="telemetry-aqua" weight="bold" uppercase letterSpacing="tighter">Recruiting</Text>
</Box>
)}
</Stack>
</Box>
<Icon icon={ChevronRight} size={4} color="text-gray-700" />
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,114 @@
'use client';
import React from 'react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Image } from '@/ui/Image';
interface TeamDetailsHeaderProps {
teamId: string;
name: string;
tag?: string;
description?: string;
logoUrl?: string;
memberCount: number;
foundedDate?: string;
isAdmin?: boolean;
onAdminClick?: () => void;
}
export function TeamDetailsHeader({
name,
tag,
description,
logoUrl,
memberCount,
foundedDate,
isAdmin,
onAdminClick,
}: TeamDetailsHeaderProps) {
return (
<Box
bg="surface-charcoal"
border
borderColor="outline-steel"
p={8}
position="relative"
overflow="hidden"
>
{/* Background accent */}
<Box
position="absolute"
top="0"
right="0"
w="64"
h="64"
bg="primary-accent/5"
rounded="full"
blur="3xl"
translate="-1/2, -1/2"
/>
<Stack direction="row" align="start" gap={8} position="relative">
<Box
w="32"
h="32"
bg="base-black"
border
borderColor="outline-steel"
display="flex"
center
overflow="hidden"
>
{logoUrl ? (
<Image src={logoUrl} alt={name} width={128} height={128} />
) : (
<Text size="2xl" weight="bold" color="text-gray-700">{name.substring(0, 2).toUpperCase()}</Text>
)}
</Box>
<Box flex="1">
<Stack direction="row" align="center" gap={3}>
<Heading level={1} weight="bold">{name}</Heading>
{tag && (
<Box px={2} py={1} bg="base-black" border borderColor="outline-steel">
<Text size="xs" font="mono" color="primary-accent" weight="bold">[{tag}]</Text>
</Box>
)}
</Stack>
<Text color="text-gray-400" mt={2} block maxWidth="2xl">
{description || 'No mission statement provided.'}
</Text>
<Stack direction="row" gap={6} mt={6}>
<Box>
<Text size="xs" color="text-gray-500" uppercase font="mono" letterSpacing="widest">Personnel</Text>
<Text block weight="bold" color="text-white">{memberCount} Units</Text>
</Box>
<Box>
<Text size="xs" color="text-gray-500" uppercase font="mono" letterSpacing="widest">Established</Text>
<Text block weight="bold" color="text-white">
{foundedDate ? new Date(foundedDate).toLocaleDateString() : 'Unknown'}
</Text>
</Box>
</Stack>
</Box>
<Stack gap={3}>
{isAdmin && (
<Button variant="secondary" size="sm" onClick={onAdminClick}>
Configure
</Button>
)}
<Button variant="primary" size="sm">
Join Request
</Button>
</Stack>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,29 @@
'use client';
import React from 'react';
import { Grid } from '@/ui/Grid';
import { TeamCard } from './TeamCard';
import type { TeamSummaryData } from '@/lib/view-data/TeamsViewData';
interface TeamGridProps {
teams: TeamSummaryData[];
onTeamClick?: (teamId: string) => void;
}
export function TeamGrid({ teams, onTeamClick }: TeamGridProps) {
return (
<Grid cols={1} mdCols={2} lgCols={3} gap={6}>
{teams.map((team) => (
<TeamCard
key={team.teamId}
id={team.teamId}
name={team.teamName}
logoUrl={team.logoUrl}
memberCount={team.memberCount}
isRecruiting={true} // Redesign feel
onClick={() => onTeamClick?.(team.teamId)}
/>
))}
</Grid>
);
}

View File

@@ -31,10 +31,8 @@ export function TeamHeroSection({
py={12}
px={8}
rounded="2xl"
style={{
background: 'linear-gradient(to bottom right, rgba(147, 51, 234, 0.3), rgba(38, 38, 38, 0.8), #0f1115)',
borderColor: 'rgba(147, 51, 234, 0.2)',
}}
bg="surface-charcoal"
borderColor="outline-steel"
border
>
{/* Background decorations */}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { getMediaUrl } from '@/lib/utilities/media';
import { TeamLeaderboardItem } from '@/ui/TeamLeaderboardItem';
import { TeamLeaderboardPreview as UiTeamLeaderboardPreview } from '@/ui/TeamLeaderboardPreview';
import { TeamLeaderboardPreview as SemanticTeamLeaderboardPreview } from '@/components/leaderboards/TeamLeaderboardPreview';
interface TeamLeaderboardPreviewProps {
topTeams: Array<{
@@ -24,58 +23,22 @@ export function TeamLeaderboardPreview({
onTeamClick,
onViewFullLeaderboard
}: TeamLeaderboardPreviewProps) {
const getMedalColor = (position: number) => {
switch (position) {
case 0: return '#facc15';
case 1: return '#d1d5db';
case 2: return '#d97706';
default: return '#6b7280';
}
};
const getMedalBg = (position: number) => {
switch (position) {
case 0: return 'rgba(250, 204, 21, 0.1)';
case 1: return 'rgba(209, 213, 219, 0.1)';
case 2: return 'rgba(217, 119, 6, 0.1)';
default: return 'rgba(38, 38, 38, 0.5)';
}
};
const getMedalBorder = (position: number) => {
switch (position) {
case 0: return 'rgba(250, 204, 21, 0.3)';
case 1: return 'rgba(209, 213, 219, 0.3)';
case 2: return 'rgba(217, 119, 6, 0.3)';
default: return 'rgba(38, 38, 38, 1)';
}
};
if (topTeams.length === 0) return null;
return (
<UiTeamLeaderboardPreview
title="Top Teams"
subtitle="Highest rated racing teams"
onViewFull={onViewFullLeaderboard}
>
{topTeams.map((team, index) => (
<TeamLeaderboardItem
key={team.id}
position={index + 1}
name={team.name}
logoUrl={team.logoUrl || getMediaUrl('team-logo', team.id)}
category={team.category}
memberCount={team.memberCount}
totalWins={team.totalWins}
isRecruiting={team.isRecruiting}
rating={team.rating}
onClick={() => onTeamClick(team.id)}
medalColor={getMedalColor(index)}
medalBg={getMedalBg(index)}
medalBorder={getMedalBorder(index)}
/>
))}
</UiTeamLeaderboardPreview>
<SemanticTeamLeaderboardPreview
teams={topTeams.map((team, index) => ({
id: team.id,
name: team.name,
tag: '', // Not available in this view data
memberCount: team.memberCount,
category: team.category,
totalWins: team.totalWins,
logoUrl: team.logoUrl || getMediaUrl('team-logo', team.id),
position: index + 1
}))}
onTeamClick={onTeamClick}
onNavigateToTeams={onViewFullLeaderboard}
/>
);
}

View File

@@ -0,0 +1,79 @@
'use client';
import React from 'react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Table, TableBody, TableHead, TableHeader, TableRow, TableCell } from '@/ui/Table';
interface Member {
driverId: string;
driverName: string;
role: string;
joinedAt: string;
}
interface TeamMembersTableProps {
members: Member[];
isAdmin?: boolean;
onRemoveMember?: (driverId: string) => void;
}
export function TeamMembersTable({ members, isAdmin, onRemoveMember }: TeamMembersTableProps) {
return (
<Box border borderColor="outline-steel" bg="surface-charcoal/30">
<Table>
<TableHead>
<TableRow>
<TableHeader>Personnel</TableHeader>
<TableHeader>Role</TableHeader>
<TableHeader>Joined</TableHeader>
<TableHeader textAlign="right">Rating</TableHeader>
{isAdmin && <TableHeader textAlign="right">Actions</TableHeader>}
</TableRow>
</TableHead>
<TableBody>
{members.map((member) => (
<TableRow key={member.driverId}>
<TableCell>
<Stack direction="row" align="center" gap={3}>
<Box w="8" h="8" bg="base-black" border borderColor="outline-steel" display="flex" center>
<Text size="xs" weight="bold" color="primary-accent">{member.driverName.substring(0, 2).toUpperCase()}</Text>
</Box>
<Text weight="bold" size="sm" color="text-white">{member.driverName}</Text>
</Stack>
</TableCell>
<TableCell>
<Box px={2} py={0.5} bg="base-black" border borderColor="outline-steel" display="inline-block">
<Text size="xs" color="text-gray-400" font="mono" uppercase>{member.role}</Text>
</Box>
</TableCell>
<TableCell>
<Text size="xs" color="text-gray-500" font="mono">
{new Date(member.joinedAt).toLocaleDateString()}
</Text>
</TableCell>
<TableCell textAlign="right">
<Text font="mono" weight="bold" color="primary-accent">1450</Text>
</TableCell>
{isAdmin && (
<TableCell textAlign="right">
{member.role !== 'owner' && (
<Button
variant="secondary"
size="sm"
onClick={() => onRemoveMember?.(member.driverId)}
>
DECOMMISSION
</Button>
)}
</TableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</Box>
);
}

View File

@@ -73,7 +73,7 @@ export function TeamPodium({ teams, onClick }: TeamPodiumProps) {
h="auto"
mb={4}
p={0}
className="transition-all"
transition
>
<Box
bg={getBgColor(position)}

View File

@@ -0,0 +1,79 @@
'use client';
import React from 'react';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Table, TableBody, TableHead, TableHeader, TableRow, TableCell } from '@/ui/Table';
interface Standing {
leagueId: string;
leagueName: string;
position: number;
points: number;
races: number;
}
interface TeamStandingsPanelProps {
standings: Standing[];
}
export function TeamStandingsPanel({ standings }: TeamStandingsPanelProps) {
return (
<Box border borderColor="outline-steel" bg="surface-charcoal/30">
<Box p={4} borderBottom borderColor="outline-steel">
<Text size="xs" weight="bold" color="text-gray-400" uppercase letterSpacing="widest">
Active Campaign Standings
</Text>
</Box>
{standings.length > 0 ? (
<Table>
<TableHead>
<TableRow>
<TableHeader>League</TableHeader>
<TableHeader textAlign="center">Pos</TableHeader>
<TableHeader textAlign="center">Races</TableHeader>
<TableHeader textAlign="right">Points</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{standings.map((s) => (
<TableRow key={s.leagueId}>
<TableCell>
<Text weight="bold" size="sm" color="text-white">{s.leagueName}</Text>
</TableCell>
<TableCell textAlign="center">
<Box
display="inline-flex"
center
w="6"
h="6"
bg={s.position <= 3 ? 'warning-amber/20' : 'base-black'}
border
borderColor={s.position <= 3 ? 'warning-amber/40' : 'outline-steel'}
>
<Text size="xs" weight="bold" color={s.position <= 3 ? 'warning-amber' : 'text-gray-400'}>
{s.position}
</Text>
</Box>
</TableCell>
<TableCell textAlign="center">
<Text size="xs" color="text-gray-500" font="mono">{s.races}</Text>
</TableCell>
<TableCell textAlign="right">
<Text font="mono" weight="bold" color="telemetry-aqua">{s.points}</Text>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<Box py={12} textAlign="center">
<Text color="text-gray-600" font="mono" size="xs" uppercase letterSpacing="widest">
No active campaign telemetry
</Text>
</Box>
)}
</Box>
);
}

View File

@@ -0,0 +1,52 @@
'use client';
import React from 'react';
import { Plus } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
interface TeamsDirectoryHeaderProps {
onCreateTeam: () => void;
}
export function TeamsDirectoryHeader({ onCreateTeam }: TeamsDirectoryHeaderProps) {
return (
<Stack
direction="row"
align="end"
justify="between"
wrap
gap={4}
borderBottom
borderColor="outline-steel"
pb={6}
>
<Box>
<Heading level={1} weight="bold">Teams</Heading>
<Text
color="text-gray-500"
size="sm"
mt={1}
font="mono"
uppercase
letterSpacing="widest"
>
Operational Units & Racing Collectives
</Text>
</Box>
<Box>
<Button
variant="primary"
onClick={onCreateTeam}
icon={<Icon icon={Plus} size={4} />}
>
Initialize Team
</Button>
</Box>
</Stack>
);
}