website refactor

This commit is contained in:
2026-01-18 16:18:18 +01:00
parent 0b301feb61
commit 13567d51af
329 changed files with 4701 additions and 4750 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable gridpilot-rules/no-raw-html-in-app */
import { Check, ChevronDown, Globe, Search } from 'lucide-react';

View File

@@ -1,3 +1,4 @@
/* eslint-disable gridpilot-rules/no-raw-html-in-app */
import React, { useCallback, useEffect, useRef, useState } from 'react';

View File

@@ -1,12 +1,15 @@
import { Button } from '@/ui/Button';
import { EmptyStateProps } from '@/ui/state-types';
import { Stack } from '@/ui/Stack';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Link } from '@/ui/Link';
import { Activity, Lock, Search } from 'lucide-react';
// Illustration components (simple SVG representations)
const Illustrations = {
racing: () => (
<svg className="w-20 h-20" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 70 L80 70 L85 50 L80 30 L20 30 L15 50 Z" fill="currentColor" opacity="0.2"/>
<path d="M30 60 L70 60 L75 50 L70 40 L30 40 L25 50 Z" fill="currentColor" opacity="0.4"/>
<circle cx="35" cy="65" r="3" fill="currentColor"/>
@@ -15,7 +18,7 @@ const Illustrations = {
</svg>
),
league: () => (
<svg className="w-20 h-20" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="35" r="15" fill="currentColor" opacity="0.3"/>
<path d="M35 50 L50 45 L65 50 L65 70 L35 70 Z" fill="currentColor" opacity="0.2"/>
<path d="M40 55 L50 52 L60 55" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
@@ -23,7 +26,7 @@ const Illustrations = {
</svg>
),
team: () => (
<svg className="w-20 h-20" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="35" cy="35" r="8" fill="currentColor" opacity="0.3"/>
<circle cx="65" cy="35" r="8" fill="currentColor" opacity="0.3"/>
<circle cx="50" cy="55" r="10" fill="currentColor" opacity="0.2"/>
@@ -31,14 +34,14 @@ const Illustrations = {
</svg>
),
sponsor: () => (
<svg className="w-20 h-20" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="25" y="25" width="50" height="50" rx="8" fill="currentColor" opacity="0.2"/>
<path d="M35 50 L45 60 L65 40" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M50 35 L50 65 M40 50 L60 50" stroke="currentColor" strokeWidth="2" opacity="0.5"/>
</svg>
),
driver: () => (
<svg className="w-20 h-20" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="30" r="8" fill="currentColor" opacity="0.3"/>
<path d="M42 38 L58 38 L55 55 L45 55 Z" fill="currentColor" opacity="0.2"/>
<path d="M45 55 L40 70 M55 55 L60 70" stroke="currentColor" strokeWidth="3" strokeLinecap="round"/>
@@ -73,35 +76,35 @@ export function EmptyState({
// Common content
const Content = () => (
<>
<Stack align="center" gap={4} mb={4}>
{/* Visual - Icon or Illustration */}
<div className="flex justify-center mb-4">
<Stack align="center" justify="center">
{IllustrationComponent ? (
<div className="text-gray-500">
<Stack color="text-gray-500">
<IllustrationComponent />
</div>
</Stack>
) : Icon ? (
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-iron-gray/60 border border-charcoal-outline/50">
<Stack h="16" w="16" align="center" justify="center" rounded="2xl" bg="iron-gray/60" border borderColor="charcoal-outline/50">
<Icon className="w-8 h-8 text-gray-500" />
</div>
</Stack>
) : null}
</div>
</Stack>
{/* Title */}
<h3 className="text-xl font-semibold text-white mb-2 text-center">
<Heading level={3} weight="semibold" color="text-white" textAlign="center">
{title}
</h3>
</Heading>
{/* Description */}
{description && (
<p className="text-gray-400 mb-6 text-center leading-relaxed">
<Text color="text-gray-400" textAlign="center" leading="relaxed">
{description}
</p>
</Text>
)}
{/* Action Button */}
{action && (
<div className="flex justify-center">
<Stack align="center" pt={2}>
<Button
variant={action.variant || 'primary'}
onClick={action.onClick}
@@ -112,98 +115,110 @@ export function EmptyState({
)}
{action.label}
</Button>
</div>
</Stack>
)}
</>
</Stack>
);
// Render different variants
switch (variant) {
case 'default':
return (
<div
className={`text-center py-12 ${className}`}
<Stack
py={12}
align="center"
className={className}
role="status"
aria-label={ariaLabel}
aria-live="polite"
>
<div className="max-w-md mx-auto">
<Stack maxWidth="md" fullWidth>
<Content />
</div>
</div>
</Stack>
</Stack>
);
case 'minimal':
return (
<div
className={`text-center py-8 ${className}`}
<Stack
py={8}
align="center"
className={className}
role="status"
aria-label={ariaLabel}
aria-live="polite"
>
<div className="max-w-sm mx-auto space-y-3">
<Stack maxWidth="sm" fullWidth gap={3}>
{/* Minimal icon */}
{Icon && (
<div className="flex justify-center">
<Stack align="center">
<Icon className="w-10 h-10 text-gray-600" />
</div>
</Stack>
)}
<h3 className="text-lg font-medium text-gray-300">
<Heading level={3} weight="medium" color="text-gray-300">
{title}
</h3>
</Heading>
{description && (
<p className="text-sm text-gray-500">
<Text size="sm" color="text-gray-500">
{description}
</p>
</Text>
)}
{action && (
<button
<Button
variant="ghost"
size="sm"
onClick={action.onClick}
className="text-sm text-primary-blue hover:text-blue-400 font-medium mt-2 inline-flex items-center gap-1"
className="text-primary-blue hover:text-blue-400 font-medium mt-2"
icon={action.icon && <action.icon size={3} />}
>
{action.label}
{action.icon && <action.icon className="w-3 h-3" />}
</button>
</Button>
)}
</div>
</div>
</Stack>
</Stack>
);
case 'full-page':
return (
<div
className={`fixed inset-0 bg-deep-graphite flex items-center justify-center p-6 ${className}`}
<Stack
position="fixed"
inset="0"
bg="bg-deep-graphite"
align="center"
justify="center"
p={6}
className={className}
role="status"
aria-label={ariaLabel}
aria-live="polite"
>
<div className="max-w-lg w-full text-center">
<div className="mb-6">
<Stack maxWidth="lg" fullWidth align="center">
<Stack mb={6} align="center">
{IllustrationComponent ? (
<div className="text-gray-500 flex justify-center">
<Stack color="text-gray-500">
<IllustrationComponent />
</div>
</Stack>
) : Icon ? (
<div className="flex justify-center">
<div className="flex h-20 w-20 items-center justify-center rounded-3xl bg-iron-gray/60 border border-charcoal-outline/50">
<Stack align="center">
<Stack h="20" w="20" align="center" justify="center" rounded="2xl" bg="iron-gray/60" border borderColor="charcoal-outline/50">
<Icon className="w-10 h-10 text-gray-500" />
</div>
</div>
</Stack>
</Stack>
) : null}
</div>
</Stack>
<h2 className="text-3xl font-bold text-white mb-4">
<Heading level={2} weight="bold" color="text-white" mb={4}>
{title}
</h2>
</Heading>
{description && (
<p className="text-gray-400 text-lg mb-8 leading-relaxed">
<Text color="text-gray-400" size="lg" mb={8} leading="relaxed">
{description}
</p>
</Text>
)}
{action && (
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<Stack direction={{ base: 'col', md: 'row' }} gap={3} justify="center">
<Button
variant={action.variant || 'primary'}
onClick={action.onClick}
@@ -214,21 +229,22 @@ export function EmptyState({
)}
{action.label}
</Button>
</div>
</Stack>
)}
{/* Additional helper text for full-page variant */}
<div className="mt-8 text-sm text-gray-500">
Need help? Contact us at{' '}
<a
href="mailto:support@gridpilot.com"
className="text-primary-blue hover:underline"
>
support@gridpilot.com
</a>
</div>
</div>
</div>
<Stack mt={8}>
<Text size="sm" color="text-gray-500">
Need help? Contact us at{' '}
<Link
href="mailto:support@gridpilot.com"
className="text-primary-blue hover:underline"
>
support@gridpilot.com
</Link>
</Text>
</Stack>
</Stack>
</Stack>
);
default:
@@ -290,8 +306,6 @@ export function FullPageEmptyState({ icon, title, description, action, className
* Pre-configured empty states for common scenarios
*/
import { Activity, Lock, Search } from 'lucide-react';
export function NoDataEmptyState({ onRetry }: { onRetry?: () => void }) {
return (
<EmptyState

View File

@@ -2,11 +2,10 @@
import { ApiError } from '@/lib/api/base/ApiError';
import { AlertCircle, ArrowLeft, Home, RefreshCw } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Button } from '@/ui/Button';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Stack } from '@/ui/Stack';
import { ErrorDisplayAction, ErrorDisplayProps } from '@/ui/state-types';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';

View File

@@ -1,6 +1,5 @@
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { LoadingWrapperProps } from '@/ui/state-types';
import { Text } from '@/ui/Text';

View File

@@ -2,11 +2,10 @@
import React from 'react';
import { Modal } from '@/ui/Modal';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Button } from '@/ui/Button';
import { Stack } from '@/ui/Stack';
import { AlertCircle } from 'lucide-react';
interface ConfirmDialogProps {

View File

@@ -1,9 +1,8 @@
'use client';
import React from 'react';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { AlertCircle, CheckCircle, Info, AlertTriangle } from 'lucide-react';
interface InlineNoticeProps {

View File

@@ -1,11 +1,11 @@
'use client';
import React, { useState, useEffect, createContext, useContext } from 'react';
import React, { useState, createContext, useContext } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { CheckCircle, AlertCircle, Info, X } from 'lucide-react';
import { IconButton } from '@/ui/IconButton';
interface Toast {
id: string;
@@ -33,7 +33,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
return (
<ToastContext.Provider value={{ showToast }}>
{children}
<Box
<Stack
position="fixed"
bottom={6}
right={6}
@@ -49,7 +49,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
/>
))}
</AnimatePresence>
</Box>
</Stack>
</ToastContext.Provider>
);
}
@@ -94,7 +94,7 @@ function ToastItem({ toast, onClose }: { toast: Toast; onClose: () => void }) {
exit={{ opacity: 0, scale: 0.95 }}
className="pointer-events-auto"
>
<Box
<Stack
px={4}
py={3}
rounded="lg"
@@ -102,22 +102,23 @@ function ToastItem({ toast, onClose }: { toast: Toast; onClose: () => void }) {
border
borderColor={config.border}
shadow="xl"
display="flex"
alignItems="center"
direction="row"
align="center"
gap={3}
minW="300px"
{...({ minWidth: "300px" } as any)}
>
<Icon className={`w-5 h-5 ${config.iconColor}`} />
<Text size="sm" color="text-white" flexGrow={1}>
{toast.message}
</Text>
<button
<IconButton
icon={X}
onClick={onClose}
variant="ghost"
size="sm"
className="text-gray-500 hover:text-white transition-colors"
>
<X className="w-4 h-4" />
</button>
</Box>
/>
</Stack>
</motion.div>
);
}