-
+
+
);
}
// Desktop: render with animations
return (
-
+
{children}
-
+
);
-}
\ No newline at end of file
+}
diff --git a/apps/website/components/mockups/ProtestWorkflowMockup.tsx b/apps/website/components/mockups/ProtestWorkflowMockup.tsx
index 36ae8e462..286ae1157 100644
--- a/apps/website/components/mockups/ProtestWorkflowMockup.tsx
+++ b/apps/website/components/mockups/ProtestWorkflowMockup.tsx
@@ -35,18 +35,9 @@ export function ProtestWorkflowMockup() {
},
];
- const getStatusStyles = (status: string) => {
- switch (status) {
- case 'pending': return 'bg-panel-gray border-gray-700 text-gray-600';
- case 'active': return 'bg-warning-amber/10 border-warning-amber text-warning-amber';
- case 'resolved': return 'bg-success-green/10 border-success-green text-success-green';
- default: return 'bg-panel-gray border-gray-700 text-gray-600';
- }
- };
-
if (isMobile) {
return (
-
+
{steps.map((step, i) => (
@@ -61,17 +52,19 @@ export function ProtestWorkflowMockup() {
mb={1}
border
borderWidth="1px"
- className={getStatusStyles(step.status)}
+ bg={step.status === 'pending' ? 'var(--ui-color-bg-surface)' : step.status === 'active' ? 'rgba(255, 190, 77, 0.1)' : 'rgba(16, 185, 129, 0.1)'}
+ borderColor={step.status === 'pending' ? 'var(--ui-color-border-default)' : step.status === 'active' ? 'var(--ui-color-intent-warning)' : 'var(--ui-color-intent-success)'}
+ color={step.status === 'pending' ? 'var(--ui-color-text-low)' : step.status === 'active' ? 'var(--ui-color-intent-warning)' : 'var(--ui-color-intent-success)'}
>
- {step.name}
+ {step.name}
{i < steps.length - 1 && (
-
+
)}
@@ -83,7 +76,7 @@ export function ProtestWorkflowMockup() {
position="absolute"
insetY="0"
left="0"
- bg="primary-accent"
+ bg="var(--ui-color-intent-primary)"
style={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
/>
@@ -104,7 +97,7 @@ export function ProtestWorkflowMockup() {
};
return (
-
+
{steps.map((step, i) => (
@@ -131,10 +124,12 @@ export function ProtestWorkflowMockup() {
mb={{ base: 1, sm: 1.5, md: 2 }}
border
borderWidth="1px"
- className={getStatusStyles(step.status)}
+ bg={step.status === 'pending' ? 'var(--ui-color-bg-surface)' : step.status === 'active' ? 'rgba(255, 190, 77, 0.1)' : 'rgba(16, 185, 129, 0.1)'}
+ borderColor={step.status === 'pending' ? 'var(--ui-color-border-default)' : step.status === 'active' ? 'var(--ui-color-intent-warning)' : 'var(--ui-color-intent-success)'}
+ color={step.status === 'pending' ? 'var(--ui-color-text-low)' : step.status === 'active' ? 'var(--ui-color-intent-warning)' : 'var(--ui-color-intent-success)'}
whileHover={shouldReduceMotion ? {} : {
scale: 1.05,
- borderColor: '#198CFF',
+ borderColor: 'var(--ui-color-intent-primary)',
transition: { duration: 0.2 }
}}
>
@@ -142,7 +137,7 @@ export function ProtestWorkflowMockup() {
{step.status === 'active' && (
-
+
)}
{step.name}
@@ -159,13 +155,13 @@ export function ProtestWorkflowMockup() {
{i < steps.length - 1 && (
-
+
)}
@@ -189,7 +185,7 @@ export function ProtestWorkflowMockup() {
position="absolute"
insetY="0"
left="0"
- bg="primary-accent"
+ bg="var(--ui-color-intent-primary)"
initial={{ width: '0%' }}
animate={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
transition={{ duration: 0.5, ease: 'easeOut' }}
diff --git a/apps/website/ui/WorkflowMockup.tsx b/apps/website/components/mockups/WorkflowMockup.tsx
similarity index 100%
rename from apps/website/ui/WorkflowMockup.tsx
rename to apps/website/components/mockups/WorkflowMockup.tsx
diff --git a/apps/website/components/notifications/ModalNotification.tsx b/apps/website/components/notifications/ModalNotification.tsx
index 828997b9b..3d981da7b 100644
--- a/apps/website/components/notifications/ModalNotification.tsx
+++ b/apps/website/components/notifications/ModalNotification.tsx
@@ -2,7 +2,7 @@
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
-import { Modal } from '@/ui/Modal';
+import { Modal } from '@/components/shared/Modal';
import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
import { Grid } from '@/ui/Grid';
diff --git a/apps/website/components/onboarding/OnboardingShell.tsx b/apps/website/components/onboarding/OnboardingShell.tsx
index 74a55674c..51201d563 100644
--- a/apps/website/components/onboarding/OnboardingShell.tsx
+++ b/apps/website/components/onboarding/OnboardingShell.tsx
@@ -1,5 +1,6 @@
import { Container } from '@/ui/Container';
import { Stack } from '@/ui/Stack';
+import { Box } from '@/ui/Box';
interface OnboardingShellProps {
children: React.ReactNode;
@@ -16,40 +17,40 @@ interface OnboardingShellProps {
*/
export function OnboardingShell({ children, header, footer, sidebar }: OnboardingShellProps) {
return (
-
+
{header && (
-
+
{header}
-
+
)}
-
+
-
+
{children}
-
+
{sidebar && (
-
+
{sidebar}
-
+
)}
-
+
{footer && (
-
+
{footer}
-
+
)}
-
+
);
}
diff --git a/apps/website/components/races/FileProtestModal.tsx b/apps/website/components/races/FileProtestModal.tsx
index 944488cd6..55325a4bb 100644
--- a/apps/website/components/races/FileProtestModal.tsx
+++ b/apps/website/components/races/FileProtestModal.tsx
@@ -5,7 +5,7 @@ import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
import { InfoBox } from '@/ui/InfoBox';
import { Input } from '@/ui/Input';
-import { Modal } from '@/ui/Modal';
+import { Modal } from '@/components/shared/Modal';
import { Stack } from '@/ui/Stack';
import { Select } from '@/ui/Select';
import { Text } from '@/ui/Text';
diff --git a/apps/website/components/races/RaceFilterModal.tsx b/apps/website/components/races/RaceFilterModal.tsx
index 3adb2cccc..a69d2c952 100644
--- a/apps/website/components/races/RaceFilterModal.tsx
+++ b/apps/website/components/races/RaceFilterModal.tsx
@@ -3,7 +3,7 @@
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
import { Input } from '@/ui/Input';
-import { Modal } from '@/ui/Modal';
+import { Modal } from '@/components/shared/Modal';
import { Stack } from '@/ui/Stack';
import { Select } from '@/ui/Select';
import { Text } from '@/ui/Text';
diff --git a/apps/website/ui/Accordion.tsx b/apps/website/components/shared/Accordion.tsx
similarity index 75%
rename from apps/website/ui/Accordion.tsx
rename to apps/website/components/shared/Accordion.tsx
index 4aee349fb..88b661fc5 100644
--- a/apps/website/ui/Accordion.tsx
+++ b/apps/website/components/shared/Accordion.tsx
@@ -1,9 +1,9 @@
import { ChevronDown, ChevronUp } from 'lucide-react';
import { ReactNode, useState } from 'react';
-import { Box } from './Box';
-import { Icon } from './Icon';
-import { Surface } from './Surface';
-import { Text } from './Text';
+import { Icon } from '@/ui/Icon';
+import { Surface } from '@/ui/Surface';
+import { Text } from '@/ui/Text';
+import { Box } from '@/ui/Box';
export interface AccordionProps {
title: string;
@@ -36,15 +36,23 @@ export const Accordion = ({
return (
-
+
{isOpen && (
diff --git a/apps/website/ui/ConfirmDialog.tsx b/apps/website/components/shared/ConfirmDialog.tsx
similarity index 95%
rename from apps/website/ui/ConfirmDialog.tsx
rename to apps/website/components/shared/ConfirmDialog.tsx
index ef12c052a..c79959d75 100644
--- a/apps/website/ui/ConfirmDialog.tsx
+++ b/apps/website/components/shared/ConfirmDialog.tsx
@@ -1,5 +1,5 @@
import { Button } from '@/ui/Button';
-import { Modal } from '@/ui/Modal';
+import { Modal } from '@/components/shared/Modal';
import { Text } from '@/ui/Text';
import { Icon } from '@/ui/Icon';
import { AlertCircle } from 'lucide-react';
diff --git a/apps/website/components/shared/CountrySelect.tsx b/apps/website/components/shared/CountrySelect.tsx
index ceca20dc8..8b8191f2a 100644
--- a/apps/website/components/shared/CountrySelect.tsx
+++ b/apps/website/components/shared/CountrySelect.tsx
@@ -1,6 +1,3 @@
-/* eslint-disable gridpilot-rules/no-raw-html-in-app */
-
-
import { Check, ChevronDown, Globe, Search } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { CountryFlag } from '@/ui/CountryFlag';
@@ -10,6 +7,7 @@ import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Surface } from '@/ui/Surface';
import { Icon } from '@/ui/Icon';
+import { Input } from '@/ui/Input';
export interface Country {
code: string;
@@ -115,32 +113,31 @@ export function CountrySelect({
return (
{/* Trigger Button */}
-
+
+
{/* Dropdown */}
{isOpen && (
@@ -167,60 +172,49 @@ export function CountrySelect({
overflow="hidden"
>
{/* Search Input */}
-
-
-
- setSearch(e.target.value)}
- placeholder="Search countries..."
- style={{
- width: '100%',
- borderRadius: 'var(--ui-radius-md)',
- border: 'none',
- padding: '0.5rem 1rem 0.5rem 2.25rem',
- backgroundColor: 'var(--ui-color-bg-base)',
- color: 'white',
- fontSize: '0.875rem',
- outline: 'none'
- }}
- />
-
+
+ setSearch(e.target.value)}
+ placeholder="Search countries..."
+ fullWidth
+ size="sm"
+ icon={}
+ />
{/* Country List */}
{filteredCountries.length > 0 ? (
filteredCountries.map((country) => (
-
+
))
) : (
diff --git a/apps/website/ui/DevErrorPanel.tsx b/apps/website/components/shared/DevErrorPanel.tsx
similarity index 100%
rename from apps/website/ui/DevErrorPanel.tsx
rename to apps/website/components/shared/DevErrorPanel.tsx
diff --git a/apps/website/ui/ErrorDisplay.tsx b/apps/website/components/shared/ErrorDisplay.tsx
similarity index 100%
rename from apps/website/ui/ErrorDisplay.tsx
rename to apps/website/components/shared/ErrorDisplay.tsx
diff --git a/apps/website/ui/InfoFlyout.tsx b/apps/website/components/shared/InfoFlyout.tsx
similarity index 91%
rename from apps/website/ui/InfoFlyout.tsx
rename to apps/website/components/shared/InfoFlyout.tsx
index 4d7c61f52..fd5cf6c2f 100644
--- a/apps/website/ui/InfoFlyout.tsx
+++ b/apps/website/components/shared/InfoFlyout.tsx
@@ -1,11 +1,11 @@
import { HelpCircle, X } from 'lucide-react';
import React, { ReactNode, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
-import { Box } from './Box';
-import { Heading } from './Heading';
-import { Icon } from './Icon';
-import { IconButton } from './IconButton';
-import { Surface } from './Surface';
+import { Box } from '@/ui/Box';
+import { Heading } from '@/ui/Heading';
+import { Icon } from '@/ui/Icon';
+import { IconButton } from '@/ui/IconButton';
+import { Surface } from '@/ui/Surface';
export interface InfoFlyoutProps {
isOpen: boolean;
diff --git a/apps/website/ui/Modal.tsx b/apps/website/components/shared/Modal.tsx
similarity index 93%
rename from apps/website/ui/Modal.tsx
rename to apps/website/components/shared/Modal.tsx
index a31180e67..fdb054ccd 100644
--- a/apps/website/ui/Modal.tsx
+++ b/apps/website/components/shared/Modal.tsx
@@ -1,12 +1,12 @@
import { X } from 'lucide-react';
import { ReactNode, useEffect } from 'react';
import { createPortal } from 'react-dom';
-import { Box } from './Box';
-import { Button } from './Button';
-import { Heading } from './Heading';
-import { IconButton } from './IconButton';
-import { Surface } from './Surface';
-import { Text } from './Text';
+import { Box } from '@/ui/Box';
+import { Button } from '@/ui/Button';
+import { Heading } from '@/ui/Heading';
+import { IconButton } from '@/ui/IconButton';
+import { Surface } from '@/ui/Surface';
+import { Text } from '@/ui/Text';
export interface ModalProps {
children: ReactNode;
diff --git a/apps/website/ui/ProgressLine.tsx b/apps/website/components/shared/ProgressLine.tsx
similarity index 100%
rename from apps/website/ui/ProgressLine.tsx
rename to apps/website/components/shared/ProgressLine.tsx
diff --git a/apps/website/ui/theme/ThemeProvider.tsx b/apps/website/components/shared/ThemeProvider.tsx
similarity index 88%
rename from apps/website/ui/theme/ThemeProvider.tsx
rename to apps/website/components/shared/ThemeProvider.tsx
index 8c62f0218..6d29794a6 100644
--- a/apps/website/ui/theme/ThemeProvider.tsx
+++ b/apps/website/components/shared/ThemeProvider.tsx
@@ -1,8 +1,8 @@
'use client';
import React, { createContext, useContext, ReactNode } from 'react';
-import { Theme } from './Theme';
-import { defaultTheme } from './themes/default';
+import { Theme } from '@/ui/theme/Theme';
+import { defaultTheme } from '@/ui/theme/themes/default';
interface ThemeContextType {
theme: Theme;
diff --git a/apps/website/components/shared/UIComponents.tsx b/apps/website/components/shared/UIComponents.tsx
index 816f0203e..9adb23d59 100644
--- a/apps/website/components/shared/UIComponents.tsx
+++ b/apps/website/components/shared/UIComponents.tsx
@@ -3,7 +3,7 @@ import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Container } from '@/ui/Container';
-import { ConfirmDialog } from '@/ui/ConfirmDialog';
+import { ConfirmDialog } from '@/components/shared/ConfirmDialog';
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
import { Card } from '@/ui/Card';
@@ -17,7 +17,7 @@ import { Skeleton } from '@/ui/Skeleton';
import { LoadingSpinner } from '@/ui/LoadingSpinner';
import { Badge } from '@/ui/Badge';
-import { ProgressLine } from '@/ui/ProgressLine';
+import { ProgressLine } from '@/components/shared/ProgressLine';
import { SharedEmptyState } from './SharedEmptyState';
export {
diff --git a/apps/website/ui/UploadDropzone.tsx b/apps/website/components/shared/UploadDropzone.tsx
similarity index 99%
rename from apps/website/ui/UploadDropzone.tsx
rename to apps/website/components/shared/UploadDropzone.tsx
index 00823e99b..4e8e0a580 100644
--- a/apps/website/ui/UploadDropzone.tsx
+++ b/apps/website/components/shared/UploadDropzone.tsx
@@ -107,7 +107,8 @@ export function UploadDropzone({
cursor: 'pointer'
}}
>
-
-
-
-
- {title && (
-
-
- {title}
-
- )}
- {children}
-
-
-
-
+
+
+ {title && (
+
+
+ {title}
+
+ )}
+ {children}
+
+
);
}
export function TeamsDirectorySection({ children, title, accentColor = "primary-accent" }: { children: ReactNode, title: string, accentColor?: string }) {
+ const intentMap: Record = {
+ 'primary-accent': 'primary',
+ 'telemetry-aqua': 'telemetry',
+ };
+
return (
-
-
-
- {title}
-
+
+
+
+ {title}
+
{children}
-
+
);
}
diff --git a/apps/website/lib/display-objects/LeagueRoleDisplay.test.ts b/apps/website/lib/display-objects/LeagueRoleDisplay.test.ts
deleted file mode 100644
index 75cf062ba..000000000
--- a/apps/website/lib/display-objects/LeagueRoleDisplay.test.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { LeagueRoleDisplay } from './LeagueRoleDisplay';
-
-describe('LeagueRoleDisplay', () => {
- it('should be defined', () => {
- expect(LeagueRoleDisplay).toBeDefined();
- });
-});
diff --git a/apps/website/lib/display-objects/LeagueWizardValidationMessages.test.ts b/apps/website/lib/display-objects/LeagueWizardValidationMessages.test.ts
deleted file mode 100644
index 65a6a4206..000000000
--- a/apps/website/lib/display-objects/LeagueWizardValidationMessages.test.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { LeagueWizardValidationMessages } from './LeagueWizardValidationMessages';
-
-describe('LeagueWizardValidationMessages', () => {
- it('should be defined', () => {
- expect(LeagueWizardValidationMessages).toBeDefined();
- });
-});
diff --git a/apps/website/templates/AdminDashboardTemplate.tsx b/apps/website/templates/AdminDashboardTemplate.tsx
index d9fe71df8..fa715eddb 100644
--- a/apps/website/templates/AdminDashboardTemplate.tsx
+++ b/apps/website/templates/AdminDashboardTemplate.tsx
@@ -6,21 +6,18 @@ import { AdminSectionHeader } from '@/components/admin/AdminSectionHeader';
import { AdminStatsPanel } from '@/components/admin/AdminStatsPanel';
import { routes } from '@/lib/routing/RouteConfig';
import { AdminDashboardViewData } from '@/lib/view-data/AdminDashboardViewData';
-import {
- SharedBox,
- SharedButton,
- SharedCard,
- SharedContainer,
- SharedIcon,
- SharedGrid,
- SharedStack,
- SharedText,
- SharedBadge
-} from '@/components/shared/UIComponents';
+import { Box } from '@/ui/Box';
+import { Button } from '@/ui/Button';
+import { Card } from '@/ui/Card';
+import { Container } from '@/ui/Container';
+import { Icon } from '@/ui/Icon';
+import { Grid } from '@/ui/Grid';
+import { Stack } from '@/ui/Stack';
+import { Text } from '@/ui/Text';
+import { Badge } from '@/ui/Badge';
import { QuickActionLink } from '@/ui/QuickActionLink';
import {
Activity,
- ArrowRight,
Clock,
RefreshCw,
Shield,
@@ -70,93 +67,95 @@ export function AdminDashboardTemplate({
];
return (
-
-
-
+
+
+
}
>
- Refresh Telemetry
-
+
+
+ Refresh Telemetry
+
+
}
/>
-
+
{/* System Health & Status */}
-
-
+
+
-
-
- Operational
-
-
+
+
+
+ Operational
+
+
}
/>
-
-
-
-
- Suspended Users
- {viewData.stats.suspendedUsers}
-
-
-
-
-
- Deleted Users
- {viewData.stats.deletedUsers}
-
-
-
-
-
- New Registrations (24h)
- {viewData.stats.newUsersToday}
-
-
-
-
-
+
+
+
+
+ Suspended Users
+ {viewData.stats.suspendedUsers}
+
+
+
+
+
+ Deleted Users
+ {viewData.stats.deletedUsers}
+
+
+
+
+
+ New Registrations (24h)
+ {viewData.stats.newUsersToday}
+
+
+
+
+
{/* Quick Operations */}
-
-
+
+
-
+
-
-
-
-
+
+
+
+
-
+
+
-
-
-
+
+
+
);
}
diff --git a/apps/website/templates/CreateLeagueWizardTemplate.tsx b/apps/website/templates/CreateLeagueWizardTemplate.tsx
index 836a0bf50..dfb5a74d2 100644
--- a/apps/website/templates/CreateLeagueWizardTemplate.tsx
+++ b/apps/website/templates/CreateLeagueWizardTemplate.tsx
@@ -1,32 +1,24 @@
'use client';
-import { FormEvent, ReactNode } from 'react';
+import { FormEvent } from 'react';
import { LeagueReviewSummary } from '@/components/leagues/LeagueReviewSummary';
-import {
- SharedBox,
- SharedButton,
- SharedStack,
- SharedText,
- SharedIcon,
- SharedContainer
-} from '@/components/shared/UIComponents';
import { Card } from '@/ui/Card';
import { Heading } from '@/ui/Heading';
import { Input } from '@/ui/Input';
+import { Box } from '@/ui/Box';
+import { Button } from '@/ui/Button';
+import { Grid } from '@/ui/Grid';
+import { Stack } from '@/ui/Stack';
+import { Text } from '@/ui/Text';
+import { Icon } from '@/ui/Icon';
import {
AlertCircle,
- Award,
- Calendar,
Check,
- CheckCircle2,
ChevronLeft,
ChevronRight,
FileText,
Loader2,
- Scale,
Sparkles,
- Trophy,
- Users,
} from 'lucide-react';
import { LeagueBasicsSection } from '@/components/leagues/LeagueBasicsSection';
import { LeagueDropSection } from '@/components/leagues/LeagueDropSection';
@@ -90,45 +82,45 @@ export function CreateLeagueWizardTemplate({
const CurrentStepIcon = currentStepData?.icon ?? FileText;
return (
-
+
{/* Header with icon */}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Create a new league
-
+
We'll also set up your first season in {steps.length} easy steps.
-
-
+
+
A league is your long-term brand. Each season is a block of races you can run again and again.
-
-
-
-
+
+
+
+
{/* Desktop Progress Bar */}
-
-
+
+
{/* Background track */}
-
+
{/* Progress fill */}
-
-
+
{steps.map((wizardStep) => {
const isCompleted = wizardStep.id < step;
const isCurrent = wizardStep.id === step;
@@ -136,7 +128,7 @@ export function CreateLeagueWizardTemplate({
const StepIcon = wizardStep.icon;
return (
-
-
{isCompleted ? (
-
+
) : (
-
+
)}
-
-
-
+
+
{wizardStep.label}
-
-
-
+
+
+
);
})}
-
-
-
-
- {/* Mobile Progress */}
-
-
-
-
- {currentStepData?.label}
-
-
- {step}/{steps.length}
-
-
-
-
-
- {/* Step dots */}
-
- {steps.map((s) => (
-
- ))}
-
-
+
+
+
{/* Main Card */}
- {/* Top gradient accent */}
-
-
{/* Step header */}
-
-
-
-
-
-
-
- {getStepTitle(step)}
-
- {getStepContextLabel(step)}
-
-
+
+
+
+
+
+
+
+ {getStepTitle(step)}
+
+
+ {getStepContextLabel(step)}
+
+
+
-
+
{getStepSubtitle(step)}
-
-
-
- Step
- {step}
- / {steps.length}
-
-
+
+
+
+ Step
+ {step}
+ / {steps.length}
+
+
- {/* Divider */}
-
-
- {/* Step content with min-height for consistency */}
-
+ {/* Step content */}
+
{step === 1 && (
-
+
-
-
-
-
- First season
-
-
- Name the first season that will run in this league.
-
-
-
-
-
+
+
+
+ First season
+
+
+ Name the first season that will run in this league.
+
+
+
+
Season name
-
+
@@ -293,75 +238,63 @@ export function CreateLeagueWizardTemplate({
}
placeholder="e.g., Season 1 (2025)"
/>
-
+
Seasons are the individual competitive runs inside your league. You can run Season 2, Season 3, or parallel seasons later.
-
-
-
-
+
+
+
+
)}
{step === 2 && (
-
-
-
+
)}
{step === 3 && (
-
-
-
+
+
+
Applies to: First season of this league.
-
-
- These settings only affect this season. Future seasons can use different formats.
-
-
+
+
-
+
)}
{step === 4 && (
-
-
-
+
+
+
Applies to: First season of this league.
-
-
- These settings only affect this season. Future seasons can use different formats.
-
-
+
+
-
+
)}
{step === 5 && (
-
-
-
+
+
+
Applies to: First season of this league.
-
-
- These settings only affect this season. Future seasons can use different formats.
-
-
- {/* Scoring Pattern Selection */}
+
+
-
- {/* Divider */}
-
- {/* Championships & Drop Rules side by side on larger screens */}
-
+
-
+
{errors.submit && (
-
-
- {errors.submit}
-
+
+
+ {errors.submit}
+
)}
-
+
)}
{step === 6 && (
-
-
-
+
+
+
Applies to: First season of this league.
-
-
- These settings only affect this season. Future seasons can use different formats.
-
-
+
+
-
+
)}
{step === 7 && (
-
+
{errors.submit && (
-
-
- {errors.submit}
-
+
+
+ {errors.submit}
+
)}
-
+
)}
-
+
{/* Navigation */}
-
-
+ }
>
- Back
-
-
-
- {/* Mobile step dots */}
-
- {steps.map((s) => (
-
- ))}
-
+
+
+ Back
+
+
+
{step < 7 ? (
- }
>
- Continue
-
+
+ Continue
+
+
+
) : (
- : }
>
- {loading ? (
- Creating…
- ) : (
- Create League
- )}
-
+
+ {loading ? : }
+ {loading ? 'Creating…' : 'Create League'}
+
+
)}
-
-
+
+
{/* Helper text */}
-
+
This will create your league and its first season. You can edit both later.
-
-
+
+
);
}
diff --git a/apps/website/templates/DashboardTemplate.tsx b/apps/website/templates/DashboardTemplate.tsx
index 345fe44f9..2a5d7ae85 100644
--- a/apps/website/templates/DashboardTemplate.tsx
+++ b/apps/website/templates/DashboardTemplate.tsx
@@ -1,25 +1,18 @@
'use client';
-import { DashboardControlBar } from '@/components/dashboard/DashboardControlBar';
import { DashboardKpiRow } from '@/components/dashboard/DashboardKpiRow';
-import { DashboardRail } from '@/components/dashboard/DashboardRail';
-import { DashboardShell } from '@/components/dashboard/DashboardShell';
import { RecentActivityTable, type ActivityItem } from '@/components/dashboard/RecentActivityTable';
import { TelemetryPanel } from '@/components/dashboard/TelemetryPanel';
-import { routes } from '@/lib/routing/RouteConfig';
import type { DashboardViewData } from '@/lib/view-data/DashboardViewData';
-import { Avatar } from '@/ui/Avatar';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
-import { IconButton } from '@/ui/IconButton';
import { Grid } from '@/ui/Grid';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
-import { Bell, Calendar, LayoutDashboard, Search, Settings, Trophy, Users } from 'lucide-react';
-import { useRouter } from 'next/navigation';
interface DashboardTemplateProps {
viewData: DashboardViewData;
+ onNavigateToRaces: () => void;
}
/**
@@ -29,8 +22,10 @@ interface DashboardTemplateProps {
* Composes semantic dashboard components into a high-density data environment.
* Complies with architectural constraints by using UI primitives.
*/
-export function DashboardTemplate({ viewData }: DashboardTemplateProps) {
- const router = useRouter();
+export function DashboardTemplate({
+ viewData,
+ onNavigateToRaces,
+}: DashboardTemplateProps) {
const {
currentDriver,
nextRace,
@@ -43,11 +38,11 @@ export function DashboardTemplate({ viewData }: DashboardTemplateProps) {
} = viewData;
const kpiItems = [
- { label: 'Rating', value: currentDriver.rating, color: 'var(--color-telemetry)' },
- { label: 'Rank', value: `#${currentDriver.rank}`, color: 'var(--color-warning)' },
+ { label: 'Rating', value: currentDriver.rating, intent: 'primary' as const },
+ { label: 'Rank', value: `#${currentDriver.rank}`, intent: 'warning' as const },
{ label: 'Starts', value: currentDriver.totalRaces },
- { label: 'Wins', value: currentDriver.wins, color: 'var(--color-success)' },
- { label: 'Podiums', value: currentDriver.podiums, color: 'var(--color-warning)' },
+ { label: 'Wins', value: currentDriver.wins, intent: 'success' as const },
+ { label: 'Podiums', value: currentDriver.podiums, intent: 'warning' as const },
{ label: 'Leagues', value: activeLeaguesCount },
];
@@ -59,68 +54,8 @@ export function DashboardTemplate({ viewData }: DashboardTemplateProps) {
status: item.type === 'race_result' ? 'success' : 'info'
}));
- const railContent = (
-
-
-
- GP
-
- router.push(routes.protected.dashboard)}
- variant="ghost"
- color="primary-accent"
- />
- router.push(routes.public.leagues)}
- variant="ghost"
- color="var(--color-text-low)"
- />
- router.push(routes.public.races)}
- variant="ghost"
- color="var(--color-text-low)"
- />
- router.push(routes.public.teams)}
- variant="ghost"
- color="var(--color-text-low)"
- />
-
-
- router.push(routes.protected.profile)}
- variant="ghost"
- color="var(--color-text-low)"
- />
-
-
- );
-
- const controlBarActions = (
-
-
-
-
-
-
-
-
- );
-
return (
- }
- >
+
{/* KPI Overview */}
@@ -132,14 +67,14 @@ export function DashboardTemplate({ viewData }: DashboardTemplateProps) {
- Next Event
+ Next Event
{nextRace.track}
- {nextRace.car}
+ {nextRace.car}
- Starts In
- {nextRace.timeUntil}
- {nextRace.formattedDate} @ {nextRace.formattedTime}
+ Starts In
+ {nextRace.timeUntil}
+ {nextRace.formattedDate} @ {nextRace.formattedTime}
@@ -150,7 +85,7 @@ export function DashboardTemplate({ viewData }: DashboardTemplateProps) {
) : (
- No recent activity recorded.
+ No recent activity recorded.
)}
@@ -164,18 +99,18 @@ export function DashboardTemplate({ viewData }: DashboardTemplateProps) {
{hasLeagueStandings ? (
{leagueStandings.map((standing) => (
-
+
{standing.leagueName}
- Pos: {standing.position} / {standing.totalDrivers}
+ Pos: {standing.position} / {standing.totalDrivers}
- {standing.points} PTS
+ {standing.points} PTS
))}
) : (
- No active championships.
+ No active championships.
)}
@@ -183,21 +118,21 @@ export function DashboardTemplate({ viewData }: DashboardTemplateProps) {
{upcomingRaces.slice(0, 3).map((race) => (
-
+
- {race.track}
- {race.timeUntil}
+ {race.track}
+ {race.timeUntil}
- {race.car}
- {race.formattedDate}
+ {race.car}
+ {race.formattedDate}
))}
@@ -206,6 +141,6 @@ export function DashboardTemplate({ viewData }: DashboardTemplateProps) {
-
+
);
}
diff --git a/apps/website/templates/DriversTemplate.tsx b/apps/website/templates/DriversTemplate.tsx
index 8d3ee3591..2590e584b 100644
--- a/apps/website/templates/DriversTemplate.tsx
+++ b/apps/website/templates/DriversTemplate.tsx
@@ -7,8 +7,9 @@ import { DriverTableRow } from '@/components/drivers/DriverTableRow';
import { EmptyState } from '@/ui/EmptyState';
import type { DriversViewData } from '@/lib/types/view-data/DriversViewData';
import { Container } from '@/ui/Container';
-import { Stack } from '@/ui/Stack';
+import { Group } from '@/ui/Group';
import { Search } from 'lucide-react';
+import React from 'react';
interface DriversTemplateProps {
viewData: DriversViewData | null;
@@ -27,14 +28,9 @@ export function DriversTemplate({
onDriverClick,
onViewLeaderboard
}: DriversTemplateProps) {
- const drivers = viewData?.drivers || [];
- const totalRaces = viewData?.totalRaces || 0;
- const totalWins = viewData?.totalWins || 0;
- const activeCount = viewData?.activeCount || 0;
-
return (
-
+
)}
-
+
);
}
diff --git a/apps/website/templates/HomeTemplate.tsx b/apps/website/templates/HomeTemplate.tsx
index a3240ab48..89296eb83 100644
--- a/apps/website/templates/HomeTemplate.tsx
+++ b/apps/website/templates/HomeTemplate.tsx
@@ -15,12 +15,8 @@ import { CompanionAutomationMockup } from '@/components/mockups/CompanionAutomat
import { RaceHistoryMockup } from '@/components/mockups/RaceHistoryMockup';
import { SimPlatformMockup } from '@/components/mockups/SimPlatformMockup';
import { ModeGuard } from '@/components/shared/ModeGuard';
+import { DiscoverySection } from '@/ui/DiscoverySection';
import { Box } from '@/ui/Box';
-import { Container } from '@/ui/Container';
-import { Heading } from '@/ui/Heading';
-import { Grid } from '@/ui/Grid';
-import { Section } from '@/ui/Section';
-import { Text } from '@/ui/Text';
export interface HomeViewData {
isAlpha: boolean;
@@ -53,7 +49,7 @@ interface HomeTemplateProps {
*/
export function HomeTemplate({ viewData }: HomeTemplateProps) {
return (
-
+
{/* Hero Section */}
-
-
-
-
-
- Live Ecosystem
-
-
-
- DISCOVER THE GRID
-
-
- Explore leagues, teams, and races that make up the GridPilot ecosystem.
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
{/* CTA & FAQ */}
diff --git a/apps/website/templates/LeagueAdminScheduleTemplate.tsx b/apps/website/templates/LeagueAdminScheduleTemplate.tsx
index ec6271f9a..ba3813655 100644
--- a/apps/website/templates/LeagueAdminScheduleTemplate.tsx
+++ b/apps/website/templates/LeagueAdminScheduleTemplate.tsx
@@ -95,7 +95,7 @@ export function LeagueAdminScheduleTemplate({
- Status: {publishedLabel}
+ Status: {publishedLabel}
-
+
{isEditing ? 'Edit race' : 'Add race'}
-
+
Track
-
+
Races
@@ -178,7 +178,7 @@ export function LeagueAdminScheduleTemplate({
>
- {race.name}
+ {race.name}
{race.scheduledAt}
diff --git a/apps/website/templates/LeagueSettingsTemplate.tsx b/apps/website/templates/LeagueSettingsTemplate.tsx
index 5d24220bb..bd978cd45 100644
--- a/apps/website/templates/LeagueSettingsTemplate.tsx
+++ b/apps/website/templates/LeagueSettingsTemplate.tsx
@@ -23,19 +23,19 @@ export function LeagueSettingsTemplate({ viewData }: LeagueSettingsTemplateProps
-
-
+
+
- LEAGUE INFORMATION
+ LEAGUE INFORMATION
Basic league details and identification
-
+
-
+
@@ -48,16 +48,16 @@ export function LeagueSettingsTemplate({ viewData }: LeagueSettingsTemplateProps
-
-
+
+
- CONFIGURATION
+ CONFIGURATION
League rules and participation limits
-
+
@@ -69,12 +69,12 @@ export function LeagueSettingsTemplate({ viewData }: LeagueSettingsTemplateProps
{/* Note about forms */}
-
-
+
+
Settings Management
-
+
Form-based editing and ownership transfer functionality will be implemented in future updates.
@@ -88,8 +88,8 @@ export function LeagueSettingsTemplate({ viewData }: LeagueSettingsTemplateProps
function InfoItem({ label, value, capitalize }: { label: string, value: string, capitalize?: boolean }) {
return (
- {label.toUpperCase()}
- {capitalize ? value.toUpperCase() : value}
+ {label.toUpperCase()}
+ {capitalize ? value.toUpperCase() : value}
);
}
@@ -97,12 +97,12 @@ function InfoItem({ label, value, capitalize }: { label: string, value: string,
function ConfigItem({ icon, label, value }: { icon: LucideIcon, label: string, value: string | number }) {
return (
-
+
- {label.toUpperCase()}
- {value}
+ {label.toUpperCase()}
+ {value}
);
diff --git a/apps/website/templates/LeagueWalletTemplate.tsx b/apps/website/templates/LeagueWalletTemplate.tsx
index 762066a18..a2c2722cf 100644
--- a/apps/website/templates/LeagueWalletTemplate.tsx
+++ b/apps/website/templates/LeagueWalletTemplate.tsx
@@ -2,15 +2,13 @@
import { WalletSummaryPanel } from '@/components/leagues/WalletSummaryPanel';
import type { LeagueWalletViewData } from '@/lib/view-data/leagues/LeagueWalletViewData';
-import {
- SharedBox,
- SharedButton,
- SharedStack,
- SharedText,
- SharedIcon,
- SharedContainer
-} from '@/components/shared/UIComponents';
+import { Box } from '@/ui/Box';
+import { Button } from '@/ui/Button';
+import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading';
+import { Icon } from '@/ui/Icon';
+import { Stack } from '@/ui/Stack';
+import { Text } from '@/ui/Text';
import { Download } from 'lucide-react';
import { TemplateProps } from '@/lib/contracts/components/ComponentContracts';
@@ -21,23 +19,23 @@ interface LeagueWalletTemplateProps extends TemplateProps
transactions: any[];
}
-export function LeagueWalletTemplate({ viewData, onExport, transactions }: LeagueWalletTemplateProps) {
+export function LeagueWalletTemplate({ viewData, onExport }: LeagueWalletTemplateProps) {
return (
-
-
+
+
{/* Header */}
-
-
+
+
League Wallet
- Manage your league's finances and payouts
-
-
-
-
- Export
-
-
-
+ Manage your league's finances and payouts
+
+
+
{/* Alpha Notice */}
-
-
- Alpha Note: Wallet management is demonstration-only.
+
+
+ Alpha Note: Wallet management is demonstration-only.
Real payment processing and bank integrations will be available when the payment system is fully implemented.
-
-
-
-
+
+
+
+
);
}
diff --git a/apps/website/templates/LeaguesTemplate.tsx b/apps/website/templates/LeaguesTemplate.tsx
index 11e9bc8e5..5de2d90d4 100644
--- a/apps/website/templates/LeaguesTemplate.tsx
+++ b/apps/website/templates/LeaguesTemplate.tsx
@@ -3,29 +3,20 @@
import { LeagueCard } from '@/components/leagues/LeagueCardWrapper';
import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
import { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
-import {
- SharedBox,
- SharedButton,
- SharedStack,
- SharedText,
- SharedIcon,
- SharedContainer
-} from '@/components/shared/UIComponents';
import { Heading } from '@/ui/Heading';
import { Input } from '@/ui/Input';
+import { Button } from '@/ui/Button';
+import { Group } from '@/ui/Group';
+import { Grid } from '@/ui/Grid';
+import { Container } from '@/ui/Container';
+import { Text } from '@/ui/Text';
+import { Icon } from '@/ui/Icon';
+import { Section } from '@/ui/Section';
+import { StatusDot } from '@/ui/StatusDot';
import {
- Award,
- Clock,
- Flag,
- Flame,
- Globe,
Plus,
Search,
- Sparkles,
- Target,
- Timer,
Trophy,
- Users,
type LucideIcon,
} from 'lucide-react';
import React from 'react';
@@ -77,44 +68,42 @@ export function LeaguesTemplate({
onClearFilters,
}: LeaguesTemplateProps) {
return (
-
-
+
+
{/* Hero */}
-
-
-
-
- Competition Hub
-
-
- Find Your Grid
+
+
+
+
+ Competition Hub
+
+
+ Find Your Grid
-
+
From casual sprints to epic endurance battles — discover the perfect league for your racing style.
-
-
+
+
-
-
- {viewData.leagues.length}
- Active Leagues
-
-
-
+
+ {viewData.leagues.length}
+ Active Leagues
+
+
+ }
>
-
-
- Create League
-
-
-
-
+ Create League
+
+
+
{/* Search & Filters */}
-
+
}
/>
-
+
{categories.map((category) => {
const isActive = activeCategory === category.id;
const CategoryIcon = category.icon;
return (
- onCategoryChange(category.id)}
variant={isActive ? 'primary' : 'secondary'}
size="sm"
+ icon={}
>
-
-
-
-
- {category.label}
-
-
+ {category.label}
+
);
})}
-
-
+
+
{/* Grid */}
-
+
{filteredLeagues.length > 0 ? (
-
+
{filteredLeagues.map((league) => (
onLeagueClick(league.id)}
/>
))}
-
+
) : (
-
-
-
-
- No Leagues Found
- Try adjusting your search or filters
-
- Clear All Filters
-
-
+
+
+
+ No Leagues Found
+ Try adjusting your search or filters
+
+
+
)}
-
-
-
+
+
+
);
}
diff --git a/apps/website/templates/ProfileLiveryUploadTemplate.tsx b/apps/website/templates/ProfileLiveryUploadTemplate.tsx
index 6c508a7be..369610587 100644
--- a/apps/website/templates/ProfileLiveryUploadTemplate.tsx
+++ b/apps/website/templates/ProfileLiveryUploadTemplate.tsx
@@ -1,6 +1,6 @@
'use client';
-import { UploadDropzone } from '@/ui/UploadDropzone';
+import { UploadDropzone } from '@/components/shared/UploadDropzone';
import { routes } from '@/lib/routing/RouteConfig';
import {
SharedBox,
diff --git a/apps/website/templates/ProfileTemplate.tsx b/apps/website/templates/ProfileTemplate.tsx
index a90c3ac46..c412dbbba 100644
--- a/apps/website/templates/ProfileTemplate.tsx
+++ b/apps/website/templates/ProfileTemplate.tsx
@@ -39,7 +39,7 @@ export function ProfileTemplate({
return (
-
+
Create Your Driver Profile
@@ -93,7 +93,7 @@ export function ProfileTemplate({
{viewData.teamMemberships.length > 0 && (
- Teams
+ Teams
({
team: { id: m.teamId, name: m.teamName },
@@ -109,8 +109,8 @@ export function ProfileTemplate({
- Achievements
- {viewData.extendedProfile.achievements.length} earned
+ Achievements
+ {viewData.extendedProfile.achievements.length} earned
({
@@ -128,7 +128,7 @@ export function ProfileTemplate({
{activeTab === 'history' && (
- Race History
+ Race History
@@ -139,14 +139,14 @@ export function ProfileTemplate({
{activeTab === 'stats' && viewData.stats && (
- Performance Overview
+ Performance Overview
diff --git a/apps/website/templates/RacesTemplate.tsx b/apps/website/templates/RacesTemplate.tsx
index df1886171..467a43154 100644
--- a/apps/website/templates/RacesTemplate.tsx
+++ b/apps/website/templates/RacesTemplate.tsx
@@ -8,12 +8,13 @@ import { RaceScheduleTable } from '@/components/races/RaceScheduleTable';
import { RaceSidebar } from '@/components/races/RaceSidebar';
import type { SessionStatus } from '@/components/races/SessionStatusBadge';
import type { RacesViewData } from '@/lib/view-data/RacesViewData';
-import { Box } from '@/ui/Box';
import { Container } from '@/ui/Container';
import { Grid } from '@/ui/Grid';
import { GridItem } from '@/ui/GridItem';
-import { Stack } from '@/ui/Stack';
+import { Group } from '@/ui/Group';
import { Text } from '@/ui/Text';
+import { Panel } from '@/ui/Panel';
+import React from 'react';
export type TimeFilter = 'all' | 'upcoming' | 'live' | 'past';
export type RaceStatusFilter = 'scheduled' | 'running' | 'completed' | 'cancelled' | 'all';
@@ -50,78 +51,77 @@ export function RacesTemplate({
setShowFilterModal,
}: RacesTemplateProps) {
return (
-
-
-
-
+
+
+
-
+
-
-
-
- setShowFilterModal(true)}
- />
-
-
-
- Race Schedule
-
- ({
- id: race.id,
- track: race.track,
- car: race.car,
- leagueName: race.leagueName,
- time: race.timeLabel,
- status: race.status as SessionStatus
- }))}
- onRaceClick={onRaceClick}
- />
-
-
-
-
-
-
+
+
+ setShowFilterModal(true)}
/>
-
-
- setShowFilterModal(false)}
- statusFilter={statusFilter}
- setStatusFilter={setStatusFilter}
- leagueFilter={leagueFilter}
- setLeagueFilter={setLeagueFilter}
- timeFilter={timeFilter}
- setTimeFilter={setTimeFilter}
- searchQuery=""
- setSearchQuery={() => {}}
- leagues={viewData.leagues}
- showSearch={false}
- showTimeFilter={false}
- />
-
-
-
+
+ ({
+ id: race.id,
+ track: race.track,
+ car: race.car,
+ leagueName: race.leagueName,
+ time: race.timeLabel,
+ status: race.status as SessionStatus
+ }))}
+ onRaceClick={onRaceClick}
+ />
+
+
+
+
+
+
+
+
+
+ setShowFilterModal(false)}
+ statusFilter={statusFilter}
+ setStatusFilter={setStatusFilter}
+ leagueFilter={leagueFilter}
+ setLeagueFilter={setLeagueFilter}
+ timeFilter={timeFilter}
+ setTimeFilter={setTimeFilter}
+ searchQuery=""
+ setSearchQuery={() => {}}
+ leagues={viewData.leagues}
+ showSearch={false}
+ showTimeFilter={false}
+ />
+
+
);
}
diff --git a/apps/website/templates/RosterAdminTemplate.tsx b/apps/website/templates/RosterAdminTemplate.tsx
index a6b60fb7a..3c05caf7d 100644
--- a/apps/website/templates/RosterAdminTemplate.tsx
+++ b/apps/website/templates/RosterAdminTemplate.tsx
@@ -42,11 +42,11 @@ export function RosterAdminTemplate({
-
- PENDING JOIN REQUESTS
+
+ PENDING JOIN REQUESTS
-
- {pendingCountLabel}
+
+ {pendingCountLabel}
@@ -58,13 +58,13 @@ export function RosterAdminTemplate({
{joinRequests.map((req) => (
-
+
- {req.driver.name}
+ {req.driver.name}
{req.formattedRequestedAt}
{req.message && (
- "{req.message}"
+ "{req.message}"
)}
@@ -91,8 +91,8 @@ export function RosterAdminTemplate({
{/* Members Section */}
-
- ACTIVE ROSTER
+
+ ACTIVE ROSTER
{loading ? (
@@ -114,7 +114,7 @@ export function RosterAdminTemplate({
{members.map((member) => (
- {member.driver.name}
+ {member.driver.name}
{member.formattedJoinedAt}
@@ -124,15 +124,13 @@ export function RosterAdminTemplate({
as="select"
value={member.role}
onChange={(e: React.ChangeEvent) => onRoleChange(member.driverId, e.target.value as MembershipRole)}
- bg="bg-iron-gray"
+ bg="rgba(255,255,255,0.05)"
border
- borderColor="border-charcoal-outline"
+ borderColor="rgba(255,255,255,0.1)"
rounded="md"
px={2}
py={1}
- fontSize="xs"
- weight="bold"
- color="text-white"
+ color="white"
>
{roleOptions.map((role) => (
{role.toUpperCase()}
@@ -146,8 +144,8 @@ export function RosterAdminTemplate({
size="sm"
>
-
- REMOVE
+
+ REMOVE
diff --git a/apps/website/templates/SponsorDashboardTemplate.tsx b/apps/website/templates/SponsorDashboardTemplate.tsx
index de7f1e9e3..380f31e4b 100644
--- a/apps/website/templates/SponsorDashboardTemplate.tsx
+++ b/apps/website/templates/SponsorDashboardTemplate.tsx
@@ -17,8 +17,8 @@ import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Link } from '@/ui/Link';
import { Grid } from '@/ui/Grid';
-import { GridItem } from '@/ui/GridItem';
import { Stack } from '@/ui/Stack';
+import { Text } from '@/ui/Text';
import {
Bell,
Car,
@@ -81,7 +81,7 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
description: a.formattedImpressions ? `${a.formattedImpressions} impressions` : '',
timestamp: a.time,
icon: Clock,
- color: a.typeColor || 'text-primary-blue',
+ color: a.typeColor || 'primary-accent',
}));
return (
@@ -97,7 +97,7 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
{/* Key Metrics */}
-
+
{/* Main Content Grid */}
-
-
+
+
{/* Sponsorship Categories */}
Your Sponsorships
- }>
- View All
+
-
+
@@ -193,12 +196,15 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
Top Performing
- }>
- Find More
+
-
+
-
+
-
+
{/* Recent Activity */}
@@ -239,23 +245,35 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
Quick Actions
- }>
- Find Leagues to Sponsor
+
- }>
- Browse Teams
+
- }>
- Discover Drivers
+
- }>
- Manage Billing
+
@@ -266,8 +284,11 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
{viewData.upcomingRenewals.length > 0 && (
- }>
- Upcoming Renewals
+
+
+
+ Upcoming Renewals
+
{viewData.upcomingRenewals.map((renewal) => (
@@ -278,7 +299,7 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
)}
-
+
diff --git a/apps/website/templates/TeamLeaderboardTemplate.tsx b/apps/website/templates/TeamLeaderboardTemplate.tsx
index be72a787e..2c14851ab 100644
--- a/apps/website/templates/TeamLeaderboardTemplate.tsx
+++ b/apps/website/templates/TeamLeaderboardTemplate.tsx
@@ -2,15 +2,17 @@
import { LeaderboardFiltersBar } from '@/components/leaderboards/LeaderboardFiltersBar';
import type { SkillLevel, SortBy, TeamLeaderboardViewData } from '@/lib/view-data/TeamLeaderboardViewData';
-import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
-import { Stack } from '@/ui/Stack';
+import { Group } from '@/ui/Group';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table';
import { Text } from '@/ui/Text';
+import { Panel } from '@/ui/Panel';
+import { Section } from '@/ui/Section';
import { Award, ChevronLeft } from 'lucide-react';
+import React from 'react';
interface TeamLeaderboardTemplateProps {
viewData: TeamLeaderboardViewData;
@@ -30,87 +32,88 @@ export function TeamLeaderboardTemplate({
const { searchQuery, filteredAndSortedTeams } = viewData;
return (
-
-
-
- {/* Header */}
-
-
- }>
- Back
-
-
- Global Standings
- Team Performance Index
-
-
-
-
+
+
+ {/* Header */}
+
+
+ }>
+ Back
+
+
+ Global Standings
+ Team Performance Index
+
+
+
+
-
+
-
-
-
-
- Rank
- Team
- Personnel
- Races
- Rating
-
-
-
- {filteredAndSortedTeams.length > 0 ? (
- filteredAndSortedTeams.map((team, index) => (
- onTeamClick(team.id)}
- cursor="pointer"
- hoverBg="surface-charcoal/50"
- >
-
-
- #{index + 1}
-
-
-
-
-
- {team.name.substring(0, 2).toUpperCase()}
-
- {team.name}
-
-
-
- {team.memberCount}
-
-
- {team.totalRaces}
-
-
- 1450
-
-
- ))
- ) : (
-
-
-
- No teams found matching criteria
+
+
+
+
+ Rank
+ Team
+ Personnel
+ Races
+ Rating
+
+
+
+ {filteredAndSortedTeams.length > 0 ? (
+ filteredAndSortedTeams.map((team, index) => (
+ onTeamClick(team.id)}
+ clickable
+ >
+
+
+ #{index + 1}
+
+
+
+ {team.name.substring(0, 2).toUpperCase()}
+
+ {team.name}
+
+
+
+ {team.memberCount}
+
+
+ {team.totalRaces}
+
+
+ 1450
+
- )}
-
-
-
-
-
-
+ ))
+ ) : (
+
+
+
+
+
+ No teams found matching criteria
+
+
+
+
+
+ )}
+
+
+
+
+
);
}
diff --git a/apps/website/templates/TeamsTemplate.tsx b/apps/website/templates/TeamsTemplate.tsx
index 452313c71..045d6a5d7 100644
--- a/apps/website/templates/TeamsTemplate.tsx
+++ b/apps/website/templates/TeamsTemplate.tsx
@@ -5,10 +5,11 @@ import { TeamGrid } from '@/components/teams/TeamGrid';
import { TeamLeaderboardPreview } from '@/components/teams/TeamLeaderboardPreviewWrapper';
import { TeamsDirectoryHeader } from '@/components/teams/TeamsDirectoryHeader';
import { TeamsDirectory, TeamsDirectorySection } from '@/components/teams/TeamsDirectory';
-import { SharedEmptyState } from '@/components/shared/UIComponents';
+import { EmptyState } from '@/ui/EmptyState';
import type { TeamsViewData } from '@/lib/view-data/TeamsViewData';
import { Users } from 'lucide-react';
import { TemplateProps } from '@/lib/contracts/components/ComponentContracts';
+import React from 'react';
interface TeamsTemplateProps extends TemplateProps {
onTeamClick?: (teamId: string) => void;
@@ -41,7 +42,7 @@ export function TeamsTemplate({ viewData, onTeamClick, onViewFullLeaderboard, on
))}
) : (
-
{mutationState.error && (
-
-
-
- {mutationState.error}
-
-
+
+
+ {mutationState.error}
+
)}
-
+
+
{mutationState.error && (
-
-
-
- {mutationState.error}
-
-
+
+
+ {mutationState.error}
+
)}
-
+
By creating an account, you agree to our{' '}
Terms
{' '}and{' '}
Privacy
-
+
);
diff --git a/apps/website/templates/layout/GlobalFooterTemplate.tsx b/apps/website/templates/layout/GlobalFooterTemplate.tsx
index 52b150ed7..b8d98e831 100644
--- a/apps/website/templates/layout/GlobalFooterTemplate.tsx
+++ b/apps/website/templates/layout/GlobalFooterTemplate.tsx
@@ -1,84 +1,77 @@
import { AppFooter } from '@/components/app/AppFooter';
-import { Box } from '@/ui/Box';
+import { Grid } from '@/ui/Grid';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
-import Image from 'next/image';
-import Link from 'next/link';
+import { Link } from '@/ui/Link';
+import { BrandMark } from '@/ui/BrandMark';
+import { Box } from '@/ui/Box';
export interface GlobalFooterViewData {}
export function GlobalFooterTemplate(_props: GlobalFooterViewData) {
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ INFRASTRUCTURE
+
+
+
+
+
The professional infrastructure for serious sim racing.
Precision telemetry, automated results, and elite league management.
-
-
-
- © 2026 GRIDPILOT
-
-
-
+
+
-
-
- PLATFORM
-
+
+ PLATFORM
-
-
+
+
Leagues
-
-
-
-
+
+
+
+
Teams
-
-
-
-
+
+
+
+
Leaderboards
-
-
+
+
-
+
-
-
- SUPPORT
-
+
+ SUPPORT
-
-
+
+
Documentation
-
-
-
-
+
+
+
+
System Status
-
-
-
-
+
+
+
+
Contact
-
-
+
+
-
-
+
+
);
}
diff --git a/apps/website/templates/layout/GlobalSidebarTemplate.tsx b/apps/website/templates/layout/GlobalSidebarTemplate.tsx
index ee2cc53e5..3af6e840a 100644
--- a/apps/website/templates/layout/GlobalSidebarTemplate.tsx
+++ b/apps/website/templates/layout/GlobalSidebarTemplate.tsx
@@ -6,6 +6,7 @@ import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
import { Box } from '@/ui/Box';
import { DashboardRail } from '@/components/dashboard/DashboardRail';
import { Text } from '@/ui/Text';
+import { Surface } from '@/ui/Surface';
import { usePathname } from 'next/navigation';
export interface GlobalSidebarViewData {}
@@ -16,21 +17,23 @@ export function GlobalSidebarTemplate(_props: GlobalSidebarViewData) {
const isAuthenticated = !!session;
return (
-
-
-
-
- NAVIGATION
-
+
+
+
+
+
+ NAVIGATION
+
+
+
+ {isAuthenticated ? (
+
+ ) : (
+
+ )}
+
-
- {isAuthenticated ? (
-
- ) : (
-
- )}
-
-
-
+
+
);
}
diff --git a/apps/website/templates/layout/RootAppShellTemplate.tsx b/apps/website/templates/layout/RootAppShellTemplate.tsx
index f28b534a9..6eb17b22b 100644
--- a/apps/website/templates/layout/RootAppShellTemplate.tsx
+++ b/apps/website/templates/layout/RootAppShellTemplate.tsx
@@ -41,10 +41,10 @@ export function RootAppShellTemplate({ children }: RootAppShellViewData) {
-
+
{showSidebar && }
-
+
{children}
diff --git a/apps/website/templates/onboarding/OnboardingTemplate.tsx b/apps/website/templates/onboarding/OnboardingTemplate.tsx
index f57398990..b69ae36a2 100644
--- a/apps/website/templates/onboarding/OnboardingTemplate.tsx
+++ b/apps/website/templates/onboarding/OnboardingTemplate.tsx
@@ -188,8 +188,8 @@ export function OnboardingTemplate({ viewData }: OnboardingTemplateProps) {
const header = (
-
- GridPilot Onboarding
+
+ GridPilot Onboarding
System Initialization
@@ -204,7 +204,7 @@ export function OnboardingTemplate({ viewData }: OnboardingTemplateProps) {
const sidebar = (
- Welcome to GridPilot. We're setting up your racing identity. This process ensures you're ready for the track with a complete profile and a unique AI-generated avatar.
+ Welcome to GridPilot. We're setting up your racing identity. This process ensures you're ready for the track with a complete profile and a unique AI-generated avatar.
{step === 2 && (
diff --git a/apps/website/ui/Box.tsx b/apps/website/ui/Box.tsx
index 32d6acad6..219fc659b 100644
--- a/apps/website/ui/Box.tsx
+++ b/apps/website/ui/Box.tsx
@@ -184,6 +184,7 @@ export interface BoxProps {
title?: string;
size?: string | number | ResponsiveValue;
accept?: string;
+ multiple?: boolean;
autoPlay?: boolean;
loop?: boolean;
muted?: boolean;
@@ -335,6 +336,7 @@ export const Box = forwardRef((
title,
size,
accept,
+ multiple,
autoPlay,
loop,
muted,
@@ -564,6 +566,7 @@ export const Box = forwardRef((
rows={rows}
href={href}
name={name}
+ multiple={multiple}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
diff --git a/apps/website/ui/BrandMark.tsx b/apps/website/ui/BrandMark.tsx
index e4f67b254..0642aafd8 100644
--- a/apps/website/ui/BrandMark.tsx
+++ b/apps/website/ui/BrandMark.tsx
@@ -1,6 +1,6 @@
import { Box } from '@/ui/Box';
import { Link } from '@/ui/Link';
-import Image from 'next/image';
+import { Image } from '@/ui/Image';
interface BrandMarkProps {
href?: string;
@@ -11,7 +11,7 @@ interface BrandMarkProps {
* BrandMark provides the consistent logo/wordmark for the application.
* Aligned with "Precision Racing Minimal" theme.
*/
-export function BrandMark({ href = '/', priority = false }: BrandMarkProps) {
+export function BrandMark({ href = '/' }: BrandMarkProps) {
return (
@@ -19,10 +19,7 @@ export function BrandMark({ href = '/', priority = false }: BrandMarkProps) {
+ {children}
+
+ );
+}
diff --git a/apps/website/ui/Center.tsx b/apps/website/ui/Center.tsx
new file mode 100644
index 000000000..a523f24dd
--- /dev/null
+++ b/apps/website/ui/Center.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Box } from './Box';
+
+interface CenterProps {
+ children: React.ReactNode;
+ fullWidth?: boolean;
+ fullHeight?: boolean;
+}
+
+/**
+ * Center - A semantic UI component for centering content.
+ */
+export function Center({ children, fullWidth, fullHeight }: CenterProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/website/ui/ContentViewport.tsx b/apps/website/ui/ContentViewport.tsx
index e3ce8e086..fc79472ca 100644
--- a/apps/website/ui/ContentViewport.tsx
+++ b/apps/website/ui/ContentViewport.tsx
@@ -21,7 +21,7 @@ export const ContentViewport = ({
};
return (
-
+
{children}
diff --git a/apps/website/ui/ControlBar.tsx b/apps/website/ui/ControlBar.tsx
index 5fcfd5986..d0e634a1c 100644
--- a/apps/website/ui/ControlBar.tsx
+++ b/apps/website/ui/ControlBar.tsx
@@ -17,6 +17,9 @@ export const ControlBar = ({
diff --git a/apps/website/ui/DiscordCTA.tsx b/apps/website/ui/DiscordCTA.tsx
new file mode 100644
index 000000000..ed95866c5
--- /dev/null
+++ b/apps/website/ui/DiscordCTA.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import { Section } from './Section';
+import { Container } from './Container';
+import { Card } from './Card';
+import { Box } from './Box';
+import { Glow } from './Glow';
+import { Heading } from './Heading';
+import { Text } from './Text';
+import { Button } from './Button';
+import { DiscordIcon } from './icons/DiscordIcon';
+import { Icon } from './Icon';
+import { LucideIcon } from 'lucide-react';
+
+interface DiscordCTAProps {
+ title: string;
+ description: string;
+ lead: string;
+ benefits: Array<{
+ icon: LucideIcon;
+ title: string;
+ description: string;
+ }>;
+ discordUrl: string;
+}
+
+/**
+ * DiscordCTA - A semantic UI component for the Discord call to action.
+ */
+export function DiscordCTA({
+ title,
+ description,
+ lead,
+ benefits,
+ discordUrl,
+}: DiscordCTAProps) {
+ return (
+
+
+
+
+
+
+ {/* Header */}
+
+
+
+ {title}
+
+
+
+ {/* Personal message */}
+
+
+ {lead}
+
+
+ {description}
+
+
+
+ {/* Benefits grid */}
+
+ {benefits.map((benefit, index) => (
+
+
+
+
+ {benefit.title}
+ {benefit.description}
+
+
+
+ ))}
+
+
+ {/* CTA Button */}
+
+ }
+ >
+ Join Discord
+
+
+
+ Early Alpha Access Available
+
+
+
+
+
+
+ );
+}
diff --git a/apps/website/ui/DiscoverySection.tsx b/apps/website/ui/DiscoverySection.tsx
new file mode 100644
index 000000000..7d92d82e9
--- /dev/null
+++ b/apps/website/ui/DiscoverySection.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { Section } from './Section';
+import { Container } from './Container';
+import { Box } from './Box';
+import { Heading } from './Heading';
+import { Text } from './Text';
+import { Grid } from './Grid';
+
+interface DiscoverySectionProps {
+ title: string;
+ subtitle: string;
+ description: string;
+ children: React.ReactNode;
+}
+
+/**
+ * DiscoverySection - A semantic section for discovering content.
+ */
+export function DiscoverySection({
+ title,
+ subtitle,
+ description,
+ children,
+}: DiscoverySectionProps) {
+ return (
+
+
+
+
+
+ {subtitle}
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/apps/website/ui/FeatureList.tsx b/apps/website/ui/FeatureList.tsx
new file mode 100644
index 000000000..7c19a8e8c
--- /dev/null
+++ b/apps/website/ui/FeatureList.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { Stack } from './Stack';
+import { Text } from './Text';
+
+interface FeatureListProps {
+ items: string[];
+ intent?: 'primary' | 'aqua' | 'amber' | 'low';
+}
+
+/**
+ * FeatureList - A semantic list of features.
+ */
+export function FeatureList({ items, intent = 'primary' }: FeatureListProps) {
+ return (
+
+ {items.map((item, index) => (
+
+ •
+ {item}
+
+ ))}
+
+ );
+}
diff --git a/apps/website/ui/FeatureQuote.tsx b/apps/website/ui/FeatureQuote.tsx
new file mode 100644
index 000000000..6ca6b9bfe
--- /dev/null
+++ b/apps/website/ui/FeatureQuote.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Box } from './Box';
+import { Text } from './Text';
+
+interface FeatureQuoteProps {
+ children: string;
+ intent?: 'primary' | 'aqua' | 'amber' | 'low';
+}
+
+/**
+ * FeatureQuote - A semantic quote component for features.
+ */
+export function FeatureQuote({ children, intent = 'primary' }: FeatureQuoteProps) {
+ const borderColor = {
+ primary: 'var(--ui-color-intent-primary)',
+ aqua: 'var(--ui-color-intent-telemetry)',
+ amber: 'var(--ui-color-intent-warning)',
+ low: 'var(--ui-color-border-default)',
+ }[intent];
+
+ const bgColor = {
+ primary: 'rgba(25, 140, 255, 0.05)',
+ aqua: 'rgba(78, 212, 224, 0.05)',
+ amber: 'rgba(255, 190, 77, 0.05)',
+ low: 'rgba(255, 255, 255, 0.02)',
+ }[intent];
+
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/apps/website/ui/FeatureSection.tsx b/apps/website/ui/FeatureSection.tsx
new file mode 100644
index 000000000..60071991d
--- /dev/null
+++ b/apps/website/ui/FeatureSection.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { Section } from './Section';
+import { Container } from './Container';
+import { Box } from './Box';
+import { Glow } from './Glow';
+import { Heading } from './Heading';
+
+interface FeatureSectionProps {
+ heading: string;
+ description: React.ReactNode;
+ mockup: React.ReactNode;
+ layout?: 'text-left' | 'text-right';
+ intent?: 'primary' | 'aqua' | 'amber';
+}
+
+/**
+ * FeatureSection - A semantic UI component for highlighting a feature.
+ * Encapsulates layout and styling.
+ */
+export function FeatureSection({
+ heading,
+ description,
+ mockup,
+ layout = 'text-left',
+ intent = 'primary',
+}: FeatureSectionProps) {
+ return (
+
+
+
+
+
+ {/* Text Content */}
+
+
+ {heading}
+
+
+ {description}
+
+
+
+ {/* Mockup Area */}
+
+ {mockup}
+
+
+
+
+ );
+}
diff --git a/apps/website/ui/Footer.tsx b/apps/website/ui/Footer.tsx
deleted file mode 100644
index 75f788ada..000000000
--- a/apps/website/ui/Footer.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Box } from './Box';
-import { Container } from './Container';
-import { Link } from './Link';
-import { Text } from './Text';
-
-export const Footer = () => {
- return (
-
-
-
-
- GridPilot
-
- The ultimate companion for sim racers. Track your performance, manage your team, and compete in leagues.
-
-
-
-
- Platform
-
- Leagues
- Teams
- Leaderboards
-
-
-
-
- Support
-
- Documentation
- System Status
- Contact Us
-
-
-
-
- Legal
-
- Privacy Policy
- Terms of Service
-
-
-
-
-
-
- © {new Date().getFullYear()} GridPilot. All rights reserved.
-
-
-
-
- );
-};
diff --git a/apps/website/ui/Form.tsx b/apps/website/ui/Form.tsx
new file mode 100644
index 000000000..267eb7964
--- /dev/null
+++ b/apps/website/ui/Form.tsx
@@ -0,0 +1,29 @@
+import React, { ReactNode, FormEventHandler, forwardRef } from 'react';
+
+export interface FormProps {
+ children: ReactNode;
+ onSubmit?: FormEventHandler;
+ noValidate?: boolean;
+ className?: string;
+}
+
+export const Form = forwardRef(({
+ children,
+ onSubmit,
+ noValidate = true,
+ className
+}, ref) => {
+ return (
+
+ );
+});
+
+Form.displayName = 'Form';
diff --git a/apps/website/ui/Image.tsx b/apps/website/ui/Image.tsx
index f888255e2..1715f391e 100644
--- a/apps/website/ui/Image.tsx
+++ b/apps/website/ui/Image.tsx
@@ -1,35 +1,28 @@
-import React, { useState } from 'react';
+import React from 'react';
import { Box, BoxProps } from './Box';
-import { ImagePlaceholder } from './ImagePlaceholder';
export interface ImageProps extends Omit, 'children'> {
src: string;
alt: string;
fallbackSrc?: string;
- fallbackComponent?: React.ReactNode;
}
+/**
+ * Image
+ *
+ * Stateless UI primitive for images.
+ * For error handling, use SafeImage component.
+ */
export const Image = ({
src,
alt,
- fallbackSrc,
- fallbackComponent,
...props
}: ImageProps) => {
- const [error, setError] = useState(false);
-
- if (error) {
- if (fallbackComponent) return <>{fallbackComponent}>;
- if (fallbackSrc) return ;
- return ;
- }
-
return (
setError(true)}
{...props}
/>
);
diff --git a/apps/website/ui/LandingHero.tsx b/apps/website/ui/LandingHero.tsx
new file mode 100644
index 000000000..2a91f2408
--- /dev/null
+++ b/apps/website/ui/LandingHero.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { Section } from './Section';
+import { Container } from './Container';
+import { Box } from './Box';
+import { Glow } from './Glow';
+import { Heading } from './Heading';
+import { Text } from './Text';
+import { ButtonGroup } from './ButtonGroup';
+import { Button } from './Button';
+
+interface LandingHeroProps {
+ title: string;
+ subtitle: string;
+ description: string;
+ primaryAction: {
+ label: string;
+ href: string;
+ };
+ secondaryAction: {
+ label: string;
+ href: string;
+ };
+}
+
+/**
+ * LandingHero - A semantic hero section for landing pages.
+ */
+export function LandingHero({
+ title,
+ subtitle,
+ description,
+ primaryAction,
+ secondaryAction,
+}: LandingHeroProps) {
+ return (
+
+
+
+
+
+
+ {subtitle}
+
+
+
+ {title}
+
+
+
+ {description}
+
+
+
+
+ {primaryAction.label}
+
+
+ {secondaryAction.label}
+
+
+
+
+
+ );
+}
diff --git a/apps/website/ui/PasswordField.tsx b/apps/website/ui/PasswordField.tsx
index 41dc79929..273d898c6 100644
--- a/apps/website/ui/PasswordField.tsx
+++ b/apps/website/ui/PasswordField.tsx
@@ -1,5 +1,4 @@
import { Eye, EyeOff } from 'lucide-react';
-import { useState } from 'react';
import { Box } from './Box';
import { IconButton } from './IconButton';
import { Input, InputProps } from './Input';
@@ -9,27 +8,19 @@ export interface PasswordFieldProps extends InputProps {
onTogglePassword?: () => void;
}
+/**
+ * PasswordField
+ *
+ * Stateless UI primitive for password inputs.
+ * For stateful behavior, manage showPassword state in the parent component/template.
+ */
export const PasswordField = ({
- showPassword: controlledShowPassword,
+ showPassword = false,
onTogglePassword,
...props
}: PasswordFieldProps) => {
- const [internalShowPassword, setInternalShowPassword] = useState(false);
-
- const isControlled = controlledShowPassword !== undefined;
- const showPassword = isControlled ? controlledShowPassword : internalShowPassword;
-
- const handleToggle = () => {
- if (onTogglePassword) {
- onTogglePassword();
- }
- if (!isControlled) {
- setInternalShowPassword(!internalShowPassword);
- }
- };
-
return (
-
+
+
+ {stats.map((stat, index) => (
+
+ ))}
+
+
+ );
+}