add auth
This commit is contained in:
@@ -1,13 +1,34 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { BrowserRouter, Routes, Route, NavLink } from "react-router-dom";
|
||||
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 (
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }}>
|
||||
<span style={{ color: "var(--text-secondary)" }}>Loading…</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
function useTheme() {
|
||||
const [dark, setDark] = useState(
|
||||
() => document.documentElement.getAttribute("data-theme") === "dark"
|
||||
@@ -22,9 +43,12 @@ function useTheme() {
|
||||
}
|
||||
|
||||
function Sidebar({ onToggleTheme, dark, onClose }: { onToggleTheme: () => void; dark: boolean; onClose?: () => void }) {
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const { data: libraries = [] } = useQuery<Library[]>({
|
||||
queryKey: ["libraries"],
|
||||
queryFn: api.libraries.list,
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
const linkStyle = ({ isActive }: { isActive: boolean }) => ({
|
||||
@@ -37,6 +61,11 @@ function Sidebar({ onToggleTheme, dark, onClose }: { onToggleTheme: () => void;
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
});
|
||||
|
||||
function handleLogout() {
|
||||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
|
||||
return (
|
||||
<nav style={{
|
||||
width: 220,
|
||||
@@ -76,12 +105,24 @@ function Sidebar({ onToggleTheme, dark, onClose }: { onToggleTheme: () => void;
|
||||
>
|
||||
{dark ? "☀ Light mode" : "☾ Dark mode"}
|
||||
</button>
|
||||
{user && (
|
||||
<div style={{ fontSize: 11, color: "var(--text-muted)", padding: "4px 12px 0" }}>
|
||||
{user.username}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={{ textAlign: "left", border: "none", background: "transparent", padding: "6px 12px", color: "var(--danger)", borderRadius: 4 }}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
function AppShell() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const { dark, toggle } = useTheme();
|
||||
const [isMobile, setIsMobile] = useState(() => window.innerWidth < 768);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
@@ -97,6 +138,15 @@ function AppShell() {
|
||||
return () => mq.removeEventListener("change", handler);
|
||||
}, []);
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="*" element={<Navigate to="/login" replace />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", height: "100vh", background: "var(--bg)", color: "var(--text)" }}>
|
||||
{/* Mobile hamburger button */}
|
||||
@@ -129,10 +179,11 @@ function AppShell() {
|
||||
|
||||
<main style={{ flex: 1, overflow: "auto", background: "var(--bg)", paddingTop: isMobile ? 48 : 0 }}>
|
||||
<Routes>
|
||||
<Route path="/" element={<SearchPage />} />
|
||||
<Route path="/search" element={<SearchPage />} />
|
||||
<Route path="/library/:libraryId" element={<BrowserPage />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="/" element={<ProtectedRoute><SearchPage /></ProtectedRoute>} />
|
||||
<Route path="/search" element={<ProtectedRoute><SearchPage /></ProtectedRoute>} />
|
||||
<Route path="/library/:libraryId" element={<ProtectedRoute><BrowserPage /></ProtectedRoute>} />
|
||||
<Route path="/settings" element={<ProtectedRoute><SettingsPage /></ProtectedRoute>} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
@@ -142,9 +193,11 @@ function AppShell() {
|
||||
export default function App() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<AppShell />
|
||||
</BrowserRouter>
|
||||
<AuthProvider>
|
||||
<BrowserRouter>
|
||||
<AppShell />
|
||||
</BrowserRouter>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user