import { useState, useRef, useCallback } from "react"; import { useTranslation } from 'react-i18next'; import { BrowserRouter, Routes, Route, NavLink, Navigate, useLocation, useNavigate } from "react-router-dom"; import Dashboard from "./Dashboard"; import Analysis from "./Analysis"; import Statistics from "./Statistics"; import Settings from "./Settings"; import Intro from "./Intro"; import Login from "./Login"; import Register from "./Register"; import { SettingsIcon, HomeIcon, ActivityIcon, BarChart3, Menu, X, LogOut, Moon, Sun } from 'lucide-react'; import { AuthProvider, useAuth } from './context/AuthContext'; import { ThemeProvider, useTheme } from './context/ThemeContext'; import LoadingOverlay from './components/LoadingOverlay'; const NAV_ITEMS = [ { icon: HomeIcon, label: "Dashboard", path: "/" }, { icon: ActivityIcon, label: "Analysis", path: "/stats" }, { icon: BarChart3, label: "Statistics", path: "/charts" }, { icon: SettingsIcon, label: "Settings", path: "/settings" }, ]; function ProtectedRoute({ children }) { const { user, loading } = useAuth(); const location = useLocation(); if (loading) return
Loading...
; if (!user) return ; return children; } function MainAppShell() { const { t } = useTranslation(); const { theme, toggleTheme } = useTheme(); const { user, logout } = useAuth(); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const navigate = useNavigate(); const location = useLocation(); const touchStartX = useRef(null); const touchStartY = useRef(null); const isHorizontalSwipe = useRef(false); const swipeDir = useRef(0); // -1 = left, +1 = right const [dragX, setDragX] = useState(0); const [slideClass, setSlideClass] = useState(''); const [showIntro, setShowIntro] = useState(() => { return !sessionStorage.getItem('introPlayed'); }); const handleIntroComplete = () => { sessionStorage.setItem('introPlayed', 'true'); setShowIntro(false); }; const handleTouchStart = useCallback((e) => { touchStartX.current = e.touches[0].clientX; touchStartY.current = e.touches[0].clientY; isHorizontalSwipe.current = false; }, []); const handleTouchMove = useCallback((e) => { if (touchStartX.current === null) return; const deltaX = e.touches[0].clientX - touchStartX.current; const deltaY = e.touches[0].clientY - touchStartY.current; // Decide swipe axis on first meaningful movement if (!isHorizontalSwipe.current && (Math.abs(deltaX) > 8 || Math.abs(deltaY) > 8)) { isHorizontalSwipe.current = Math.abs(deltaX) > Math.abs(deltaY); } if (isHorizontalSwipe.current) { // Prevent vertical scroll while swiping horizontally e.preventDefault(); const currentIndex = NAV_ITEMS.findIndex(item => item.path === location.pathname); // Block drag if already at edge const canGoLeft = currentIndex < NAV_ITEMS.length - 1; const canGoRight = currentIndex > 0; if ((deltaX < 0 && !canGoLeft) || (deltaX > 0 && !canGoRight)) { // Allow small rubber-band at edges setDragX(deltaX * 0.1); } else { setDragX(deltaX * 0.35); // dampened drag feel } } }, [location.pathname]); const handleTouchEnd = useCallback((e) => { if (touchStartX.current === null) return; const deltaX = e.changedTouches[0].clientX - touchStartX.current; const deltaY = e.changedTouches[0].clientY - touchStartY.current; if (isHorizontalSwipe.current && Math.abs(deltaX) > 60) { const currentIndex = NAV_ITEMS.findIndex(item => item.path === location.pathname); if (deltaX < 0 && currentIndex < NAV_ITEMS.length - 1) { swipeDir.current = -1; setSlideClass('slide-in-left'); setDragX(0); navigate(NAV_ITEMS[currentIndex + 1].path); } else if (deltaX > 0 && currentIndex > 0) { swipeDir.current = 1; setSlideClass('slide-in-right'); setDragX(0); navigate(NAV_ITEMS[currentIndex - 1].path); } else { setDragX(0); } } else { setDragX(0); } touchStartX.current = null; touchStartY.current = null; isHorizontalSwipe.current = false; }, [location.pathname, navigate]); return ( <> {showIntro && }
{/* SIDEBAR (Desktop Only) */} {/* MAIN CONTENT WRAPPER */}
{/* MOBILE HEADER */}
Logo
setSlideClass('')} style={{ transform: dragX !== 0 ? `translateX(${dragX}px)` : undefined, transition: dragX === 0 ? 'transform 0.25s ease' : 'none', willChange: 'transform', }} > } /> } /> } /> } />
{/* MOBILE BOTTOM NAV — fixed to viewport bottom */}
); } function AuthLoadingGate({ children }) { const { authLoading, authLoadingMessage } = useAuth(); return ( <> {authLoading && } {children} ); } export function App() { return ( } /> } /> } /> ); } export default App;