website refactor
This commit is contained in:
@@ -15,9 +15,7 @@ import { DangerZone } from '@/ui/DangerZone';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { TextArea } from '@/ui/TextArea';
|
||||
import { SectionHeader } from '@/ui/SectionHeader';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
@@ -40,14 +38,12 @@ export function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
description: team.description || '',
|
||||
});
|
||||
|
||||
// Use hooks for data fetching
|
||||
const { data: joinRequests = [], isLoading: loading } = useTeamJoinRequests(
|
||||
team.id,
|
||||
team.ownerId,
|
||||
true
|
||||
);
|
||||
|
||||
// Use hooks for mutations
|
||||
const updateTeamMutation = useUpdateTeam({
|
||||
onSuccess: () => {
|
||||
setEditMode(false);
|
||||
@@ -96,17 +92,22 @@ export function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={6}>
|
||||
<Stack gap="lg">
|
||||
<Panel
|
||||
title="Team Settings"
|
||||
actions={!editMode && (
|
||||
<Button variant="secondary" size="sm" onClick={() => setEditMode(true)}>
|
||||
Edit Details
|
||||
</Button>
|
||||
)}
|
||||
variant="default"
|
||||
padding="md"
|
||||
>
|
||||
<Box display="flex" justifyContent="between" alignItems="center" marginBottom={6}>
|
||||
<Heading level={3} uppercase>Team Settings</Heading>
|
||||
{!editMode && (
|
||||
<Button variant="secondary" size="sm" onClick={() => setEditMode(true)}>
|
||||
Edit Details
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{editMode ? (
|
||||
<Stack gap={4}>
|
||||
<Stack gap="md">
|
||||
<Input
|
||||
label="Team Name"
|
||||
value={editedTeam.name}
|
||||
@@ -128,7 +129,7 @@ export function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
onChange={(e) => setEditedTeam({ ...editedTeam, description: e.target.value })}
|
||||
/>
|
||||
|
||||
<Group gap={2}>
|
||||
<Box display="flex" gap="sm">
|
||||
<Button variant="primary" onClick={handleSaveChanges} disabled={updateTeamMutation.isPending}>
|
||||
{updateTeamMutation.isPending ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
@@ -145,27 +146,30 @@ export function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack gap={4}>
|
||||
<Stack gap={1}>
|
||||
<Text size="sm" variant="low" block>Team Name</Text>
|
||||
<Text variant="high" weight="medium" block>{team.name}</Text>
|
||||
<Stack gap="md">
|
||||
<Stack gap="xs">
|
||||
<Text size="xs" variant="low" uppercase>Team Name</Text>
|
||||
<Text weight="bold">{team.name}</Text>
|
||||
</Stack>
|
||||
<Stack gap={1}>
|
||||
<Text size="sm" variant="low" block>Team Tag</Text>
|
||||
<Text variant="high" weight="medium" block>{team.tag}</Text>
|
||||
<Stack gap="xs">
|
||||
<Text size="xs" variant="low" uppercase>Team Tag</Text>
|
||||
<Text weight="bold">{team.tag}</Text>
|
||||
</Stack>
|
||||
<Stack gap={1}>
|
||||
<Text size="sm" variant="low" block>Description</Text>
|
||||
<Text variant="high" block>{team.description}</Text>
|
||||
<Stack gap="xs">
|
||||
<Text size="xs" variant="low" uppercase>Description</Text>
|
||||
<Text variant="med">{team.description}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
</Panel>
|
||||
|
||||
<Panel title="Join Requests">
|
||||
<Panel variant="default" padding="md">
|
||||
<Box marginBottom={6}>
|
||||
<Heading level={3} uppercase>Join Requests</Heading>
|
||||
</Box>
|
||||
{loading ? (
|
||||
<LoadingWrapper variant="spinner" message="Loading requests..." />
|
||||
) : joinRequests.length > 0 ? (
|
||||
@@ -202,3 +206,5 @@ export function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
|
||||
@@ -1,56 +1,71 @@
|
||||
'use client';
|
||||
|
||||
import { TeamLogo } from '@/components/teams/TeamLogo';
|
||||
import { TeamCard as UITeamCard } from '@/ui/TeamCard';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
|
||||
import React from 'react';
|
||||
import { TeamCard as UiTeamCard } from '@/ui/TeamCard';
|
||||
import { TeamSummaryData } from '@/lib/view-data/TeamsViewData';
|
||||
import { Image } from '@/ui/Image';
|
||||
|
||||
interface TeamCardProps {
|
||||
name: string;
|
||||
description?: string;
|
||||
team?: TeamSummaryData;
|
||||
// Compatibility props
|
||||
name?: string;
|
||||
leagueName?: string;
|
||||
logo?: string;
|
||||
memberCount: number;
|
||||
isRecruiting?: boolean;
|
||||
performanceBadge?: ReactNode;
|
||||
specializationContent?: ReactNode;
|
||||
categoryBadge?: ReactNode;
|
||||
memberCount?: number;
|
||||
ratingLabel?: string;
|
||||
winsLabel?: string;
|
||||
racesLabel?: string;
|
||||
region?: string;
|
||||
languagesContent?: ReactNode;
|
||||
statsContent?: ReactNode;
|
||||
onClick?: () => void;
|
||||
isRecruiting?: boolean;
|
||||
performanceLevel?: string;
|
||||
description?: string;
|
||||
onClick?: (id: string) => void;
|
||||
}
|
||||
|
||||
export function TeamCard({
|
||||
|
||||
export function TeamCard({
|
||||
team,
|
||||
name,
|
||||
description,
|
||||
leagueName,
|
||||
logo,
|
||||
memberCount,
|
||||
isRecruiting,
|
||||
performanceBadge,
|
||||
specializationContent,
|
||||
categoryBadge,
|
||||
ratingLabel,
|
||||
winsLabel,
|
||||
racesLabel,
|
||||
region,
|
||||
languagesContent,
|
||||
onClick,
|
||||
isRecruiting,
|
||||
performanceLevel,
|
||||
description,
|
||||
onClick
|
||||
}: TeamCardProps) {
|
||||
const data = team || {
|
||||
teamId: '',
|
||||
teamName: name || '',
|
||||
leagueName: leagueName || '',
|
||||
memberCount: memberCount || 0,
|
||||
logoUrl: logo,
|
||||
ratingLabel: ratingLabel || '-',
|
||||
winsLabel: winsLabel || '-',
|
||||
racesLabel: racesLabel || '-',
|
||||
region: region,
|
||||
isRecruiting: isRecruiting || false,
|
||||
performanceLevel: performanceLevel,
|
||||
description: description,
|
||||
};
|
||||
|
||||
return (
|
||||
<UITeamCard
|
||||
name={name}
|
||||
description={description}
|
||||
memberCount={memberCount}
|
||||
isRecruiting={isRecruiting}
|
||||
region={region}
|
||||
onClick={onClick}
|
||||
logo={
|
||||
<TeamLogo src={logo} alt={name} size={64} />
|
||||
}
|
||||
badges={
|
||||
<>
|
||||
{performanceBadge}
|
||||
{specializationContent}
|
||||
{categoryBadge}
|
||||
{languagesContent}
|
||||
</>
|
||||
}
|
||||
<UiTeamCard
|
||||
name={data.teamName}
|
||||
leagueName={data.leagueName}
|
||||
logo={data.logoUrl ? <Image src={data.logoUrl} alt={data.teamName} fullWidth fullHeight objectFit="cover" /> : undefined}
|
||||
memberCount={data.memberCount}
|
||||
rating={data.ratingLabel}
|
||||
wins={data.winsLabel}
|
||||
races={data.racesLabel}
|
||||
region={data.region}
|
||||
isRecruiting={data.isRecruiting}
|
||||
performanceLevel={data.performanceLevel}
|
||||
description={data.description}
|
||||
onClick={() => onClick?.(data.teamId)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@ import { Badge } from '@/ui/Badge';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { BadgeGroup } from '@/ui/BadgeGroup';
|
||||
import {
|
||||
Clock,
|
||||
Crown,
|
||||
Languages,
|
||||
Shield,
|
||||
Star,
|
||||
TrendingUp,
|
||||
@@ -34,35 +32,8 @@ interface TeamCardProps {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
function getPerformanceBadge(level?: string) {
|
||||
switch (level) {
|
||||
case 'pro':
|
||||
return { icon: Crown, label: 'Pro', variant: 'warning' as const };
|
||||
case 'advanced':
|
||||
return { icon: Star, label: 'Advanced', variant: 'primary' as const };
|
||||
case 'intermediate':
|
||||
return { icon: TrendingUp, label: 'Intermediate', variant: 'default' as const };
|
||||
case 'beginner':
|
||||
return { icon: Shield, label: 'Beginner', variant: 'success' as const };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getSpecializationBadge(specialization?: string) {
|
||||
switch (specialization) {
|
||||
case 'endurance':
|
||||
return { icon: Clock, label: 'Endurance', intent: 'warning' as const };
|
||||
case 'sprint':
|
||||
return { icon: Zap, label: 'Sprint', intent: 'telemetry' as const };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function TeamCard({
|
||||
name,
|
||||
description,
|
||||
logo,
|
||||
memberCount,
|
||||
ratingLabel,
|
||||
@@ -70,53 +41,21 @@ export function TeamCard({
|
||||
racesLabel,
|
||||
performanceLevel,
|
||||
isRecruiting,
|
||||
specialization,
|
||||
region,
|
||||
languages,
|
||||
category,
|
||||
onClick,
|
||||
}: TeamCardProps) {
|
||||
const performanceBadge = getPerformanceBadge(performanceLevel);
|
||||
const specializationBadge = getSpecializationBadge(specialization);
|
||||
|
||||
return (
|
||||
<UiTeamCard
|
||||
name={name}
|
||||
description={description}
|
||||
logo={logo}
|
||||
memberCount={memberCount}
|
||||
ratingLabel={ratingLabel}
|
||||
winsLabel={winsLabel}
|
||||
racesLabel={racesLabel}
|
||||
isRecruiting={isRecruiting}
|
||||
onClick={onClick}
|
||||
region={region}
|
||||
performanceBadge={performanceBadge && (
|
||||
<Badge variant={performanceBadge.variant} icon={performanceBadge.icon}>
|
||||
{performanceBadge.label}
|
||||
</Badge>
|
||||
)}
|
||||
specializationContent={specializationBadge && (
|
||||
<Group gap={1}>
|
||||
<Icon icon={specializationBadge.icon} size={3} intent={specializationBadge.intent} />
|
||||
<Text size="xs" variant="low">{specializationBadge.label}</Text>
|
||||
</Group>
|
||||
)}
|
||||
categoryBadge={category && (
|
||||
<Badge variant="primary">
|
||||
{category}
|
||||
</Badge>
|
||||
)}
|
||||
languagesContent={languages && languages.length > 0 && (
|
||||
<Badge variant="default" icon={Languages}>
|
||||
{languages.slice(0, 2).join(', ')}
|
||||
{languages.length > 2 && ` +${languages.length - 2}`}
|
||||
</Badge>
|
||||
)}
|
||||
statsContent={
|
||||
<Group gap={4} justify="center">
|
||||
<TeamStatItem label="Rating" value={ratingLabel} intent="primary" align="center" />
|
||||
<TeamStatItem label="Wins" value={winsLabel} intent="success" align="center" />
|
||||
<TeamStatItem label="Races" value={racesLabel} intent="high" align="center" />
|
||||
</Group>
|
||||
}
|
||||
performanceLevel={performanceLevel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { TeamHero } from '@/ui/TeamHero';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { StatGrid } from '@/ui/StatGrid';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
import React from 'react';
|
||||
import { Users, Calendar, Shield, Settings, Globe } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
|
||||
interface TeamDetailsHeaderProps {
|
||||
teamId: string;
|
||||
name: string;
|
||||
@@ -17,81 +17,126 @@ interface TeamDetailsHeaderProps {
|
||||
description?: string;
|
||||
logoUrl?: string;
|
||||
memberCount: number;
|
||||
memberCountLabel?: string;
|
||||
foundedDate?: string;
|
||||
foundedDateLabel?: string;
|
||||
isAdmin?: boolean;
|
||||
onAdminClick?: () => void;
|
||||
}
|
||||
|
||||
|
||||
export function TeamDetailsHeader({
|
||||
name,
|
||||
tag,
|
||||
description,
|
||||
logoUrl,
|
||||
memberCount,
|
||||
memberCountLabel,
|
||||
foundedDate,
|
||||
foundedDateLabel,
|
||||
isAdmin,
|
||||
onAdminClick,
|
||||
}: TeamDetailsHeaderProps) {
|
||||
return (
|
||||
<TeamHero
|
||||
title={
|
||||
<Group gap={3}>
|
||||
{name}
|
||||
{tag && <Badge variant="outline">[{tag}]</Badge>}
|
||||
</Group>
|
||||
}
|
||||
description={description || 'No mission statement provided.'}
|
||||
sideContent={
|
||||
<Surface
|
||||
variant="muted"
|
||||
rounded="lg"
|
||||
width="8rem"
|
||||
height="8rem"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
overflow="hidden"
|
||||
border
|
||||
>
|
||||
{logoUrl ? (
|
||||
<Image src={logoUrl} alt={name} width={128} height={128} />
|
||||
) : (
|
||||
<Text size="2xl" weight="bold" variant="low">{name.substring(0, 2).toUpperCase()}</Text>
|
||||
)}
|
||||
</Surface>
|
||||
}
|
||||
stats={
|
||||
<StatGrid
|
||||
columns={2}
|
||||
variant="box"
|
||||
stats={[
|
||||
{
|
||||
label: 'Personnel',
|
||||
value: `${memberCount} Units`,
|
||||
},
|
||||
{
|
||||
label: 'Established',
|
||||
value: foundedDateLabel || 'Unknown',
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
<Group gap={3}>
|
||||
{isAdmin && (
|
||||
<Button variant="secondary" onClick={onAdminClick}>
|
||||
Configure
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="primary">
|
||||
Join Request
|
||||
</Button>
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
<Box marginBottom={12}>
|
||||
<Surface variant="precision" padding="none">
|
||||
<Box padding={10}>
|
||||
<Box display="flex" alignItems="start" gap={10} flexWrap="wrap">
|
||||
{/* Logo Container */}
|
||||
<Box
|
||||
width={48}
|
||||
height={48}
|
||||
rounded="xl"
|
||||
bg="var(--ui-color-bg-base)"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
overflow="hidden"
|
||||
border="1px solid var(--ui-color-border-muted)"
|
||||
flexShrink={0}
|
||||
shadow="xl"
|
||||
>
|
||||
{logoUrl ? (
|
||||
<Box as="img" src={logoUrl} alt={name} width="100%" height="100%" objectFit="cover" />
|
||||
) : (
|
||||
<Box display="flex" flexDirection="col" alignItems="center" gap="xs">
|
||||
<Icon icon={Users} size={12} intent="low" />
|
||||
<Text size="sm" variant="low" weight="bold" mono>{name.substring(0, 3).toUpperCase()}</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Info Section */}
|
||||
<Box flex={1} minWidth="300px">
|
||||
<Stack gap="lg">
|
||||
<Box>
|
||||
<Box display="flex" alignItems="center" gap="md" flexWrap="wrap" marginBottom={4}>
|
||||
<Heading level={1} weight="bold" style={{ fontSize: '3rem' }}>{name}</Heading>
|
||||
{tag && (
|
||||
<Box paddingX={3} paddingY={1} bg="rgba(25, 140, 255, 0.1)" border="1px solid var(--ui-color-intent-primary)" rounded="md">
|
||||
<Text variant="primary" size="sm" weight="bold" mono>{tag}</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box maxWidth="800px">
|
||||
<Text variant="med" size="xl" leading="relaxed">
|
||||
{description || 'No mission statement provided.'}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Metadata Grid */}
|
||||
<Box display="flex" gap={12} flexWrap="wrap" paddingTop={6} borderTop="1px solid var(--ui-color-border-muted)">
|
||||
<Stack direction="row" align="center" gap="md">
|
||||
<Box width={12} height={12} rounded="lg" bg="rgba(25, 140, 255, 0.05)" display="flex" alignItems="center" justifyContent="center" border="1px solid rgba(25, 140, 255, 0.1)">
|
||||
<Icon icon={Users} size={5} intent="primary" />
|
||||
</Box>
|
||||
<Stack gap="none">
|
||||
<Box>
|
||||
<Text size="xs" variant="low" uppercase>Personnel</Text>
|
||||
</Box>
|
||||
<Text weight="bold" mono size="lg">{memberCount} UNITS</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" align="center" gap="md">
|
||||
<Box width={12} height={12} rounded="lg" bg="rgba(78, 212, 224, 0.05)" display="flex" alignItems="center" justifyContent="center" border="1px solid rgba(78, 212, 224, 0.1)">
|
||||
<Icon icon={Calendar} size={5} intent="telemetry" />
|
||||
</Box>
|
||||
<Stack gap="none">
|
||||
<Box>
|
||||
<Text size="xs" variant="low" uppercase>Established</Text>
|
||||
</Box>
|
||||
<Text weight="bold" mono size="lg">{foundedDateLabel || 'UNKNOWN'}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" align="center" gap="md">
|
||||
<Box width={12} height={12} rounded="lg" bg="rgba(255, 255, 255, 0.02)" display="flex" alignItems="center" justifyContent="center" border="1px solid var(--ui-color-border-muted)">
|
||||
<Icon icon={Globe} size={5} intent="low" />
|
||||
</Box>
|
||||
<Stack gap="none">
|
||||
<Box>
|
||||
<Text size="xs" variant="low" uppercase>Region</Text>
|
||||
</Box>
|
||||
<Text weight="bold" mono size="lg">GLOBAL</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<Box>
|
||||
<Stack direction="row" gap="md">
|
||||
{isAdmin && (
|
||||
<Button variant="secondary" onClick={onAdminClick} icon={<Settings size={18} />}>
|
||||
Configure
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="primary" icon={<Shield size={18} />}>
|
||||
Join Request
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Surface>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface TeamGridProps {
|
||||
children: ReactNode;
|
||||
@@ -7,7 +9,10 @@ interface TeamGridProps {
|
||||
|
||||
export function TeamGrid({ children }: TeamGridProps) {
|
||||
return (
|
||||
<Grid cols={{ base: 1, md: 2, lg: 3 }} gap={4}>
|
||||
<Grid
|
||||
cols={3}
|
||||
gap="lg"
|
||||
>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '@/ui/Table';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
interface Member {
|
||||
driverId: string;
|
||||
@@ -21,40 +24,54 @@ interface TeamMembersTableProps {
|
||||
|
||||
export function TeamMembersTable({ members, isAdmin, onRemoveMember }: TeamMembersTableProps) {
|
||||
return (
|
||||
<Stack border borderColor="outline-steel" bg="surface-charcoal/30">
|
||||
<Surface variant="precision" padding="none">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeader>Personnel</TableHeader>
|
||||
<TableHeader>Role</TableHeader>
|
||||
<TableHeader>Joined</TableHeader>
|
||||
<TableHeader textAlign="right">Rating</TableHeader>
|
||||
{isAdmin && <TableHeader textAlign="right">Actions</TableHeader>}
|
||||
</TableRow>
|
||||
<TableHeaderCell>Personnel</TableHeaderCell>
|
||||
<TableHeaderCell>Role</TableHeaderCell>
|
||||
<TableHeaderCell>Joined</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="right">Rating</TableHeaderCell>
|
||||
{isAdmin && <TableHeaderCell textAlign="right">Actions</TableHeaderCell>}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{members.map((member) => (
|
||||
<TableRow key={member.driverId}>
|
||||
<TableCell>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Stack 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>
|
||||
</Stack>
|
||||
<Text weight="bold" size="sm" color="text-white">{member.driverName}</Text>
|
||||
<Stack direction="row" align="center" gap="sm">
|
||||
<Box
|
||||
width={10}
|
||||
height={10}
|
||||
bg="var(--ui-color-bg-base)"
|
||||
border="1px solid var(--ui-color-border-muted)"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="md"
|
||||
>
|
||||
<Text size="xs" weight="bold" variant="primary" mono>{member.driverName.substring(0, 2).toUpperCase()}</Text>
|
||||
</Box>
|
||||
<Text weight="bold" size="sm">{member.driverName}</Text>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Stack 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>
|
||||
</Stack>
|
||||
<Box
|
||||
paddingX={2}
|
||||
paddingY={0.5}
|
||||
bg="rgba(255,255,255,0.02)"
|
||||
border="1px solid var(--ui-color-border-muted)"
|
||||
display="inline-block"
|
||||
rounded="sm"
|
||||
>
|
||||
<Text size="xs" variant="low" mono uppercase>{member.role}</Text>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text size="xs" color="text-gray-500" font="mono">
|
||||
<Text size="xs" variant="low" mono>
|
||||
{member.joinedAtLabel}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell textAlign="right">
|
||||
<Text font="mono" weight="bold" color="primary-accent">1450</Text>
|
||||
<Text mono weight="bold" variant="primary">1450</Text>
|
||||
</TableCell>
|
||||
{isAdmin && (
|
||||
<TableCell textAlign="right">
|
||||
@@ -73,6 +90,6 @@ export function TeamMembersTable({ members, isAdmin, onRemoveMember }: TeamMembe
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Stack>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
|
||||
'use client';
|
||||
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Search } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import React from 'react';
|
||||
|
||||
interface TeamSearchBarProps {
|
||||
@@ -14,13 +13,16 @@ interface TeamSearchBarProps {
|
||||
|
||||
export function TeamSearchBar({ searchQuery, onSearchChange }: TeamSearchBarProps) {
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search teams by name, description, region, or language..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
icon={<Icon icon={Search} size={5} intent="low" />}
|
||||
fullWidth
|
||||
/>
|
||||
<Box marginBottom={8}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="FILTER BY TEAM NAME, REGION OR TAGS..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
icon={<Icon icon={Search} size={4} intent="low" />}
|
||||
variant="search"
|
||||
fullWidth
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '@/ui/Table';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
interface Standing {
|
||||
leagueId: string;
|
||||
@@ -18,9 +20,9 @@ interface TeamStandingsPanelProps {
|
||||
|
||||
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">
|
||||
<Surface variant="precision" padding="none">
|
||||
<Box padding={6} borderBottom="1px solid var(--ui-color-border-muted)">
|
||||
<Text size="xs" weight="bold" variant="low" uppercase letterSpacing="0.1em">
|
||||
Active Campaign Standings
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -28,51 +30,49 @@ export function TeamStandingsPanel({ standings }: TeamStandingsPanelProps) {
|
||||
{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>
|
||||
<TableHeaderCell>League</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="center">Pos</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="center">Races</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="right">Points</TableHeaderCell>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{standings.map((s) => (
|
||||
<TableRow key={s.leagueId}>
|
||||
<TableCell>
|
||||
<Text weight="bold" size="sm" color="text-white">{s.leagueName}</Text>
|
||||
<Text weight="bold" size="sm">{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'}
|
||||
width={8}
|
||||
height={8}
|
||||
bg={s.position <= 3 ? 'rgba(255, 190, 77, 0.1)' : 'var(--ui-color-bg-base)'}
|
||||
border={s.position <= 3 ? '1px solid var(--ui-color-intent-warning)' : '1px solid var(--ui-color-border-muted)'}
|
||||
rounded="md"
|
||||
>
|
||||
<Text size="xs" weight="bold" color={s.position <= 3 ? 'warning-amber' : 'text-gray-400'}>
|
||||
<Text size="xs" weight="bold" variant={s.position <= 3 ? 'warning' : 'low'} mono>
|
||||
{s.position}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell textAlign="center">
|
||||
<Text size="xs" color="text-gray-500" font="mono">{s.races}</Text>
|
||||
<Text size="xs" variant="low" mono>{s.races}</Text>
|
||||
</TableCell>
|
||||
<TableCell textAlign="right">
|
||||
<Text font="mono" weight="bold" color="telemetry-aqua">{s.points}</Text>
|
||||
<Text mono weight="bold" variant="telemetry">{s.points}</Text>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<Box py={12} textAlign="center">
|
||||
<Text color="text-gray-600" font="mono" size="xs" uppercase letterSpacing="widest">
|
||||
<Box paddingY={16} textAlign="center">
|
||||
<Text variant="low" mono size="xs" uppercase letterSpacing="0.1em">
|
||||
No active campaign telemetry
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,46 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { StatusDot } from '@/ui/StatusDot';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
|
||||
interface TeamsDirectoryProps {
|
||||
children: ReactNode;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
export function TeamsDirectory({ children, title, subtitle }: TeamsDirectoryProps) {
|
||||
export function TeamsDirectory({ children }: TeamsDirectoryProps) {
|
||||
return (
|
||||
<Container size="lg" py={12}>
|
||||
<Group direction="column" gap={10} fullWidth>
|
||||
{title && (
|
||||
<Group direction="row" align="center" gap={2}>
|
||||
<StatusDot intent="primary" size="md" />
|
||||
<Text size="xs" weight="bold" variant="low" uppercase>{title}</Text>
|
||||
</Group>
|
||||
)}
|
||||
<Box paddingY={12} bg="var(--ui-color-bg-base)" minHeight="100vh">
|
||||
<Container size="xl">
|
||||
{children}
|
||||
</Group>
|
||||
</Container>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function TeamsDirectorySection({ children, title, accentColor = "primary-accent" }: { children: ReactNode, title: string, accentColor?: string }) {
|
||||
const intentMap: Record<string, 'primary' | 'success' | 'warning' | 'critical' | 'telemetry'> = {
|
||||
'primary-accent': 'primary',
|
||||
'telemetry-aqua': 'telemetry',
|
||||
};
|
||||
interface TeamsDirectorySectionProps {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
accentColor?: string;
|
||||
}
|
||||
|
||||
export function TeamsDirectorySection({ title, children }: TeamsDirectorySectionProps) {
|
||||
return (
|
||||
<Group direction="column" gap={6} fullWidth>
|
||||
<Group direction="row" align="center" gap={2}>
|
||||
<StatusDot intent={intentMap[accentColor] || 'primary'} size="md" />
|
||||
<Text size="xs" weight="bold" variant="low" uppercase>{title}</Text>
|
||||
</Group>
|
||||
<Box marginBottom={16}>
|
||||
<Box marginBottom={8} borderBottom="1px solid var(--ui-color-border-muted)" paddingBottom={4}>
|
||||
<Heading level={2} weight="bold" uppercase>{title}</Heading>
|
||||
</Box>
|
||||
{children}
|
||||
</Group>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
'use client';
|
||||
|
||||
|
||||
import React from 'react';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { PageHeader } from '@/ui/PageHeader';
|
||||
import { Plus, Users } from 'lucide-react';
|
||||
|
||||
import { TeamsHeader } from '@/ui/TeamsHeader';
|
||||
|
||||
interface TeamsDirectoryHeaderProps {
|
||||
onCreateTeam: () => void;
|
||||
}
|
||||
|
||||
|
||||
export function TeamsDirectoryHeader({ onCreateTeam }: TeamsDirectoryHeaderProps) {
|
||||
return (
|
||||
<PageHeader
|
||||
icon={Users}
|
||||
title="Teams"
|
||||
description="Operational Units & Racing Collectives"
|
||||
<TeamsHeader
|
||||
title="Directory"
|
||||
subtitle="Professional Racing Rosters"
|
||||
action={
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onCreateTeam}
|
||||
icon={<Icon icon={Plus} size={4} />}
|
||||
icon={<Plus size={16} />}
|
||||
>
|
||||
Initialize Team
|
||||
Register Team
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user