From 61b5cf3b648f7884d9c1d93ee388a63c5421208f Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 19 Jan 2026 18:01:30 +0100 Subject: [PATCH] website refactor --- apps/website/app/dashboard/page.tsx | 6 +- apps/website/app/leagues/create/page.tsx | 30 +- apps/website/app/page.tsx | 7 +- apps/website/app/sponsor/settings/page.tsx | 2 +- apps/website/app/teams/create/page.tsx | 38 +- .../client-wrapper/CreateLeagueWizard.tsx | 23 +- .../client-wrapper/CreateTeamPageClient.tsx | 42 ++ .../client-wrapper/DashboardPageClient.tsx | 26 ++ .../website/client-wrapper/HomePageClient.tsx | 20 + apps/website/components/AppWrapper.tsx | 2 +- .../components/admin/AdminHeaderPanel.tsx | 2 +- apps/website/components/app/AppFooter.tsx | 78 +++- apps/website/components/auth/AuthCard.tsx | 5 +- .../components/auth/AuthFooterLinks.tsx | 10 +- apps/website/components/auth/AuthForm.tsx | 11 +- .../components/auth/AuthWorkflowMockup.tsx | 2 +- apps/website/components/dev/DevToolbar.tsx | 2 +- .../components/drivers/DriverEntryRow.tsx | 27 +- .../components/drivers/DriverHeaderPanel.tsx | 22 +- .../components/drivers/DriverSearchBar.tsx | 6 +- .../components/drivers/DriverTable.tsx | 14 +- .../components/drivers/DriverTableRow.tsx | 7 +- .../drivers/DriversDirectoryHeader.tsx | 28 +- .../website/components/drivers/LiveryCard.tsx | 6 +- .../drivers/PerformanceOverview.tsx | 34 +- .../components/drivers/ProfileHero.tsx | 70 +-- .../components/drivers/SkillDistribution.tsx | 36 +- .../components/errors/ApiErrorBoundary.tsx | 4 +- .../errors/EnhancedErrorBoundary.tsx | 4 +- .../components/errors/ErrorDetails.tsx | 2 +- .../components/errors/ErrorDetailsBlock.tsx | 2 +- .../components/errors/ErrorDisplay.tsx | 2 +- .../components/errors/NotFoundScreen.tsx | 62 +-- apps/website/components/home/FAQSection.tsx | 57 +++ .../home/HomeFeatureDescription.tsx | 45 +- .../components/home/HomeFeatureSection.tsx | 52 +-- .../website/components/home/HomeFooterCTA.tsx | 131 ++---- apps/website/components/home/HomeHeader.tsx | 58 +-- .../components/home/HomeStatsStrip.tsx | 60 ++- .../components/home/LeagueSummaryPanel.tsx | 47 +- .../components/home/RecentRacesPanel.tsx | 52 +-- .../components/home/TeamSummaryPanel.tsx | 47 +- .../components/landing/BenefitCard.tsx | 29 +- .../components/landing/DiscoverySection.tsx | 22 +- apps/website/components/landing/FAQ.tsx | 111 +---- .../components/landing/FeatureGrid.tsx | 62 +-- .../components/landing/LandingHero.tsx | 33 +- apps/website/components/layout/AuthedNav.tsx | 1 + apps/website/components/layout/PublicNav.tsx | 1 + .../components/leagues/EndRaceModal.tsx | 2 +- .../components/leagues/JoinLeagueButton.tsx | 2 +- .../components/leagues/JoinRequestItem.tsx | 6 +- .../components/leagues/LeagueDropSection.tsx | 2 +- .../components/leagues/ReviewProtestModal.tsx | 2 +- .../components/media/MediaViewerModal.tsx | 2 +- .../components/mockups/MockupStack.tsx | 67 ++- .../mockups/ProtestWorkflowMockup.tsx | 40 +- .../mockups}/WorkflowMockup.tsx | 0 .../notifications/ModalNotification.tsx | 2 +- .../components/onboarding/OnboardingShell.tsx | 25 +- .../components/races/FileProtestModal.tsx | 2 +- .../components/races/RaceFilterModal.tsx | 2 +- .../{ui => components/shared}/Accordion.tsx | 22 +- .../shared}/ConfirmDialog.tsx | 2 +- .../components/shared/CountrySelect.tsx | 122 +++--- .../shared}/DevErrorPanel.tsx | 0 .../shared}/ErrorDisplay.tsx | 0 .../{ui => components/shared}/InfoFlyout.tsx | 10 +- .../{ui => components/shared}/Modal.tsx | 12 +- .../shared}/ProgressLine.tsx | 0 .../shared}/ThemeProvider.tsx | 4 +- .../components/shared/UIComponents.tsx | 4 +- .../shared}/UploadDropzone.tsx | 3 +- .../components/shared/state/PageWrapper.tsx | 2 +- .../shared/state/StateContainer.tsx | 2 +- .../sponsors/SponsorWorkflowMockup.tsx | 2 +- .../components/teams/TeamsDirectory.tsx | 47 +- .../display-objects/LeagueRoleDisplay.test.ts | 8 - .../LeagueWizardValidationMessages.test.ts | 8 - .../templates/AdminDashboardTemplate.tsx | 129 +++--- .../templates/CreateLeagueWizardTemplate.tsx | 407 +++++++----------- apps/website/templates/DashboardTemplate.tsx | 119 ++--- apps/website/templates/DriversTemplate.tsx | 12 +- apps/website/templates/HomeTemplate.tsx | 40 +- .../templates/LeagueAdminScheduleTemplate.tsx | 10 +- .../templates/LeagueSettingsTemplate.tsx | 34 +- .../templates/LeagueWalletTemplate.tsx | 56 ++- apps/website/templates/LeaguesTemplate.tsx | 139 +++--- .../templates/ProfileLiveryUploadTemplate.tsx | 2 +- apps/website/templates/ProfileTemplate.tsx | 18 +- apps/website/templates/RacesTemplate.tsx | 142 +++--- .../website/templates/RosterAdminTemplate.tsx | 32 +- .../templates/SponsorDashboardTemplate.tsx | 79 ++-- .../templates/TeamLeaderboardTemplate.tsx | 161 +++---- apps/website/templates/TeamsTemplate.tsx | 5 +- .../templates/auth/ForgotPasswordTemplate.tsx | 49 +-- apps/website/templates/auth/LoginTemplate.tsx | 77 ++-- .../templates/auth/ResetPasswordTemplate.tsx | 47 +- .../website/templates/auth/SignupTemplate.tsx | 87 ++-- .../templates/layout/GlobalFooterTemplate.tsx | 107 +++-- .../layout/GlobalSidebarTemplate.tsx | 33 +- .../templates/layout/RootAppShellTemplate.tsx | 4 +- .../onboarding/OnboardingTemplate.tsx | 6 +- apps/website/ui/Box.tsx | 3 + apps/website/ui/BrandMark.tsx | 9 +- apps/website/ui/CardStack.tsx | 18 + apps/website/ui/Center.tsx | 25 ++ apps/website/ui/ContentViewport.tsx | 2 +- apps/website/ui/ControlBar.tsx | 3 + apps/website/ui/DiscordCTA.tsx | 99 +++++ apps/website/ui/DiscoverySection.tsx | 57 +++ apps/website/ui/FeatureList.tsx | 24 ++ apps/website/ui/FeatureQuote.tsx | 50 +++ apps/website/ui/FeatureSection.tsx | 73 ++++ apps/website/ui/Footer.tsx | 53 --- apps/website/ui/Form.tsx | 29 ++ apps/website/ui/Image.tsx | 21 +- apps/website/ui/LandingHero.tsx | 75 ++++ apps/website/ui/PasswordField.tsx | 27 +- apps/website/ui/StatsStrip.tsx | 42 ++ 120 files changed, 2226 insertions(+), 2021 deletions(-) create mode 100644 apps/website/client-wrapper/CreateTeamPageClient.tsx create mode 100644 apps/website/client-wrapper/DashboardPageClient.tsx create mode 100644 apps/website/client-wrapper/HomePageClient.tsx create mode 100644 apps/website/components/home/FAQSection.tsx rename apps/website/{ui => components/mockups}/WorkflowMockup.tsx (100%) rename apps/website/{ui => components/shared}/Accordion.tsx (75%) rename apps/website/{ui => components/shared}/ConfirmDialog.tsx (95%) rename apps/website/{ui => components/shared}/DevErrorPanel.tsx (100%) rename apps/website/{ui => components/shared}/ErrorDisplay.tsx (100%) rename apps/website/{ui => components/shared}/InfoFlyout.tsx (91%) rename apps/website/{ui => components/shared}/Modal.tsx (93%) rename apps/website/{ui => components/shared}/ProgressLine.tsx (100%) rename apps/website/{ui/theme => components/shared}/ThemeProvider.tsx (88%) rename apps/website/{ui => components/shared}/UploadDropzone.tsx (99%) delete mode 100644 apps/website/lib/display-objects/LeagueRoleDisplay.test.ts delete mode 100644 apps/website/lib/display-objects/LeagueWizardValidationMessages.test.ts create mode 100644 apps/website/ui/CardStack.tsx create mode 100644 apps/website/ui/Center.tsx create mode 100644 apps/website/ui/DiscordCTA.tsx create mode 100644 apps/website/ui/DiscoverySection.tsx create mode 100644 apps/website/ui/FeatureList.tsx create mode 100644 apps/website/ui/FeatureQuote.tsx create mode 100644 apps/website/ui/FeatureSection.tsx delete mode 100644 apps/website/ui/Footer.tsx create mode 100644 apps/website/ui/Form.tsx create mode 100644 apps/website/ui/LandingHero.tsx create mode 100644 apps/website/ui/StatsStrip.tsx diff --git a/apps/website/app/dashboard/page.tsx b/apps/website/app/dashboard/page.tsx index d262c3285..8c298baf2 100644 --- a/apps/website/app/dashboard/page.tsx +++ b/apps/website/app/dashboard/page.tsx @@ -1,6 +1,6 @@ import { notFound, redirect } from 'next/navigation'; import { DashboardPageQuery } from '@/lib/page-queries/DashboardPageQuery'; -import { DashboardTemplate } from '@/templates/DashboardTemplate'; +import { DashboardPageClient } from '@/client-wrapper/DashboardPageClient'; import { logger } from '@/lib/infrastructure/logging/logger'; export default async function DashboardPage() { @@ -23,5 +23,5 @@ export default async function DashboardPage() { // Success const viewData = result.unwrap(); - return ; -} \ No newline at end of file + return ; +} diff --git a/apps/website/app/leagues/create/page.tsx b/apps/website/app/leagues/create/page.tsx index 72a1af1bd..296e91570 100644 --- a/apps/website/app/leagues/create/page.tsx +++ b/apps/website/app/leagues/create/page.tsx @@ -1,7 +1,3 @@ -'use client'; - -import React from 'react'; -import { useRouter, useSearchParams } from 'next/navigation'; import { CreateLeagueWizard } from '@/client-wrapper/CreateLeagueWizard'; import { Section } from '@/ui/Section'; import { Container } from '@/ui/Container'; @@ -10,11 +6,13 @@ import { SearchParamBuilder } from '@/lib/routing/search-params/SearchParamBuild type StepName = 'basics' | 'visibility' | 'structure' | 'schedule' | 'scoring' | 'stewarding' | 'review'; -export default function CreateLeaguePage() { - const router = useRouter(); - const searchParams = useSearchParams(); +interface CreateLeaguePageProps { + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +} - const wizardParams = SearchParamParser.parseWizard(searchParams as unknown as URLSearchParams).unwrap(); +export default async function CreateLeaguePage({ searchParams }: CreateLeaguePageProps) { + const resolvedParams = await searchParams; + const wizardParams = SearchParamParser.parseWizard(resolvedParams as any).unwrap(); const rawStep = wizardParams.step; let currentStepName: StepName = 'basics'; @@ -28,23 +26,11 @@ export default function CreateLeaguePage() { currentStepName = rawStep; } - const handleStepChange = (stepName: StepName) => { - const builder = new SearchParamBuilder(); - // Copy existing params if needed, but here we just want to set the step - if (searchParams) { - searchParams.forEach((value, key) => { - if (key !== 'step') builder.set(key, value); - }); - } - builder.step(stepName); - router.push(`/leagues/create${builder.build()}`); - }; - return (
- +
); -} \ No newline at end of file +} diff --git a/apps/website/app/page.tsx b/apps/website/app/page.tsx index c56c8188e..e144db2eb 100644 --- a/apps/website/app/page.tsx +++ b/apps/website/app/page.tsx @@ -1,5 +1,4 @@ -import { PageWrapper } from '@/components/shared/state/PageWrapper'; -import { HomeTemplate, type HomeViewData } from '@/templates/HomeTemplate'; +import { HomePageClient } from '@/client-wrapper/HomePageClient'; import { PageDataFetcher } from '@/lib/page/PageDataFetcher'; import { HomePageQuery } from '@/lib/page-queries/HomePageQuery'; import { notFound, redirect } from 'next/navigation'; @@ -19,7 +18,5 @@ export default async function Page() { notFound(); } - const Template = ({ viewData }: { viewData: HomeViewData }) => ; - - return ; + return ; } diff --git a/apps/website/app/sponsor/settings/page.tsx b/apps/website/app/sponsor/settings/page.tsx index 20edcf390..f601a450f 100644 --- a/apps/website/app/sponsor/settings/page.tsx +++ b/apps/website/app/sponsor/settings/page.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { SponsorSettingsTemplate } from '@/templates/SponsorSettingsTemplate'; import { logoutAction } from '@/app/actions/logoutAction'; -import { ConfirmDialog } from '@/ui/ConfirmDialog'; +import { ConfirmDialog } from '@/components/shared/ConfirmDialog'; import { useRouter } from 'next/navigation'; import { routes } from '@/lib/routing/RouteConfig'; import { logger } from '@/lib/infrastructure/logging/logger'; diff --git a/apps/website/app/teams/create/page.tsx b/apps/website/app/teams/create/page.tsx index b79967070..6b25ece8e 100644 --- a/apps/website/app/teams/create/page.tsx +++ b/apps/website/app/teams/create/page.tsx @@ -1,37 +1,5 @@ -'use client'; +import { CreateTeamPageClient } from '@/client-wrapper/CreateTeamPageClient'; -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/Stack'; -import { Section } from '@/ui/Section'; -import { useRouter } from 'next/navigation'; - -export default function CreateTeamPage() { - const router = useRouter(); - - const handleNavigate = (teamId: string) => { - router.push(routes.team.detail(teamId)); - }; - - const handleCancel = () => { - router.back(); - }; - - return ( -
- - - - Create a Team - - - - -
- ); +export default async function CreateTeamPage() { + return ; } diff --git a/apps/website/client-wrapper/CreateLeagueWizard.tsx b/apps/website/client-wrapper/CreateLeagueWizard.tsx index 7ef2b437d..d8d79f7d9 100644 --- a/apps/website/client-wrapper/CreateLeagueWizard.tsx +++ b/apps/website/client-wrapper/CreateLeagueWizard.tsx @@ -12,6 +12,7 @@ import type { Weekday } from '@/lib/types/Weekday'; import type { WizardErrors } from '@/lib/types/WizardErrors'; import type { LeagueScoringPresetViewModel } from '@/lib/view-models/LeagueScoringPresetViewModel'; import { CreateLeagueWizardTemplate, Step } from '@/templates/CreateLeagueWizardTemplate'; +import { SearchParamBuilder } from '@/lib/routing/search-params/SearchParamBuilder'; import { Award, Calendar, @@ -91,7 +92,7 @@ type LeagueWizardFormModel = LeagueConfigFormModel & { interface CreateLeagueWizardProps { stepName: StepName; - onStepChange: (stepName: StepName) => void; + onStepChange?: (stepName: StepName) => void; } function stepNameToStep(stepName: StepName): Step { @@ -200,6 +201,16 @@ export function CreateLeagueWizard({ stepName, onStepChange }: CreateLeagueWizar const router = useRouter(); const { session } = useAuth(); + const handleStepChange = useCallback((newStepName: StepName) => { + if (onStepChange) { + onStepChange(newStepName); + } else { + const builder = new SearchParamBuilder(); + builder.step(newStepName); + router.push(`/leagues/create${builder.build()}`); + } + }, [onStepChange, router]); + const step = stepNameToStep(stepName); const [loading, setLoading] = useState(false); const [presetsLoading, setPresetsLoading] = useState(true); @@ -330,19 +341,19 @@ export function CreateLeagueWizard({ stepName, onStepChange }: CreateLeagueWizar const nextStep = (step < 7 ? ((step + 1) as Step) : step); saveHighestStep(nextStep); setHighestCompletedStep((prev) => Math.max(prev, nextStep)); - onStepChange(stepToStepName(nextStep)); + handleStepChange(stepToStepName(nextStep)); }; const goToPreviousStep = () => { const prevStep = (step > 1 ? ((step - 1) as Step) : step); - onStepChange(stepToStepName(prevStep)); + handleStepChange(stepToStepName(prevStep)); }; const goToStep = useCallback((targetStep: Step) => { if (targetStep <= highestCompletedStep) { - onStepChange(stepToStepName(targetStep)); + handleStepChange(stepToStepName(targetStep)); } - }, [highestCompletedStep, onStepChange]); + }, [highestCompletedStep, handleStepChange]); const handleSubmit = async (event: FormEvent) => { event.preventDefault(); @@ -413,7 +424,7 @@ export function CreateLeagueWizard({ stepName, onStepChange }: CreateLeagueWizar })); if (LeagueWizardCommandModel.hasWizardErrors(allErrors)) { - onStepChange('basics'); + handleStepChange('basics'); return; } diff --git a/apps/website/client-wrapper/CreateTeamPageClient.tsx b/apps/website/client-wrapper/CreateTeamPageClient.tsx new file mode 100644 index 000000000..5b2e6c042 --- /dev/null +++ b/apps/website/client-wrapper/CreateTeamPageClient.tsx @@ -0,0 +1,42 @@ +'use client'; + +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/Stack'; +import { Section } from '@/ui/Section'; +import { useRouter } from 'next/navigation'; + +/** + * CreateTeamPageClient + * + * Client wrapper for the Create Team page. + */ +export function CreateTeamPageClient() { + const router = useRouter(); + + const handleNavigate = (teamId: string) => { + router.push(routes.team.detail(teamId)); + }; + + const handleCancel = () => { + router.back(); + }; + + return ( +
+ + + + Create a Team + + + + +
+ ); +} diff --git a/apps/website/client-wrapper/DashboardPageClient.tsx b/apps/website/client-wrapper/DashboardPageClient.tsx new file mode 100644 index 000000000..6cc4ea8bb --- /dev/null +++ b/apps/website/client-wrapper/DashboardPageClient.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { DashboardTemplate } from '@/templates/DashboardTemplate'; +import type { DashboardViewData } from '@/lib/view-data/DashboardViewData'; +import { ClientWrapperProps } from '@/lib/contracts/components/ComponentContracts'; +import { useRouter } from 'next/navigation'; +import { routes } from '@/lib/routing/RouteConfig'; + +/** + * DashboardPageClient + * + * Client wrapper for the Dashboard page. + * Manages client-side interactions for the dashboard. + */ +export function DashboardPageClient({ viewData }: ClientWrapperProps) { + const router = useRouter(); + + const handleNavigateToRaces = () => router.push(routes.public.races); + + return ( + + ); +} diff --git a/apps/website/client-wrapper/HomePageClient.tsx b/apps/website/client-wrapper/HomePageClient.tsx new file mode 100644 index 000000000..2e0eccf79 --- /dev/null +++ b/apps/website/client-wrapper/HomePageClient.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { HomeTemplate, type HomeViewData } from '@/templates/HomeTemplate'; +import { PageWrapper } from '@/components/shared/state/PageWrapper'; + +interface HomePageClientProps { + viewData: HomeViewData; +} + +/** + * HomePageClient - Client wrapper for the Home page. + * Manages state and handlers for the home page. + */ +export function HomePageClient({ viewData }: HomePageClientProps) { + const Template = ({ viewData }: { viewData: HomeViewData }) => ( + + ); + + return ; +} diff --git a/apps/website/components/AppWrapper.tsx b/apps/website/components/AppWrapper.tsx index 7a165b486..0c75834f4 100644 --- a/apps/website/components/AppWrapper.tsx +++ b/apps/website/components/AppWrapper.tsx @@ -8,7 +8,7 @@ import { NotificationProvider } from '@/components/notifications/NotificationPro import { NotificationIntegration } from '@/components/errors/NotificationIntegration'; import { EnhancedErrorBoundary } from '@/components/errors/EnhancedErrorBoundary'; import { DevToolbar } from '@/components/dev/DevToolbar'; -import { ThemeProvider } from '@/ui/theme/ThemeProvider'; +import { ThemeProvider } from '@/components/shared/ThemeProvider'; import React from 'react'; interface AppWrapperProps { diff --git a/apps/website/components/admin/AdminHeaderPanel.tsx b/apps/website/components/admin/AdminHeaderPanel.tsx index cde7c0fa8..f544a7bdb 100644 --- a/apps/website/components/admin/AdminHeaderPanel.tsx +++ b/apps/website/components/admin/AdminHeaderPanel.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ProgressLine } from '@/ui/ProgressLine'; +import { ProgressLine } from '@/components/shared/ProgressLine'; import { SectionHeader } from '@/ui/SectionHeader'; import React from 'react'; diff --git a/apps/website/components/app/AppFooter.tsx b/apps/website/components/app/AppFooter.tsx index da2fddd42..e8fcb060e 100644 --- a/apps/website/components/app/AppFooter.tsx +++ b/apps/website/components/app/AppFooter.tsx @@ -1,5 +1,12 @@ import React from 'react'; -import { Footer } from '@/ui/Footer'; +import { Container } from '@/ui/Container'; +import { Grid } from '@/ui/Grid'; +import { Stack } from '@/ui/Stack'; +import { Text } from '@/ui/Text'; +import { Link } from '@/ui/Link'; +import { Surface } from '@/ui/Surface'; +import { BrandMark } from '@/ui/BrandMark'; +import { Box } from '@/ui/Box'; interface AppFooterProps { children?: React.ReactNode; @@ -7,9 +14,76 @@ interface AppFooterProps { /** * AppFooter is the bottom section of the application. + * It follows the "Telemetry Workspace" structure: matte surface, thin separators, minimal. + * Aligned with ControlBar (Header) design. */ export function AppFooter({ children }: AppFooterProps) { return ( -