website refactor
This commit is contained in:
19
apps/website/components/drivers/DriverGrid.tsx
Normal file
19
apps/website/components/drivers/DriverGrid.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
|
||||
interface DriverGridProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* DriverGrid - A semantic layout for displaying driver cards.
|
||||
*/
|
||||
export function DriverGrid({ children }: DriverGridProps) {
|
||||
return (
|
||||
<Grid cols={{ base: 1, md: 2, lg: 3, xl: 4 }} gap={4}>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { ButtonGroup } from '@/ui/ButtonGroup';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { LandingHero } from '@/ui/LandingHero';
|
||||
|
||||
/**
|
||||
* Hero - Refined with Dieter Rams principles.
|
||||
@@ -15,51 +9,12 @@ import { Box } from '@/ui/Box';
|
||||
*/
|
||||
export function Hero() {
|
||||
return (
|
||||
<Section variant="default" py={32}>
|
||||
<Box maxWidth="54rem">
|
||||
<Box marginBottom={24}>
|
||||
<Text
|
||||
variant="primary"
|
||||
weight="bold"
|
||||
uppercase
|
||||
size="xs"
|
||||
leading="none"
|
||||
block
|
||||
letterSpacing="0.2em"
|
||||
marginBottom={10}
|
||||
>
|
||||
Sim Racing Infrastructure
|
||||
</Text>
|
||||
|
||||
<Heading level={1} weight="bold" size="4xl">
|
||||
Professional League Management.<br />
|
||||
Engineered for Control.
|
||||
</Heading>
|
||||
</Box>
|
||||
|
||||
<Box marginBottom={24}>
|
||||
<Text size="xl" variant="med" leading="relaxed" block maxWidth="42rem">
|
||||
GridPilot eliminates the administrative overhead of running iRacing leagues.
|
||||
No spreadsheets. No manual points. No protest chaos.
|
||||
Just pure competition, structured for growth.
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<ButtonGroup gap={10}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
>
|
||||
Create Your League
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
>
|
||||
View Demo
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
</Section>
|
||||
<LandingHero
|
||||
subtitle="Sim Racing Infrastructure"
|
||||
title="Professional League Management. Engineered for Control."
|
||||
description="GridPilot eliminates the administrative overhead of running iRacing leagues. No spreadsheets. No manual points. No protest chaos. Just pure competition, structured for growth."
|
||||
primaryAction={{ label: 'Create Your League', href: '#' }}
|
||||
secondaryAction={{ label: 'View Demo', href: '#' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,11 +12,22 @@ import { Box } from '@/ui/Box';
|
||||
import { StatusBadge } from '@/ui/StatusBadge';
|
||||
import { Trophy, Globe, Settings2, Palette, ShieldCheck, BarChart3 } from 'lucide-react';
|
||||
|
||||
interface LeagueIdentityPreviewProps {
|
||||
league?: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* LeagueIdentityPreview - Radically redesigned for "Modern Precision" and "Dieter Rams" style.
|
||||
* Focuses on the professional identity and deep customization options for admins.
|
||||
*/
|
||||
export function LeagueIdentityPreview() {
|
||||
export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
|
||||
const leagueName = league?.name || 'Apex Racing League';
|
||||
const subdomain = league ? `${league.name.toLowerCase().replace(/\s+/g, '')}.gridpilot.racing` : 'apex.gridpilot.racing';
|
||||
|
||||
return (
|
||||
<Section variant="default" py={32}>
|
||||
<Box>
|
||||
@@ -48,7 +59,7 @@ export function LeagueIdentityPreview() {
|
||||
<Globe size={20} className="text-[var(--ui-color-text-low)]" />
|
||||
<Stack gap={1}>
|
||||
<Text weight="bold">Custom Subdomains</Text>
|
||||
<Text size="xs" variant="low">yourleague.gridpilot.racing</Text>
|
||||
<Text size="xs" variant="low">{subdomain}</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Panel>
|
||||
@@ -77,8 +88,8 @@ export function LeagueIdentityPreview() {
|
||||
<Trophy size={20} className="text-[var(--ui-color-text-low)]" />
|
||||
</Box>
|
||||
<Stack gap={0}>
|
||||
<Text weight="bold" size="sm">Apex Racing League</Text>
|
||||
<Text size="xs" variant="low">apex.gridpilot.racing</Text>
|
||||
<Text weight="bold" size="sm">{leagueName}</Text>
|
||||
<Text size="xs" variant="low">{subdomain}</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Panel>
|
||||
|
||||
@@ -13,11 +13,30 @@ import { Grid } from '@/ui/Grid';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Gavel, Clock, User, MessageSquare } from 'lucide-react';
|
||||
|
||||
interface StewardingPreviewProps {
|
||||
race?: {
|
||||
id: string;
|
||||
track: string;
|
||||
car: string;
|
||||
formattedDate: string;
|
||||
};
|
||||
team?: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* StewardingPreview - Refined for "Modern Precision" and "Dieter Rams" style.
|
||||
* Thorough down to the last detail.
|
||||
*/
|
||||
export function StewardingPreview() {
|
||||
export function StewardingPreview({ race, team }: StewardingPreviewProps) {
|
||||
const incidentId = race ? `${race.id.slice(0, 3).toUpperCase()}-WG` : '402-WG';
|
||||
const trackName = race?.track || 'Watkins Glen - Cup';
|
||||
const carName = race?.car || 'Porsche 911 GT3 R';
|
||||
const teamName = team?.name || 'Alex Miller';
|
||||
|
||||
return (
|
||||
<Section variant="muted" py={32}>
|
||||
<Box>
|
||||
@@ -36,9 +55,9 @@ export function StewardingPreview() {
|
||||
<Group gap={2}>
|
||||
<Text variant="low" size="xs" uppercase weight="bold" letterSpacing="0.1em">Incident Report</Text>
|
||||
<Text variant="low" size="xs">•</Text>
|
||||
<Text variant="low" size="xs" uppercase weight="bold" letterSpacing="0.1em">ID: 402-WG</Text>
|
||||
<Text variant="low" size="xs" uppercase weight="bold" letterSpacing="0.1em">ID: {incidentId}</Text>
|
||||
</Group>
|
||||
<Heading level={3} weight="bold">Turn 1 Contact: Miller vs Chen</Heading>
|
||||
<Heading level={3} weight="bold">Turn 1 Contact: {teamName} vs David Chen</Heading>
|
||||
</Stack>
|
||||
<StatusBadge variant="warning">UNDER REVIEW</StatusBadge>
|
||||
</Group>
|
||||
@@ -50,8 +69,8 @@ export function StewardingPreview() {
|
||||
<User size={14} className="text-[var(--ui-color-intent-primary)]" />
|
||||
<Text size="xs" uppercase weight="bold" variant="low">Protestor</Text>
|
||||
</Group>
|
||||
<Text weight="bold">Alex Miller</Text>
|
||||
<Text size="sm" variant="low">#42 - Porsche 911 GT3 R</Text>
|
||||
<Text weight="bold">{teamName}</Text>
|
||||
<Text size="sm" variant="low">#42 - {carName}</Text>
|
||||
</Stack>
|
||||
</Panel>
|
||||
<Panel variant="bordered" padding="md">
|
||||
@@ -71,7 +90,7 @@ export function StewardingPreview() {
|
||||
<Text size="xs" uppercase weight="bold" variant="low">Session Info</Text>
|
||||
</Group>
|
||||
<Text weight="bold">Lap 1, 00:42.150</Text>
|
||||
<Text size="sm" variant="low">Watkins Glen - Cup</Text>
|
||||
<Text size="sm" variant="low">{trackName}</Text>
|
||||
</Stack>
|
||||
</Panel>
|
||||
</Grid>
|
||||
|
||||
@@ -38,7 +38,7 @@ export function TelemetryStrip() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Section variant="default" py={16}>
|
||||
<Section variant="default" padding="md">
|
||||
<StatsStrip stats={stats} />
|
||||
</Section>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Search, Command, ArrowRight, X } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
@@ -46,71 +48,81 @@ export function CommandModal({ isOpen, onClose }: CommandModalProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-[9999] flex items-start justify-center pt-[20vh] px-4">
|
||||
<Box className="fixed inset-0 z-[9999] flex items-start justify-center pt-[20vh] px-4">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
<Box
|
||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm transition-opacity"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Modal Content */}
|
||||
<div className="relative w-full max-w-lg bg-surface-charcoal border border-outline-steel rounded-xl shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200">
|
||||
<div className="flex items-center border-b border-outline-steel px-4 py-3 gap-3">
|
||||
<Box className="relative w-full max-w-lg bg-surface-charcoal border border-outline-steel rounded-xl shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200">
|
||||
<Box display="flex" alignItems="center" className="border-b border-outline-steel px-4 py-3 gap-3">
|
||||
<Search className="text-text-low" size={18} />
|
||||
<input
|
||||
<Input
|
||||
autoFocus
|
||||
type="text"
|
||||
variant="ghost"
|
||||
placeholder="Type a command or search..."
|
||||
className="flex-1 bg-transparent border-none outline-none text-text-high placeholder:text-text-low/50 text-base h-6"
|
||||
className="flex-1 text-base h-6"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
<button onClick={onClose} className="text-text-low hover:text-text-high transition-colors">
|
||||
<span className="sr-only">Close</span>
|
||||
<kbd className="hidden sm:inline-block px-1.5 py-0.5 text-[10px] font-mono bg-white/5 rounded border border-white/5">ESC</kbd>
|
||||
</button>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={onClose} className="text-text-low hover:text-text-high transition-colors">
|
||||
<Text as="span" className="sr-only">Close</Text>
|
||||
<Text as="kbd" className="hidden sm:inline-block px-1.5 py-0.5 text-[10px] font-mono bg-white/5 rounded border border-white/5">ESC</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<div className="p-2">
|
||||
<Box p={2}>
|
||||
{results.length > 0 ? (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="px-2 py-1.5 text-[10px] font-mono uppercase tracking-wider text-text-low/50 font-bold">
|
||||
Suggestions
|
||||
</div>
|
||||
<Box display="flex" flexDirection="col" gap={1}>
|
||||
<Box paddingX={2} paddingY={1.5}>
|
||||
<Text size="xs" weight="bold" uppercase mono className="tracking-wider text-text-low/50">
|
||||
Suggestions
|
||||
</Text>
|
||||
</Box>
|
||||
{results.map((result, i) => (
|
||||
<button
|
||||
<Button
|
||||
key={i}
|
||||
variant="ghost"
|
||||
fullWidth
|
||||
className="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-white/5 text-left group transition-colors"
|
||||
onClick={onClose}
|
||||
>
|
||||
<span className="text-sm text-text-med group-hover:text-text-high font-medium">
|
||||
<Text size="sm" weight="medium" className="text-text-med group-hover:text-text-high">
|
||||
{result.label}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[10px] font-mono text-text-low bg-white/5 px-1.5 py-0.5 rounded border border-white/5">
|
||||
</Text>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Text as="span" size="xs" mono className="text-text-low bg-white/5 px-1.5 py-0.5 rounded border border-white/5">
|
||||
{result.shortcut}
|
||||
</span>
|
||||
</Text>
|
||||
<ArrowRight size={14} className="text-text-low opacity-0 group-hover:opacity-100 transition-opacity -translate-x-2 group-hover:translate-x-0" />
|
||||
</div>
|
||||
</button>
|
||||
</Box>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</Box>
|
||||
) : (
|
||||
<div className="px-4 py-8 text-center text-text-low text-sm">
|
||||
No results found.
|
||||
</div>
|
||||
<Box paddingX={4} paddingY={8} className="text-center">
|
||||
<Text size="sm" variant="low">
|
||||
No results found.
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="px-4 py-2 bg-white/2 border-t border-white/5 flex items-center justify-between text-[10px] text-text-low">
|
||||
<div className="flex gap-3">
|
||||
<span><strong className="text-text-med">↑↓</strong> to navigate</span>
|
||||
<span><strong className="text-text-med">↵</strong> to select</span>
|
||||
</div>
|
||||
<span>GridPilot Command</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<Box paddingX={4} paddingY={2} display="flex" alignItems="center" justifyContent="space-between" className="bg-white/2 border-t border-white/5">
|
||||
<Box display="flex" gap={3}>
|
||||
<Text size="xs" variant="low">
|
||||
<Text as="strong" variant="med">↑↓</Text> to navigate
|
||||
</Text>
|
||||
<Text size="xs" variant="low">
|
||||
<Text as="strong" variant="med">↵</Text> to select
|
||||
</Text>
|
||||
</Box>
|
||||
<Text size="xs" variant="low">GridPilot Command</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ interface TeamLeaderboardPreviewProps {
|
||||
totalWins: number;
|
||||
logoUrl: string;
|
||||
position: number;
|
||||
rating?: number;
|
||||
performanceLevel: string;
|
||||
}[];
|
||||
onTeamClick: (id: string) => void;
|
||||
onNavigateToTeams: () => void;
|
||||
@@ -28,12 +30,12 @@ export function TeamLeaderboardPreview({ teams, onTeamClick, onNavigateToTeams }
|
||||
|
||||
return (
|
||||
<LeaderboardPreviewShell
|
||||
title="Team Rankings"
|
||||
title="Team Standings"
|
||||
subtitle="Top Performing Teams"
|
||||
onViewFull={onNavigateToTeams}
|
||||
icon={Users}
|
||||
iconColor="var(--neon-purple)"
|
||||
iconBgGradient="linear-gradient(to bottom right, rgba(168, 85, 247, 0.2), rgba(168, 85, 247, 0.1))"
|
||||
iconColor="var(--ui-color-intent-primary)"
|
||||
iconBgGradient="linear-gradient(to bottom right, rgba(25, 140, 255, 0.2), rgba(25, 140, 255, 0.1))"
|
||||
viewFullLabel="View All"
|
||||
>
|
||||
<LeaderboardList>
|
||||
@@ -72,7 +74,7 @@ export function TeamLeaderboardPreview({ teams, onTeamClick, onNavigateToTeams }
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
overflow="hidden"
|
||||
groupHoverBorderColor="purple-400/50"
|
||||
groupHoverBorderColor="primary-blue/50"
|
||||
transition
|
||||
>
|
||||
<Image
|
||||
@@ -91,31 +93,26 @@ export function TeamLeaderboardPreview({ teams, onTeamClick, onNavigateToTeams }
|
||||
weight="semibold"
|
||||
color="text-white"
|
||||
truncate
|
||||
groupHoverTextColor="text-purple-400"
|
||||
groupHoverTextColor="text-primary-blue"
|
||||
transition
|
||||
block
|
||||
>
|
||||
{team.name}
|
||||
</Text>
|
||||
<Box display="flex" alignItems="center" gap={2} flexWrap="wrap">
|
||||
{team.category && (
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-purple-400">
|
||||
<Box w="1.5" h="1.5" rounded="full" bg="bg-purple-400" />
|
||||
<Text size="xs" weight="medium">{team.category}</Text>
|
||||
</Box>
|
||||
)}
|
||||
<Text size="xs" variant="low" uppercase font="mono">{team.performanceLevel}</Text>
|
||||
<Box w="1" h="1" rounded="full" bg="bg-gray-700" />
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Icon icon={Users} size={3} color="text-gray-500" />
|
||||
<Text size="xs" color="text-gray-500">{team.memberCount} members</Text>
|
||||
<Text size="xs" color="text-gray-500">{team.memberCount}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" gap={6}>
|
||||
<Box textAlign="right">
|
||||
<Text color="text-purple-400" font="mono" weight="bold" block size="sm">{team.memberCount}</Text>
|
||||
<Text fontSize="10px" color="text-gray-500" block uppercase letterSpacing="wider" weight="bold">Members</Text>
|
||||
<Text color="text-primary-blue" font="mono" weight="bold" block size="sm">{team.rating?.toFixed(0) || '1000'}</Text>
|
||||
<Text fontSize="10px" color="text-gray-500" block uppercase letterSpacing="wider" weight="bold">Rating</Text>
|
||||
</Box>
|
||||
<Box textAlign="right" minWidth="12">
|
||||
<Text color="text-performance-green" font="mono" weight="bold" block size="sm">{team.totalWins}</Text>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Stack } from '@/ui/Stack';
|
||||
import { Select } from '@/ui/Select';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { StatusDot } from '@/ui/StatusDot';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Filter, Search } from 'lucide-react';
|
||||
|
||||
export type TimeFilter = 'all' | 'upcoming' | 'live' | 'past';
|
||||
@@ -77,9 +78,9 @@ export function RaceFilterModal({
|
||||
onClick={() => setTimeFilter(filter)}
|
||||
>
|
||||
{filter === 'live' && (
|
||||
<Stack mr={2}>
|
||||
<Box mr={2}>
|
||||
<StatusDot intent="success" size={1.5} pulse />
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
{filter.charAt(0).toUpperCase() + filter.slice(1)}
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use thought';
|
||||
'use client';
|
||||
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Group } from '@/ui/Group';
|
||||
|
||||
import { VerticalBar } from '@/ui/VerticalBar';
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
@@ -15,34 +18,35 @@ interface PageHeaderProps {
|
||||
*/
|
||||
export function PageHeader({ title, subtitle, action }: PageHeaderProps) {
|
||||
return (
|
||||
<Box
|
||||
marginBottom={12}
|
||||
display="flex"
|
||||
flexDirection={{ base: 'col', md: 'row' }}
|
||||
alignItems={{ base: 'start', md: 'end' }}
|
||||
justifyContent="between"
|
||||
gap={6}
|
||||
borderBottom
|
||||
borderColor="var(--ui-color-border-muted)"
|
||||
paddingBottom={8}
|
||||
<Container
|
||||
size="full"
|
||||
padding="none"
|
||||
py={12}
|
||||
>
|
||||
<Box display="flex" flexDirection="col" gap={2}>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box width="4px" height="32px" bg="var(--ui-color-intent-primary)" />
|
||||
<Heading level={1} weight="bold" uppercase>{title}</Heading>
|
||||
</Box>
|
||||
{subtitle && (
|
||||
<Text variant="low" size="lg" uppercase weight="bold" letterSpacing="widest">
|
||||
{subtitle}
|
||||
</Text>
|
||||
<Group
|
||||
justify="between"
|
||||
align="end"
|
||||
wrap
|
||||
gap={6}
|
||||
>
|
||||
<Group direction="col" gap={2}>
|
||||
<Group align="center" gap={3}>
|
||||
<VerticalBar height="2rem" />
|
||||
<Heading level={1} weight="bold" uppercase>{title}</Heading>
|
||||
</Group>
|
||||
{subtitle && (
|
||||
<Text variant="low" size="lg" uppercase weight="bold" letterSpacing="widest">
|
||||
{subtitle}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{action && (
|
||||
<Group align="center">
|
||||
{action}
|
||||
</Group>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{action && (
|
||||
<Box display="flex" alignItems="center">
|
||||
{action}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Group>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import React from 'react';
|
||||
import { getMediaUrl } from '@/lib/utilities/media';
|
||||
import { TeamLeaderboardPreview as SemanticTeamLeaderboardPreview } from '@/components/leaderboards/TeamLeaderboardPreview';
|
||||
import type { LeaderboardTeamItem } from '@/lib/view-data/LeaderboardTeamItem';
|
||||
|
||||
interface TeamLeaderboardPreviewProps {
|
||||
topTeams: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
logoUrl?: string;
|
||||
category?: string;
|
||||
memberCount: number;
|
||||
totalWins: number;
|
||||
isRecruiting: boolean;
|
||||
rating?: number;
|
||||
performanceLevel: string;
|
||||
}>;
|
||||
topTeams: LeaderboardTeamItem[];
|
||||
onTeamClick: (id: string) => void;
|
||||
onViewFullLeaderboard: () => void;
|
||||
}
|
||||
@@ -27,15 +18,17 @@ export function TeamLeaderboardPreview({
|
||||
|
||||
return (
|
||||
<SemanticTeamLeaderboardPreview
|
||||
teams={topTeams.map((team, index) => ({
|
||||
teams={topTeams.map((team) => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
tag: '', // Not available in this view data
|
||||
tag: team.tag,
|
||||
memberCount: team.memberCount,
|
||||
category: team.category,
|
||||
totalWins: team.totalWins,
|
||||
logoUrl: team.logoUrl || getMediaUrl('team-logo', team.id),
|
||||
position: index + 1
|
||||
position: team.position,
|
||||
rating: team.rating,
|
||||
performanceLevel: team.performanceLevel
|
||||
}))}
|
||||
onTeamClick={onTeamClick}
|
||||
onNavigateToTeams={onViewFullLeaderboard}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/Box';
|
||||
|
||||
interface TeamsHeaderProps {
|
||||
title: string;
|
||||
@@ -10,24 +11,32 @@ interface TeamsHeaderProps {
|
||||
|
||||
export function TeamsHeader({ title, subtitle, action }: TeamsHeaderProps) {
|
||||
return (
|
||||
<div className="mb-12 flex flex-col md:flex-row md:items-end justify-between gap-6 border-b border-[var(--ui-color-border-muted)] pb-8">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-1 h-8 bg-[var(--ui-color-intent-primary)]" />
|
||||
<Box
|
||||
marginBottom={12}
|
||||
display="flex"
|
||||
flexDirection={{ base: 'col', md: 'row' }}
|
||||
alignItems={{ md: 'end' }}
|
||||
justifyContent="space-between"
|
||||
gap={6}
|
||||
className="border-b border-[var(--ui-color-border-muted)] pb-8"
|
||||
>
|
||||
<Box className="space-y-2">
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box width={1} height={8} className="bg-[var(--ui-color-intent-primary)]" />
|
||||
<Heading level={1} weight="bold" uppercase>{title}</Heading>
|
||||
</div>
|
||||
</Box>
|
||||
{subtitle && (
|
||||
<Text variant="low" size="lg" uppercase mono className="tracking-[0.2em]">
|
||||
{subtitle}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{action && (
|
||||
<div className="flex items-center">
|
||||
<Box display="flex" alignItems="center">
|
||||
{action}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user