diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fbbd914..ac28906 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -47,6 +47,9 @@ const AdminLogsMobileScreen = lazy(() => import('./features/admin/mobile/AdminLo const AdminCommunityStationsPage = lazy(() => import('./features/admin/pages/AdminCommunityStationsPage').then(m => ({ default: m.AdminCommunityStationsPage }))); const AdminCommunityStationsMobileScreen = lazy(() => import('./features/admin/mobile/AdminCommunityStationsMobileScreen').then(m => ({ default: m.AdminCommunityStationsMobileScreen }))); +// Public pages (lazy-loaded) +const GuidePage = lazy(() => import('./pages/GuidePage/GuidePage').then(m => ({ default: m.GuidePage }))); + // Auth pages (lazy-loaded) const SignupPage = lazy(() => import('./features/auth/pages/SignupPage').then(m => ({ default: m.SignupPage }))); const VerifyEmailPage = lazy(() => import('./features/auth/pages/VerifyEmailPage').then(m => ({ default: m.VerifyEmailPage }))); @@ -368,7 +371,7 @@ function App() { // Skip on auth routes -- their query params must survive until Auth0 SDK processes them useEffect(() => { const path = window.location.pathname; - if (path === '/callback' || path === '/signup' || path === '/verify-email') return; + if (path === '/callback' || path === '/signup' || path === '/verify-email' || path === '/guide') return; const screen = routeToScreen[path]; if (screen && screen !== activeScreen) { navigateToScreen(screen, { source: 'url-sync' }); @@ -380,7 +383,7 @@ function App() { // Auth0 SDK needs for handleRedirectCallback (child effects fire before parent effects) useEffect(() => { const path = window.location.pathname; - if (path === '/callback' || path === '/signup' || path === '/verify-email') return; + if (path === '/callback' || path === '/signup' || path === '/verify-email' || path === '/guide') return; const targetPath = screenToRoute[activeScreen]; if (targetPath && path !== targetPath) { window.history.replaceState(null, '', targetPath); @@ -499,8 +502,9 @@ function App() { const isSignupRoute = location.pathname === '/signup'; const isVerifyEmailRoute = location.pathname === '/verify-email'; const isOnboardingRoute = location.pathname === '/onboarding'; + const isGuideRoute = location.pathname === '/guide'; const isAuthRoute = isSignupRoute || isVerifyEmailRoute || isOnboardingRoute; - const shouldShowHomePage = !isGarageRoute && !isCallbackRoute && !isAuthRoute; + const shouldShowHomePage = !isGarageRoute && !isCallbackRoute && !isAuthRoute && !isGuideRoute; const [callbackTimedOut, setCallbackTimedOut] = useState(false); useEffect(() => { @@ -635,6 +639,21 @@ function App() { ); } + if (isGuideRoute) { + return ( + + +
Loading guide...
+ + }> + +
+ +
+ ); + } + // Signup route is public - no authentication required if (isSignupRoute) { return ( diff --git a/frontend/src/pages/GuidePage/GuidePage.tsx b/frontend/src/pages/GuidePage/GuidePage.tsx new file mode 100644 index 0000000..2ddb6fa --- /dev/null +++ b/frontend/src/pages/GuidePage/GuidePage.tsx @@ -0,0 +1,238 @@ +import { useState, useEffect, useCallback, Suspense } from 'react'; +import { useAuth0 } from '@auth0/auth0-react'; +import { useNavigate } from 'react-router-dom'; +import { GuideTableOfContents } from './GuideTableOfContents'; +import { guideSections } from './guideTypes'; +import { + GettingStartedSection, + DashboardSection, + VehiclesSection, + FuelLogsSection, + MaintenanceSection, + GasStationsSection, + DocumentsSection, + SettingsSection, + SubscriptionSection, + MobileExperienceSection, +} from './sections'; + +export const GuidePage = () => { + const { loginWithRedirect, isAuthenticated } = useAuth0(); + const navigate = useNavigate(); + const [activeSection, setActiveSection] = useState(guideSections[0].id); + const [isScrolled, setIsScrolled] = useState(false); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + + const handleAuthAction = useCallback(() => { + if (isAuthenticated) { + navigate('/garage'); + return; + } + loginWithRedirect({ appState: { returnTo: '/garage' } }); + }, [isAuthenticated, navigate, loginWithRedirect]); + + const handleSignup = useCallback(() => { + navigate('/signup'); + }, [navigate]); + + // Track scroll position for nav background and active section + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 50); + + // Determine active section from scroll position + const sectionElements = guideSections.map((s) => ({ + id: s.id, + element: document.getElementById(s.id), + })); + + for (let i = sectionElements.length - 1; i >= 0; i--) { + const { id, element } = sectionElements[i]; + if (element) { + const rect = element.getBoundingClientRect(); + if (rect.top <= 120) { + setActiveSection(id); + break; + } + } + } + }; + + window.addEventListener('scroll', handleScroll, { passive: true }); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const sectionFallback = ( +
Loading section...
+ ); + + return ( +
+ {/* Navigation Bar - matches HomePage style */} + + + {/* Page Content */} +
+ {/* Page Header */} +
+

+ User Guide +

+

+ Precision Vehicle Management -- Track every mile. Own every detail. +

+
+ + {/* Layout: TOC sidebar + content */} +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + {/* Footer */} + +
+ ); +}; diff --git a/frontend/src/pages/GuidePage/GuideTableOfContents.tsx b/frontend/src/pages/GuidePage/GuideTableOfContents.tsx new file mode 100644 index 0000000..a2b522c --- /dev/null +++ b/frontend/src/pages/GuidePage/GuideTableOfContents.tsx @@ -0,0 +1,92 @@ +import { useState, useEffect } from 'react'; +import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { guideSections } from './guideTypes'; + +interface GuideTableOfContentsProps { + activeSection: string; +} + +export const GuideTableOfContents = ({ activeSection }: GuideTableOfContentsProps) => { + const [isMobile, setIsMobile] = useState(false); + const [tocExpanded, setTocExpanded] = useState(false); + + useEffect(() => { + const check = () => setIsMobile(window.innerWidth < 768); + check(); + window.addEventListener('resize', check); + return () => window.removeEventListener('resize', check); + }, []); + + const handleClick = (sectionId: string) => { + const element = document.getElementById(sectionId); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + if (isMobile) { + setTocExpanded(false); + } + }; + + const tocContent = ( + + ); + + if (isMobile) { + return ( +
+ setTocExpanded(expanded)} + sx={{ + backgroundColor: 'rgba(255,255,255,0.03)', + border: '1px solid rgba(255,255,255,0.08)', + borderRadius: '8px !important', + '&:before': { display: 'none' }, + }} + > + } + sx={{ + color: 'rgba(242,243,246,0.9)', + fontWeight: 600, + minHeight: 48, + '& .MuiAccordionSummary-content': { margin: '12px 0' }, + }} + > + Table of Contents + + + {tocContent} + + +
+ ); + } + + return ( + + ); +}; diff --git a/frontend/src/pages/GuidePage/components/GuideScreenshot.tsx b/frontend/src/pages/GuidePage/components/GuideScreenshot.tsx new file mode 100644 index 0000000..aa7c5cf --- /dev/null +++ b/frontend/src/pages/GuidePage/components/GuideScreenshot.tsx @@ -0,0 +1,26 @@ +interface GuideScreenshotProps { + src: string; + alt: string; + caption?: string; + mobile?: boolean; +} + +export const GuideScreenshot = ({ src, alt, caption, mobile }: GuideScreenshotProps) => { + return ( +
+
+ {alt} +
+ {caption && ( +
+ {caption} +
+ )} +
+ ); +}; diff --git a/frontend/src/pages/GuidePage/components/GuideTable.tsx b/frontend/src/pages/GuidePage/components/GuideTable.tsx new file mode 100644 index 0000000..9c5efbc --- /dev/null +++ b/frontend/src/pages/GuidePage/components/GuideTable.tsx @@ -0,0 +1,68 @@ +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, +} from '@mui/material'; + +interface GuideTableProps { + headers: string[]; + rows: string[][]; +} + +export const GuideTable = ({ headers, rows }: GuideTableProps) => { + return ( + + + + + {headers.map((header, idx) => ( + + {header} + + ))} + + + + {rows.map((row, rowIdx) => ( + + {row.map((cell, cellIdx) => ( + + {cell} + + ))} + + ))} + +
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/guideTypes.ts b/frontend/src/pages/GuidePage/guideTypes.ts new file mode 100644 index 0000000..272d420 --- /dev/null +++ b/frontend/src/pages/GuidePage/guideTypes.ts @@ -0,0 +1,114 @@ +export interface GuideSubSection { + id: string; + title: string; +} + +export interface GuideSection { + id: string; + title: string; + subSections: GuideSubSection[]; +} + +export const guideSections: GuideSection[] = [ + { + id: 'getting-started', + title: '1. Getting Started', + subSections: [ + { id: 'creating-an-account', title: 'Creating an Account' }, + { id: 'logging-in', title: 'Logging In' }, + { id: 'onboarding', title: 'Onboarding' }, + { id: 'trouble-logging-in', title: 'Trouble Logging In' }, + ], + }, + { + id: 'dashboard', + title: '2. Dashboard', + subSections: [ + { id: 'your-fleet-overview', title: 'Your Fleet Overview' }, + { id: 'quick-actions', title: 'Quick Actions' }, + ], + }, + { + id: 'vehicles', + title: '3. Vehicles', + subSections: [ + { id: 'viewing-your-vehicles', title: 'Viewing Your Vehicles' }, + { id: 'adding-a-vehicle', title: 'Adding a Vehicle' }, + { id: 'vin-decode', title: 'VIN Decode' }, + { id: 'vehicle-detail-page', title: 'Vehicle Detail Page' }, + { id: 'editing-a-vehicle', title: 'Editing a Vehicle' }, + { id: 'deleting-a-vehicle', title: 'Deleting a Vehicle' }, + ], + }, + { + id: 'fuel-logs', + title: '4. Fuel Logs', + subSections: [ + { id: 'fuel-logs-overview', title: 'Fuel Logs Overview' }, + { id: 'logging-fuel', title: 'Logging Fuel' }, + { id: 'receipt-scanning', title: 'Receipt Scanning' }, + { id: 'editing-and-deleting-fuel-logs', title: 'Editing and Deleting Fuel Logs' }, + ], + }, + { + id: 'maintenance', + title: '5. Maintenance', + subSections: [ + { id: 'maintenance-records', title: 'Maintenance Records' }, + { id: 'adding-a-maintenance-record', title: 'Adding a Maintenance Record' }, + { id: 'maintenance-schedules', title: 'Maintenance Schedules' }, + { id: 'creating-a-schedule', title: 'Creating a Schedule' }, + ], + }, + { + id: 'gas-stations', + title: '6. Gas Stations', + subSections: [ + { id: 'finding-stations', title: 'Finding Stations' }, + { id: 'saved-stations', title: 'Saved Stations' }, + { id: 'premium-93-stations', title: 'Premium 93 Stations' }, + ], + }, + { + id: 'documents', + title: '7. Documents', + subSections: [ + { id: 'documents-overview', title: 'Documents Overview' }, + { id: 'adding-a-document', title: 'Adding a Document' }, + { id: 'document-types', title: 'Document Types' }, + ], + }, + { + id: 'settings', + title: '8. Settings', + subSections: [ + { id: 'profile', title: 'Profile' }, + { id: 'security-and-privacy', title: 'Security and Privacy' }, + { id: 'subscription', title: 'Subscription' }, + { id: 'notifications', title: 'Notifications' }, + { id: 'appearance-and-units', title: 'Appearance and Units' }, + { id: 'data-import-and-export', title: 'Data Import and Export' }, + { id: 'account-actions', title: 'Account Actions' }, + ], + }, + { + id: 'subscription-tiers', + title: '9. Subscription Tiers and Pro Features', + subSections: [ + { id: 'tier-comparison', title: 'Tier Comparison' }, + { id: 'vin-camera-scanning', title: 'VIN Camera Scanning (Pro)' }, + { id: 'fuel-receipt-scanning', title: 'Fuel Receipt Scanning (Pro)' }, + { id: 'maintenance-receipt-scanning', title: 'Maintenance Receipt Scanning (Pro)' }, + { id: 'maintenance-manual-pdf', title: 'Maintenance Manual PDF (Pro)' }, + { id: 'email-ingestion', title: 'Email Ingestion (Pro)' }, + { id: 'shared-vehicle-documents', title: 'Shared Vehicle Documents (Pro)' }, + { id: 'community-station-submissions', title: 'Community Station Submissions (Pro)' }, + { id: 'managing-your-subscription', title: 'Managing Your Subscription' }, + ], + }, + { + id: 'mobile-experience', + title: '10. Mobile Experience', + subSections: [], + }, +]; diff --git a/frontend/src/pages/GuidePage/sections/DashboardSection.tsx b/frontend/src/pages/GuidePage/sections/DashboardSection.tsx new file mode 100644 index 0000000..271b7f0 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/DashboardSection.tsx @@ -0,0 +1,8 @@ +export const DashboardSection = () => { + return ( +
+

2. Dashboard

+

Content loading in Milestone 2...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/DocumentsSection.tsx b/frontend/src/pages/GuidePage/sections/DocumentsSection.tsx new file mode 100644 index 0000000..c098c8a --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/DocumentsSection.tsx @@ -0,0 +1,8 @@ +export const DocumentsSection = () => { + return ( +
+

7. Documents

+

Content loading in Milestone 3...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/FuelLogsSection.tsx b/frontend/src/pages/GuidePage/sections/FuelLogsSection.tsx new file mode 100644 index 0000000..c1e45a2 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/FuelLogsSection.tsx @@ -0,0 +1,8 @@ +export const FuelLogsSection = () => { + return ( +
+

4. Fuel Logs

+

Content loading in Milestone 2...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/GasStationsSection.tsx b/frontend/src/pages/GuidePage/sections/GasStationsSection.tsx new file mode 100644 index 0000000..0c46913 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/GasStationsSection.tsx @@ -0,0 +1,8 @@ +export const GasStationsSection = () => { + return ( +
+

6. Gas Stations

+

Content loading in Milestone 3...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/GettingStartedSection.tsx b/frontend/src/pages/GuidePage/sections/GettingStartedSection.tsx new file mode 100644 index 0000000..c20832a --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/GettingStartedSection.tsx @@ -0,0 +1,8 @@ +export const GettingStartedSection = () => { + return ( +
+

1. Getting Started

+

Content loading in Milestone 2...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/MaintenanceSection.tsx b/frontend/src/pages/GuidePage/sections/MaintenanceSection.tsx new file mode 100644 index 0000000..f58fbe2 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/MaintenanceSection.tsx @@ -0,0 +1,8 @@ +export const MaintenanceSection = () => { + return ( +
+

5. Maintenance

+

Content loading in Milestone 2...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/MobileExperienceSection.tsx b/frontend/src/pages/GuidePage/sections/MobileExperienceSection.tsx new file mode 100644 index 0000000..7f9f764 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/MobileExperienceSection.tsx @@ -0,0 +1,8 @@ +export const MobileExperienceSection = () => { + return ( +
+

10. Mobile Experience

+

Content loading in Milestone 3...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/SettingsSection.tsx b/frontend/src/pages/GuidePage/sections/SettingsSection.tsx new file mode 100644 index 0000000..a8fcb52 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/SettingsSection.tsx @@ -0,0 +1,8 @@ +export const SettingsSection = () => { + return ( +
+

8. Settings

+

Content loading in Milestone 3...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/SubscriptionSection.tsx b/frontend/src/pages/GuidePage/sections/SubscriptionSection.tsx new file mode 100644 index 0000000..9a0061f --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/SubscriptionSection.tsx @@ -0,0 +1,8 @@ +export const SubscriptionSection = () => { + return ( +
+

9. Subscription Tiers and Pro Features

+

Content loading in Milestone 3...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/VehiclesSection.tsx b/frontend/src/pages/GuidePage/sections/VehiclesSection.tsx new file mode 100644 index 0000000..a4e7fc0 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/VehiclesSection.tsx @@ -0,0 +1,8 @@ +export const VehiclesSection = () => { + return ( +
+

3. Vehicles

+

Content loading in Milestone 2...

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/index.ts b/frontend/src/pages/GuidePage/sections/index.ts new file mode 100644 index 0000000..5a6ce7b --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/index.ts @@ -0,0 +1,12 @@ +import { lazy } from 'react'; + +export const GettingStartedSection = lazy(() => import('./GettingStartedSection').then(m => ({ default: m.GettingStartedSection }))); +export const DashboardSection = lazy(() => import('./DashboardSection').then(m => ({ default: m.DashboardSection }))); +export const VehiclesSection = lazy(() => import('./VehiclesSection').then(m => ({ default: m.VehiclesSection }))); +export const FuelLogsSection = lazy(() => import('./FuelLogsSection').then(m => ({ default: m.FuelLogsSection }))); +export const MaintenanceSection = lazy(() => import('./MaintenanceSection').then(m => ({ default: m.MaintenanceSection }))); +export const GasStationsSection = lazy(() => import('./GasStationsSection').then(m => ({ default: m.GasStationsSection }))); +export const DocumentsSection = lazy(() => import('./DocumentsSection').then(m => ({ default: m.DocumentsSection }))); +export const SettingsSection = lazy(() => import('./SettingsSection').then(m => ({ default: m.SettingsSection }))); +export const SubscriptionSection = lazy(() => import('./SubscriptionSection').then(m => ({ default: m.SubscriptionSection }))); +export const MobileExperienceSection = lazy(() => import('./MobileExperienceSection').then(m => ({ default: m.MobileExperienceSection })));