website refactor

This commit is contained in:
2026-01-19 14:07:49 +01:00
parent 54f42bab9f
commit 6154d54435
88 changed files with 755 additions and 566 deletions

View File

@@ -28,9 +28,9 @@ interface SkillLevelSectionProps {
description?: string;
logoUrl?: string;
memberCount: number;
rating?: number;
totalWins: number;
totalRaces: number;
ratingLabel: string;
winsLabel: string;
racesLabel: string;
performanceLevel: string;
isRecruiting: boolean;
specialization?: string;
@@ -86,9 +86,9 @@ export function SkillLevelSection({
description={team.description ?? ''}
logo={team.logoUrl}
memberCount={team.memberCount}
rating={team.rating}
totalWins={team.totalWins}
totalRaces={team.totalRaces}
ratingLabel={team.ratingLabel}
winsLabel={team.winsLabel}
racesLabel={team.racesLabel}
performanceLevel={team.performanceLevel as SkillLevel}
isRecruiting={team.isRecruiting}
specialization={specialization(team.specialization)}

View File

@@ -21,9 +21,9 @@ interface TeamCardProps {
description?: string;
logo?: string;
memberCount: number;
rating?: number | null;
totalWins?: number;
totalRaces?: number;
ratingLabel: string;
winsLabel: string;
racesLabel: string;
performanceLevel?: 'beginner' | 'intermediate' | 'advanced' | 'pro';
isRecruiting?: boolean;
specialization?: 'endurance' | 'sprint' | 'mixed' | undefined;
@@ -65,9 +65,9 @@ export function TeamCard({
description,
logo,
memberCount,
rating,
totalWins,
totalRaces,
ratingLabel,
winsLabel,
racesLabel,
performanceLevel,
isRecruiting,
specialization,
@@ -112,9 +112,9 @@ export function TeamCard({
)}
statsContent={
<Group gap={4} justify="center">
<TeamStatItem label="Rating" value={typeof rating === 'number' ? Math.round(rating).toLocaleString() : '—'} intent="primary" align="center" />
<TeamStatItem label="Wins" value={totalWins ?? 0} intent="success" align="center" />
<TeamStatItem label="Races" value={totalRaces ?? 0} intent="high" align="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>
}
/>

View File

@@ -11,10 +11,10 @@ interface TeamLeaderboardItemProps {
name: string;
logoUrl: string;
category?: string;
memberCount: number;
totalWins: number;
memberCountLabel: string;
totalWinsLabel: string;
isRecruiting: boolean;
rating?: number;
ratingLabel: string;
onClick?: () => void;
medalColor: string;
medalBg: string;
@@ -26,10 +26,10 @@ export function TeamLeaderboardItem({
name,
logoUrl,
category,
memberCount,
totalWins,
memberCountLabel,
totalWinsLabel,
isRecruiting,
rating,
ratingLabel,
onClick,
medalColor,
medalBg,
@@ -99,11 +99,11 @@ export function TeamLeaderboardItem({
)}
<Stack direction="row" align="center" gap={1}>
<Icon icon={Users} size={3} color="var(--text-gray-600)" />
<Text size="xs" color="text-gray-500">{memberCount}</Text>
<Text size="xs" color="text-gray-500">{memberCountLabel}</Text>
</Stack>
<Stack direction="row" align="center" gap={1}>
<Icon icon={Trophy} size={3} color="var(--text-gray-600)" />
<Text size="xs" color="text-gray-500">{totalWins} wins</Text>
<Text size="xs" color="text-gray-500">{totalWinsLabel}</Text>
</Stack>
{isRecruiting && (
<Stack direction="row" align="center" gap={1}>
@@ -117,7 +117,7 @@ export function TeamLeaderboardItem({
{/* Rating */}
<Stack textAlign="right">
<Text font="mono" weight="semibold" color="text-purple-400" block>
{typeof rating === 'number' ? Math.round(rating).toLocaleString() : '—'}
{ratingLabel}
</Text>
<Text size="xs" color="text-gray-500">Rating</Text>
</Stack>

View File

@@ -9,10 +9,10 @@ interface TeamLeaderboardPanelProps {
id: string;
name: string;
logoUrl?: string;
rating: number;
wins: number;
races: number;
memberCount: number;
ratingLabel: string;
winsLabel: string;
racesLabel: string;
memberCountLabel: string;
}>;
onTeamClick: (id: string) => void;
}
@@ -53,16 +53,16 @@ export function TeamLeaderboardPanel({ teams, onTeamClick }: TeamLeaderboardPane
</Stack>
</TableCell>
<TableCell className="text-center">
<Text font="mono" weight="bold" color="text-primary-blue">{team.rating}</Text>
<Text font="mono" weight="bold" color="text-primary-blue">{team.ratingLabel}</Text>
</TableCell>
<TableCell className="text-center">
<Text font="mono" color="text-gray-300">{team.wins}</Text>
<Text font="mono" color="text-gray-300">{team.winsLabel}</Text>
</TableCell>
<TableCell className="text-center">
<Text font="mono" color="text-gray-300">{team.races}</Text>
<Text font="mono" color="text-gray-300">{team.racesLabel}</Text>
</TableCell>
<TableCell className="text-center">
<Text font="mono" color="text-gray-400" size="xs">{team.memberCount}</Text>
<Text font="mono" color="text-gray-400" size="xs">{team.memberCountLabel}</Text>
</TableCell>
</TableRow>
))}

View File

@@ -11,14 +11,14 @@ import { ChevronRight, Users } from 'lucide-react';
interface TeamMembershipCardProps {
teamName: string;
role: string;
joinedAt: string;
joinedAtLabel: string;
href: string;
}
export function TeamMembershipCard({
teamName,
role,
joinedAt,
joinedAtLabel,
href,
}: TeamMembershipCardProps) {
return (
@@ -52,7 +52,7 @@ export function TeamMembershipCard({
{role}
</Badge>
<Text size="xs" color="text-gray-400">
Since {new Date(joinedAt).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
Since {joinedAtLabel}
</Text>
</Stack>
</Stack>

View File

@@ -13,7 +13,7 @@ interface TeamMembership {
name: string;
};
role: string;
joinedAt: Date;
joinedAtLabel: string;
}
interface TeamMembershipGridProps {
@@ -46,7 +46,7 @@ export function TeamMembershipGrid({ memberships }: TeamMembershipGridProps) {
<Surface variant="muted" rounded="full" padding={1} style={{ paddingLeft: '0.5rem', paddingRight: '0.5rem', backgroundColor: 'rgba(147, 51, 234, 0.2)', color: '#a855f7' }}>
<Text size="xs" weight="medium" style={{ textTransform: 'capitalize' }}>{membership.role}</Text>
</Surface>
<Text size="xs" color="text-gray-500">Since {membership.joinedAt.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}</Text>
<Text size="xs" color="text-gray-400">Since {membership.joinedAtLabel}</Text>
</Stack>
</Stack>
<ChevronRight style={{ width: '1rem', height: '1rem', color: '#737373' }} />

View File

@@ -15,6 +15,10 @@ import { Select } from '@/ui/Select';
import { Text } from '@/ui/Text';
import { useMemo, useState } from 'react';
import { MemberDisplay } from '@/lib/display-objects/MemberDisplay';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
export type TeamRole = 'owner' | 'admin' | 'member';
export type TeamMemberRole = 'owner' | 'manager' | 'member';
@@ -67,9 +71,10 @@ export function TeamRoster({
return sortMembers(teamMembers as unknown as TeamMember[], sortBy);
}, [teamMembers, sortBy]);
const teamAverageRating = useMemo(() => {
if (teamMembers.length === 0) return 0;
return teamMembers.reduce((sum: number, m: { rating?: number | null }) => sum + (m.rating || 0), 0) / teamMembers.length;
const teamAverageRatingLabel = useMemo(() => {
if (teamMembers.length === 0) return '—';
const avg = teamMembers.reduce((sum: number, m: { rating?: number | null }) => sum + (m.rating || 0), 0) / teamMembers.length;
return RatingDisplay.format(avg);
}, [teamMembers]);
if (loading) {
@@ -88,8 +93,8 @@ export function TeamRoster({
<Stack>
<Heading level={3}>Team Roster</Heading>
<Text size="sm" color="text-gray-400" block mt={1}>
{memberships.length} {memberships.length === 1 ? 'member' : 'members'} Avg Rating:{' '}
<Text color="text-primary-blue" weight="medium">{teamAverageRating.toFixed(0)}</Text>
{MemberDisplay.formatCount(memberships.length)} Avg Rating:{' '}
<Text color="text-primary-blue" weight="medium">{teamAverageRatingLabel}</Text>
</Text>
</Stack>
@@ -124,9 +129,9 @@ export function TeamRoster({
driver={driver as DriverViewModel}
href={`${routes.driver.detail(driver.id)}?from=team&teamId=${teamId}`}
roleLabel={getRoleLabel(role)}
joinedAt={joinedAt}
rating={rating}
overallRank={overallRank}
joinedAtLabel={DateDisplay.formatShort(joinedAt)}
ratingLabel={RatingDisplay.format(rating)}
overallRankLabel={overallRank !== null ? `#${overallRank}` : null}
actions={canManageMembership ? (
<>
<Stack width="32">

View File

@@ -8,9 +8,9 @@ interface TeamRosterItemProps {
driver: DriverViewModel;
href: string;
roleLabel: string;
joinedAt: string | Date;
rating: number | null;
overallRank: number | null;
joinedAtLabel: string;
ratingLabel: string | null;
overallRankLabel: string | null;
actions?: ReactNode;
}
@@ -18,9 +18,9 @@ export function TeamRosterItem({
driver,
href,
roleLabel,
joinedAt,
rating,
overallRank,
joinedAtLabel,
ratingLabel,
overallRankLabel,
actions,
}: TeamRosterItemProps) {
return (
@@ -38,23 +38,23 @@ export function TeamRosterItem({
contextLabel={roleLabel}
meta={
<Text size="xs" color="text-gray-400">
{driver.country} Joined {new Date(joinedAt).toLocaleDateString()}
{driver.country} Joined {joinedAtLabel}
</Text>
}
size="md"
/>
{rating !== null && (
{ratingLabel !== null && (
<Stack direction="row" align="center" gap={6}>
<Stack display="flex" flexDirection="col" alignItems="center">
<Text size="lg" weight="bold" color="text-primary-blue" block>
{rating}
{ratingLabel}
</Text>
<Text size="xs" color="text-gray-400">Rating</Text>
</Stack>
{overallRank !== null && (
{overallRankLabel !== null && (
<Stack display="flex" flexDirection="col" alignItems="center">
<Text size="sm" color="text-gray-300" block>#{overallRank}</Text>
<Text size="sm" color="text-gray-300" block>{overallRankLabel}</Text>
<Text size="xs" color="text-gray-500">Rank</Text>
</Stack>
)}