website refactor
This commit is contained in:
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' }} />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user