gammanexus/client/src/App.tsx

117 lines
4.0 KiB
TypeScript

import { useEffect } from "react";
import { Switch, Route, Router, useLocation } from "wouter";
import { useHashLocation } from "wouter/use-hash-location";
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { SidebarProvider } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/app-sidebar";
import { TopBar } from "@/components/top-bar";
import { SymbolProvider } from "@/lib/symbol-context";
import { ThemeProvider } from "@/lib/theme-context";
import { AuthProvider, useAuth } from "@/lib/auth-context";
import DashboardPage from "@/pages/dashboard";
import GammaLevelsPage from "@/pages/gamma-levels";
import ExpiryMatrixPage from "@/pages/expiry-matrix";
import ScreenerPage from "@/pages/screener";
import SettingsPage from "@/pages/settings";
import AccountPage from "@/pages/account";
import LoginPage from "@/pages/login";
import NotFound from "@/pages/not-found";
import { Loader2 } from "lucide-react";
function usePageTitle(): string {
const [location] = useLocation();
if (location.startsWith("/gamma")) return "Gamma Levels";
if (location.startsWith("/expiry")) return "Expiry Matrix";
if (location.startsWith("/screener")) return "Screener";
if (location.startsWith("/settings")) return "Settings & API";
if (location.startsWith("/account")) return "Account";
if (location.startsWith("/login")) return "Sign In";
return "Dashboard";
}
// Redirect to login if not authenticated
function ProtectedRoute({ component: Component }: { component: () => JSX.Element }) {
const { user, loading } = useAuth();
const [, setLocation] = useLocation();
// Must call hooks unconditionally before any early return
useEffect(() => {
if (!loading && !user) {
setLocation("/login");
}
}, [loading, user, setLocation]);
if (loading) {
return (
<div className="flex h-screen items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
);
}
if (!user) {
return null;
}
return <Component />;
}
function AppShell() {
const title = usePageTitle();
const [location] = useLocation();
const isAuthPage = location === "/login";
return (
<div className="flex h-screen w-full overflow-hidden">
{!isAuthPage && <AppSidebar />}
<div className="flex min-w-0 flex-1 flex-col">
<TopBar title={title} />
<main
className="flex-1 overflow-y-auto"
style={{ overscrollBehavior: "contain" }}
data-testid="main-content"
>
<Switch>
<Route path="/login" component={LoginPage} />
<Route path="/account" component={AccountPage} />
<Route path="/" component={() => <ProtectedRoute component={DashboardPage} />} />
<Route path="/gamma" component={() => <ProtectedRoute component={GammaLevelsPage} />} />
<Route path="/expiry" component={() => <ProtectedRoute component={ExpiryMatrixPage} />} />
<Route path="/screener" component={() => <ProtectedRoute component={ScreenerPage} />} />
<Route path="/settings" component={() => <ProtectedRoute component={SettingsPage} />} />
<Route component={NotFound} />
</Switch>
</main>
</div>
</div>
);
}
export default function App() {
const sidebarStyle = {
"--sidebar-width": "16rem",
"--sidebar-width-icon": "3.25rem",
} as React.CSSProperties;
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<TooltipProvider delayDuration={200}>
<SymbolProvider>
<AuthProvider>
<Router hook={useHashLocation}>
<SidebarProvider style={sidebarStyle}>
<AppShell />
</SidebarProvider>
</Router>
<Toaster />
</AuthProvider>
</SymbolProvider>
</TooltipProvider>
</ThemeProvider>
</QueryClientProvider>
);
}