Homepage Improvements

This commit is contained in:
Eric Gullickson
2025-11-05 11:15:33 -06:00
parent 0c3ed01f4b
commit e4e7e32a4f
10 changed files with 70 additions and 43 deletions

View File

@@ -4,7 +4,7 @@
import React, { useState, useEffect, useTransition, useCallback, lazy } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { Routes, Route, Navigate } from 'react-router-dom';
import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
import { useAuth0 } from '@auth0/auth0-react';
import { useIsAuthInitialized } from './core/auth/auth-gate';
import { motion, AnimatePresence } from 'framer-motion';
@@ -238,6 +238,7 @@ const AddVehicleScreen: React.FC<AddVehicleScreenProps> = ({ onBack, onAdded })
function App() {
const { isLoading, isAuthenticated, user } = useAuth0();
const location = useLocation();
const isAuthGateReady = useIsAuthInitialized();
const [_isPending, startTransition] = useTransition();
console.log('[DEBUG App] Render check - isLoading:', isLoading, 'isAuthenticated:', isAuthenticated, 'isAuthGateReady:', isAuthGateReady);
@@ -363,6 +364,10 @@ function App() {
console.log('MotoVaultPro status:', { isLoading, isAuthenticated, mobileMode, activeScreen, vehicleSubScreen, userAgent: navigator.userAgent });
const isGarageRoute = location.pathname === '/garage' || location.pathname.startsWith('/garage/');
const isCallbackRoute = location.pathname === '/callback';
const shouldShowHomePage = !isGarageRoute && !isCallbackRoute;
// Enhanced navigation handlers for mobile
const handleVehicleSelect = useCallback((vehicle: Vehicle) => {
setSelectedVehicle(vehicle);
@@ -416,7 +421,18 @@ function App() {
);
}
if (!isAuthenticated) {
if (isCallbackRoute) {
return (
<ThemeProvider theme={md3Theme}>
<CssBaseline />
<div className="flex items-center justify-center min-h-screen">
<div className="text-lg">Processing login...</div>
</div>
</ThemeProvider>
);
}
if (shouldShowHomePage) {
return (
<ThemeProvider theme={md3Theme}>
<CssBaseline />
@@ -426,6 +442,10 @@ function App() {
);
}
if (!isAuthenticated) {
return <Navigate to="/" replace />;
}
// Wait for auth gate to be ready before rendering protected routes
// This prevents a race condition where the page renders before the auth token is ready
if (!isAuthGateReady) {
@@ -615,17 +635,16 @@ function App() {
<Layout mobileMode={false}>
<RouteSuspense>
<Routes>
<Route path="/" element={<Navigate to="/vehicles" replace />} />
<Route path="/callback" element={<div>Processing login...</div>} />
<Route path="/vehicles" element={<VehiclesPage />} />
<Route path="/vehicles/:id" element={<VehicleDetailPage />} />
<Route path="/fuel-logs" element={<FuelLogsPage />} />
<Route path="/documents" element={<DocumentsPage />} />
<Route path="/documents/:id" element={<DocumentDetailPage />} />
<Route path="/maintenance" element={<MaintenancePage />} />
<Route path="/stations" element={<StationsPage />} />
<Route path="/settings" element={<SettingsPage />} />
<Route path="*" element={<Navigate to="/vehicles" replace />} />
<Route path="/garage" element={<Navigate to="/garage/vehicles" replace />} />
<Route path="/garage/vehicles" element={<VehiclesPage />} />
<Route path="/garage/vehicles/:id" element={<VehicleDetailPage />} />
<Route path="/garage/fuel-logs" element={<FuelLogsPage />} />
<Route path="/garage/documents" element={<DocumentsPage />} />
<Route path="/garage/documents/:id" element={<DocumentDetailPage />} />
<Route path="/garage/maintenance" element={<MaintenancePage />} />
<Route path="/garage/stations" element={<StationsPage />} />
<Route path="/garage/settings" element={<SettingsPage />} />
<Route path="*" element={<Navigate to="/garage/vehicles" replace />} />
</Routes>
</RouteSuspense>
<DebugInfo />

View File

@@ -37,12 +37,12 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
}, [mobileMode, setSidebarOpen]); // Removed sidebarOpen from dependencies
const navigation = [
{ name: 'Vehicles', href: '/vehicles', icon: <DirectionsCarRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Fuel Logs', href: '/fuel-logs', icon: <LocalGasStationRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Maintenance', href: '/maintenance', icon: <BuildRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Gas Stations', href: '/stations', icon: <PlaceRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Documents', href: '/documents', icon: <DescriptionRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Settings', href: '/settings', icon: <SettingsRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Vehicles', href: '/garage/vehicles', icon: <DirectionsCarRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Fuel Logs', href: '/garage/fuel-logs', icon: <LocalGasStationRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Maintenance', href: '/garage/maintenance', icon: <BuildRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Gas Stations', href: '/garage/stations', icon: <PlaceRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Documents', href: '/garage/documents', icon: <DescriptionRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Settings', href: '/garage/settings', icon: <SettingsRoundedIcon sx={{ fontSize: 20 }} /> },
];
// Mobile layout

View File

@@ -26,7 +26,8 @@ export const Auth0Provider: React.FC<Auth0ProviderProps> = ({ children }) => {
const onRedirectCallback = (appState?: { returnTo?: string }) => {
console.log('[Auth0Provider] Redirect callback triggered', { appState, returnTo: appState?.returnTo });
navigate(appState?.returnTo || '/dashboard');
const target = appState?.returnTo || '/garage';
navigate(target, { replace: true });
};
return (

View File

@@ -187,7 +187,7 @@ describe('DocumentsMobileScreen', () => {
const openButtons = screen.getAllByText('Open');
await user.click(openButtons[0]);
expect(mockNavigate).toHaveBeenCalledWith('/documents/doc-1');
expect(mockNavigate).toHaveBeenCalledWith('/garage/documents/doc-1');
});
it('should navigate to correct document for each Open button', async () => {
@@ -197,10 +197,10 @@ describe('DocumentsMobileScreen', () => {
const openButtons = screen.getAllByText('Open');
await user.click(openButtons[0]);
expect(mockNavigate).toHaveBeenCalledWith('/documents/doc-1');
expect(mockNavigate).toHaveBeenCalledWith('/garage/documents/doc-1');
await user.click(openButtons[1]);
expect(mockNavigate).toHaveBeenCalledWith('/documents/doc-2');
expect(mockNavigate).toHaveBeenCalledWith('/garage/documents/doc-2');
});
});
@@ -406,4 +406,4 @@ describe('DocumentsMobileScreen', () => {
expect(uploadButtons).toHaveLength(mockDocuments.length);
});
});
});
});

View File

@@ -185,7 +185,7 @@ export const DocumentsMobileScreen: React.FC = () => {
<div className="text-xs text-slate-500">{doc.document_type} {vehicleLabel}</div>
</div>
<div className="flex gap-2 items-center">
<Button onClick={() => navigate(`/documents/${doc.id}`)}>Open</Button>
<Button onClick={() => navigate(`/garage/documents/${doc.id}`)}>Open</Button>
<Button onClick={() => triggerUpload(doc.id)}>Upload</Button>
{upload.isPending && currentId === doc.id && (
<span className="text-xs text-slate-500">{upload.progress}%</span>

View File

@@ -64,7 +64,7 @@ export const DocumentDetailPage: React.FC = () => {
<p className="text-slate-600 mb-6">Please log in to view this document</p>
<div className="space-x-3">
<Button onClick={() => loginWithRedirect()}>Login to Continue</Button>
<Button variant="secondary" onClick={() => navigate('/documents')}>Back to Documents</Button>
<Button variant="secondary" onClick={() => navigate('/garage/documents')}>Back to Documents</Button>
</div>
</div>
</Card>
@@ -88,7 +88,7 @@ export const DocumentDetailPage: React.FC = () => {
<p className="text-slate-600 mb-6">Your session has expired. Please log in again to continue.</p>
<div className="space-x-3">
<Button onClick={() => loginWithRedirect()}>Login Again</Button>
<Button variant="secondary" onClick={() => navigate('/documents')}>Back to Documents</Button>
<Button variant="secondary" onClick={() => navigate('/garage/documents')}>Back to Documents</Button>
</div>
</div>
</Card>
@@ -112,7 +112,7 @@ export const DocumentDetailPage: React.FC = () => {
<p className="text-slate-600 mb-6">The document you're looking for could not be found.</p>
<div className="space-x-3">
<Button onClick={() => window.location.reload()}>Retry</Button>
<Button variant="secondary" onClick={() => navigate('/documents')}>Back to Documents</Button>
<Button variant="secondary" onClick={() => navigate('/garage/documents')}>Back to Documents</Button>
</div>
</div>
</Card>
@@ -127,7 +127,7 @@ export const DocumentDetailPage: React.FC = () => {
<div className="p-8 text-center">
<h3 className="text-lg font-semibold text-slate-800 mb-2">Document Not Found</h3>
<p className="text-slate-600 mb-6">The document you're looking for does not exist.</p>
<Button onClick={() => navigate('/documents')}>Back to Documents</Button>
<Button onClick={() => navigate('/garage/documents')}>Back to Documents</Button>
</div>
</Card>
</div>

View File

@@ -115,7 +115,7 @@ export const DocumentsPage: React.FC = () => {
</div>
<h3 className="text-lg font-semibold text-slate-800 mb-2">No Documents Yet</h3>
<p className="text-slate-600 mb-6">You haven't added any documents yet. Documents will appear here once you create them.</p>
<Button onClick={() => navigate('/vehicles')}>
<Button onClick={() => navigate('/garage/vehicles')}>
Go to Vehicles
</Button>
</div>
@@ -131,7 +131,7 @@ export const DocumentsPage: React.FC = () => {
<div className="text-sm text-slate-500">Type: {doc.document_type}</div>
<div className="text-sm text-slate-500">Vehicle: {doc.vehicle_id}</div>
<div className="flex gap-2 pt-2">
<Button onClick={() => navigate(`/documents/${doc.id}`)}>Open</Button>
<Button onClick={() => navigate(`/garage/documents/${doc.id}`)}>Open</Button>
<Button variant="danger" onClick={() => removeDoc.mutate(doc.id)}>Delete</Button>
</div>
</div>

View File

@@ -129,7 +129,7 @@ export const VehicleDetailPage: React.FC = () => {
}, [id]);
const handleBack = () => {
navigate('/vehicles');
navigate('/garage/vehicles');
};
const handleEdit = () => {

View File

@@ -53,7 +53,7 @@ export const VehiclesPage: React.FC = () => {
startTransition(() => {
const vehicle = optimisticVehicles.find(v => v.id === id);
setSelectedVehicle(vehicle || null);
navigate(`/vehicles/${id}`);
navigate(`/garage/vehicles/${id}`);
});
};
@@ -191,4 +191,4 @@ export const VehiclesPage: React.FC = () => {
</Box>
</VehicleListSuspense>
);
};
};

View File

@@ -1,15 +1,22 @@
import { useState } from 'react';
import { useAuth0 } from '@auth0/auth0-react';
import { useNavigate } from 'react-router-dom';
import { HeroCarousel } from './HomePage/HeroCarousel';
import { FeaturesGrid } from './HomePage/FeaturesGrid';
import { motion } from 'framer-motion';
export const HomePage = () => {
const { loginWithRedirect } = useAuth0();
const { loginWithRedirect, isAuthenticated } = useAuth0();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const navigate = useNavigate();
const handleGetStarted = () => {
loginWithRedirect();
const handleAuthAction = () => {
if (isAuthenticated) {
navigate('/garage');
return;
}
loginWithRedirect({ appState: { returnTo: '/garage' } });
};
return (
@@ -38,10 +45,10 @@ export const HomePage = () => {
About
</a>
<button
onClick={handleGetStarted}
onClick={handleAuthAction}
className="bg-primary-500 hover:bg-primary-700 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300"
>
Get Started
Login
</button>
</div>
@@ -97,10 +104,10 @@ export const HomePage = () => {
About
</a>
<button
onClick={handleGetStarted}
onClick={handleAuthAction}
className="w-full bg-primary-500 hover:bg-primary-700 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300"
>
Get Started
Login
</button>
</motion.div>
)}
@@ -133,7 +140,7 @@ export const HomePage = () => {
your needs.
</p>
<button
onClick={handleGetStarted}
onClick={handleAuthAction}
className="bg-primary-500 hover:bg-primary-700 text-white font-semibold py-3 px-8 rounded-lg transition-colors duration-300"
>
Get Started
@@ -200,7 +207,7 @@ export const HomePage = () => {
We are a cloud-based platform accessible anywhere, anytime.
</h2>
<button
onClick={handleGetStarted}
onClick={handleAuthAction}
className="bg-white text-primary-500 hover:bg-gray-100 font-semibold py-3 px-8 rounded-lg transition-colors duration-300"
>
Get Started