website refactor
This commit is contained in:
@@ -6,7 +6,7 @@ import { AdminUsersTemplate } from '@/templates/AdminUsersTemplate';
|
||||
import { AdminUsersViewData } from '@/lib/view-data/AdminUsersViewData';
|
||||
import { updateUserStatus, deleteUser } from '@/app/actions/adminActions';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { ConfirmDialog } from '@/components/shared/ux/ConfirmDialog';
|
||||
import { ConfirmDialog } from '@/ui/ConfirmDialog';
|
||||
|
||||
interface AdminUsersWrapperProps {
|
||||
initialViewData: AdminUsersViewData;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { DriversViewData } from '@/lib/types/view-data/DriversViewData';
|
||||
import { DriversTemplate } from '@/templates/DriversTemplate';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ProfileTab } from '@/components/profile/ProfileTabs';
|
||||
import type { DriverProfileViewData } from '@/lib/types/view-data/DriverProfileViewData';
|
||||
import { DriverProfileTemplate } from '@/templates/DriverProfileTemplate';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -4,26 +4,26 @@ import { LeagueCard } from '@/components/leagues/LeagueCardWrapper';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
|
||||
import { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import {
|
||||
Award,
|
||||
Clock,
|
||||
Flag,
|
||||
Flame,
|
||||
Globe,
|
||||
Plus,
|
||||
Search,
|
||||
Sparkles,
|
||||
Target,
|
||||
Timer,
|
||||
Trophy,
|
||||
Users,
|
||||
type LucideIcon,
|
||||
Award,
|
||||
Clock,
|
||||
Flag,
|
||||
Flame,
|
||||
Globe,
|
||||
Plus,
|
||||
Search,
|
||||
Sparkles,
|
||||
Target,
|
||||
Timer,
|
||||
Trophy,
|
||||
Users,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
createRaceAction,
|
||||
deleteRaceAction,
|
||||
publishScheduleAction,
|
||||
unpublishScheduleAction,
|
||||
updateRaceAction
|
||||
createRaceAction,
|
||||
deleteRaceAction,
|
||||
publishScheduleAction,
|
||||
unpublishScheduleAction,
|
||||
updateRaceAction
|
||||
} from '@/app/actions/leagueScheduleActions';
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { ConfirmDialog } from '@/components/shared/ux/ConfirmDialog';
|
||||
import { PageWrapper } from '@/ui/PageWrapper';
|
||||
import { ConfirmDialog } from '@/ui/ConfirmDialog';
|
||||
import {
|
||||
useLeagueAdminSchedule,
|
||||
useLeagueAdminStatus,
|
||||
useLeagueSeasons
|
||||
useLeagueAdminSchedule,
|
||||
useLeagueAdminStatus,
|
||||
useLeagueSeasons
|
||||
} from "@/hooks/league/useLeagueScheduleAdminPageData";
|
||||
import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId";
|
||||
import { RaceScheduleCommandModel } from '@/lib/command-models/leagues/RaceScheduleCommandModel';
|
||||
import { LeagueAdminScheduleTemplate } from '@/templates/LeagueAdminScheduleTemplate';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -11,10 +11,10 @@ import type { StewardingViewData } from '@/lib/view-data/leagues/StewardingViewD
|
||||
import { DriverViewModel } from '@/lib/view-models/DriverViewModel';
|
||||
import { ProtestViewModel } from '@/lib/view-models/ProtestViewModel';
|
||||
import { RaceViewModel } from '@/lib/view-models/RaceViewModel';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
|
||||
@@ -7,44 +7,44 @@ import { PROTEST_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import {
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
ArrowLeft,
|
||||
Calendar,
|
||||
CheckCircle,
|
||||
ChevronDown,
|
||||
Clock,
|
||||
ExternalLink,
|
||||
Flag,
|
||||
Gavel,
|
||||
Grid3x3,
|
||||
MapPin,
|
||||
MessageCircle,
|
||||
Send,
|
||||
Shield,
|
||||
ShieldAlert,
|
||||
TrendingDown,
|
||||
User,
|
||||
Video,
|
||||
XCircle,
|
||||
type LucideIcon
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
ArrowLeft,
|
||||
Calendar,
|
||||
CheckCircle,
|
||||
ChevronDown,
|
||||
Clock,
|
||||
ExternalLink,
|
||||
Flag,
|
||||
Gavel,
|
||||
Grid3x3,
|
||||
MapPin,
|
||||
MessageCircle,
|
||||
Send,
|
||||
Shield,
|
||||
ShieldAlert,
|
||||
TrendingDown,
|
||||
User,
|
||||
Video,
|
||||
XCircle,
|
||||
type LucideIcon
|
||||
} from 'lucide-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
// Shared state components
|
||||
import { LoadingWrapper } from '@/components/shared/state/LoadingWrapper';
|
||||
import { StateContainer } from '@/components/shared/state/StateContainer';
|
||||
import { LoadingWrapper } from '@/ui/LoadingWrapper';
|
||||
import { StateContainer } from '@/ui/StateContainer';
|
||||
import { useLeagueAdminStatus } from "@/hooks/league/useLeagueAdminStatus";
|
||||
import { useProtestDetail } from "@/hooks/league/useProtestDetail";
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { GridItem } from '@/ui/primitives/GridItem';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon as UIIcon } from '@/ui/Icon';
|
||||
import { Link as UILink } from '@/ui/Link';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { GridItem } from '@/ui/GridItem';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
type PenaltyUiConfig = {
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
import { WalletSummaryPanel } from '@/components/leagues/WalletSummaryPanel';
|
||||
import type { LeagueWalletViewData } from '@/lib/view-data/leagues/LeagueWalletViewData';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon as UIIcon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import {
|
||||
Download
|
||||
Download
|
||||
} from 'lucide-react';
|
||||
|
||||
interface WalletTemplateProps {
|
||||
|
||||
@@ -2,28 +2,28 @@
|
||||
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
import { LeagueReviewSummary } from '@/components/leagues/LeagueReviewSummary';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import {
|
||||
AlertCircle,
|
||||
Award,
|
||||
Calendar,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
FileText,
|
||||
Loader2,
|
||||
Scale,
|
||||
Sparkles,
|
||||
Trophy,
|
||||
Users,
|
||||
AlertCircle,
|
||||
Award,
|
||||
Calendar,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
FileText,
|
||||
Loader2,
|
||||
Scale,
|
||||
Sparkles,
|
||||
Trophy,
|
||||
Users,
|
||||
} from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { FormEvent, useCallback, useEffect, useState } from 'react';
|
||||
@@ -33,8 +33,8 @@ import { LeagueWizardCommandModel } from '@/lib/command-models/leagues/LeagueWiz
|
||||
import { LeagueBasicsSection } from '@/components/leagues/LeagueBasicsSection';
|
||||
import { LeagueDropSection } from '@/components/leagues/LeagueDropSection';
|
||||
import {
|
||||
ChampionshipsSection,
|
||||
ScoringPatternSection
|
||||
ChampionshipsSection,
|
||||
ScoringPatternSection
|
||||
} from '@/components/leagues/LeagueScoringSection';
|
||||
import { LeagueStewardingSection } from '@/components/leagues/LeagueStewardingSection';
|
||||
import { LeagueStructureSection } from '@/components/leagues/LeagueStructureSection';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageWrapper } from '@/ui/PageWrapper';
|
||||
import { HomeTemplate, type HomeViewData } from '@/templates/HomeTemplate';
|
||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||
import { HomePageQuery } from '@/lib/page-queries/HomePageQuery';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { UploadDropzone } from '@/components/media/UploadDropzone';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Container } from '@/ui/Container';
|
||||
@@ -9,7 +10,6 @@ import { Heading } from '@/ui/Heading';
|
||||
import { MediaMetaPanel, mapMediaMetadata } from '@/ui/MediaMetaPanel';
|
||||
import { MediaPreviewCard } from '@/ui/MediaPreviewCard';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { InlineNotice } from '@/components/shared/ux/InlineNotice';
|
||||
import { ProgressLine } from '@/components/shared/ux/ProgressLine';
|
||||
import { InlineNotice } from '@/ui/InlineNotice';
|
||||
import { ProgressLine } from '@/ui/ProgressLine';
|
||||
import type { Result } from '@/lib/contracts/Result';
|
||||
import type { ProfileViewData } from '@/lib/view-data/ProfileViewData';
|
||||
import { ProfileSettingsTemplate } from '@/templates/ProfileSettingsTemplate';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { InlineNotice } from '@/components/shared/ux/InlineNotice';
|
||||
import { ProgressLine } from '@/components/shared/ux/ProgressLine';
|
||||
import { InlineNotice } from '@/ui/InlineNotice';
|
||||
import { ProgressLine } from '@/ui/ProgressLine';
|
||||
import type { Result } from '@/lib/contracts/Result';
|
||||
import type { SponsorshipRequestsViewData } from '@/lib/view-data/SponsorshipRequestsViewData';
|
||||
import { SponsorshipRequestsTemplate } from '@/templates/SponsorshipRequestsTemplate';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageWrapper } from '@/ui/PageWrapper';
|
||||
import { RaceDetailPageQuery } from '@/lib/page-queries/races/RaceDetailPageQuery';
|
||||
import { RaceDetailPageClient } from './RaceDetailPageClient';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageWrapper } from '@/ui/PageWrapper';
|
||||
import { RaceResultsPageQuery } from '@/lib/page-queries/races/RaceResultsPageQuery';
|
||||
import { RaceResultsPageClient } from './RaceResultsPageClient';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { notFound } from 'next/navigation';
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageWrapper } from '@/ui/PageWrapper';
|
||||
import { RaceStewardingTemplate, type StewardingTab } from '@/templates/RaceStewardingTemplate';
|
||||
import { RaceStewardingPageQuery } from '@/lib/page-queries/races/RaceStewardingPageQuery';
|
||||
import { type RaceStewardingViewData } from '@/lib/view-data/races/RaceStewardingViewData';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { StatefulPageWrapper } from '@/components/shared/state/StatefulPageWrapper';
|
||||
import { StatefulPageWrapper } from '@/ui/StatefulPageWrapper';
|
||||
import { RacesAllTemplate } from '@/templates/RacesAllTemplate';
|
||||
import { useAllRacesPageData } from '@/hooks/race/useAllRacesPageData';
|
||||
import { type RacesViewData, type RaceViewData } from '@/lib/view-data/RacesViewData';
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { useSponsorBilling } from "@/hooks/sponsor/useSponsorBilling";
|
||||
import { SponsorBillingTemplate } from "@/templates/SponsorBillingTemplate";
|
||||
import { Box } from "@/ui/primitives/Box";
|
||||
import { Text } from "@/ui/Text";
|
||||
import { Box } from "@/ui/Box";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { DollarSign, AlertTriangle, Calendar, TrendingUp } from "lucide-react";
|
||||
import { Text } from "@/ui/Text";
|
||||
import { AlertTriangle, Calendar, DollarSign, TrendingUp } from "lucide-react";
|
||||
|
||||
export default function SponsorBillingPage() {
|
||||
const { data: billingData, isLoading, error, retry } = useSponsorBilling('demo-sponsor-1');
|
||||
@@ -40,11 +40,6 @@ export default function SponsorBillingPage() {
|
||||
console.log('Setting default payment method:', methodId);
|
||||
};
|
||||
|
||||
const handleRemoveMethod = (methodId: string) => {
|
||||
if (window.confirm('Remove this payment method?')) {
|
||||
console.log('Removing payment method:', methodId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadInvoice = (invoiceId: string) => {
|
||||
console.log('Downloading invoice:', invoiceId);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useSponsorSponsorships } from "@/hooks/sponsor/useSponsorSponsorships";
|
||||
import { SponsorCampaignsTemplate, SponsorshipType } from "@/templates/SponsorCampaignsTemplate";
|
||||
import { Box } from "@/ui/primitives/Box";
|
||||
import { Text } from "@/ui/Text";
|
||||
import { Box } from "@/ui/Box";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { Text } from "@/ui/Text";
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function SponsorCampaignsPage() {
|
||||
const [typeFilter, setTypeFilter] = useState<SponsorshipType>('all');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createRouteGuard } from '@/lib/auth/createRouteGuard';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { headers } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageWrapper } from '@/ui/PageWrapper';
|
||||
import { SponsorLeagueDetailPageClient } from './SponsorLeagueDetailPageClient';
|
||||
import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageWrapper } from '@/ui/PageWrapper';
|
||||
import { SponsorLeaguesPageClient } from './SponsorLeaguesPageClient';
|
||||
import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState } from 'react';
|
||||
import { SponsorSettingsTemplate } from '@/templates/SponsorSettingsTemplate';
|
||||
import { logoutAction } from '@/app/actions/logoutAction';
|
||||
import { ConfirmDialog } from '@/components/shared/ux/ConfirmDialog';
|
||||
import { ConfirmDialog } from '@/ui/ConfirmDialog';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { logger } from '@/lib/infrastructure/logging/logger';
|
||||
|
||||
@@ -5,31 +5,31 @@ import { SponsorHero } from '@/components/sponsors/SponsorHero';
|
||||
import { SponsorWorkflowMockup } from '@/components/sponsors/SponsorWorkflowMockup';
|
||||
import { SponsorSignupCommandModel } from '@/lib/command-models/sponsors/SponsorSignupCommandModel';
|
||||
import { siteConfig } from '@/lib/siteConfig';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import {
|
||||
ArrowRight,
|
||||
BarChart3,
|
||||
Building2,
|
||||
Car,
|
||||
CheckCircle2,
|
||||
Eye,
|
||||
Flag,
|
||||
Globe,
|
||||
Mail,
|
||||
Megaphone,
|
||||
Shield,
|
||||
Target,
|
||||
TrendingUp,
|
||||
Trophy,
|
||||
Upload,
|
||||
Users
|
||||
ArrowRight,
|
||||
BarChart3,
|
||||
Building2,
|
||||
Car,
|
||||
CheckCircle2,
|
||||
Eye,
|
||||
Flag,
|
||||
Globe,
|
||||
Mail,
|
||||
Megaphone,
|
||||
Shield,
|
||||
Target,
|
||||
TrendingUp,
|
||||
Trophy,
|
||||
Upload,
|
||||
Users
|
||||
} from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CreateTeamForm } from '@/components/teams/CreateTeamForm';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Badge } from '@/ui/Badge';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Hash, LucideIcon, Percent, Search, Star, Trophy } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Input } from '@/ui/Input';
|
||||
import { ControlBar } from '@/ui/ControlBar';
|
||||
import { Select } from '@/ui/Select';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { ButtonGroup } from '@/ui/ButtonGroup';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function ActionFiltersBar() {
|
||||
@@ -12,39 +12,41 @@ export function ActionFiltersBar() {
|
||||
|
||||
return (
|
||||
<ControlBar
|
||||
actions={
|
||||
leftContent={
|
||||
<Input
|
||||
placeholder="SEARCH_ID..."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Group direction="row" align="center" gap={2}>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase>Filter:</Text>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'All Types', value: 'all' },
|
||||
{ label: 'User Update', value: 'user' },
|
||||
{ label: 'Onboarding', value: 'onboarding' }
|
||||
]}
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
fullWidth={false}
|
||||
/>
|
||||
</Group>
|
||||
<Group direction="row" align="center" gap={2}>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase>Status:</Text>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'All Status', value: 'all' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
{ label: 'Pending', value: 'pending' },
|
||||
{ label: 'Failed', value: 'failed' }
|
||||
]}
|
||||
value="all"
|
||||
onChange={() => {}}
|
||||
fullWidth={false}
|
||||
/>
|
||||
</Group>
|
||||
<ButtonGroup gap={4}>
|
||||
<ButtonGroup gap={2}>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase>Filter:</Text>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'All Types', value: 'all' },
|
||||
{ label: 'User Update', value: 'user' },
|
||||
{ label: 'Onboarding', value: 'onboarding' }
|
||||
]}
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
fullWidth={false}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup gap={2}>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase>Status:</Text>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'All Status', value: 'all' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
{ label: 'Pending', value: 'pending' },
|
||||
{ label: 'Failed', value: 'failed' }
|
||||
]}
|
||||
value="all"
|
||||
onChange={() => {}}
|
||||
fullWidth={false}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
</ControlBar>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Header } from '@/ui/Header';
|
||||
import { StatusIndicator } from '@/ui/StatusIndicator';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ButtonGroup } from '@/ui/ButtonGroup';
|
||||
import { Activity } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -15,12 +16,11 @@ export function ActionsHeader({ title }: ActionsHeaderProps) {
|
||||
<Header
|
||||
actions={<StatusIndicator icon={Activity} variant="info" label="SYSTEM_READY" />}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<div style={{ width: '0.5rem', height: '1.5rem', backgroundColor: 'var(--ui-color-intent-primary)', borderRadius: 'var(--ui-radius-sm)' }} />
|
||||
<ButtonGroup gap={4}>
|
||||
<Text as="h1" size="xl" weight="medium" variant="high" uppercase>
|
||||
{title}
|
||||
</Text>
|
||||
</div>
|
||||
</ButtonGroup>
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import React from 'react';
|
||||
|
||||
interface AuthFooterLinksProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import React from 'react';
|
||||
|
||||
interface AuthFormProps {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LoadingSpinner } from '../../ui/LoadingSpinner';
|
||||
import { Box } from '../../ui/primitives/Box';
|
||||
import { Stack } from '../../ui/primitives/Stack';
|
||||
import { Stack } from '../../ui/Stack';
|
||||
import { Text } from '../../ui/Text';
|
||||
import { Section } from '../../ui/Section';
|
||||
|
||||
interface AuthLoadingProps {
|
||||
message?: string;
|
||||
@@ -9,19 +9,13 @@ interface AuthLoadingProps {
|
||||
|
||||
export function AuthLoading({ message = 'Authenticating...' }: AuthLoadingProps) {
|
||||
return (
|
||||
<Box
|
||||
fullWidth
|
||||
minHeight="100vh"
|
||||
display="flex"
|
||||
center
|
||||
bg="bg-[#0f1115]"
|
||||
>
|
||||
<Stack align="center" gap={4}>
|
||||
<Section variant="dark" padding="none">
|
||||
<Stack align="center" justify="center" gap={4} fullWidth>
|
||||
<LoadingSpinner size={10} />
|
||||
<Text color="text-gray-400" weight="medium">
|
||||
<Text variant="low" weight="medium">
|
||||
{message}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Card } from '@/ui/Card';
|
||||
|
||||
interface KpiItem {
|
||||
label: string;
|
||||
value: string | number;
|
||||
color?: string;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'high' | 'med' | 'low';
|
||||
}
|
||||
|
||||
interface DashboardKpiRowProps {
|
||||
@@ -16,32 +17,31 @@ interface DashboardKpiRowProps {
|
||||
* DashboardKpiRow
|
||||
*
|
||||
* A horizontal row of key performance indicators with telemetry styling.
|
||||
* Uses UI primitives to comply with architectural constraints.
|
||||
*/
|
||||
export function DashboardKpiRow({ items }: DashboardKpiRowProps) {
|
||||
return (
|
||||
<Grid responsiveGridCols={{ base: 2, md: 3, lg: 6 }} gap={4}>
|
||||
<Grid cols={{ base: 2, md: 3, lg: 6 }} gap={4}>
|
||||
{items.map((item, index) => (
|
||||
<Stack key={index} borderLeft pl={4} borderColor="var(--color-outline)">
|
||||
<Text
|
||||
size="xs"
|
||||
weight="bold"
|
||||
uppercase
|
||||
letterSpacing="tighter"
|
||||
color="var(--color-text-low)"
|
||||
block
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
<Text
|
||||
size="xl"
|
||||
font="mono"
|
||||
weight="bold"
|
||||
color={item.color || 'var(--color-text-high)'}
|
||||
>
|
||||
{item.value}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Card key={index} variant="dark">
|
||||
<Stack gap={1}>
|
||||
<Text
|
||||
size="xs"
|
||||
weight="bold"
|
||||
uppercase
|
||||
variant="low"
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
<Text
|
||||
size="xl"
|
||||
font="mono"
|
||||
weight="bold"
|
||||
variant={item.intent || 'high'}
|
||||
>
|
||||
{item.value}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import React from 'react';
|
||||
|
||||
interface DashboardRailProps {
|
||||
@@ -13,7 +13,7 @@ interface DashboardRailProps {
|
||||
*/
|
||||
export function DashboardRail({ children }: DashboardRailProps) {
|
||||
return (
|
||||
<Stack as="nav" h="full" align="center" py={4} gap={4}>
|
||||
<Stack align="center" gap={4} fullWidth>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Sidebar } from '@/ui/Sidebar';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Header } from '@/ui/Header';
|
||||
import { MainContent } from '@/ui/MainContent';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Sidebar } from '@/ui/Sidebar';
|
||||
import React from 'react';
|
||||
|
||||
interface DashboardShellProps {
|
||||
|
||||
@@ -8,6 +8,8 @@ import { Text } from '@/ui/Text';
|
||||
import { FloatingAction } from '@/ui/FloatingAction';
|
||||
import { DebugPanel } from '@/ui/DebugPanel';
|
||||
import { StatGrid } from '@/ui/StatGrid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { ButtonGroup } from '@/ui/ButtonGroup';
|
||||
import { Bug, Shield } from 'lucide-react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
@@ -207,13 +209,13 @@ export function DebugModeToggle({ show }: DebugModeToggleProps) {
|
||||
onClose={() => setIsOpen(false)}
|
||||
icon={<Icon icon={Bug} size={4} intent="success" />}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||
<Stack gap={4}>
|
||||
{/* Debug Toggle */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0.5rem', backgroundColor: 'var(--ui-color-bg-surface-muted)', borderRadius: '0.5rem' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<Stack align="center" justify="between">
|
||||
<ButtonGroup gap={2}>
|
||||
<Icon icon={Shield} size={4} intent={debugEnabled ? 'success' : 'low'} />
|
||||
<Text size="sm" weight="medium">Debug Mode</Text>
|
||||
</div>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
onClick={toggleDebug}
|
||||
size="sm"
|
||||
@@ -221,7 +223,7 @@ export function DebugModeToggle({ show }: DebugModeToggleProps) {
|
||||
>
|
||||
{debugEnabled ? 'ON' : 'OFF'}
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
{/* Metrics */}
|
||||
{debugEnabled && (
|
||||
@@ -238,41 +240,41 @@ export function DebugModeToggle({ show }: DebugModeToggleProps) {
|
||||
|
||||
{/* Actions */}
|
||||
{debugEnabled && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" weight="semibold" variant="low" uppercase>Test Actions</Text>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.5rem' }}>
|
||||
<Button onClick={triggerTestError} variant="danger" size="sm">Test Error</Button>
|
||||
<Button onClick={triggerTestApiCall} size="sm">Test API</Button>
|
||||
</div>
|
||||
<ButtonGroup gap={2}>
|
||||
<Button onClick={triggerTestError} variant="danger" size="sm" fullWidth>Test Error</Button>
|
||||
<Button onClick={triggerTestApiCall} size="sm" fullWidth>Test API</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<Text size="xs" weight="semibold" variant="low" uppercase style={{ marginTop: '0.5rem' }}>Utilities</Text>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.5rem' }}>
|
||||
<Button onClick={copyDebugInfo} variant="secondary" size="sm">Copy Info</Button>
|
||||
<Button onClick={clearAllLogs} variant="secondary" size="sm">Clear Logs</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Text size="xs" weight="semibold" variant="low" uppercase>Utilities</Text>
|
||||
<ButtonGroup gap={2}>
|
||||
<Button onClick={copyDebugInfo} variant="secondary" size="sm" fullWidth>Copy Info</Button>
|
||||
<Button onClick={clearAllLogs} variant="secondary" size="sm" fullWidth>Clear Logs</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Quick Links */}
|
||||
{debugEnabled && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>
|
||||
<Stack gap={1}>
|
||||
<Text size="xs" weight="semibold" variant="low" uppercase>Quick Access</Text>
|
||||
<div style={{ fontSize: '10px', color: 'var(--ui-color-text-low)', fontFamily: 'var(--ui-font-mono)' }}>
|
||||
<div>• window.__GRIDPILOT_GLOBAL_HANDLER__</div>
|
||||
<div>• window.__GRIDPILOT_API_LOGGER__</div>
|
||||
<div>• window.__GRIDPILOT_REACT_ERRORS__</div>
|
||||
</div>
|
||||
</div>
|
||||
<Stack gap={0.5}>
|
||||
<Text size="xs" variant="low" font="mono">• window.__GRIDPILOT_GLOBAL_HANDLER__</Text>
|
||||
<Text size="xs" variant="low" font="mono">• window.__GRIDPILOT_API_LOGGER__</Text>
|
||||
<Text size="xs" variant="low" font="mono">• window.__GRIDPILOT_REACT_ERRORS__</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Status */}
|
||||
<div style={{ paddingTop: '0.5rem', borderTop: '1px solid var(--ui-color-border-muted)', textAlign: 'center' }}>
|
||||
<Text size="xs" variant="low" style={{ fontSize: '10px' }}>
|
||||
<Stack align="center">
|
||||
<Text size="xs" variant="low">
|
||||
{debugEnabled ? 'Debug features active' : 'Debug mode disabled'}
|
||||
{isDev && ' • Development Environment'}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DebugPanel>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useNotifications } from '@/components/notifications/NotificationProvide
|
||||
import type { NotificationVariant } from '@/components/notifications/notificationTypes';
|
||||
import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId";
|
||||
import { ApiConnectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
|
||||
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
|
||||
import { CircuitBreakerRegistry } from '@/lib/api/base/CircuitBreakerRegistry';
|
||||
import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
|
||||
import { Activity, AlertTriangle, ChevronDown, ChevronUp, MessageSquare, Wrench, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -16,7 +16,7 @@ import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { IconButton } from '@/ui/IconButton';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { APIStatusSection } from './sections/APIStatusSection';
|
||||
import { NotificationSendSection } from './sections/NotificationSendSection';
|
||||
import { NotificationTypeSection } from './sections/NotificationTypeSection';
|
||||
@@ -229,7 +229,7 @@ export function DevToolbar() {
|
||||
|
||||
if (isMinimized) {
|
||||
return (
|
||||
<Stack position="fixed" bottom="4" right="4" zIndex={50}>
|
||||
<Stack align="end" justify="end">
|
||||
<IconButton
|
||||
icon={Wrench}
|
||||
onClick={() => setIsMinimized(false)}
|
||||
@@ -242,25 +242,13 @@ export function DevToolbar() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
position="fixed"
|
||||
bottom="4"
|
||||
right="4"
|
||||
zIndex={50}
|
||||
w="80"
|
||||
bg="bg-deep-graphite"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="xl"
|
||||
shadow="2xl"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Stack gap={4}>
|
||||
{/* Header */}
|
||||
<Stack direction="row" align="center" justify="between" px={4} py={3} bg="bg-iron-gray/50" borderBottom borderColor="border-charcoal-outline">
|
||||
<Stack direction="row" align="center" justify="between" gap={4}>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Wrench} size={4} color="rgb(59, 130, 246)" />
|
||||
<Text size="sm" weight="semibold" color="text-white">Dev Toolbar</Text>
|
||||
<Badge variant="primary" size="xs">
|
||||
<Icon icon={Wrench} size={4} intent="primary" />
|
||||
<Text size="sm" weight="semibold" variant="high">Dev Toolbar</Text>
|
||||
<Badge variant="primary" size="sm">
|
||||
DEMO
|
||||
</Badge>
|
||||
</Stack>
|
||||
@@ -282,12 +270,11 @@ export function DevToolbar() {
|
||||
|
||||
{/* Content */}
|
||||
{isExpanded && (
|
||||
<Stack p={4}>
|
||||
<Stack gap={4}>
|
||||
<Stack gap={3}>
|
||||
{/* Notification Section - Accordion */}
|
||||
<Accordion
|
||||
title="Notifications"
|
||||
icon={<Icon icon={MessageSquare} size={4} color="rgb(156, 163, 175)" />}
|
||||
isOpen={openAccordion === 'notifications'}
|
||||
onToggle={() => setOpenAccordion(openAccordion === 'notifications' ? null : 'notifications')}
|
||||
>
|
||||
@@ -313,7 +300,6 @@ export function DevToolbar() {
|
||||
{/* API Status Section - Accordion */}
|
||||
<Accordion
|
||||
title="API Status"
|
||||
icon={<Icon icon={Activity} size={4} color="rgb(156, 163, 175)" />}
|
||||
isOpen={openAccordion === 'apiStatus'}
|
||||
onToggle={() => setOpenAccordion(openAccordion === 'apiStatus' ? null : 'apiStatus')}
|
||||
>
|
||||
@@ -331,27 +317,26 @@ export function DevToolbar() {
|
||||
{/* Error Stats Section - Accordion */}
|
||||
<Accordion
|
||||
title="Error Stats"
|
||||
icon={<Icon icon={AlertTriangle} size={4} color="rgb(156, 163, 175)" />}
|
||||
isOpen={openAccordion === 'errors'}
|
||||
onToggle={() => setOpenAccordion(openAccordion === 'errors' ? null : 'errors')}
|
||||
>
|
||||
<Stack gap={2}>
|
||||
<Stack direction="row" justify="between" align="center" p={2} bg="bg-iron-gray/30" rounded="md">
|
||||
<Text size="xs" color="text-gray-400">Total Errors</Text>
|
||||
<Text size="xs" font="mono" weight="bold" color="text-red-400">{errorStats.total}</Text>
|
||||
<Stack direction="row" justify="between" align="center" gap={2}>
|
||||
<Text size="xs" variant="low">Total Errors</Text>
|
||||
<Text size="xs" font="mono" weight="bold" variant="critical">{errorStats.total}</Text>
|
||||
</Stack>
|
||||
{Object.keys(errorStats.byType).length > 0 ? (
|
||||
<Stack gap={1}>
|
||||
{Object.entries(errorStats.byType).map(([type, count]) => (
|
||||
<Stack key={type} direction="row" justify="between" align="center" p={1.5} bg="bg-deep-graphite" rounded="md">
|
||||
<Text size="xs" color="text-gray-300">{type}</Text>
|
||||
<Text size="xs" font="mono" color="text-warning-amber">{count}</Text>
|
||||
<Stack key={type} direction="row" justify="between" align="center" gap={2}>
|
||||
<Text size="xs" variant="low">{type}</Text>
|
||||
<Text size="xs" font="mono" variant="warning">{count}</Text>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack align="center" py={2}>
|
||||
<Text size="xs" color="text-gray-500">No errors yet</Text>
|
||||
<Stack align="center">
|
||||
<Text size="xs" variant="low">No errors yet</Text>
|
||||
</Stack>
|
||||
)}
|
||||
<Button
|
||||
@@ -374,8 +359,8 @@ export function DevToolbar() {
|
||||
|
||||
{/* Collapsed state hint */}
|
||||
{!isExpanded && (
|
||||
<Stack px={4} py={2}>
|
||||
<Text size="xs" color="text-gray-500">Click ↑ to expand dev tools</Text>
|
||||
<Stack>
|
||||
<Text size="xs" variant="low">Click ↑ to expand dev tools</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -4,8 +4,8 @@ import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Badge, StatRow, StatusIndicator } from '@/ui/StatusIndicator';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Activity, RefreshCw, Terminal, Wifi } from 'lucide-react';
|
||||
|
||||
interface APIStatusSectionProps {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Bell, Loader2 } from 'lucide-react';
|
||||
import type { DemoNotificationType, DemoUrgency } from '../types';
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { AlertTriangle, Award, LucideIcon, MessageSquare, Shield, TrendingUp, Vote } from 'lucide-react';
|
||||
import type { DemoNotificationType } from '../types';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getGlobalReplaySystem } from '@/lib/infrastructure/ErrorReplay';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { IconButton } from '@/ui/IconButton';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box, Clock, Copy, Download, Play, Trash2 } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { AlertCircle, Bell, BellRing, LucideIcon } from 'lucide-react';
|
||||
import type { DemoUrgency } from '../types';
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Card } from '@/ui/Card';
|
||||
import { GoalCard } from '@/ui/GoalCard';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { MilestoneItem } from '@/components/achievements/MilestoneItem';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
|
||||
interface Achievement {
|
||||
id: string;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { StatGridItem } from '@/ui/StatGridItem';
|
||||
import { TrendingUp } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Button } from '@/ui/Button';
|
||||
import { InfoBox } from '@/ui/InfoBox';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { TextArea } from '@/ui/TextArea';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { DriverIdentity } from '@/components/drivers/DriverIdentity';
|
||||
import { DriverIdentity } from '@/ui/DriverIdentity';
|
||||
import { DriverStats } from '@/components/drivers/DriverStats';
|
||||
import { RankBadge } from '@/components/leaderboards/RankBadge';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { DriverViewModel } from '@/lib/view-models/DriverViewModel';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
|
||||
export interface DriverCardProps {
|
||||
id: string;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Zap } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RatingBadge } from '@/components/drivers/RatingBadge';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
|
||||
interface DriverPerformanceOverviewProps {
|
||||
stats: {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useDriverProfile } from "@/hooks/driver/useDriverProfile";
|
||||
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { StatCard } from '@/ui/StatCard';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ProfileStats } from './ProfileStats';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { RatingBadge } from '@/components/drivers/RatingBadge';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Check, Globe, Trophy, UserPlus } from 'lucide-react';
|
||||
import { SafetyRatingBadge } from './SafetyRatingBadge';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { BarChart3, LayoutDashboard, ShieldCheck } from 'lucide-react';
|
||||
|
||||
export type ProfileTab = 'overview' | 'stats' | 'ratings';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Car, Clock, MailCheck, MapPin, Users2 } from 'lucide-react';
|
||||
|
||||
interface DriverRacingProfileProps {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Star, Trophy } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
interface DriverSearchBarProps {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
interface DriverStatsProps {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
|
||||
interface StatItem {
|
||||
label: string;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { PlaceholderImage } from '@/ui/PlaceholderImage';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Table, TableHead, TableBody, TableRow, TableHeaderCell } from '@/ui/Table';
|
||||
import { TrendingUp } from 'lucide-react';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import React from 'react';
|
||||
|
||||
interface DriverTableProps {
|
||||
@@ -14,31 +17,29 @@ export function DriverTable({ children }: DriverTableProps) {
|
||||
return (
|
||||
<Stack gap={4}>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Stack display="flex" h="10" w="10" alignItems="center" justifyContent="center" rounded="xl" bg="bg-primary-blue/10" border borderColor="border-primary-blue/20">
|
||||
<TrendingUp size={20} color="#198CFF" />
|
||||
</Stack>
|
||||
<Card variant="dark">
|
||||
<Icon icon={TrendingUp} size={5} intent="primary" />
|
||||
</Card>
|
||||
<Stack>
|
||||
<Heading level={2}>Driver Rankings</Heading>
|
||||
<Text size="xs" color="text-gray-500">Top performers by skill rating</Text>
|
||||
<Text size="xs" variant="low">Top performers by skill rating</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack overflow="hidden" rounded="xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal/50">
|
||||
<Stack as="table" w="full" textAlign="left">
|
||||
<Stack as="thead">
|
||||
<Stack as="tr" borderBottom borderColor="border-charcoal-outline" bg="bg-deep-charcoal/80">
|
||||
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="center" width="60px">#</Stack>
|
||||
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500">Driver</Stack>
|
||||
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500" width="150px">Nationality</Stack>
|
||||
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="right" width="100px">Rating</Stack>
|
||||
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="right" width="80px">Wins</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack as="tbody">
|
||||
{children}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell textAlign="center" w="60px">#</TableHeaderCell>
|
||||
<TableHeaderCell>Driver</TableHeaderCell>
|
||||
<TableHeaderCell w="150px">Nationality</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="right" w="100px">Rating</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="right" w="80px">Wins</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{children}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import { RatingBadge } from '@/components/drivers/RatingBadge';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { TableRow, TableCell } from '@/ui/Table';
|
||||
import { Avatar } from '@/ui/Avatar';
|
||||
|
||||
interface DriverTableRowProps {
|
||||
rank: number;
|
||||
@@ -24,61 +26,45 @@ export function DriverTableRow({
|
||||
wins,
|
||||
onClick,
|
||||
}: DriverTableRowProps) {
|
||||
const defaultAvatar = 'https://cdn.gridpilot.com/avatars/default.png';
|
||||
|
||||
return (
|
||||
<Stack
|
||||
as="tr"
|
||||
onClick={onClick}
|
||||
cursor="pointer"
|
||||
transition
|
||||
hoverBg="bg-primary-blue/5"
|
||||
group
|
||||
borderBottom
|
||||
borderColor="border-charcoal-outline/50"
|
||||
>
|
||||
<Stack as="td" px={6} py={4} textAlign="center">
|
||||
<TableRow onClick={onClick} clickable>
|
||||
<TableCell textAlign="center">
|
||||
<Text
|
||||
size="sm"
|
||||
weight="bold"
|
||||
font="mono"
|
||||
color={rank <= 3 ? 'text-warning-amber' : 'text-gray-500'}
|
||||
variant={rank <= 3 ? 'warning' : 'low'}
|
||||
>
|
||||
{rank}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack as="td" px={6} py={4}>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Stack position="relative" h="8" w="8" overflow="hidden" rounded="full" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal">
|
||||
<Image
|
||||
src={avatarUrl || defaultAvatar}
|
||||
alt={name}
|
||||
fill
|
||||
objectFit="cover"
|
||||
/>
|
||||
</Stack>
|
||||
<Avatar
|
||||
src={avatarUrl || undefined}
|
||||
alt={name}
|
||||
size="sm"
|
||||
/>
|
||||
<Text
|
||||
size="sm"
|
||||
weight="semibold"
|
||||
color="text-white"
|
||||
groupHoverTextColor="text-primary-blue"
|
||||
transition
|
||||
variant="high"
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack as="td" px={6} py={4}>
|
||||
<Text size="xs" color="text-gray-400">{nationality}</Text>
|
||||
</Stack>
|
||||
<Stack as="td" px={6} py={4} textAlign="right">
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text size="xs" variant="low">{nationality}</Text>
|
||||
</TableCell>
|
||||
<TableCell textAlign="right">
|
||||
<RatingBadge rating={rating} size="sm" />
|
||||
</Stack>
|
||||
<Stack as="td" px={6} py={4} textAlign="right">
|
||||
<Text size="sm" weight="semibold" font="mono" color="text-performance-green">
|
||||
</TableCell>
|
||||
<TableCell textAlign="right">
|
||||
<Text size="sm" weight="semibold" font="mono" variant="success">
|
||||
{wins}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Trophy, Users } from 'lucide-react';
|
||||
|
||||
interface DriverStat {
|
||||
label: string;
|
||||
value: string | number;
|
||||
color?: string;
|
||||
animate?: boolean;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'telemetry';
|
||||
}
|
||||
|
||||
interface DriversDirectoryHeaderProps {
|
||||
@@ -29,71 +32,53 @@ export function DriversDirectoryHeader({
|
||||
onViewLeaderboard,
|
||||
}: DriversDirectoryHeaderProps) {
|
||||
const stats: DriverStat[] = [
|
||||
{ label: 'drivers', value: totalDrivers, color: 'text-primary-blue' },
|
||||
{ label: 'active', value: activeDrivers, color: 'text-performance-green', animate: true },
|
||||
{ label: 'total wins', value: totalWins.toLocaleString(), color: 'text-warning-amber' },
|
||||
{ label: 'races', value: totalRaces.toLocaleString(), color: 'text-neon-aqua' },
|
||||
{ label: 'drivers', value: totalDrivers, intent: 'primary' },
|
||||
{ label: 'active', value: activeDrivers, intent: 'success' },
|
||||
{ label: 'total wins', value: totalWins.toLocaleString(), intent: 'warning' },
|
||||
{ label: 'races', value: totalRaces.toLocaleString(), intent: 'telemetry' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
as="header"
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
rounded="2xl"
|
||||
border
|
||||
borderColor="border-charcoal-outline/50"
|
||||
bg="bg-gradient-to-br from-iron-gray/80 via-deep-graphite to-iron-gray/60"
|
||||
p={{ base: 8, lg: 10 }}
|
||||
>
|
||||
{/* Background Accents */}
|
||||
<Stack position="absolute" right="-24" top="-24" w="96" h="96" rounded="full" bg="bg-primary-blue/5" blur="3xl" />
|
||||
<Stack position="absolute" bottom="-16" left="-16" w="64" h="64" rounded="full" bg="bg-neon-aqua/5" blur="3xl" />
|
||||
|
||||
<Stack position="relative" display="flex" flexDirection={{ base: 'col', lg: 'row' }} alignItems={{ lg: 'center' }} justifyContent="between" gap={8}>
|
||||
<Stack maxWidth="2xl">
|
||||
<Stack direction="row" align="center" gap={3} mb={4}>
|
||||
<Stack display="flex" h="12" w="12" alignItems="center" justifyContent="center" rounded="xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal" shadow="lg">
|
||||
<Users size={24} color="#198CFF" />
|
||||
<Section variant="dark" padding="md">
|
||||
<Container>
|
||||
<Stack direction="row" align="center" justify="between" gap={8}>
|
||||
<Stack gap={6}>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Card variant="dark">
|
||||
<Icon icon={Users} size={6} intent="primary" />
|
||||
</Card>
|
||||
<Heading level={1}>Drivers</Heading>
|
||||
</Stack>
|
||||
<Heading level={1}>Drivers</Heading>
|
||||
</Stack>
|
||||
|
||||
<Text size="lg" color="text-gray-400" block leading="relaxed">
|
||||
Meet the racers who make every lap count. From rookies to champions, track their journey and see who's dominating the grid.
|
||||
</Text>
|
||||
|
||||
<Text size="lg" variant="low">
|
||||
Meet the racers who make every lap count. From rookies to champions, track their journey and see who's dominating the grid.
|
||||
</Text>
|
||||
|
||||
<Stack display="flex" flexWrap="wrap" gap={6} mt={6}>
|
||||
{stats.map((stat, index) => (
|
||||
<Stack key={index} direction="row" align="center" gap={2}>
|
||||
<Stack
|
||||
w="2"
|
||||
h="2"
|
||||
rounded="full"
|
||||
bg={stat.color?.replace('text-', 'bg-') || 'bg-primary-blue'}
|
||||
animate={stat.animate ? 'pulse' : 'none'}
|
||||
/>
|
||||
<Text size="sm" color="text-gray-400">
|
||||
<Text as="span" weight="semibold" color="text-white">{stat.value}</Text> {stat.label}
|
||||
</Text>
|
||||
</Stack>
|
||||
))}
|
||||
<Stack direction="row" gap={6} wrap>
|
||||
{stats.map((stat, index) => (
|
||||
<Stack key={index} direction="row" align="center" gap={2}>
|
||||
<Text size="sm" variant="low">
|
||||
<Text as="span" weight="semibold" variant="high">{stat.value}</Text> {stat.label}
|
||||
</Text>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack gap={2} align="center">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onViewLeaderboard}
|
||||
icon={<Icon icon={Trophy} size={5} />}
|
||||
>
|
||||
View Leaderboard
|
||||
</Button>
|
||||
<Text size="xs" variant="low" align="center">
|
||||
See full driver rankings
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack gap={2}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onViewLeaderboard}
|
||||
icon={<Trophy size={20} />}
|
||||
>
|
||||
View Leaderboard
|
||||
</Button>
|
||||
<Text size="xs" color="text-gray-500" align="center" block>
|
||||
See full driver rankings
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
interface DriversSearchProps {
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { MedalBadge } from '@/components/leaderboards/MedalBadge';
|
||||
import { mediaConfig } from '@/lib/config/mediaConfig';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { MiniStat } from '@/ui/MiniStat';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Flag, Shield, Star, TrendingUp } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Car, Download, Edit, Trash2 } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
interface PerformanceMetricsProps {
|
||||
|
||||
@@ -5,9 +5,9 @@ import { CircularProgress } from '@/ui/CircularProgress';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { HorizontalBarChart } from '@/ui/HorizontalBarChart';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { GridItem } from '@/ui/primitives/GridItem';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { GridItem } from '@/ui/GridItem';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Activity, BarChart3, Target, TrendingUp } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CountryFlag } from '@/ui/CountryFlag';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { PlaceholderImage } from '@/ui/PlaceholderImage';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
interface ProfileHeaderProps {
|
||||
|
||||
@@ -6,8 +6,8 @@ import { Button } from '@/ui/Button';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Surface } from '@/ui/primitives/Surface';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Calendar, Clock, ExternalLink, Globe, Star, Trophy, UserPlus } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Card } from '@/ui/Card';
|
||||
import { Checkbox } from '@/ui/Checkbox';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Select } from '@/ui/Select';
|
||||
import { TextArea } from '@/ui/TextArea';
|
||||
import { Toggle } from '@/ui/Toggle';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { RankBadge } from '@/components/leaderboards/RankBadge';
|
||||
import { useDriverProfile } from "@/hooks/driver/useDriverProfile";
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { StatCard } from '@/ui/StatCard';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Card, Card as Surface } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Flag, UserPlus, Users } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -3,33 +3,22 @@ import { Badge } from '@/ui/Badge';
|
||||
|
||||
interface RatingBadgeProps {
|
||||
rating: number;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
className?: string;
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
export function RatingBadge({ rating, size = 'md', className = '' }: RatingBadgeProps) {
|
||||
const getColor = (val: number) => {
|
||||
if (val >= 2500) return { variant: 'warning' as const };
|
||||
if (val >= 2000) return { color: 'text-purple-400', bg: 'bg-purple-400/10', borderColor: 'border-purple-400/20' };
|
||||
if (val >= 1500) return { variant: 'primary' as const };
|
||||
if (val >= 1000) return { variant: 'success' as const };
|
||||
return { variant: 'default' as const };
|
||||
export function RatingBadge({ rating, size = 'md' }: RatingBadgeProps) {
|
||||
const getVariant = (val: number): 'warning' | 'primary' | 'success' | 'default' => {
|
||||
if (val >= 2500) return 'warning';
|
||||
if (val >= 2000) return 'primary'; // Simplified
|
||||
if (val >= 1500) return 'primary';
|
||||
if (val >= 1000) return 'success';
|
||||
return 'default';
|
||||
};
|
||||
|
||||
const sizeMap: Record<string, 'xs' | 'sm' | 'md'> = {
|
||||
sm: 'xs',
|
||||
md: 'sm',
|
||||
lg: 'md',
|
||||
};
|
||||
|
||||
const config = getColor(rating);
|
||||
|
||||
return (
|
||||
<Badge
|
||||
{...config}
|
||||
size={sizeMap[size]}
|
||||
className={`font-mono ${className}`}
|
||||
rounded="sm"
|
||||
variant={getVariant(rating)}
|
||||
size={size}
|
||||
>
|
||||
{rating.toLocaleString()}
|
||||
</Badge>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { RatingComponent } from '@/components/drivers/RatingComponent';
|
||||
import { RatingHistoryItem } from '@/components/drivers/RatingHistoryItem';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
interface RatingBreakdownProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { ProgressBar } from '@/ui/ProgressBar';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
interface RatingHistoryItemProps {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Shield } from 'lucide-react';
|
||||
|
||||
interface SafetyRatingBadgeProps {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { BarChart3 } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ChevronRight, LucideIcon, UserPlus } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { Component, ReactNode, ErrorInfo, useState, version } from 'react
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
|
||||
import { DevErrorPanel } from './DevErrorPanel';
|
||||
import { ErrorDisplay } from '@/components/shared/state/ErrorDisplay';
|
||||
import { ErrorDisplay } from '@/ui/ErrorDisplay';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getErrorSeverity, isConnectivityError, isRetryable, parseApiError } fro
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { IconButton } from '@/ui/IconButton';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { IconButton } from '@/ui/IconButton';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import {
|
||||
Activity,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import { ErrorDisplay as UiErrorDisplay } from '@/components/shared/state/ErrorDisplay';
|
||||
import { ErrorDisplay as UiErrorDisplay } from '@/ui/ErrorDisplay';
|
||||
|
||||
interface ErrorDisplayProps {
|
||||
error: ApiError;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { ErrorPageContainer } from '@/ui/ErrorPageContainer';
|
||||
import { AppErrorBoundaryView } from './AppErrorBoundaryView';
|
||||
import { ErrorDetailsBlock } from './ErrorDetailsBlock';
|
||||
@@ -32,13 +33,13 @@ export function ErrorScreen({ error, reset, onHome }: ErrorScreenProps) {
|
||||
description="The application encountered an unexpected state. Our telemetry has logged the incident."
|
||||
>
|
||||
{/* Error Message Summary */}
|
||||
<div style={{ width: '100%', marginBottom: '1.5rem' }}>
|
||||
<Stack fullWidth marginBottom={6}>
|
||||
<Card variant="outline">
|
||||
<Text font="mono" size="sm" variant="warning" block>
|
||||
{error.message || 'Unknown execution error'}
|
||||
</Text>
|
||||
</Card>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
<ErrorRecoveryActions onRetry={reset} onHome={onHome} />
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import { Icon } from '@/ui/Icon';
|
||||
import { EmptyState } from '@/ui/EmptyState';
|
||||
import { Button } from '@/ui/Button';
|
||||
|
||||
import { Stack } from '@/ui/Stack';
|
||||
|
||||
interface FeedItem {
|
||||
id: string;
|
||||
headline: string;
|
||||
@@ -32,7 +34,7 @@ export function ActivityFeed({ items, hasItems }: ActivityFeedProps) {
|
||||
actions={<Icon icon={Activity} size={5} intent="primary" />}
|
||||
/>
|
||||
{hasItems ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||
<Stack gap={4}>
|
||||
{items.slice(0, 5).map((item) => (
|
||||
<ActivityItem
|
||||
key={item.id}
|
||||
@@ -47,7 +49,7 @@ export function ActivityFeed({ items, hasItems }: ActivityFeedProps) {
|
||||
)}
|
||||
</ActivityItem>
|
||||
))}
|
||||
</div>
|
||||
</Stack>
|
||||
) : (
|
||||
<EmptyState
|
||||
icon={Activity}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { UpcomingRacesSidebar } from '@/components/races/UpcomingRacesSidebar';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
|
||||
interface HomeFeatureDescriptionProps {
|
||||
lead: string;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import React from 'react';
|
||||
import { Panel } from '@/ui/Panel';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Section } from '@/ui/Section';
|
||||
@@ -19,7 +19,6 @@ interface HomeFeatureSectionProps {
|
||||
|
||||
/**
|
||||
* HomeFeatureSection - A semantic section highlighting a feature.
|
||||
* Refactored to use semantic HTML and Tailwind.
|
||||
*/
|
||||
export function HomeFeatureSection({
|
||||
heading,
|
||||
@@ -28,18 +27,6 @@ export function HomeFeatureSection({
|
||||
layout,
|
||||
accentColor = 'primary',
|
||||
}: HomeFeatureSectionProps) {
|
||||
const accentBorderColor = {
|
||||
primary: 'border-primary-accent/40',
|
||||
aqua: 'border-telemetry-aqua/40',
|
||||
amber: 'border-warning-amber/40',
|
||||
}[accentColor];
|
||||
|
||||
const accentBgColor = {
|
||||
primary: 'primary-accent',
|
||||
aqua: 'telemetry-aqua',
|
||||
amber: 'warning-amber',
|
||||
}[accentColor];
|
||||
|
||||
const glowColor = ({
|
||||
primary: 'primary',
|
||||
aqua: 'aqua',
|
||||
@@ -47,7 +34,7 @@ export function HomeFeatureSection({
|
||||
}[accentColor] || 'primary') as 'primary' | 'aqua' | 'amber' | 'purple';
|
||||
|
||||
return (
|
||||
<Section variant="dark" py={24} borderBottom borderColor="border-gray" overflow="hidden" position="relative">
|
||||
<Section variant="dark" padding="lg">
|
||||
<Glow
|
||||
color={glowColor}
|
||||
size="lg"
|
||||
@@ -56,31 +43,25 @@ export function HomeFeatureSection({
|
||||
/>
|
||||
|
||||
<Container>
|
||||
<Grid cols={1} lgCols={2} gap={12} alignItems="center">
|
||||
<Grid cols={{ base: 1, lg: 2 }} gap={12}>
|
||||
{/* Text Content */}
|
||||
<Stack gap={8} order={{ lg: layout === 'text-right' ? 2 : 1 }}>
|
||||
<Stack gap={8}>
|
||||
<Stack gap={4}>
|
||||
<Stack w="12" h="1" bg={accentBgColor}>{null}</Stack>
|
||||
<Heading level={2} fontSize={{ base: '3xl', md: '5xl' }} weight="bold" letterSpacing="tighter" lineHeight="none" color="text-white">
|
||||
<Heading level={2} weight="bold">
|
||||
{heading}
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Stack color="text-gray-500" borderLeft borderStyle="solid" borderColor="border-gray/20" pl={6}>
|
||||
<Stack>
|
||||
{description}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Mockup Panel */}
|
||||
<Stack order={{ lg: layout === 'text-right' ? 1 : 2 }}>
|
||||
<Panel padding={1} variant="dark" border={true} position="relative" group overflow="hidden">
|
||||
<Stack bg="graphite-black" minHeight="300px" align="center" justify="center">
|
||||
{mockup}
|
||||
</Stack>
|
||||
{/* Decorative corner accents */}
|
||||
<Stack position="absolute" top="0" left="0" w="4" h="4" borderTop borderLeft borderColor={accentBorderColor} opacity={0.4}>{null}</Stack>
|
||||
<Stack position="absolute" bottom="0" right="0" w="4" h="4" borderBottom borderRight borderColor={accentBorderColor} opacity={0.4}>{null}</Stack>
|
||||
</Panel>
|
||||
</Stack>
|
||||
<Panel variant="dark">
|
||||
<Stack align="center" justify="center">
|
||||
{mockup}
|
||||
</Stack>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
@@ -8,8 +8,8 @@ import { DiscordIcon } from '@/ui/icons/DiscordIcon';
|
||||
import { Code, Lightbulb, LucideIcon, MessageSquare, Users } from 'lucide-react';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { Container } from '@/ui/Container';
|
||||
@@ -18,71 +18,59 @@ export function HomeFooterCTA() {
|
||||
const discordUrl = process.env.NEXT_PUBLIC_DISCORD_URL || '#';
|
||||
|
||||
return (
|
||||
<Section variant="dark" py={32} borderBottom borderColor="border-gray/50" overflow="hidden" position="relative">
|
||||
<Section variant="dark" padding="lg">
|
||||
<Glow color="primary" size="xl" position="center" opacity={0.05} />
|
||||
|
||||
<Container>
|
||||
<Card position="relative" overflow="hidden" variant="outline" p={{ base: 8, md: 12 }} className="bg-panel-gray/40">
|
||||
{/* Discord brand accent */}
|
||||
<Stack position="absolute" top={0} left={0} right={0} h="1" bg="primary-accent">{null}</Stack>
|
||||
|
||||
<Stack align="center" gap={12} center>
|
||||
<Card variant="outline">
|
||||
<Stack align="center" gap={12}>
|
||||
{/* Header */}
|
||||
<Stack align="center" gap={6}>
|
||||
<Stack position="relative" align="center" justify="center" w={{ base: '16', md: '20' }} h={{ base: '16', md: '20' }} bg="primary-accent/10" border borderColor="primary-accent/30">
|
||||
<DiscordIcon color="text-primary-accent" size={40} />
|
||||
<Stack position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent">{null}</Stack>
|
||||
<Stack position="absolute" bottom="-1px" right="-1px" w="2" h="2" borderBottom borderRight borderColor="primary-accent">{null}</Stack>
|
||||
</Stack>
|
||||
<DiscordIcon size={40} />
|
||||
|
||||
<Stack gap={4} align="center">
|
||||
<Heading level={2} weight="bold" color="text-white" fontSize={{ base: '2xl', md: '4xl' }} letterSpacing="tight">
|
||||
<Heading level={2} weight="bold" align="center">
|
||||
Join the Grid on Discord
|
||||
</Heading>
|
||||
<Stack w="16" h="1" bg="primary-accent">{null}</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Personal message */}
|
||||
<Stack maxWidth="2xl" mx="auto" align="center">
|
||||
<Stack gap={6}>
|
||||
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed" textAlign="center">
|
||||
GridPilot is a <Text as="span" color="text-white" weight="bold">solo developer project</Text> built for the community.
|
||||
</Text>
|
||||
<Text size="base" color="text-gray-400" weight="normal" leading="relaxed" textAlign="center">
|
||||
We are in early alpha. Join us to help shape the future of motorsport infrastructure. Your feedback directly influences the roadmap.
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack align="center" gap={6}>
|
||||
<Text size="lg" variant="high" weight="medium" align="center">
|
||||
GridPilot is a <Text as="span" variant="high" weight="bold">solo developer project</Text> built for the community.
|
||||
</Text>
|
||||
<Text size="base" variant="low" align="center">
|
||||
We are in early alpha. Join us to help shape the future of motorsport infrastructure. Your feedback directly influences the roadmap.
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
{/* Benefits grid */}
|
||||
<Stack maxWidth="4xl" mx="auto" fullWidth>
|
||||
<Grid cols={1} mdCols={2} gap={6}>
|
||||
<BenefitItem
|
||||
icon={MessageSquare}
|
||||
title="Share your pain points"
|
||||
description="Tell us what frustrates you about league racing today."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Lightbulb}
|
||||
title="Shape the product"
|
||||
description="Your ideas directly influence our roadmap."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Users}
|
||||
title="Connect with racers"
|
||||
description="Join a community of like-minded competitive drivers."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Code}
|
||||
title="Early Access"
|
||||
description="Test new features before they go public."
|
||||
/>
|
||||
</Grid>
|
||||
</Stack>
|
||||
<Grid cols={{ base: 1, md: 2 }} gap={6}>
|
||||
<BenefitItem
|
||||
icon={MessageSquare}
|
||||
title="Share your pain points"
|
||||
description="Tell us what frustrates you about league racing today."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Lightbulb}
|
||||
title="Shape the product"
|
||||
description="Your ideas directly influence our roadmap."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Users}
|
||||
title="Connect with racers"
|
||||
description="Join a community of like-minded competitive drivers."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Code}
|
||||
title="Early Access"
|
||||
description="Test new features before they go public."
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* CTA Button */}
|
||||
<Stack gap={6} pt={4} align="center">
|
||||
<Stack gap={6} align="center">
|
||||
<Button
|
||||
as="a"
|
||||
href={discordUrl}
|
||||
@@ -90,19 +78,14 @@ export function HomeFooterCTA() {
|
||||
rel="noopener noreferrer"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
px={16}
|
||||
py={4}
|
||||
h="auto"
|
||||
icon={<DiscordIcon size={24} />}
|
||||
>
|
||||
Join Discord
|
||||
</Button>
|
||||
|
||||
<Stack border borderStyle="dashed" borderColor="primary-accent/50" px={4} py={1}>
|
||||
<Text size="xs" color="text-primary-accent" weight="bold" font="mono" uppercase letterSpacing="widest">
|
||||
Early Alpha Access Available
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text size="xs" variant="primary" weight="bold" font="mono" uppercase>
|
||||
Early Alpha Access Available
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
@@ -113,14 +96,14 @@ export function HomeFooterCTA() {
|
||||
|
||||
function BenefitItem({ icon, title, description }: { icon: LucideIcon, title: string, description: string }) {
|
||||
return (
|
||||
<Stack direction="row" align="start" gap={5} p={6} bg="panel-gray/20" border borderColor="border-gray" className="transition-all hover:border-primary-accent/30 group">
|
||||
<Stack align="center" justify="center" flexShrink={0} w="10" h="10" bg="primary-accent/5" border borderColor="border-gray/50" className="transition-all group-hover:border-primary-accent/30">
|
||||
<Icon icon={icon} size={5} color="text-primary-accent" />
|
||||
<Card variant="dark">
|
||||
<Stack align="start" gap={5}>
|
||||
<Icon icon={icon} size={5} intent="primary" />
|
||||
<Stack gap={2}>
|
||||
<Text size="base" weight="bold" variant="high">{title}</Text>
|
||||
<Text size="sm" variant="low">{description}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack gap={2}>
|
||||
<Text size="base" weight="bold" color="text-white" letterSpacing="wide">{title}</Text>
|
||||
<Text size="sm" color="text-gray-400" leading="relaxed">{description}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import { Container } from '@/ui/Container';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { ButtonGroup } from '@/ui/ButtonGroup';
|
||||
|
||||
interface HomeHeaderProps {
|
||||
title: string;
|
||||
@@ -23,7 +25,6 @@ interface HomeHeaderProps {
|
||||
|
||||
/**
|
||||
* HomeHeader - Semantic hero section for the landing page.
|
||||
* Follows "Precision Racing Minimal" theme.
|
||||
*/
|
||||
export function HomeHeader({
|
||||
title,
|
||||
@@ -33,43 +34,29 @@ export function HomeHeader({
|
||||
secondaryAction,
|
||||
}: HomeHeaderProps) {
|
||||
return (
|
||||
<Stack as="header" position="relative" overflow="hidden" bg="graphite-black" py={{ base: 24, lg: 32 }} borderBottom borderColor="border-gray">
|
||||
<Section variant="dark" padding="lg">
|
||||
<Glow color="primary" size="xl" position="top-right" opacity={0.1} />
|
||||
|
||||
<Container>
|
||||
<Stack maxWidth="4xl" fullWidth>
|
||||
<Stack direction="row" align="center" gap={3} borderLeft borderStyle="solid" borderWidth="2px" borderColor="primary-accent" bg="primary-accent/5" px={4} py={1} mb={8}>
|
||||
<Text size="xs" weight="bold" uppercase letterSpacing="0.3em" color="text-primary-accent">
|
||||
{subtitle}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack gap={8}>
|
||||
<Text size="xs" weight="bold" uppercase variant="primary">
|
||||
{subtitle}
|
||||
</Text>
|
||||
|
||||
<Heading
|
||||
level={1}
|
||||
fontSize={{ base: '5xl', md: '7xl', lg: '8xl' }}
|
||||
weight="bold"
|
||||
color="text-white"
|
||||
letterSpacing="tighter"
|
||||
lineHeight="0.9"
|
||||
mb={8}
|
||||
>
|
||||
<Heading level={1} weight="bold">
|
||||
{title}
|
||||
</Heading>
|
||||
|
||||
<Stack borderLeft borderStyle="solid" borderColor="border-gray" pl={8} mb={12} maxWidth="2xl">
|
||||
<Text size="lg" color="text-gray-400" leading="relaxed" opacity={0.8}>
|
||||
{description}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text size="lg" variant="low">
|
||||
{description}
|
||||
</Text>
|
||||
|
||||
<Stack direction={{ base: 'col', md: 'row' }} gap={4}>
|
||||
<ButtonGroup gap={4}>
|
||||
<Button
|
||||
as="a"
|
||||
href={primaryAction.href}
|
||||
variant="primary"
|
||||
h="14"
|
||||
px={12}
|
||||
fontSize="xs"
|
||||
size="lg"
|
||||
>
|
||||
{primaryAction.label}
|
||||
</Button>
|
||||
@@ -77,18 +64,13 @@ export function HomeHeader({
|
||||
as="a"
|
||||
href={secondaryAction.href}
|
||||
variant="secondary"
|
||||
h="14"
|
||||
px={12}
|
||||
fontSize="xs"
|
||||
bg="transparent"
|
||||
borderColor="border-gray"
|
||||
hoverBorderColor="primary-accent/50"
|
||||
size="lg"
|
||||
>
|
||||
{secondaryAction.label}
|
||||
</Button>
|
||||
</Stack>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,58 +4,40 @@ import React from 'react';
|
||||
import { MetricCard } from '@/ui/MetricCard';
|
||||
import { Activity, Users, Trophy, Calendar } from 'lucide-react';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
|
||||
/**
|
||||
* HomeStatsStrip - A thin strip showing some status or quick info.
|
||||
* Part of the "Telemetry-workspace" feel.
|
||||
* Refactored to use semantic HTML and Tailwind.
|
||||
*/
|
||||
export function HomeStatsStrip() {
|
||||
return (
|
||||
<Stack bg="graphite-black" borderBottom borderTop borderColor="border-gray/30" py={0}>
|
||||
<Container>
|
||||
<Grid cols={2} mdCols={4} gap={0} borderLeft borderRight borderColor="border-gray/30">
|
||||
<MetricCard
|
||||
label="Active Drivers"
|
||||
value="1,284"
|
||||
icon={Users}
|
||||
trend={{ value: 12, isPositive: true }}
|
||||
border={false}
|
||||
bg="transparent"
|
||||
/>
|
||||
<Stack borderLeft borderColor="border-gray/30">
|
||||
<MetricCard
|
||||
label="Live Sessions"
|
||||
value="42"
|
||||
icon={Activity}
|
||||
color="text-telemetry-aqua"
|
||||
border={false}
|
||||
bg="transparent"
|
||||
/>
|
||||
</Stack>
|
||||
<Stack borderLeft borderColor="border-gray/30">
|
||||
<MetricCard
|
||||
label="Total Races"
|
||||
value="15,402"
|
||||
icon={Trophy}
|
||||
color="text-warning-amber"
|
||||
border={false}
|
||||
bg="transparent"
|
||||
/>
|
||||
</Stack>
|
||||
<Stack borderLeft borderColor="border-gray/30">
|
||||
<MetricCard
|
||||
label="Next Event"
|
||||
value="14:00"
|
||||
icon={Calendar}
|
||||
border={false}
|
||||
bg="transparent"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Container>
|
||||
</Stack>
|
||||
<Container>
|
||||
<Grid cols={{ base: 2, md: 4 }} gap={4}>
|
||||
<MetricCard
|
||||
label="Active Drivers"
|
||||
value="1,284"
|
||||
icon={Users}
|
||||
trend={{ value: 12, isPositive: true }}
|
||||
/>
|
||||
<MetricCard
|
||||
label="Live Sessions"
|
||||
value="42"
|
||||
icon={Activity}
|
||||
intent="telemetry"
|
||||
/>
|
||||
<MetricCard
|
||||
label="Total Races"
|
||||
value="15,402"
|
||||
icon={Trophy}
|
||||
intent="warning"
|
||||
/>
|
||||
<MetricCard
|
||||
label="Next Event"
|
||||
value="14:00"
|
||||
icon={Calendar}
|
||||
/>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import { LeagueCard } from '@/components/leagues/LeagueCard';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
|
||||
interface League {
|
||||
id: string;
|
||||
|
||||
@@ -4,13 +4,11 @@ import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { ButtonGroup } from '@/ui/ButtonGroup';
|
||||
import { Plus, Search, Shield, Users } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* QuickLinksPanel - Semantic quick actions bar.
|
||||
* Replaces HomeQuickActions with a more semantic implementation.
|
||||
*/
|
||||
export function QuickLinksPanel() {
|
||||
const links = [
|
||||
@@ -21,34 +19,20 @@ export function QuickLinksPanel() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack as="nav" bg="panel-gray/50" py={8} borderBottom borderColor="border-gray/30">
|
||||
<Container>
|
||||
<Stack direction="row" wrap justify="center" gap={4}>
|
||||
{links.map((link) => (
|
||||
<Button
|
||||
key={link.label}
|
||||
as="a"
|
||||
href={link.href}
|
||||
variant="secondary"
|
||||
px={6}
|
||||
bg="graphite-black"
|
||||
borderColor="border-gray/50"
|
||||
className="flex items-center gap-3 transition-all hover:border-primary-accent/50 group"
|
||||
>
|
||||
<Icon
|
||||
icon={link.icon}
|
||||
size={4}
|
||||
color="text-gray-500"
|
||||
groupHoverTextColor="primary-accent"
|
||||
transition
|
||||
/>
|
||||
<Text size="xs" weight="bold" uppercase letterSpacing="widest">
|
||||
{link.label}
|
||||
</Text>
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
</Container>
|
||||
</Stack>
|
||||
<Container>
|
||||
<ButtonGroup alignment="center" gap={4} marginTop={8}>
|
||||
{links.map((link) => (
|
||||
<Button
|
||||
key={link.label}
|
||||
as="a"
|
||||
href={link.href}
|
||||
variant="secondary"
|
||||
icon={<Icon icon={link.icon} size={4} intent="low" />}
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Panel } from '@/ui/Panel';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
interface Race {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import { TeamCard } from '@/components/teams/TeamCard';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
|
||||
interface Team {
|
||||
id: string;
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { useParallax } from "@/hooks/useScrollProgress";
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { useRef } from 'react';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { Panel } from '@/ui/Panel';
|
||||
|
||||
interface AlternatingSectionProps {
|
||||
heading: string;
|
||||
description: string | React.ReactNode;
|
||||
mockup: React.ReactNode;
|
||||
layout: 'text-left' | 'text-right';
|
||||
backgroundImage?: string;
|
||||
backgroundVideo?: string;
|
||||
}
|
||||
|
||||
export function AlternatingSection({
|
||||
@@ -19,112 +18,49 @@ export function AlternatingSection({
|
||||
description,
|
||||
mockup,
|
||||
layout,
|
||||
backgroundImage,
|
||||
backgroundVideo
|
||||
}: AlternatingSectionProps) {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
const bgParallax = useParallax(sectionRef, 0.1);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
as="section"
|
||||
ref={sectionRef}
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
bg="graphite-black"
|
||||
py={{ base: 20, md: 32 }}
|
||||
className="border-b border-border-gray"
|
||||
>
|
||||
{backgroundVideo && (
|
||||
<Stack
|
||||
position="absolute"
|
||||
inset="0"
|
||||
fullWidth
|
||||
fullHeight
|
||||
overflow="hidden"
|
||||
>
|
||||
<Stack
|
||||
as="video"
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
fullWidth
|
||||
fullHeight
|
||||
objectFit="cover"
|
||||
opacity={0.1}
|
||||
>
|
||||
<Stack as="source" src={backgroundVideo} type="video/mp4" />
|
||||
</Stack>
|
||||
{/* Dark overlay to ensure readability */}
|
||||
<Stack position="absolute" inset="0" bg="linear-gradient(to bottom, #0C0D0F, transparent, #0C0D0F)" />
|
||||
</Stack>
|
||||
)}
|
||||
{backgroundImage && !backgroundVideo && (
|
||||
<Stack
|
||||
position="absolute"
|
||||
inset="0"
|
||||
fullWidth
|
||||
fullHeight
|
||||
overflow="hidden"
|
||||
>
|
||||
<Stack
|
||||
position="absolute"
|
||||
inset="0"
|
||||
bg={`url(${backgroundImage})`}
|
||||
backgroundSize="cover"
|
||||
backgroundPosition="center"
|
||||
opacity={0.1}
|
||||
style={{ transform: `translateY(${bgParallax * 0.3}px)` }}
|
||||
/>
|
||||
{/* Dark overlay to ensure readability */}
|
||||
<Stack position="absolute" inset="0" bg="linear-gradient(to bottom, #0C0D0F, transparent, #0C0D0F)" />
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Container size="lg" position="relative" zIndex={10}>
|
||||
<Stack display="grid" gridCols={{ base: 1, lg: 2 }} gap={{ base: 12, lg: 24 }} alignItems="center">
|
||||
{/* Text Content */}
|
||||
<Stack
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
gap={8}
|
||||
order={{ lg: layout === 'text-right' ? 2 : 1 }}
|
||||
>
|
||||
<Stack gap={4}>
|
||||
<Stack w="8" h="1" bg="primary-accent" />
|
||||
<Heading level={2} fontSize={{ base: '3xl', md: '5xl' }} weight="bold" className="tracking-tighter uppercase leading-none">
|
||||
{heading}
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Stack className="text-gray-500 border-l border-border-gray/20 pl-6">
|
||||
{typeof description === 'string' ? (
|
||||
<Text size="lg" leading="relaxed" weight="normal">{description}</Text>
|
||||
) : (
|
||||
description
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Mockup */}
|
||||
<Stack
|
||||
position="relative"
|
||||
order={{ lg: layout === 'text-right' ? 1 : 2 }}
|
||||
className="bg-panel-gray/20 border border-border-gray/30 rounded-none p-1 shadow-2xl group"
|
||||
>
|
||||
<Stack
|
||||
fullWidth
|
||||
minHeight={{ base: '240px', md: '380px' }}
|
||||
className="overflow-hidden rounded-none border border-border-gray/20 bg-graphite-black"
|
||||
>
|
||||
{mockup}
|
||||
</Stack>
|
||||
{/* Decorative corner accents */}
|
||||
<Stack position="absolute" top="-1px" left="-1px" w="3" h="3" borderTop borderLeft borderColor="primary-accent/40" />
|
||||
<Stack position="absolute" bottom="-1px" right="-1px" w="3" h="3" borderBottom borderRight borderColor="primary-accent/40" />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Container>
|
||||
const textContent = (
|
||||
<Stack gap={8}>
|
||||
<Stack gap={4}>
|
||||
<Heading level={2} weight="bold">
|
||||
{heading}
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Stack>
|
||||
{typeof description === 'string' ? (
|
||||
<Text size="lg" variant="low">{description}</Text>
|
||||
) : (
|
||||
description
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const mockupContent = (
|
||||
<Panel variant="dark">
|
||||
<Stack align="center" justify="center">
|
||||
{mockup}
|
||||
</Stack>
|
||||
</Panel>
|
||||
);
|
||||
|
||||
return (
|
||||
<Section variant="dark" padding="lg">
|
||||
<Container size="lg">
|
||||
<Grid cols={{ base: 1, lg: 2 }} gap={12}>
|
||||
{layout === 'text-left' ? (
|
||||
<>
|
||||
{textContent}
|
||||
{mockupContent}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{mockupContent}
|
||||
{textContent}
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Surface } from '@/ui/primitives/Surface';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { StandingsTableMockup } from '@/components/mockups/StandingsTableMockup'
|
||||
import { TeamCompetitionMockup } from '@/components/mockups/TeamCompetitionMockup';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user