wip
This commit is contained in:
@@ -10,13 +10,14 @@ import { Text } from '@/ui/Text';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { AchievementDisplay } from '@/lib/display-objects/AchievementDisplay';
|
||||
|
||||
interface Achievement {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
rarity: string;
|
||||
rarity: 'common' | 'rare' | 'epic' | 'legendary' | string;
|
||||
earnedAt: Date;
|
||||
}
|
||||
|
||||
@@ -50,28 +51,31 @@ export function AchievementGrid({ achievements }: AchievementGridProps) {
|
||||
<Grid cols={1} gap={4}>
|
||||
{achievements.map((achievement) => {
|
||||
const AchievementIcon = getAchievementIcon(achievement.icon);
|
||||
const rarity = AchievementDisplay.getRarityColor(achievement.rarity);
|
||||
return (
|
||||
<Surface
|
||||
key={achievement.id}
|
||||
variant="muted"
|
||||
variant={rarity.surface}
|
||||
rounded="xl"
|
||||
border
|
||||
padding={4}
|
||||
>
|
||||
<Stack direction="row" align="start" gap={3}>
|
||||
<Surface variant="muted" rounded="lg" padding={3}>
|
||||
<Icon icon={AchievementIcon} size={5} color="#facc15" />
|
||||
<Icon icon={AchievementIcon} size={5} color={rarity.icon} />
|
||||
</Surface>
|
||||
<Box>
|
||||
<Text weight="semibold" size="sm" color="text-white" block>{achievement.title}</Text>
|
||||
<Text size="xs" color="text-gray-400" block mt={1}>{achievement.description}</Text>
|
||||
<Text size="xs" color="text-gray-500" block mt={2}>
|
||||
{achievement.earnedAt.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Text>
|
||||
<Stack direction="row" align="center" gap={2} mt={2}>
|
||||
<Text size="xs" color={rarity.text} weight="medium">
|
||||
{achievement.rarity.toUpperCase()}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500">•</Text>
|
||||
<Text size="xs" color="text-gray-500">
|
||||
{AchievementDisplay.formatDate(achievement.earnedAt)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Surface>
|
||||
|
||||
@@ -5,7 +5,10 @@ import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
|
||||
import DriverRating from '@/components/profile/DriverRatingPill';
|
||||
import PlaceholderImage from '@/ui/PlaceholderImage';
|
||||
import { PlaceholderImage } from '@/ui/PlaceholderImage';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
export interface DriverSummaryPillProps {
|
||||
driver: DriverViewModel;
|
||||
@@ -16,14 +19,14 @@ export interface DriverSummaryPillProps {
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export default function DriverSummaryPill(props: DriverSummaryPillProps) {
|
||||
export function DriverSummaryPill(props: DriverSummaryPillProps) {
|
||||
const { driver, rating, rank, avatarSrc, onClick, href } = props;
|
||||
|
||||
const resolvedAvatar = avatarSrc;
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<div className="w-8 h-8 rounded-full overflow-hidden bg-charcoal-outline flex items-center justify-center border border-charcoal-outline/80">
|
||||
<Box width={8} height={8} rounded="full" className="overflow-hidden bg-charcoal-outline flex items-center justify-center border border-charcoal-outline/80">
|
||||
{resolvedAvatar ? (
|
||||
<Image
|
||||
src={resolvedAvatar}
|
||||
@@ -35,23 +38,25 @@ export default function DriverSummaryPill(props: DriverSummaryPillProps) {
|
||||
) : (
|
||||
<PlaceholderImage size={32} />
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="flex flex-col leading-tight text-left">
|
||||
<span className="text-xs font-semibold text-white truncate max-w-[140px]">
|
||||
<Stack direction="col" align="start" justify="center" className="leading-tight">
|
||||
<Text size="xs" weight="semibold" color="text-white" className="truncate max-w-[140px]" block>
|
||||
{driver.name}
|
||||
</span>
|
||||
</Text>
|
||||
|
||||
<DriverRating rating={rating} rank={rank} />
|
||||
</div>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
||||
const baseClasses = "flex items-center gap-3 rounded-full bg-iron-gray/70 px-3 py-1.5 border border-charcoal-outline/80 shadow-[0_0_18px_rgba(0,0,0,0.45)] hover:border-primary-blue/60 hover:bg-iron-gray transition-colors";
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className="flex items-center gap-3 rounded-full bg-iron-gray/70 px-3 py-1.5 border border-charcoal-outline/80 shadow-[0_0_18px_rgba(0,0,0,0.45)] hover:border-primary-blue/60 hover:bg-iron-gray transition-colors"
|
||||
className={baseClasses}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
@@ -63,7 +68,7 @@ export default function DriverSummaryPill(props: DriverSummaryPillProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="flex items-center gap-3 rounded-full bg-iron-gray/70 px-3 py-1.5 border border-charcoal-outline/80 shadow-[0_0_18px_rgba(0,0,0,0.45)] hover:border-primary-blue/60 hover:bg-iron-gray transition-colors"
|
||||
className={baseClasses}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
@@ -71,8 +76,8 @@ export default function DriverSummaryPill(props: DriverSummaryPillProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 rounded-full bg-iron-gray/70 px-3 py-1.5 border border-charcoal-outline/80">
|
||||
<Box className="flex items-center gap-3 rounded-full bg-iron-gray/70 px-3 py-1.5 border border-charcoal-outline/80">
|
||||
{content}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import Card from '@/ui/Card';
|
||||
import Button from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Car, Download, Trash2, Edit } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
|
||||
interface DriverLiveryItem {
|
||||
id: string;
|
||||
@@ -18,59 +24,64 @@ interface LiveryCardProps {
|
||||
onDelete?: (id: string) => void;
|
||||
}
|
||||
|
||||
export default function LiveryCard({ livery, onEdit, onDownload, onDelete }: LiveryCardProps) {
|
||||
export function LiveryCard({ livery, onEdit, onDownload, onDelete }: LiveryCardProps) {
|
||||
return (
|
||||
<Card className="overflow-hidden hover:border-primary-blue/50 transition-colors">
|
||||
{/* Livery Preview */}
|
||||
<div className="aspect-video bg-deep-graphite rounded-lg mb-4 flex items-center justify-center border border-charcoal-outline">
|
||||
<Car className="w-16 h-16 text-gray-600" />
|
||||
</div>
|
||||
<Box height={48} backgroundColor="deep-graphite" rounded="lg" mb={4} display="flex" center border borderColor="charcoal-outline">
|
||||
<Icon icon={Car} size={16} color="text-gray-600" />
|
||||
</Box>
|
||||
|
||||
{/* Livery Info */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-white">{livery.carName}</h3>
|
||||
<Stack gap={3}>
|
||||
<Stack direction="row" align="center" justify="between">
|
||||
<Heading level={3}>{livery.carName}</Heading>
|
||||
{livery.isValidated ? (
|
||||
<span className="px-2 py-0.5 text-xs bg-performance-green/10 text-performance-green border border-performance-green/30 rounded-full">
|
||||
<Badge variant="success">
|
||||
Validated
|
||||
</span>
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="px-2 py-0.5 text-xs bg-warning-amber/10 text-warning-amber border border-warning-amber/30 rounded-full">
|
||||
<Badge variant="warning">
|
||||
Pending
|
||||
</span>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
<p className="text-xs text-gray-500">
|
||||
<Text size="xs" color="text-gray-500">
|
||||
Uploaded {new Date(livery.uploadedAt).toLocaleDateString()}
|
||||
</p>
|
||||
</Text>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2 pt-2">
|
||||
<Stack direction="row" gap={2} pt={2}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex-1 px-3 py-1.5"
|
||||
size="sm"
|
||||
fullWidth
|
||||
onClick={() => onEdit?.(livery.id)}
|
||||
icon={<Icon icon={Edit} size={4} />}
|
||||
>
|
||||
<Edit className="w-4 h-4 mr-1" />
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="px-3 py-1.5"
|
||||
size="sm"
|
||||
onClick={() => onDownload?.(livery.id)}
|
||||
icon={<Icon icon={Download} size={4} />}
|
||||
aria-label="Download"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
{null}
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
className="px-3 py-1.5"
|
||||
size="sm"
|
||||
onClick={() => onDelete?.(livery.id)}
|
||||
icon={<Icon icon={Trash2} size={4} />}
|
||||
aria-label="Delete"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
{null}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import { Text } from '@/ui/Text';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { GridItem } from '@/ui/GridItem';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { CircularProgress } from '@/components/drivers/CircularProgress';
|
||||
import { HorizontalBarChart } from '@/components/drivers/HorizontalBarChart';
|
||||
import { CircularProgress } from '@/components/charts/CircularProgress';
|
||||
import { HorizontalBarChart } from '@/components/charts/HorizontalBarChart';
|
||||
|
||||
interface PerformanceOverviewProps {
|
||||
stats: {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { User, BarChart3 } from 'lucide-react';
|
||||
import { User, BarChart3, TrendingUp } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
|
||||
type ProfileTab = 'overview' | 'stats';
|
||||
export type ProfileTab = 'overview' | 'stats' | 'ratings';
|
||||
|
||||
interface ProfileTabsProps {
|
||||
activeTab: ProfileTab;
|
||||
@@ -34,6 +34,14 @@ export function ProfileTabs({ activeTab, onTabChange }: ProfileTabsProps) {
|
||||
>
|
||||
Detailed Stats
|
||||
</Button>
|
||||
<Button
|
||||
variant={activeTab === 'ratings' ? 'primary' : 'ghost'}
|
||||
onClick={() => onTabChange('ratings')}
|
||||
size="sm"
|
||||
icon={<Icon icon={TrendingUp} size={4} />}
|
||||
>
|
||||
Ratings
|
||||
</Button>
|
||||
</Box>
|
||||
</Surface>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user