Homepage Improvements
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect, useTransition, useCallback, lazy } from 'react';
|
import React, { useState, useEffect, useTransition, useCallback, lazy } from 'react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
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 { useAuth0 } from '@auth0/auth0-react';
|
||||||
import { useIsAuthInitialized } from './core/auth/auth-gate';
|
import { useIsAuthInitialized } from './core/auth/auth-gate';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
@@ -238,6 +238,7 @@ const AddVehicleScreen: React.FC<AddVehicleScreenProps> = ({ onBack, onAdded })
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { isLoading, isAuthenticated, user } = useAuth0();
|
const { isLoading, isAuthenticated, user } = useAuth0();
|
||||||
|
const location = useLocation();
|
||||||
const isAuthGateReady = useIsAuthInitialized();
|
const isAuthGateReady = useIsAuthInitialized();
|
||||||
const [_isPending, startTransition] = useTransition();
|
const [_isPending, startTransition] = useTransition();
|
||||||
console.log('[DEBUG App] Render check - isLoading:', isLoading, 'isAuthenticated:', isAuthenticated, 'isAuthGateReady:', isAuthGateReady);
|
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 });
|
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
|
// Enhanced navigation handlers for mobile
|
||||||
const handleVehicleSelect = useCallback((vehicle: Vehicle) => {
|
const handleVehicleSelect = useCallback((vehicle: Vehicle) => {
|
||||||
setSelectedVehicle(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 (
|
return (
|
||||||
<ThemeProvider theme={md3Theme}>
|
<ThemeProvider theme={md3Theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
@@ -426,6 +442,10 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for auth gate to be ready before rendering protected routes
|
// 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
|
// This prevents a race condition where the page renders before the auth token is ready
|
||||||
if (!isAuthGateReady) {
|
if (!isAuthGateReady) {
|
||||||
@@ -615,17 +635,16 @@ function App() {
|
|||||||
<Layout mobileMode={false}>
|
<Layout mobileMode={false}>
|
||||||
<RouteSuspense>
|
<RouteSuspense>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Navigate to="/vehicles" replace />} />
|
<Route path="/garage" element={<Navigate to="/garage/vehicles" replace />} />
|
||||||
<Route path="/callback" element={<div>Processing login...</div>} />
|
<Route path="/garage/vehicles" element={<VehiclesPage />} />
|
||||||
<Route path="/vehicles" element={<VehiclesPage />} />
|
<Route path="/garage/vehicles/:id" element={<VehicleDetailPage />} />
|
||||||
<Route path="/vehicles/:id" element={<VehicleDetailPage />} />
|
<Route path="/garage/fuel-logs" element={<FuelLogsPage />} />
|
||||||
<Route path="/fuel-logs" element={<FuelLogsPage />} />
|
<Route path="/garage/documents" element={<DocumentsPage />} />
|
||||||
<Route path="/documents" element={<DocumentsPage />} />
|
<Route path="/garage/documents/:id" element={<DocumentDetailPage />} />
|
||||||
<Route path="/documents/:id" element={<DocumentDetailPage />} />
|
<Route path="/garage/maintenance" element={<MaintenancePage />} />
|
||||||
<Route path="/maintenance" element={<MaintenancePage />} />
|
<Route path="/garage/stations" element={<StationsPage />} />
|
||||||
<Route path="/stations" element={<StationsPage />} />
|
<Route path="/garage/settings" element={<SettingsPage />} />
|
||||||
<Route path="/settings" element={<SettingsPage />} />
|
<Route path="*" element={<Navigate to="/garage/vehicles" replace />} />
|
||||||
<Route path="*" element={<Navigate to="/vehicles" replace />} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</RouteSuspense>
|
</RouteSuspense>
|
||||||
<DebugInfo />
|
<DebugInfo />
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
|||||||
}, [mobileMode, setSidebarOpen]); // Removed sidebarOpen from dependencies
|
}, [mobileMode, setSidebarOpen]); // Removed sidebarOpen from dependencies
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{ name: 'Vehicles', href: '/vehicles', icon: <DirectionsCarRoundedIcon sx={{ fontSize: 20 }} /> },
|
{ name: 'Vehicles', href: '/garage/vehicles', icon: <DirectionsCarRoundedIcon sx={{ fontSize: 20 }} /> },
|
||||||
{ name: 'Fuel Logs', href: '/fuel-logs', icon: <LocalGasStationRoundedIcon sx={{ fontSize: 20 }} /> },
|
{ name: 'Fuel Logs', href: '/garage/fuel-logs', icon: <LocalGasStationRoundedIcon sx={{ fontSize: 20 }} /> },
|
||||||
{ name: 'Maintenance', href: '/maintenance', icon: <BuildRoundedIcon sx={{ fontSize: 20 }} /> },
|
{ name: 'Maintenance', href: '/garage/maintenance', icon: <BuildRoundedIcon sx={{ fontSize: 20 }} /> },
|
||||||
{ name: 'Gas Stations', href: '/stations', icon: <PlaceRoundedIcon sx={{ fontSize: 20 }} /> },
|
{ name: 'Gas Stations', href: '/garage/stations', icon: <PlaceRoundedIcon sx={{ fontSize: 20 }} /> },
|
||||||
{ name: 'Documents', href: '/documents', icon: <DescriptionRoundedIcon sx={{ fontSize: 20 }} /> },
|
{ name: 'Documents', href: '/garage/documents', icon: <DescriptionRoundedIcon sx={{ fontSize: 20 }} /> },
|
||||||
{ name: 'Settings', href: '/settings', icon: <SettingsRoundedIcon sx={{ fontSize: 20 }} /> },
|
{ name: 'Settings', href: '/garage/settings', icon: <SettingsRoundedIcon sx={{ fontSize: 20 }} /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Mobile layout
|
// Mobile layout
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export const Auth0Provider: React.FC<Auth0ProviderProps> = ({ children }) => {
|
|||||||
|
|
||||||
const onRedirectCallback = (appState?: { returnTo?: string }) => {
|
const onRedirectCallback = (appState?: { returnTo?: string }) => {
|
||||||
console.log('[Auth0Provider] Redirect callback triggered', { appState, returnTo: appState?.returnTo });
|
console.log('[Auth0Provider] Redirect callback triggered', { appState, returnTo: appState?.returnTo });
|
||||||
navigate(appState?.returnTo || '/dashboard');
|
const target = appState?.returnTo || '/garage';
|
||||||
|
navigate(target, { replace: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ describe('DocumentsMobileScreen', () => {
|
|||||||
const openButtons = screen.getAllByText('Open');
|
const openButtons = screen.getAllByText('Open');
|
||||||
await user.click(openButtons[0]);
|
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 () => {
|
it('should navigate to correct document for each Open button', async () => {
|
||||||
@@ -197,10 +197,10 @@ describe('DocumentsMobileScreen', () => {
|
|||||||
const openButtons = screen.getAllByText('Open');
|
const openButtons = screen.getAllByText('Open');
|
||||||
|
|
||||||
await user.click(openButtons[0]);
|
await user.click(openButtons[0]);
|
||||||
expect(mockNavigate).toHaveBeenCalledWith('/documents/doc-1');
|
expect(mockNavigate).toHaveBeenCalledWith('/garage/documents/doc-1');
|
||||||
|
|
||||||
await user.click(openButtons[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);
|
expect(uploadButtons).toHaveLength(mockDocuments.length);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export const DocumentsMobileScreen: React.FC = () => {
|
|||||||
<div className="text-xs text-slate-500">{doc.document_type} • {vehicleLabel}</div>
|
<div className="text-xs text-slate-500">{doc.document_type} • {vehicleLabel}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<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>
|
<Button onClick={() => triggerUpload(doc.id)}>Upload</Button>
|
||||||
{upload.isPending && currentId === doc.id && (
|
{upload.isPending && currentId === doc.id && (
|
||||||
<span className="text-xs text-slate-500">{upload.progress}%</span>
|
<span className="text-xs text-slate-500">{upload.progress}%</span>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const DocumentDetailPage: React.FC = () => {
|
|||||||
<p className="text-slate-600 mb-6">Please log in to view this document</p>
|
<p className="text-slate-600 mb-6">Please log in to view this document</p>
|
||||||
<div className="space-x-3">
|
<div className="space-x-3">
|
||||||
<Button onClick={() => loginWithRedirect()}>Login to Continue</Button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</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>
|
<p className="text-slate-600 mb-6">Your session has expired. Please log in again to continue.</p>
|
||||||
<div className="space-x-3">
|
<div className="space-x-3">
|
||||||
<Button onClick={() => loginWithRedirect()}>Login Again</Button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</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>
|
<p className="text-slate-600 mb-6">The document you're looking for could not be found.</p>
|
||||||
<div className="space-x-3">
|
<div className="space-x-3">
|
||||||
<Button onClick={() => window.location.reload()}>Retry</Button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -127,7 +127,7 @@ export const DocumentDetailPage: React.FC = () => {
|
|||||||
<div className="p-8 text-center">
|
<div className="p-8 text-center">
|
||||||
<h3 className="text-lg font-semibold text-slate-800 mb-2">Document Not Found</h3>
|
<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>
|
<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>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export const DocumentsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold text-slate-800 mb-2">No Documents Yet</h3>
|
<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>
|
<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
|
Go to Vehicles
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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">Type: {doc.document_type}</div>
|
||||||
<div className="text-sm text-slate-500">Vehicle: {doc.vehicle_id}</div>
|
<div className="text-sm text-slate-500">Vehicle: {doc.vehicle_id}</div>
|
||||||
<div className="flex gap-2 pt-2">
|
<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>
|
<Button variant="danger" onClick={() => removeDoc.mutate(doc.id)}>Delete</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export const VehicleDetailPage: React.FC = () => {
|
|||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
navigate('/vehicles');
|
navigate('/garage/vehicles');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const VehiclesPage: React.FC = () => {
|
|||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
const vehicle = optimisticVehicles.find(v => v.id === id);
|
const vehicle = optimisticVehicles.find(v => v.id === id);
|
||||||
setSelectedVehicle(vehicle || null);
|
setSelectedVehicle(vehicle || null);
|
||||||
navigate(`/vehicles/${id}`);
|
navigate(`/garage/vehicles/${id}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -191,4 +191,4 @@ export const VehiclesPage: React.FC = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</VehicleListSuspense>
|
</VehicleListSuspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useAuth0 } from '@auth0/auth0-react';
|
import { useAuth0 } from '@auth0/auth0-react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { HeroCarousel } from './HomePage/HeroCarousel';
|
import { HeroCarousel } from './HomePage/HeroCarousel';
|
||||||
import { FeaturesGrid } from './HomePage/FeaturesGrid';
|
import { FeaturesGrid } from './HomePage/FeaturesGrid';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
export const HomePage = () => {
|
export const HomePage = () => {
|
||||||
const { loginWithRedirect } = useAuth0();
|
const { loginWithRedirect, isAuthenticated } = useAuth0();
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleGetStarted = () => {
|
const handleAuthAction = () => {
|
||||||
loginWithRedirect();
|
if (isAuthenticated) {
|
||||||
|
navigate('/garage');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loginWithRedirect({ appState: { returnTo: '/garage' } });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -38,10 +45,10 @@ export const HomePage = () => {
|
|||||||
About
|
About
|
||||||
</a>
|
</a>
|
||||||
<button
|
<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"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -97,10 +104,10 @@ export const HomePage = () => {
|
|||||||
About
|
About
|
||||||
</a>
|
</a>
|
||||||
<button
|
<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"
|
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>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@@ -133,7 +140,7 @@ export const HomePage = () => {
|
|||||||
your needs.
|
your needs.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<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"
|
className="bg-primary-500 hover:bg-primary-700 text-white font-semibold py-3 px-8 rounded-lg transition-colors duration-300"
|
||||||
>
|
>
|
||||||
Get Started
|
Get Started
|
||||||
@@ -200,7 +207,7 @@ export const HomePage = () => {
|
|||||||
We are a cloud-based platform accessible anywhere, anytime.
|
We are a cloud-based platform accessible anywhere, anytime.
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<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"
|
className="bg-white text-primary-500 hover:bg-gray-100 font-semibold py-3 px-8 rounded-lg transition-colors duration-300"
|
||||||
>
|
>
|
||||||
Get Started
|
Get Started
|
||||||
|
|||||||
Reference in New Issue
Block a user