website refactor

This commit is contained in:
2026-01-18 22:55:55 +01:00
parent b43a23a48c
commit aeaa43f4d3
179 changed files with 4736 additions and 6832 deletions

View File

@@ -1,11 +1,13 @@
'use client';
import React from 'react';
import { Activity } from 'lucide-react';
import { Card } from '@/ui/Card';
import { Heading } from '@/ui/Heading';
import { SectionHeader } from '@/ui/SectionHeader';
import { ActivityItem } from '@/ui/ActivityItem';
import { Icon } from '@/ui/Icon';
import { ActivityFeedList } from '@/components/feed/ActivityFeedList';
import { MinimalEmptyState } from '@/components/shared/state/EmptyState';
import { EmptyState } from '@/ui/EmptyState';
import { Button } from '@/ui/Button';
interface FeedItem {
id: string;
@@ -24,27 +26,34 @@ interface ActivityFeedProps {
export function ActivityFeed({ items, hasItems }: ActivityFeedProps) {
return (
<Card>
<Heading level={2} icon={<Icon icon={Activity} size={5} color="var(--primary-blue)" />} mb={4}>
Recent Activity
</Heading>
<SectionHeader
title="Recent Activity"
variant="minimal"
actions={<Icon icon={Activity} size={5} intent="primary" />}
/>
{hasItems ? (
<ActivityFeedList>
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
{items.slice(0, 5).map((item) => (
<ActivityItem
key={item.id}
headline={item.headline}
body={item.body}
formattedTime={item.formattedTime}
ctaHref={item.ctaHref}
ctaLabel={item.ctaLabel}
/>
title={item.headline}
description={item.body}
timestamp={item.formattedTime}
>
{item.ctaHref && item.ctaLabel && (
<Button variant="ghost" size="sm" as="a" href={item.ctaHref}>
{item.ctaLabel}
</Button>
)}
</ActivityItem>
))}
</ActivityFeedList>
</div>
) : (
<MinimalEmptyState
<EmptyState
icon={Activity}
title="No activity yet"
description="Join leagues and add friends to see activity here"
variant="minimal"
/>
)}
</Card>

View File

@@ -1,9 +1,7 @@
'use client';
import { Box } from '@/ui/primitives/Box';
import { Surface } from '@/ui/primitives/Surface';
import { Text } from '@/ui/Text';
import { ReactNode } from 'react';
import { ActivityItem } from '@/ui/ActivityItem';
import React, { ReactNode } from 'react';
interface ActivityFeedItemProps {
icon: ReactNode;
@@ -17,34 +15,13 @@ export function ActivityFeedItem({
timestamp,
}: ActivityFeedItemProps) {
return (
<Box
display="flex"
alignItems="start"
gap={3}
py={3}
borderBottom
style={{ borderColor: 'rgba(38, 38, 38, 0.3)' }}
className="last:border-0"
<ActivityItem
title=""
description={typeof content === 'string' ? content : undefined}
timestamp={timestamp}
icon={icon}
>
<Surface
variant="muted"
w="8"
h="8"
rounded="full"
display="flex"
center
flexShrink={0}
>
{icon}
</Surface>
<Box style={{ flex: 1, minWidth: 0 }}>
<Text size="sm" leading="relaxed" block>
{content}
</Text>
<Text size="xs" color="text-gray-500" mt={1} block>
{timestamp}
</Text>
</Box>
</Box>
{typeof content !== 'string' && content}
</ActivityItem>
);
}

View File

@@ -1,5 +1,4 @@
import { Stack } from '@/ui/primitives/Stack';
import { ReactNode } from 'react';
import React, { ReactNode } from 'react';
interface ActivityFeedListProps {
children: ReactNode;
@@ -7,8 +6,8 @@ interface ActivityFeedListProps {
export function ActivityFeedList({ children }: ActivityFeedListProps) {
return (
<Stack gap={4}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
{children}
</Stack>
</div>
);
}

View File

@@ -3,6 +3,7 @@
import React, { useEffect, useState } from 'react';
import { Button } from '@/ui/Button';
import { FeedItem } from '@/ui/FeedItem';
import { TimeDisplay } from '@/lib/display-objects/TimeDisplay';
interface FeedItemData {
id: string;
@@ -15,18 +16,6 @@ interface FeedItemData {
ctaLabel?: string;
}
function timeAgo(timestamp: Date | string): string {
const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
const diffMs = Date.now() - date.getTime();
const diffMinutes = Math.floor(diffMs / 60000);
if (diffMinutes < 1) return 'Just now';
if (diffMinutes < 60) return `${diffMinutes} min ago`;
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) return `${diffHours} h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays} d ago`;
}
async function resolveActor() {
return null;
}
@@ -55,20 +44,23 @@ export function FeedItemCard({ item }: FeedItemCardProps) {
return (
<FeedItem
actorName={actor?.name}
actorAvatarUrl={actor?.avatarUrl}
typeLabel={item.type.startsWith('friend') ? 'FR' : 'LG'}
headline={item.headline}
body={item.body}
timeAgo={timeAgo(item.timestamp)}
cta={item.ctaHref && item.ctaLabel ? (
user={{
name: actor?.name || 'Unknown',
avatar: actor?.avatarUrl
}}
timestamp={TimeDisplay.timeAgo(item.timestamp)}
content={
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
<div style={{ fontWeight: 'bold' }}>{item.headline}</div>
{item.body && <div>{item.body}</div>}
</div>
}
actions={item.ctaHref && item.ctaLabel ? (
<Button
as="a"
href={item.ctaHref}
variant="secondary"
size="sm"
px={4}
py={2}
>
{item.ctaLabel}
</Button>

View File

@@ -1,6 +1,6 @@
import { FeedItemCard } from '@/components/feed/FeedItemCard';
import { FeedEmptyState } from '@/ui/FeedEmptyState';
import { Stack } from '@/ui/primitives/Stack';
import React from 'react';
interface FeedItemData {
id: string;
@@ -23,10 +23,10 @@ export function FeedList({ items }: FeedListProps) {
}
return (
<Stack gap={4}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
{items.map(item => (
<FeedItemCard key={item.id} item={item} />
))}
</Stack>
</div>
);
}

View File

@@ -1,28 +1,13 @@
import { ActiveDriverCard } from '@/components/drivers/ActiveDriverCard';
'use client';
import { ActiveDriverCard } from '@/ui/ActiveDriverCard';
import { mediaConfig } from '@/lib/config/mediaConfig';
import { Heading } from '@/ui/Heading';
import { SectionHeader } from '@/ui/SectionHeader';
import { Icon } from '@/ui/Icon';
import { Box } from '@/ui/primitives/Box';
import { Text } from '@/ui/Text';
import { Activity } from 'lucide-react';
const SKILL_LEVELS = [
{ id: 'pro', label: 'Pro', color: 'text-yellow-400' },
{ id: 'advanced', label: 'Advanced', color: 'text-purple-400' },
{ id: 'intermediate', label: 'Intermediate', color: 'text-primary-blue' },
{ id: 'beginner', label: 'Beginner', color: 'text-green-400' },
];
const CATEGORIES = [
{ id: 'beginner', label: 'Beginner', color: 'text-green-400' },
{ id: 'intermediate', label: 'Intermediate', color: 'text-primary-blue' },
{ id: 'advanced', label: 'Advanced', color: 'text-purple-400' },
{ id: 'pro', label: 'Pro', color: 'text-yellow-400' },
{ id: 'endurance', label: 'Endurance', color: 'text-orange-400' },
{ id: 'sprint', label: 'Sprint', color: 'text-red-400' },
];
import React from 'react';
interface RecentActivityProps {
drivers: {
@@ -40,45 +25,28 @@ export function RecentActivity({ drivers, onDriverClick }: RecentActivityProps)
const activeDrivers = drivers.filter((d) => d.isActive).slice(0, 6);
return (
<Box mb={10}>
<Box display="flex" alignItems="center" gap={3} mb={4}>
<Box
display="flex"
h="10"
w="10"
alignItems="center"
justifyContent="center"
rounded="xl"
bg="bg-performance-green/10"
border
borderColor="border-performance-green/20"
>
<Icon icon={Activity} size={5} color="rgb(16, 185, 129)" />
</Box>
<Box>
<Heading level={2}>Active Drivers</Heading>
<Text size="xs" color="text-gray-500">Currently competing in leagues</Text>
</Box>
</Box>
<div style={{ marginBottom: '2.5rem' }}>
<SectionHeader
title="Active Drivers"
description="Currently competing in leagues"
variant="minimal"
actions={<Icon icon={Activity} size={5} intent="success" />}
/>
<Box display="grid" responsiveGridCols={{ base: 2, md: 3, lg: 6 }} gap={3}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(10rem, 1fr))', gap: '0.75rem' }}>
{activeDrivers.map((driver) => {
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
const categoryConfig = CATEGORIES.find((c) => c.id === driver.category);
return (
<ActiveDriverCard
key={driver.id}
name={driver.name}
avatarUrl={driver.avatarUrl || mediaConfig.avatars.defaultFallback}
categoryLabel={categoryConfig?.label}
categoryColor={categoryConfig?.color}
skillLevelLabel={levelConfig?.label}
skillLevelColor={levelConfig?.color}
categoryLabel={driver.category}
skillLevelLabel={driver.skillLevel}
onClick={() => onDriverClick(driver.id)}
/>
);
})}
</Box>
</Box>
</div>
</div>
);
}