Initial Commit
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @ai-summary Main app component with routing and mobile navigation
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useTransition, lazy } from 'react';
|
||||
import { useState, useEffect, useTransition, useCallback, lazy } from 'react';
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { useAuth0 } from '@auth0/auth0-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
@@ -14,9 +14,13 @@ import LocalGasStationRoundedIcon from '@mui/icons-material/LocalGasStationRound
|
||||
import SettingsRoundedIcon from '@mui/icons-material/SettingsRounded';
|
||||
import { md3Theme } from './shared-minimal/theme/md3Theme';
|
||||
import { Layout } from './components/Layout';
|
||||
import { UnitsProvider } from './core/units/UnitsContext';
|
||||
|
||||
// Lazy load route components for better initial bundle size
|
||||
const VehiclesPage = lazy(() => import('./features/vehicles/pages/VehiclesPage').then(m => ({ default: m.VehiclesPage })));
|
||||
const VehicleDetailPage = lazy(() => import('./features/vehicles/pages/VehicleDetailPage').then(m => ({ default: m.VehicleDetailPage })));
|
||||
const SettingsPage = lazy(() => import('./pages/SettingsPage').then(m => ({ default: m.SettingsPage })));
|
||||
const FuelLogsPage = lazy(() => import('./features/fuel-logs/pages/FuelLogsPage').then(m => ({ default: m.FuelLogsPage })));
|
||||
const VehiclesMobileScreen = lazy(() => import('./features/vehicles/mobile/VehiclesMobileScreen').then(m => ({ default: m.VehiclesMobileScreen })));
|
||||
const VehicleDetailMobile = lazy(() => import('./features/vehicles/mobile/VehicleDetailMobile').then(m => ({ default: m.VehicleDetailMobile })));
|
||||
import { BottomNavigation, NavigationItem } from './shared-minimal/components/mobile/BottomNavigation';
|
||||
@@ -24,26 +28,53 @@ import { GlassCard } from './shared-minimal/components/mobile/GlassCard';
|
||||
import { Button } from './shared-minimal/components/Button';
|
||||
import { RouteSuspense } from './components/SuspenseWrappers';
|
||||
import { Vehicle } from './features/vehicles/types/vehicles.types';
|
||||
import { FuelLogForm } from './features/fuel-logs/components/FuelLogForm';
|
||||
import { FuelLogsList } from './features/fuel-logs/components/FuelLogsList';
|
||||
import { useFuelLogs } from './features/fuel-logs/hooks/useFuelLogs';
|
||||
import { VehicleForm } from './features/vehicles/components/VehicleForm';
|
||||
import { useOptimisticVehicles } from './features/vehicles/hooks/useOptimisticVehicles';
|
||||
import { CreateVehicleRequest } from './features/vehicles/types/vehicles.types';
|
||||
import { MobileSettingsScreen } from './features/settings/mobile/MobileSettingsScreen';
|
||||
import { useNavigationStore, useUserStore } from './core/store';
|
||||
import { useDataSync } from './core/hooks/useDataSync';
|
||||
import { MobileDebugPanel } from './core/debug/MobileDebugPanel';
|
||||
import { MobileErrorBoundary } from './core/error-boundaries/MobileErrorBoundary';
|
||||
|
||||
|
||||
function App() {
|
||||
const { isLoading, isAuthenticated, loginWithRedirect } = useAuth0();
|
||||
const { isLoading, isAuthenticated, loginWithRedirect, user } = useAuth0();
|
||||
const [_isPending, startTransition] = useTransition();
|
||||
|
||||
// Mobile navigation state - detect mobile screen size with responsive updates
|
||||
|
||||
// Initialize data synchronization
|
||||
const { prefetchForNavigation } = useDataSync();
|
||||
|
||||
// Enhanced navigation and user state management
|
||||
const {
|
||||
activeScreen,
|
||||
vehicleSubScreen,
|
||||
navigateToScreen,
|
||||
navigateToVehicleSubScreen,
|
||||
goBack,
|
||||
canGoBack,
|
||||
} = useNavigationStore();
|
||||
|
||||
const { setUserProfile } = useUserStore();
|
||||
|
||||
// Mobile mode detection - detect mobile screen size with responsive updates
|
||||
const [mobileMode, setMobileMode] = useState(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.innerWidth <= 768;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const [activeScreen, setActiveScreen] = useState("Vehicles");
|
||||
|
||||
const [selectedVehicle, setSelectedVehicle] = useState<Vehicle | null>(null);
|
||||
const [showAddVehicle, setShowAddVehicle] = useState(false);
|
||||
|
||||
// Update mobile mode on window resize
|
||||
useEffect(() => {
|
||||
const checkMobileMode = () => {
|
||||
const isMobile = window.innerWidth <= 768 ||
|
||||
const isMobile = window.innerWidth <= 768 ||
|
||||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
console.log('Window width:', window.innerWidth, 'User agent mobile:', /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), 'Mobile mode:', isMobile);
|
||||
setMobileMode(isMobile);
|
||||
@@ -51,11 +82,35 @@ function App() {
|
||||
|
||||
// Check on mount
|
||||
checkMobileMode();
|
||||
|
||||
|
||||
window.addEventListener('resize', checkMobileMode);
|
||||
return () => window.removeEventListener('resize', checkMobileMode);
|
||||
}, []);
|
||||
|
||||
// Update user profile when authenticated
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && user) {
|
||||
setUserProfile(user);
|
||||
}
|
||||
}, [isAuthenticated, user, setUserProfile]);
|
||||
|
||||
// Handle mobile back button and navigation errors
|
||||
useEffect(() => {
|
||||
const handlePopState = (event: PopStateEvent) => {
|
||||
event.preventDefault();
|
||||
if (canGoBack() && mobileMode) {
|
||||
goBack();
|
||||
}
|
||||
};
|
||||
|
||||
if (mobileMode) {
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [goBack, canGoBack, mobileMode]);
|
||||
|
||||
// Mobile navigation items
|
||||
const mobileNavItems: NavigationItem[] = [
|
||||
{ key: "Dashboard", label: "Dashboard", icon: <HomeRoundedIcon /> },
|
||||
@@ -64,13 +119,33 @@ function App() {
|
||||
{ key: "Settings", label: "Settings", icon: <SettingsRoundedIcon /> },
|
||||
];
|
||||
|
||||
console.log('MotoVaultPro status:', { isLoading, isAuthenticated, mobileMode, userAgent: navigator.userAgent });
|
||||
console.log('MotoVaultPro status:', { isLoading, isAuthenticated, mobileMode, activeScreen, vehicleSubScreen, userAgent: navigator.userAgent });
|
||||
|
||||
// Debug component for testing
|
||||
// Enhanced navigation handlers for mobile
|
||||
const handleVehicleSelect = useCallback((vehicle: Vehicle) => {
|
||||
setSelectedVehicle(vehicle);
|
||||
navigateToVehicleSubScreen('detail', vehicle.id, { source: 'vehicle-list' });
|
||||
}, [navigateToVehicleSubScreen]);
|
||||
|
||||
const handleAddVehicle = useCallback(() => {
|
||||
setShowAddVehicle(true);
|
||||
navigateToVehicleSubScreen('add', undefined, { source: 'vehicle-list' });
|
||||
}, [navigateToVehicleSubScreen]);
|
||||
|
||||
const handleBackToList = useCallback(() => {
|
||||
setSelectedVehicle(null);
|
||||
setShowAddVehicle(false);
|
||||
navigateToVehicleSubScreen('list', undefined, { source: 'back-navigation' });
|
||||
}, [navigateToVehicleSubScreen]);
|
||||
|
||||
const handleVehicleAdded = useCallback(() => {
|
||||
setShowAddVehicle(false);
|
||||
navigateToVehicleSubScreen('list', undefined, { source: 'vehicle-added' });
|
||||
}, [navigateToVehicleSubScreen]);
|
||||
|
||||
// Enhanced debug component
|
||||
const DebugInfo = () => (
|
||||
<div className="fixed bottom-0 right-0 bg-black/80 text-white p-2 text-xs z-50 rounded-tl-lg">
|
||||
Mode: {mobileMode ? 'Mobile' : 'Desktop'} | Auth: {isAuthenticated ? 'Yes' : 'No'} | Screen: {typeof window !== 'undefined' ? window.innerWidth : 'N/A'}px
|
||||
</div>
|
||||
<MobileDebugPanel visible={import.meta.env.MODE === 'development'} />
|
||||
);
|
||||
|
||||
// Placeholder screens for mobile
|
||||
@@ -85,27 +160,85 @@ function App() {
|
||||
</div>
|
||||
);
|
||||
|
||||
const LogFuelScreen = () => (
|
||||
<div className="space-y-4">
|
||||
<GlassCard>
|
||||
<div className="text-center py-12">
|
||||
<h2 className="text-lg font-semibold text-slate-800 mb-2">Log Fuel</h2>
|
||||
<p className="text-slate-500">Coming soon - Fuel logging functionality</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
const LogFuelScreen = () => {
|
||||
const { fuelLogs, isLoading, error } = useFuelLogs();
|
||||
|
||||
const SettingsScreen = () => (
|
||||
<div className="space-y-4">
|
||||
<GlassCard>
|
||||
<div className="text-center py-12">
|
||||
<h2 className="text-lg font-semibold text-slate-800 mb-2">Settings</h2>
|
||||
<p className="text-slate-500">Coming soon - App settings and preferences</p>
|
||||
if (error) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<GlassCard>
|
||||
<div className="text-center py-8">
|
||||
<p className="text-red-600 mb-4">Failed to load fuel logs</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<FuelLogForm />
|
||||
<GlassCard>
|
||||
<div className="py-2">
|
||||
{isLoading ? (
|
||||
<div className="text-center py-8 text-slate-500">
|
||||
Loading fuel logs...
|
||||
</div>
|
||||
) : (
|
||||
<FuelLogsList logs={fuelLogs || []} />
|
||||
)}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Mobile settings now uses the dedicated MobileSettingsScreen component
|
||||
const SettingsScreen = MobileSettingsScreen;
|
||||
|
||||
const AddVehicleScreen = () => {
|
||||
// Vehicle creation logic
|
||||
const { optimisticCreateVehicle } = useOptimisticVehicles([]);
|
||||
|
||||
const handleCreateVehicle = async (data: CreateVehicleRequest) => {
|
||||
try {
|
||||
await optimisticCreateVehicle(data);
|
||||
// Success - navigate back to list
|
||||
handleVehicleAdded();
|
||||
} catch (error) {
|
||||
console.error('Failed to create vehicle:', error);
|
||||
// Error handling is done by the useOptimisticVehicles hook via toast
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<GlassCard>
|
||||
<div className="p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-slate-800">Add Vehicle</h2>
|
||||
<button
|
||||
onClick={handleBackToList}
|
||||
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<VehicleForm
|
||||
onSubmit={handleCreateVehicle}
|
||||
onCancel={handleBackToList}
|
||||
/>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
if (mobileMode) {
|
||||
@@ -175,50 +308,99 @@ function App() {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<Layout mobileMode={true}>
|
||||
<AnimatePresence mode="wait">
|
||||
<UnitsProvider>
|
||||
<Layout mobileMode={true}>
|
||||
<AnimatePresence mode="popLayout" initial={false}>
|
||||
{activeScreen === "Dashboard" && (
|
||||
<motion.div key="dashboard" initial={{opacity:0, y:8}} animate={{opacity:1, y:0}} exit={{opacity:0, y:-8}}>
|
||||
<DashboardScreen />
|
||||
<motion.div
|
||||
key="dashboard"
|
||||
initial={{opacity:0, y:8}}
|
||||
animate={{opacity:1, y:0}}
|
||||
exit={{opacity:0, y:-8}}
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
>
|
||||
<MobileErrorBoundary screenName="Dashboard">
|
||||
<DashboardScreen />
|
||||
</MobileErrorBoundary>
|
||||
</motion.div>
|
||||
)}
|
||||
{activeScreen === "Vehicles" && (
|
||||
<motion.div key="vehicles" initial={{opacity:0, y:8}} animate={{opacity:1, y:0}} exit={{opacity:0, y:-8}} className="space-y-6">
|
||||
{selectedVehicle ? (
|
||||
<VehicleDetailMobile
|
||||
vehicle={selectedVehicle}
|
||||
onBack={() => setSelectedVehicle(null)}
|
||||
onLogFuel={() => setActiveScreen("Log Fuel")}
|
||||
/>
|
||||
) : (
|
||||
<VehiclesMobileScreen
|
||||
onVehicleSelect={(vehicle) => setSelectedVehicle(vehicle)}
|
||||
/>
|
||||
)}
|
||||
<motion.div
|
||||
key="vehicles"
|
||||
initial={{opacity:0, y:8}}
|
||||
animate={{opacity:1, y:0}}
|
||||
exit={{opacity:0, y:-8}}
|
||||
className="space-y-6"
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
>
|
||||
<MobileErrorBoundary screenName="Vehicles">
|
||||
{vehicleSubScreen === 'add' || showAddVehicle ? (
|
||||
<AddVehicleScreen />
|
||||
) : selectedVehicle && (vehicleSubScreen === 'detail') ? (
|
||||
<VehicleDetailMobile
|
||||
vehicle={selectedVehicle}
|
||||
onBack={handleBackToList}
|
||||
onLogFuel={() => navigateToScreen("Log Fuel")}
|
||||
/>
|
||||
) : (
|
||||
<VehiclesMobileScreen
|
||||
onVehicleSelect={handleVehicleSelect}
|
||||
onAddVehicle={handleAddVehicle}
|
||||
/>
|
||||
)}
|
||||
</MobileErrorBoundary>
|
||||
</motion.div>
|
||||
)}
|
||||
{activeScreen === "Log Fuel" && (
|
||||
<motion.div key="logfuel" initial={{opacity:0, y:8}} animate={{opacity:1, y:0}} exit={{opacity:0, y:-8}}>
|
||||
<LogFuelScreen />
|
||||
<motion.div
|
||||
key="logfuel"
|
||||
initial={{opacity:0, y:8}}
|
||||
animate={{opacity:1, y:0}}
|
||||
exit={{opacity:0, y:-8}}
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
>
|
||||
<MobileErrorBoundary screenName="Log Fuel">
|
||||
<LogFuelScreen />
|
||||
</MobileErrorBoundary>
|
||||
</motion.div>
|
||||
)}
|
||||
{activeScreen === "Settings" && (
|
||||
<motion.div key="settings" initial={{opacity:0, y:8}} animate={{opacity:1, y:0}} exit={{opacity:0, y:-8}}>
|
||||
<SettingsScreen />
|
||||
<motion.div
|
||||
key="settings"
|
||||
initial={{opacity:0, y:8}}
|
||||
animate={{opacity:1, y:0}}
|
||||
exit={{opacity:0, y:-8}}
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
>
|
||||
<MobileErrorBoundary screenName="Settings">
|
||||
<SettingsScreen />
|
||||
</MobileErrorBoundary>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<DebugInfo />
|
||||
</Layout>
|
||||
|
||||
<BottomNavigation
|
||||
<BottomNavigation
|
||||
items={mobileNavItems}
|
||||
activeItem={activeScreen}
|
||||
onItemSelect={(screen) => startTransition(() => {
|
||||
setActiveScreen(screen);
|
||||
setSelectedVehicle(null); // Reset selected vehicle on navigation
|
||||
// Prefetch data for the target screen
|
||||
prefetchForNavigation(screen);
|
||||
|
||||
// Reset states first, then navigate to prevent race conditions
|
||||
if (screen !== 'Vehicles') {
|
||||
setSelectedVehicle(null); // Reset selected vehicle when leaving Vehicles
|
||||
}
|
||||
if (screen !== 'Vehicles' || vehicleSubScreen !== 'add') {
|
||||
setShowAddVehicle(false); // Reset add vehicle form when appropriate
|
||||
}
|
||||
|
||||
// Navigate after state cleanup
|
||||
navigateToScreen(screen as any, { source: 'bottom-navigation' });
|
||||
})}
|
||||
/>
|
||||
</UnitsProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
@@ -227,22 +409,26 @@ function App() {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<Layout mobileMode={false}>
|
||||
<UnitsProvider>
|
||||
<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={<div>Vehicle Details (TODO)</div>} />
|
||||
<Route path="/fuel-logs" element={<div>Fuel Logs (TODO)</div>} />
|
||||
<Route path="/vehicles/:id" element={<VehicleDetailPage />} />
|
||||
<Route path="/fuel-logs" element={<FuelLogsPage />} />
|
||||
<Route path="/maintenance" element={<div>Maintenance (TODO)</div>} />
|
||||
<Route path="/stations" element={<div>Stations (TODO)</div>} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="*" element={<Navigate to="/vehicles" replace />} />
|
||||
</Routes>
|
||||
</RouteSuspense>
|
||||
<DebugInfo />
|
||||
</Layout>
|
||||
</UnitsProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user