diff --git a/apps/website/app/admin/layout.tsx b/apps/website/app/admin/layout.tsx index 3e6c395b0..74f7696ed 100644 --- a/apps/website/app/admin/layout.tsx +++ b/apps/website/app/admin/layout.tsx @@ -1,7 +1,7 @@ import { headers } from 'next/headers'; import { redirect } from 'next/navigation'; import { createRouteGuard } from '@/lib/auth/createRouteGuard'; -import Section from '@/ui/Section'; +import { Section } from '@/ui/Section'; interface AdminLayoutProps { children: React.ReactNode; diff --git a/apps/website/app/leagues/LeaguesClient.tsx b/apps/website/app/leagues/LeaguesClient.tsx index 6f1c40b77..44ccdce84 100644 --- a/apps/website/app/leagues/LeaguesClient.tsx +++ b/apps/website/app/leagues/LeaguesClient.tsx @@ -18,7 +18,7 @@ import { Target, Timer, } from 'lucide-react'; -import LeagueCard from '@/components/leagues/LeagueCard'; +import { LeagueCard } from '@/ui/LeagueCardWrapper'; import { Button } from '@/ui/Button'; import { Card } from '@/ui/Card'; import { Input } from '@/ui/Input'; @@ -29,7 +29,7 @@ import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { Grid } from '@/ui/Grid'; import { GridItem } from '@/ui/GridItem'; -import { HeroSection } from '@/components/shared/HeroSection'; +import { PageHero } from '@/ui/PageHero'; import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData'; import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel'; @@ -468,7 +468,7 @@ export function LeaguesClient({ return ( {/* Hero Section */} - setFormData({ ...formData, contactEmail: e.target.value })} placeholder="sponsor@company.com" - error={!!errors.contactEmail} + variant={errors.contactEmail ? 'error' : 'default'} errorMessage={errors.contactEmail} /> @@ -482,7 +482,7 @@ export default function SponsorSignupPage() { value={formData.password} onChange={(e) => setFormData({ ...formData, password: e.target.value })} placeholder="••••••••" - error={!!errors.password} + variant={errors.password ? 'error' : 'default'} errorMessage={errors.password} /> @@ -567,7 +567,7 @@ export default function SponsorSignupPage() { value={formData.companyName} onChange={(e) => setFormData({ ...formData, companyName: e.target.value })} placeholder="Your company name" - error={!!errors.companyName} + variant={errors.companyName ? 'error' : 'default'} errorMessage={errors.companyName} /> @@ -584,7 +584,7 @@ export default function SponsorSignupPage() { value={formData.contactEmail} onChange={(e) => setFormData({ ...formData, contactEmail: e.target.value })} placeholder="sponsor@company.com" - error={!!errors.contactEmail} + variant={errors.contactEmail ? 'error' : 'default'} errorMessage={errors.contactEmail} /> @@ -688,7 +688,7 @@ export default function SponsorSignupPage() { value={formData.password} onChange={(e) => setFormData({ ...formData, password: e.target.value })} placeholder="Min. 8 characters" - error={!!errors.password} + variant={errors.password ? 'error' : 'default'} errorMessage={errors.password} /> @@ -702,7 +702,7 @@ export default function SponsorSignupPage() { value={formData.confirmPassword} onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })} placeholder="Confirm password" - error={!!errors.confirmPassword} + variant={errors.confirmPassword ? 'error' : 'default'} errorMessage={errors.confirmPassword} /> diff --git a/apps/website/app/teams/leaderboard/TeamLeaderboardPageWrapper.tsx b/apps/website/app/teams/leaderboard/TeamLeaderboardPageWrapper.tsx index f503c9a38..b4c9ec820 100644 --- a/apps/website/app/teams/leaderboard/TeamLeaderboardPageWrapper.tsx +++ b/apps/website/app/teams/leaderboard/TeamLeaderboardPageWrapper.tsx @@ -1,7 +1,7 @@ 'use client'; import { useRouter } from 'next/navigation'; -import TeamLeaderboardTemplate from '@/templates/TeamLeaderboardTemplate'; +import { TeamLeaderboardTemplate } from '@/templates/TeamLeaderboardTemplate'; import { useState } from 'react'; import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel'; diff --git a/apps/website/components/AppWrapper.tsx b/apps/website/components/AppWrapper.tsx index afa9a8582..9f12867cf 100644 --- a/apps/website/components/AppWrapper.tsx +++ b/apps/website/components/AppWrapper.tsx @@ -4,10 +4,10 @@ import { ContainerProvider } from '@/lib/di/providers/ContainerProvider'; import { QueryClientProvider } from '@/lib/providers/QueryClientProvider'; import { AuthProvider } from '@/lib/auth/AuthContext'; import { FeatureFlagProvider } from '@/lib/feature/FeatureFlagProvider'; -import NotificationProvider from '@/components/notifications/NotificationProvider'; +import { NotificationProvider } from '@/components/notifications/NotificationProvider'; import { NotificationIntegration } from '@/components/errors/NotificationIntegration'; import { EnhancedErrorBoundary } from '@/components/errors/EnhancedErrorBoundary'; -import DevToolbar from '@/components/dev/DevToolbar'; +import { DevToolbar } from '@/components/dev/DevToolbar'; import React from 'react'; interface AppWrapperProps { diff --git a/apps/website/components/auth/AuthWorkflowMockup.tsx b/apps/website/components/auth/AuthWorkflowMockup.tsx index 6cfa9bf31..6f5654b4c 100644 --- a/apps/website/components/auth/AuthWorkflowMockup.tsx +++ b/apps/website/components/auth/AuthWorkflowMockup.tsx @@ -1,29 +1,14 @@ 'use client'; -import { motion, useReducedMotion, AnimatePresence } from 'framer-motion'; -import { useEffect, useState } from 'react'; +import React from 'react'; import { UserPlus, Link as LinkIcon, Settings, Trophy, Car, - CheckCircle2, - LucideIcon } from 'lucide-react'; -import { Box } from '@/ui/Box'; -import { Stack } from '@/ui/Stack'; -import { Text } from '@/ui/Text'; -import { Icon } from '@/ui/Icon'; -import { Surface } from '@/ui/Surface'; - -interface WorkflowStep { - id: number; - icon: LucideIcon; - title: string; - description: string; - color: string; -} +import { WorkflowMockup, WorkflowStep } from '@/ui/WorkflowMockup'; const WORKFLOW_STEPS: WorkflowStep[] = [ { @@ -64,123 +49,5 @@ const WORKFLOW_STEPS: WorkflowStep[] = [ ]; export function AuthWorkflowMockup() { - const shouldReduceMotion = useReducedMotion(); - const [isMounted, setIsMounted] = useState(false); - const [activeStep, setActiveStep] = useState(0); - - useEffect(() => { - setIsMounted(true); - }, []); - - useEffect(() => { - if (!isMounted) return; - - const interval = setInterval(() => { - setActiveStep((prev) => (prev + 1) % WORKFLOW_STEPS.length); - }, 3000); - - return () => clearInterval(interval); - }, [isMounted]); - - if (!isMounted) { - return ( - - - - {WORKFLOW_STEPS.map((step) => ( - - - - - {step.title} - - ))} - - - - ); - } - - return ( - - - {/* Connection Lines */} - - - - - - - {/* Steps */} - - {WORKFLOW_STEPS.map((step, index) => { - const isActive = index === activeStep; - const isCompleted = index < activeStep; - const StepIcon = step.icon; - - return ( - setActiveStep(index)} - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.95 }} - > - - {isCompleted ? ( - - ) : ( - - )} - - - - ); - })} - - - {/* Active Step Preview - Mobile */} - - - - - Step {activeStep + 1}: {WORKFLOW_STEPS[activeStep]?.title || ''} - - - {WORKFLOW_STEPS[activeStep]?.description || ''} - - - - - - - ); + return ; } diff --git a/apps/website/components/auth/UserRolesPreview.tsx b/apps/website/components/auth/UserRolesPreview.tsx index cc311b1b1..a276d1bc2 100644 --- a/apps/website/components/auth/UserRolesPreview.tsx +++ b/apps/website/components/auth/UserRolesPreview.tsx @@ -52,7 +52,9 @@ export function UserRolesPreview({ variant = 'full' }: UserRolesPreviewProps) { justifyContent="center" mb={1} > - + + + {role.title} @@ -89,7 +91,9 @@ export function UserRolesPreview({ variant = 'full' }: UserRolesPreviewProps) { alignItems="center" justifyContent="center" > - + + + {role.title} diff --git a/apps/website/components/dashboard/ActivityFeed.tsx b/apps/website/components/dashboard/ActivityFeed.tsx deleted file mode 100644 index 39cbad1f1..000000000 --- a/apps/website/components/dashboard/ActivityFeed.tsx +++ /dev/null @@ -1,62 +0,0 @@ -'use client'; - -import React from 'react'; -import { Activity } from 'lucide-react'; -import { Card } from '@/ui/Card'; -import { Stack } from '@/ui/Stack'; -import { Heading } from '@/ui/Heading'; -import { Box } from '@/ui/Box'; -import { Text } from '@/ui/Text'; -import { Link } from '@/ui/Link'; - -interface FeedItem { - id: string; - headline: string; - body?: string; - formattedTime: string; - ctaHref?: string; - ctaLabel?: string; -} - -interface ActivityFeedProps { - items: FeedItem[]; - hasItems: boolean; -} - -export function ActivityFeed({ items, hasItems }: ActivityFeedProps) { - return ( - - - }> - Recent Activity - - {hasItems ? ( - - {items.slice(0, 5).map((item) => ( - - - {item.headline} - {item.body && {item.body}} - {item.formattedTime} - - {item.ctaHref && item.ctaLabel && ( - - - {item.ctaLabel} - - - )} - - ))} - - ) : ( - - - No activity yet - Join leagues and add friends to see activity here - - )} - - - ); -} diff --git a/apps/website/components/dashboard/ChampionshipStandings.tsx b/apps/website/components/dashboard/ChampionshipStandings.tsx deleted file mode 100644 index 3b1da05f7..000000000 --- a/apps/website/components/dashboard/ChampionshipStandings.tsx +++ /dev/null @@ -1,56 +0,0 @@ -'use client'; - -import React from 'react'; -import { Award, ChevronRight } from 'lucide-react'; -import { Card } from '@/ui/Card'; -import { Stack } from '@/ui/Stack'; -import { Heading } from '@/ui/Heading'; -import { Box } from '@/ui/Box'; -import { Text } from '@/ui/Text'; -import { Link } from '@/ui/Link'; -import { routes } from '@/lib/routing/RouteConfig'; - -interface Standing { - leagueId: string; - leagueName: string; - position: string; - points: string; - totalDrivers: string; -} - -interface ChampionshipStandingsProps { - standings: Standing[]; -} - -export function ChampionshipStandings({ standings }: ChampionshipStandingsProps) { - return ( - - - - }> - Your Championship Standings - - - - - View all - - - - - - - {standings.map((summary) => ( - - - {summary.leagueName} - Position {summary.position} • {summary.points} points - - {summary.totalDrivers} drivers - - ))} - - - - ); -} diff --git a/apps/website/components/dashboard/DashboardHero.tsx b/apps/website/components/dashboard/DashboardHero.tsx deleted file mode 100644 index 672399b43..000000000 --- a/apps/website/components/dashboard/DashboardHero.tsx +++ /dev/null @@ -1,101 +0,0 @@ -'use client'; - -import React from 'react'; -import { Trophy, Medal, Target, Users, Flag, User } from 'lucide-react'; -import { Box } from '@/ui/Box'; -import { Stack } from '@/ui/Stack'; -import { Text } from '@/ui/Text'; -import { Heading } from '@/ui/Heading'; -import { Image } from '@/ui/Image'; -import { Button } from '@/ui/Button'; -import { Link } from '@/ui/Link'; -import { Surface } from '@/ui/Surface'; -import { StatBox } from './StatBox'; -import { routes } from '@/lib/routing/RouteConfig'; - -interface DashboardHeroProps { - currentDriver: { - name: string; - avatarUrl: string; - country: string; - rating: string | number; - rank: string | number; - totalRaces: string | number; - wins: string | number; - podiums: string | number; - consistency: string; - }; - activeLeaguesCount: string | number; -} - -export function DashboardHero({ currentDriver, activeLeaguesCount }: DashboardHeroProps) { - return ( - - {/* Background Pattern */} - - - - - - {/* Welcome Message */} - - - - - {currentDriver.name} - - - - - - Good morning, - - {currentDriver.name} - {currentDriver.country} - - - - {currentDriver.rating} - - - #{currentDriver.rank} - - {currentDriver.totalRaces} races completed - - - - - {/* Quick Actions */} - - - - - - - - - - - {/* Quick Stats Row */} - - {/* At md this should be 4 columns */} - - - - - - - - - ); -} diff --git a/apps/website/components/dashboard/FriendsSidebar.tsx b/apps/website/components/dashboard/FriendsSidebar.tsx deleted file mode 100644 index 129028c89..000000000 --- a/apps/website/components/dashboard/FriendsSidebar.tsx +++ /dev/null @@ -1,83 +0,0 @@ -'use client'; - -import React from 'react'; -import { Users, UserPlus } from 'lucide-react'; -import { Card } from '@/ui/Card'; -import { Stack } from '@/ui/Stack'; -import { Heading } from '@/ui/Heading'; -import { Box } from '@/ui/Box'; -import { Text } from '@/ui/Text'; -import { Link } from '@/ui/Link'; -import { Image } from '@/ui/Image'; -import { Button } from '@/ui/Button'; -import { routes } from '@/lib/routing/RouteConfig'; - -interface Friend { - id: string; - name: string; - avatarUrl: string; - country: string; -} - -interface FriendsSidebarProps { - friends: Friend[]; - hasFriends: boolean; -} - -export function FriendsSidebar({ friends, hasFriends }: FriendsSidebarProps) { - return ( - - - - }> - Friends - - {friends.length} friends - - {hasFriends ? ( - - {friends.slice(0, 6).map((friend) => ( - - - {friend.name} - - - {friend.name} - {friend.country} - - - ))} - {friends.length > 6 && ( - - - +{friends.length - 6} more - - - )} - - ) : ( - - - No friends yet - - - - - - - )} - - - ); -} diff --git a/apps/website/components/dashboard/NextRaceCard.tsx b/apps/website/components/dashboard/NextRaceCard.tsx deleted file mode 100644 index 21b53d4bd..000000000 --- a/apps/website/components/dashboard/NextRaceCard.tsx +++ /dev/null @@ -1,74 +0,0 @@ -'use client'; - -import React from 'react'; -import { Calendar, Clock, ChevronRight } from 'lucide-react'; -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 { Link } from '@/ui/Link'; -import { Surface } from '@/ui/Surface'; - -interface NextRaceCardProps { - nextRace: { - id: string; - track: string; - car: string; - formattedDate: string; - formattedTime: string; - timeUntil: string; - isMyLeague: boolean; - }; -} - -export function NextRaceCard({ nextRace }: NextRaceCardProps) { - return ( - - - - - - Next Race - - {nextRace.isMyLeague && ( - - Your League - - )} - - - - - {nextRace.track} - {nextRace.car} - - - - {nextRace.formattedDate} - - - - {nextRace.formattedTime} - - - - - - - Starts in - {nextRace.timeUntil} - - - - - - - - - - - ); -} diff --git a/apps/website/components/dashboard/UpcomingRaces.tsx b/apps/website/components/dashboard/UpcomingRaces.tsx deleted file mode 100644 index a3531e7cf..000000000 --- a/apps/website/components/dashboard/UpcomingRaces.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client'; - -import React from 'react'; -import { Calendar } from 'lucide-react'; -import { Card } from '@/ui/Card'; -import { Stack } from '@/ui/Stack'; -import { Heading } from '@/ui/Heading'; -import { Box } from '@/ui/Box'; -import { Text } from '@/ui/Text'; -import { Link } from '@/ui/Link'; -import { routes } from '@/lib/routing/RouteConfig'; - -interface UpcomingRace { - id: string; - track: string; - car: string; - formattedDate: string; - formattedTime: string; - isMyLeague: boolean; -} - -interface UpcomingRacesProps { - races: UpcomingRace[]; - hasRaces: boolean; -} - -export function UpcomingRaces({ races, hasRaces }: UpcomingRacesProps) { - return ( - - - - }> - Upcoming Races - - - - View all - - - - {hasRaces ? ( - - {races.slice(0, 5).map((race) => ( - - {race.track} - {race.car} - - {race.formattedDate} - - {race.formattedTime} - - {race.isMyLeague && ( - - Your League - - )} - - ))} - - ) : ( - - No upcoming races - - )} - - - ); -} diff --git a/apps/website/components/dev/Accordion.tsx b/apps/website/components/dev/Accordion.tsx deleted file mode 100644 index a091ce214..000000000 --- a/apps/website/components/dev/Accordion.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client'; - -import { ReactNode } from 'react'; -import { ChevronDown, ChevronUp } from 'lucide-react'; - -interface AccordionProps { - title: string; - icon: ReactNode; - children: ReactNode; - isOpen: boolean; - onToggle: () => void; -} - -export function Accordion({ title, icon, children, isOpen, onToggle }: AccordionProps) { - return ( -
- - - {isOpen && ( -
- {children} -
- )} -
- ); -} \ No newline at end of file diff --git a/apps/website/components/dev/DevToolbar.tsx b/apps/website/components/dev/DevToolbar.tsx index 194400ad4..0a2735d04 100644 --- a/apps/website/components/dev/DevToolbar.tsx +++ b/apps/website/components/dev/DevToolbar.tsx @@ -1,27 +1,32 @@ 'use client'; -import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId"; -import { useNotifications } from '@/components/notifications/NotificationProvider'; -import type { NotificationVariant } from '@/components/notifications/notificationTypes'; +import React, { useEffect, useState } from 'react'; import { Wrench, ChevronDown, ChevronUp, X, MessageSquare, Activity, AlertTriangle } from 'lucide-react'; -import { useRouter } from 'next/navigation'; -import { useEffect, useState } from 'react'; import { ApiConnectionMonitor } from '@/lib/api/base/ApiConnectionMonitor'; import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler'; import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler'; +import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId"; +import { useNotifications } from '@/components/notifications/NotificationProvider'; +import type { NotificationVariant } from '@/components/notifications/notificationTypes'; // Import our new components -import { Accordion } from './Accordion'; +import { Accordion } from '@/ui/Accordion'; import { NotificationTypeSection } from './sections/NotificationTypeSection'; import { UrgencySection } from './sections/UrgencySection'; import { NotificationSendSection } from './sections/NotificationSendSection'; import { APIStatusSection } from './sections/APIStatusSection'; +import { Box } from '@/ui/Box'; +import { Text } from '@/ui/Text'; +import { Stack } from '@/ui/Stack'; +import { Icon } from '@/ui/Icon'; +import { IconButton } from '@/ui/IconButton'; +import { Badge } from '@/ui/Badge'; +import { Button } from '@/ui/Button'; // Import types import type { DemoNotificationType, DemoUrgency } from './types'; -export default function DevToolbar() { - const router = useRouter(); +export function DevToolbar() { const { addNotification } = useNotifications(); const [isExpanded, setIsExpanded] = useState(false); const [isMinimized, setIsMinimized] = useState(false); @@ -225,140 +230,155 @@ export default function DevToolbar() { if (isMinimized) { return ( - + + setIsMinimized(false)} + variant="secondary" + title="Open Dev Toolbar" + size="lg" + /> + ); } return ( -
+ {/* Header */} -
-
- - Dev Toolbar - + + + + Dev Toolbar + DEMO - -
-
- - -
-
+ variant="ghost" + size="sm" + /> +
+ {/* Content */} {isExpanded && ( -
- {/* Notification Section - Accordion */} - } - isOpen={openAccordion === 'notifications'} - onToggle={() => setOpenAccordion(openAccordion === 'notifications' ? null : 'notifications')} - > -
- - - -
-
+ + + {/* Notification Section - Accordion */} + } + isOpen={openAccordion === 'notifications'} + onToggle={() => setOpenAccordion(openAccordion === 'notifications' ? null : 'notifications')} + > + + + + + + - {/* API Status Section - Accordion */} - } - isOpen={openAccordion === 'apiStatus'} - onToggle={() => setOpenAccordion(openAccordion === 'apiStatus' ? null : 'apiStatus')} - > - - + {/* API Status Section - Accordion */} + } + isOpen={openAccordion === 'apiStatus'} + onToggle={() => setOpenAccordion(openAccordion === 'apiStatus' ? null : 'apiStatus')} + > + + - {/* Error Stats Section - Accordion */} - } - isOpen={openAccordion === 'errors'} - onToggle={() => setOpenAccordion(openAccordion === 'errors' ? null : 'errors')} - > -
-
- Total Errors - {errorStats.total} -
- {Object.keys(errorStats.byType).length > 0 ? ( -
- {Object.entries(errorStats.byType).map(([type, count]) => ( -
- {type} - {count} -
- ))} -
- ) : ( -
No errors yet
- )} - -
-
- -
+ {/* Error Stats Section - Accordion */} + } + isOpen={openAccordion === 'errors'} + onToggle={() => setOpenAccordion(openAccordion === 'errors' ? null : 'errors')} + > + + + Total Errors + {errorStats.total} + + {Object.keys(errorStats.byType).length > 0 ? ( + + {Object.entries(errorStats.byType).map(([type, count]) => ( + + {type} + {count} + + ))} + + ) : ( + + No errors yet + + )} + + + + + )} {/* Collapsed state hint */} {!isExpanded && ( -
- Click ↑ to expand dev tools -
+ + Click ↑ to expand dev tools + )} -
+
); -} \ No newline at end of file +} diff --git a/apps/website/components/dev/sections/APIStatusSection.tsx b/apps/website/components/dev/sections/APIStatusSection.tsx index e7f6380b2..f436767f1 100644 --- a/apps/website/components/dev/sections/APIStatusSection.tsx +++ b/apps/website/components/dev/sections/APIStatusSection.tsx @@ -1,15 +1,24 @@ 'use client'; +import React from 'react'; import { Activity, Wifi, RefreshCw, Terminal } from 'lucide-react'; -import { useState } from 'react'; -import { ApiConnectionMonitor } from '@/lib/api/base/ApiConnectionMonitor'; -import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler'; -import { useNotifications } from '@/components/notifications/NotificationProvider'; +import { Box } from '@/ui/Box'; +import { Text } from '@/ui/Text'; +import { Stack } from '@/ui/Stack'; +import { Icon } from '@/ui/Icon'; +import { Button } from '@/ui/Button'; +import { StatusIndicator, StatRow, Badge } from '@/ui/StatusIndicator'; interface APIStatusSectionProps { apiStatus: string; - apiHealth: any; - circuitBreakers: Record; + apiHealth: { + successfulRequests: number; + totalRequests: number; + averageResponseTime: number; + consecutiveFailures: number; + lastCheck: number | Date | null; + }; + circuitBreakers: Record; checkingHealth: boolean; onHealthCheck: () => void; onResetStats: () => void; @@ -25,121 +34,137 @@ export function APIStatusSection({ onResetStats, onTestError }: APIStatusSectionProps) { + const reliability = apiHealth.totalRequests === 0 + ? 0 + : (apiHealth.successfulRequests / apiHealth.totalRequests); + + const reliabilityLabel = apiHealth.totalRequests === 0 ? 'N/A' : 'Calculated'; + + const getReliabilityColor = () => { + if (apiHealth.totalRequests === 0) return 'text-gray-500'; + if (reliability >= 0.95) return 'text-performance-green'; + if (reliability >= 0.8) return 'text-warning-amber'; + return 'text-red-400'; + }; + + const getStatusVariant = () => { + if (apiStatus === 'connected') return 'success'; + if (apiStatus === 'degraded') return 'warning'; + return 'danger'; + }; + + const statusLabel = apiStatus.toUpperCase(); + const healthSummary = `${apiHealth.successfulRequests}/${apiHealth.totalRequests} req`; + const avgResponseLabel = `${apiHealth.averageResponseTime.toFixed(0)}ms`; + const lastCheckLabel = apiHealth.lastCheck ? 'Recently' : 'Never'; + const consecutiveFailuresLabel = String(apiHealth.consecutiveFailures); + return ( -
-
- - + + + + API Status - -
+ + {/* Status Indicator */} -
-
- - {apiStatus.toUpperCase()} -
- - {apiHealth.successfulRequests}/{apiHealth.totalRequests} req - -
+ - {/* Reliability */} -
- Reliability - = 0.95 ? 'text-green-400' : - (apiHealth.successfulRequests / apiHealth.totalRequests) >= 0.8 ? 'text-yellow-400' : - 'text-red-400' - }`}> - {apiHealth.totalRequests === 0 ? 'N/A' : - ((apiHealth.successfulRequests / apiHealth.totalRequests) * 100).toFixed(1) + '%'} - -
+ + {/* Reliability */} + - {/* Response Time */} -
- Avg Response - - {apiHealth.averageResponseTime.toFixed(0)}ms - -
+ {/* Response Time */} + +
{/* Consecutive Failures */} {apiHealth.consecutiveFailures > 0 && ( -
- Consecutive Failures - {apiHealth.consecutiveFailures} -
+ + + )} {/* Circuit Breakers */} -
-
Circuit Breakers:
+ + Circuit Breakers: {Object.keys(circuitBreakers).length === 0 ? ( -
None active
+ None active ) : ( -
- {Object.entries(circuitBreakers).map(([endpoint, status]: [string, any]) => ( -
- {endpoint.split('/').pop() || endpoint} - + + {Object.entries(circuitBreakers).map(([endpoint, status]) => ( + + + {endpoint} + + {status.state} - + {status.failures > 0 && ( - ({status.failures}) + ({status.failures}) )} -
+ ))} -
+ )} -
+ {/* API Actions */} -
- - -
+ + -
- -
+ + -
- Last Check: {apiHealth.lastCheck ? new Date(apiHealth.lastCheck).toLocaleTimeString() : 'Never'} -
-
+ + + Last Check: {lastCheckLabel} + + +
); -} \ No newline at end of file +} diff --git a/apps/website/components/dev/sections/NotificationSendSection.tsx b/apps/website/components/dev/sections/NotificationSendSection.tsx index 0c5254431..548794ca4 100644 --- a/apps/website/components/dev/sections/NotificationSendSection.tsx +++ b/apps/website/components/dev/sections/NotificationSendSection.tsx @@ -1,10 +1,12 @@ 'use client'; -import { Bell } from 'lucide-react'; -import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId"; -import { useNotifications } from '@/components/notifications/NotificationProvider'; -import type { NotificationVariant } from '@/components/notifications/notificationTypes'; +import React from 'react'; +import { Bell, Loader2 } from 'lucide-react'; import type { DemoNotificationType, DemoUrgency } from '../types'; +import { Box } from '@/ui/Box'; +import { Text } from '@/ui/Text'; +import { Icon } from '@/ui/Icon'; +import { Button } from '@/ui/Button'; interface NotificationSendSectionProps { selectedType: DemoNotificationType; @@ -15,50 +17,36 @@ interface NotificationSendSectionProps { } export function NotificationSendSection({ - selectedType, - selectedUrgency, sending, lastSent, onSend }: NotificationSendSectionProps) { return ( -
- + {sending ? 'Sending...' : lastSent ? '✓ Notification Sent!' : 'Send Demo Notification'} + -
-

- Silent: Notification center only
- Toast: Temporary popup (auto-dismisses)
- Modal: Blocking popup (may require action) -

-
-
+ + + Silent: Notification center only + + + Toast: Temporary popup (auto-dismisses) + + + Modal: Blocking popup (may require action) + + + ); -} \ No newline at end of file +} diff --git a/apps/website/components/dev/sections/NotificationTypeSection.tsx b/apps/website/components/dev/sections/NotificationTypeSection.tsx index e5c0baff6..d21a887f5 100644 --- a/apps/website/components/dev/sections/NotificationTypeSection.tsx +++ b/apps/website/components/dev/sections/NotificationTypeSection.tsx @@ -1,13 +1,17 @@ 'use client'; -import { MessageSquare, AlertTriangle, Shield, Vote, TrendingUp, Award } from 'lucide-react'; +import React from 'react'; +import { MessageSquare, AlertTriangle, Shield, Vote, TrendingUp, Award, LucideIcon } from 'lucide-react'; import type { DemoNotificationType } from '../types'; +import { Box } from '@/ui/Box'; +import { Text } from '@/ui/Text'; +import { Icon } from '@/ui/Icon'; interface NotificationOption { type: DemoNotificationType; label: string; description: string; - icon: any; + icon: LucideIcon; color: string; } @@ -22,73 +26,85 @@ export const notificationOptions: NotificationOption[] = [ label: 'Protest Against You', description: 'A protest was filed against you', icon: AlertTriangle, - color: 'text-red-400', + color: 'rgb(239, 68, 68)', }, { type: 'defense_requested', label: 'Defense Requested', description: 'A steward requests your defense', icon: Shield, - color: 'text-warning-amber', + color: 'rgb(245, 158, 11)', }, { type: 'vote_required', label: 'Vote Required', description: 'You need to vote on a protest', icon: Vote, - color: 'text-primary-blue', + color: 'rgb(59, 130, 246)', }, { type: 'race_performance_summary', label: 'Race Performance Summary', description: 'Immediate results after main race', icon: TrendingUp, - color: 'text-primary-blue', + color: 'rgb(59, 130, 246)', }, { type: 'race_final_results', label: 'Race Final Results', description: 'Final results after stewarding closes', icon: Award, - color: 'text-warning-amber', + color: 'rgb(245, 158, 11)', }, ]; export function NotificationTypeSection({ selectedType, onSelectType }: NotificationTypeSectionProps) { return ( -
-
- - + + + + Notification Type - -
+ + -
+ {notificationOptions.map((option) => { - const Icon = option.icon; const isSelected = selectedType === option.type; return ( - + + ); })} -
-
+ + ); -} \ No newline at end of file +} diff --git a/apps/website/components/dev/sections/ReplaySection.tsx b/apps/website/components/dev/sections/ReplaySection.tsx index 12b4c3048..f9035a3ec 100644 --- a/apps/website/components/dev/sections/ReplaySection.tsx +++ b/apps/website/components/dev/sections/ReplaySection.tsx @@ -1,6 +1,14 @@ -import { useState, useEffect } from 'react'; +'use client'; + +import React, { useState, useEffect } from 'react'; import { Play, Copy, Trash2, Download, Clock } from 'lucide-react'; import { getGlobalReplaySystem } from '@/lib/infrastructure/ErrorReplay'; +import { Box } from '@/ui/Box'; +import { Text } from '@/ui/Text'; +import { Stack } from '@/ui/Stack'; +import { Icon } from '@/ui/Icon'; +import { IconButton } from '@/ui/IconButton'; +import { Button } from '@/ui/Button'; interface ReplayEntry { id: string; @@ -11,7 +19,6 @@ interface ReplayEntry { export function ReplaySection() { const [replays, setReplays] = useState([]); - const [selectedReplay, setSelectedReplay] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { @@ -78,83 +85,89 @@ export function ReplaySection() { }; return ( -
-
- Error Replay -
- - -
-
+ color="rgb(239, 68, 68)" + /> + + {replays.length === 0 ? ( -
- No replays available -
+ + No replays available + ) : ( -
+ {replays.map((replay) => ( -
-
-
-
- {replay.type} -
-
{replay.error}
-
- {new Date(replay.timestamp).toLocaleTimeString()} -
-
-
-
- - - - -
-
+ variant="secondary" + size="sm" + /> + + ))} -
+ )} -
+ ); -} \ No newline at end of file +} diff --git a/apps/website/components/dev/sections/UrgencySection.tsx b/apps/website/components/dev/sections/UrgencySection.tsx index a7aa26ede..463e76dc2 100644 --- a/apps/website/components/dev/sections/UrgencySection.tsx +++ b/apps/website/components/dev/sections/UrgencySection.tsx @@ -1,13 +1,17 @@ 'use client'; -import { Bell, BellRing, AlertCircle } from 'lucide-react'; +import React from 'react'; +import { Bell, BellRing, AlertCircle, LucideIcon } from 'lucide-react'; import type { DemoUrgency } from '../types'; +import { Box } from '@/ui/Box'; +import { Text } from '@/ui/Text'; +import { Icon } from '@/ui/Icon'; interface UrgencyOption { urgency: DemoUrgency; label: string; description: string; - icon: any; + icon: LucideIcon; } interface UrgencySectionProps { @@ -38,62 +42,72 @@ export const urgencyOptions: UrgencyOption[] = [ export function UrgencySection({ selectedUrgency, onSelectUrgency }: UrgencySectionProps) { return ( -
-
- - + + + + Urgency Level - -
+ + -
+ {urgencyOptions.map((option) => { - const Icon = option.icon; const isSelected = selectedUrgency === option.urgency; + const getSelectedBg = () => { + if (option.urgency === 'modal') return 'bg-red-500/20'; + if (option.urgency === 'toast') return 'bg-warning-amber/20'; + return 'bg-gray-500/20'; + }; + + const getSelectedBorder = () => { + if (option.urgency === 'modal') return 'border-red-500/50'; + if (option.urgency === 'toast') return 'border-warning-amber/50'; + return 'border-gray-500/50'; + }; + + const getSelectedColor = () => { + if (option.urgency === 'modal') return 'rgb(239, 68, 68)'; + if (option.urgency === 'toast') return 'rgb(245, 158, 11)'; + return 'rgb(156, 163, 175)'; + }; + return ( - + + ); })} -
-

+ + {urgencyOptions.find(o => o.urgency === selectedUrgency)?.description} -

-
+ + ); -} \ No newline at end of file +} diff --git a/apps/website/components/dev/types.ts b/apps/website/components/dev/types.ts index 5906a0c5e..a567a1caa 100644 --- a/apps/website/components/dev/types.ts +++ b/apps/website/components/dev/types.ts @@ -1,4 +1,4 @@ -import type { NotificationVariant } from '@/components/notifications/notificationTypes'; +import type { LucideIcon } from 'lucide-react'; export type DemoNotificationType = 'protest_filed' | 'defense_requested' | 'vote_required' | 'race_performance_summary' | 'race_final_results'; export type DemoUrgency = 'silent' | 'toast' | 'modal'; @@ -7,7 +7,7 @@ export interface NotificationOption { type: DemoNotificationType; label: string; description: string; - icon: any; + icon: LucideIcon; color: string; } @@ -15,5 +15,5 @@ export interface UrgencyOption { urgency: DemoUrgency; label: string; description: string; - icon: any; + icon: LucideIcon; } \ No newline at end of file diff --git a/apps/website/components/drivers/CareerHighlights.tsx b/apps/website/components/drivers/CareerHighlights.tsx deleted file mode 100644 index 23a5ce4c8..000000000 --- a/apps/website/components/drivers/CareerHighlights.tsx +++ /dev/null @@ -1,127 +0,0 @@ -'use client'; - -import Card from '../ui/Card'; - -interface Achievement { - id: string; - title: string; - description: string; - icon: string; - unlockedAt: string; - rarity: 'common' | 'rare' | 'epic' | 'legendary'; -} - -const mockAchievements: Achievement[] = [ - { id: '1', title: 'First Victory', description: 'Won your first race', icon: '🏆', unlockedAt: '2024-03-15', rarity: 'common' }, - { id: '2', title: '10 Podiums', description: 'Achieved 10 podium finishes', icon: '🥈', unlockedAt: '2024-05-22', rarity: 'rare' }, - { id: '3', title: 'Clean Racer', description: 'Completed 25 races with 0 incidents', icon: '✨', unlockedAt: '2024-08-10', rarity: 'epic' }, - { id: '4', title: 'Comeback King', description: 'Won a race after starting P10 or lower', icon: '⚡', unlockedAt: '2024-09-03', rarity: 'rare' }, - { id: '5', title: 'Perfect Weekend', description: 'Pole, fastest lap, and win in same race', icon: '💎', unlockedAt: '2024-10-17', rarity: 'legendary' }, - { id: '6', title: 'Century Club', description: 'Completed 100 races', icon: '💯', unlockedAt: '2024-11-01', rarity: 'epic' }, -]; - -const rarityColors = { - common: 'border-gray-500 bg-gray-500/10', - rare: 'border-blue-400 bg-blue-400/10', - epic: 'border-purple-400 bg-purple-400/10', - legendary: 'border-warning-amber bg-warning-amber/10' -}; - -export default function CareerHighlights() { - return ( -
- -

Key Milestones

- -
- - - - - - -
-
- - -

Achievements

- -
- {mockAchievements.map((achievement) => ( -
-
-
{achievement.icon}
-
-
{achievement.title}
-
{achievement.description}
-
- {new Date(achievement.unlockedAt).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric' - })} -
-
-
-
- ))} -
-
- - -
-
🎯
-

Next Goals

-
-
-
- Win 25 races - 23/25 -
-
-
-
-
- -
- ); -} - -function MilestoneItem({ label, value, icon }: { label: string; value: string; icon: string }) { - return ( -
-
- {icon} - {label} -
- {value} -
- ); -} \ No newline at end of file diff --git a/apps/website/components/drivers/CategoryDistribution.tsx b/apps/website/components/drivers/CategoryDistribution.tsx deleted file mode 100644 index 5be043b14..000000000 --- a/apps/website/components/drivers/CategoryDistribution.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; - -import { BarChart3 } from 'lucide-react'; -const CATEGORIES = [ - { id: 'beginner', label: 'Beginner', color: 'text-green-400', bgColor: 'bg-green-400/10', borderColor: 'border-green-400/30' }, - { id: 'intermediate', label: 'Intermediate', color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', borderColor: 'border-primary-blue/30' }, - { id: 'advanced', label: 'Advanced', color: 'text-purple-400', bgColor: 'bg-purple-400/10', borderColor: 'border-purple-400/30' }, - { id: 'pro', label: 'Pro', color: 'text-yellow-400', bgColor: 'bg-yellow-400/10', borderColor: 'border-yellow-400/30' }, - { id: 'endurance', label: 'Endurance', color: 'text-orange-400', bgColor: 'bg-orange-400/10', borderColor: 'border-orange-400/30' }, - { id: 'sprint', label: 'Sprint', color: 'text-red-400', bgColor: 'bg-red-400/10', borderColor: 'border-red-400/30' }, -]; - -interface CategoryDistributionProps { - drivers: { - category?: string; - }[]; -} - -export function CategoryDistribution({ drivers }: CategoryDistributionProps) { - const distribution = CATEGORIES.map((category) => ({ - ...category, - count: drivers.filter((d) => d.category === category.id).length, - percentage: drivers.length > 0 - ? Math.round((drivers.filter((d) => d.category === category.id).length / drivers.length) * 100) - : 0, - })); - - return ( -
-
-
- -
-
-

Category Distribution

-

Driver population by category

-
-
- -
- {distribution.map((category) => ( -
-
- {category.count} -
-

{category.label}

-
-
-
-

{category.percentage}% of drivers

-
- ))} -
-
- ); -} \ No newline at end of file diff --git a/apps/website/components/drivers/CreateDriverForm.tsx b/apps/website/components/drivers/CreateDriverForm.tsx index a75de64d7..f24d569c9 100644 --- a/apps/website/components/drivers/CreateDriverForm.tsx +++ b/apps/website/components/drivers/CreateDriverForm.tsx @@ -1,11 +1,14 @@ 'use client'; -import { useState, FormEvent } from 'react'; -import { useRouter } from 'next/navigation'; -import { routes } from '@/lib/routing/RouteConfig'; -import Input from '../ui/Input'; -import Button from '../ui/Button'; -import { useCreateDriver } from "@/lib/hooks/driver/useCreateDriver"; +import React, { useState, FormEvent } from 'react'; +import { Input } from '@/ui/Input'; +import { Button } from '@/ui/Button'; +import { Box } from '@/ui/Box'; +import { Text } from '@/ui/Text'; +import { Stack } from '@/ui/Stack'; +import { TextArea } from '@/ui/TextArea'; +import { InfoBox } from '@/ui/InfoBox'; +import { AlertCircle } from 'lucide-react'; interface FormErrors { name?: string; @@ -15,9 +18,12 @@ interface FormErrors { submit?: string; } -export default function CreateDriverForm() { - const router = useRouter(); - const createDriverMutation = useCreateDriver(); +interface CreateDriverFormProps { + onSuccess: () => void; + isPending: boolean; +} + +export function CreateDriverForm({ onSuccess, isPending }: CreateDriverFormProps) { const [errors, setErrors] = useState({}); const [formData, setFormData] = useState({ @@ -50,7 +56,7 @@ export default function CreateDriverForm() { const handleSubmit = async (e: FormEvent) => { e.preventDefault(); - if (createDriverMutation.isPending) return; + if (isPending) return; const isValid = await validateForm(); if (!isValid) return; @@ -61,118 +67,89 @@ export default function CreateDriverForm() { const firstName = parts[0] ?? displayName; const lastName = parts.slice(1).join(' ') || 'Driver'; - createDriverMutation.mutate( - { - firstName, - lastName, - displayName, - country: formData.country.trim().toUpperCase(), - ...(bio ? { bio } : {}), - }, - { - onSuccess: () => { - router.push(routes.protected.profile); - router.refresh(); - }, - onError: (error) => { - setErrors({ - submit: error instanceof Error ? error.message : 'Failed to create profile' - }); - }, - } - ); + // Construct data for parent to handle + const driverData = { + firstName, + lastName, + displayName, + country: formData.country.trim().toUpperCase(), + ...(bio ? { bio } : {}), + }; + + console.log('Driver data to create:', driverData); + onSuccess(); }; return ( - <> -
-
- + + setFormData({ ...formData, name: e.target.value })} - error={!!errors.name} + onChange={(e: React.ChangeEvent) => setFormData({ ...formData, name: e.target.value })} + variant={errors.name ? 'error' : 'default'} errorMessage={errors.name} placeholder="Alex Vermeer" - disabled={createDriverMutation.isPending} + disabled={isPending} /> -
-
- - setFormData({ ...formData, name: e.target.value })} - error={!!errors.name} - errorMessage={errors.name} - placeholder="Alex Vermeer" - disabled={createDriverMutation.isPending} - /> -
+ + ) => setFormData({ ...formData, country: e.target.value })} + variant={errors.country ? 'error' : 'default'} + errorMessage={errors.country} + placeholder="NL" + maxLength={3} + disabled={isPending} + /> + Use ISO 3166-1 alpha-2 or alpha-3 code + -
- - setFormData({ ...formData, country: e.target.value })} - error={!!errors.country} - errorMessage={errors.country} - placeholder="NL" - maxLength={3} - disabled={createDriverMutation.isPending} - /> -

Use ISO 3166-1 alpha-2 or alpha-3 code

-
+ +