feat: add guide page foundation and routing (refs #203)

- Create GuidePage with responsive layout (sticky TOC sidebar desktop, collapsible accordion mobile)
- Add GuideTableOfContents with scroll-based active section tracking
- Create GuideScreenshot and GuideTable shared components
- Add guideTypes.ts with section metadata for all 10 sections
- Add lazy-loaded /guide route in App.tsx with public access
- Placeholder section components for all 10 guide sections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-15 16:45:17 -06:00
parent d8ab00970d
commit 864da55cec
17 changed files with 652 additions and 3 deletions

View File

@@ -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 = (
<div className="py-8 text-center text-titanio/50">Loading section...</div>
);
return (
<div className="min-h-screen bg-nero text-avus">
{/* Navigation Bar - matches HomePage style */}
<nav
className={`fixed top-0 left-0 right-0 z-50 transition-colors duration-300 ${
isScrolled ? 'bg-nero/95 backdrop-blur-sm' : 'bg-nero'
}`}
>
<div className="w-full px-4 md:px-8 lg:px-12">
<div className="flex justify-between items-center h-16">
<div className="flex-shrink-0">
<a href="/" className="flex items-center">
<img
src="/images/logos/motovaultpro-title-slogan.png"
alt="MotoVaultPro - Precision Vehicle Management"
className="h-8 md:h-10 w-auto"
/>
</a>
</div>
{/* Desktop Menu */}
<div className="hidden md:flex items-center space-x-8">
<a href="/#home" className="text-white/75 hover:text-white transition-colors">
Home
</a>
<a href="/#features" className="text-white/75 hover:text-white transition-colors">
Features
</a>
<a href="/#about" className="text-white/75 hover:text-white transition-colors">
About
</a>
<a
href="/guide"
className="text-primary-400 font-semibold transition-colors"
>
Guide
</a>
<button
onClick={handleSignup}
className="border border-primary-500/90 text-primary-500 hover:bg-primary-500/10 hover:border-primary-500 font-semibold py-2 px-6 rounded-lg transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
>
Sign Up
</button>
<button
onClick={handleAuthAction}
className="bg-primary-500 hover:bg-primary-600 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300 shadow-lg shadow-black/30 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
>
Login
</button>
</div>
{/* Mobile Menu Button */}
<div className="md:hidden">
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="text-white/80 hover:text-white focus:outline-none"
>
<svg
className="h-6 w-6"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
{mobileMenuOpen ? (
<path d="M6 18L18 6M6 6l12 12" />
) : (
<path d="M4 6h16M4 12h16M4 18h16" />
)}
</svg>
</button>
</div>
</div>
{/* Mobile Menu */}
{mobileMenuOpen && (
<div className="md:hidden py-4 space-y-3 bg-nero/95 backdrop-blur-sm border-t border-white/10">
<a href="/" className="block text-white/75 hover:text-white transition-colors py-2">
Home
</a>
<a href="/#features" className="block text-white/75 hover:text-white transition-colors py-2">
Features
</a>
<a href="/#about" className="block text-white/75 hover:text-white transition-colors py-2">
About
</a>
<a href="/guide" className="block text-primary-400 font-semibold py-2">
Guide
</a>
<button
onClick={handleSignup}
className="w-full border border-primary-500/90 text-primary-500 hover:bg-primary-500/10 font-semibold py-2 px-6 rounded-lg transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
>
Sign Up
</button>
<button
onClick={handleAuthAction}
className="w-full bg-primary-500 hover:bg-primary-600 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
>
Login
</button>
</div>
)}
</div>
</nav>
{/* Page Content */}
<div className="pt-20 px-4 md:px-8 lg:px-12 max-w-7xl mx-auto">
{/* Page Header */}
<div className="py-8 md:py-12 border-b border-white/10 mb-8">
<h1 className="text-3xl md:text-4xl font-bold text-avus mb-3">
User Guide
</h1>
<p className="text-lg text-titanio/70">
Precision Vehicle Management -- Track every mile. Own every detail.
</p>
</div>
{/* Layout: TOC sidebar + content */}
<div className="flex flex-col md:flex-row gap-8 pb-16">
<GuideTableOfContents activeSection={activeSection} />
<main className="flex-1 min-w-0">
<Suspense fallback={sectionFallback}>
<GettingStartedSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<DashboardSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<VehiclesSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<FuelLogsSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<MaintenanceSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<GasStationsSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<DocumentsSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<SettingsSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<SubscriptionSection />
</Suspense>
<Suspense fallback={sectionFallback}>
<MobileExperienceSection />
</Suspense>
</main>
</div>
</div>
{/* Footer */}
<footer className="bg-black text-white py-8 px-4 md:px-8 border-t border-white/10">
<div className="max-w-7xl mx-auto text-center">
<p className="text-white/50">
&copy; {new Date().getFullYear()} FB Technologies LLC. All rights reserved.
</p>
</div>
</footer>
</div>
);
};