import { useEffect, useState } from "react"; import { BrowserRouter, Routes, Route, NavLink, Navigate, useNavigate } from "react-router-dom"; import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query"; import { api, type Library } from "./api/client"; import { AuthProvider } from "./auth/AuthContext"; import { useAuth } from "./auth/useAuth"; import BrowserPage from "./pages/BrowserPage"; import SettingsPage from "./pages/SettingsPage"; import SearchPage from "./pages/SearchPage"; import LoginPage from "./pages/LoginPage"; const queryClient = new QueryClient(); function ProtectedRoute({ children }: { children: React.ReactNode }) { const { isAuthenticated, isLoading } = useAuth(); if (isLoading) { return (
Loading…
); } if (!isAuthenticated) { return ; } return <>{children}; } function useTheme() { const [dark, setDark] = useState( () => document.documentElement.getAttribute("data-theme") === "dark" ); useEffect(() => { document.documentElement.setAttribute("data-theme", dark ? "dark" : "light"); localStorage.setItem("theme", dark ? "dark" : "light"); }, [dark]); return { dark, toggle: () => setDark((d) => !d) }; } function Sidebar({ onToggleTheme, dark, onClose }: { onToggleTheme: () => void; dark: boolean; onClose?: () => void }) { const { user, logout } = useAuth(); const navigate = useNavigate(); const { data: libraries = [] } = useQuery({ queryKey: ["libraries"], queryFn: api.libraries.list, enabled: !!user, }); const linkStyle = ({ isActive }: { isActive: boolean }) => ({ display: "block", padding: "6px 12px", textDecoration: "none", borderRadius: 4, color: isActive ? "var(--accent)" : "var(--text)", background: isActive ? "var(--accent-bg)" : "transparent", fontWeight: isActive ? 600 : 400, }); function handleLogout() { logout(); navigate("/login"); } return ( ); } function AppShell() { const { isAuthenticated } = useAuth(); const { dark, toggle } = useTheme(); const [isMobile, setIsMobile] = useState(() => window.innerWidth < 768); const [sidebarOpen, setSidebarOpen] = useState(false); useEffect(() => { const mq = window.matchMedia("(max-width: 767px)"); setIsMobile(mq.matches); const handler = (e: MediaQueryListEvent) => { setIsMobile(e.matches); if (!e.matches) setSidebarOpen(false); }; mq.addEventListener("change", handler); return () => mq.removeEventListener("change", handler); }, []); if (!isAuthenticated) { return ( } /> } /> ); } return (
{/* Mobile hamburger button */} {isMobile && ( )} {/* Mobile backdrop */} {isMobile && sidebarOpen && (
setSidebarOpen(false)} style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)", zIndex: 299 }} /> )} {/* Sidebar */}
setSidebarOpen(false) : undefined} />
} /> } /> } /> } /> } />
); } export default function App() { return ( ); }