Spaces:
Runtime error
Runtime error
File size: 4,720 Bytes
34b0226 013ff92 00a4363 aeea10c 00a4363 aeea10c 00a4363 aeea10c 00a4363 e1222dc 00a4363 b74c6ae 00a4363 b74c6ae e1222dc bdd1640 00a4363 34b0226 00a4363 1e1198f 013ff92 b74c6ae 34b0226 00a4363 1e1198f 00a4363 aeea10c b74c6ae aeea10c 8e2a6eb aeea10c 8e2a6eb aeea10c 23cef78 aeea10c 3c705c5 aeea10c 3c705c5 aeea10c 1e1198f aeea10c 00a4363 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | import { useState } from "react";
import { NavLink, Outlet, Navigate, useLocation } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
import { UpgradeProvider } from "../hooks/useUpgrade";
import Logo from "./Logo";
import { FuelGauge, NudgeDock } from "./Nudges";
import SkyCanvas from "./SkyCanvas";
import UpgradeModal from "./UpgradeModal";
import {
LaunchpadIcon,
LibraryIcon,
OrbitIcon,
PlanetIcon,
SatelliteDishIcon,
} from "./icons/Icons";
// Keep the primary navigation focused; related pages remain reachable through these destinations.
const NAV: { to: string; label: string; icon: typeof PlanetIcon; end?: boolean; also?: string[] }[] = [
{ to: "/app/notebooks", label: "Notebooks", icon: PlanetIcon },
{ to: "/app/sources", label: "Sources", icon: SatelliteDishIcon, also: ["/app/map"] },
{ to: "/app/archive", label: "Library", icon: LibraryIcon, also: ["/app/videos"] },
{ to: "/app/launchpad", label: "Launchpad", icon: LaunchpadIcon, also: ["/app/resurface", "/app/launch-kit"] },
];
const COLLAPSED_KEY = "ns_sidebar_collapsed";
function readCollapsed(): boolean {
try {
return localStorage.getItem(COLLAPSED_KEY) === "1";
} catch {
return false;
}
}
export default function AppShell() {
const { user, loading, signingOut, logout } = useAuth();
const location = useLocation();
const notebookFocus = /^\/app\/notebooks\/[^/]+$/.test(location.pathname);
const [collapsed, setCollapsed] = useState(readCollapsed);
const toggle = () =>
setCollapsed((c) => {
try {
localStorage.setItem(COLLAPSED_KEY, c ? "0" : "1");
} catch {
/* private mode: remember for this visit only */
}
return !c;
});
if (loading) return <div className="boot"><div className="orbit-loader"><span /></div></div>;
if (!user) return <Navigate to="/" replace />;
return (
<UpgradeProvider>
<div className={`shell${collapsed ? " collapsed" : ""}${notebookFocus ? " notebook-focus" : ""}`}>
<SkyCanvas intensity={0.35} />
<aside className="sidebar">
<div className="sidebar-top">
{collapsed ? (
<button type="button" className="sidebar-brand sidebar-brand-btn" onClick={toggle} aria-label="Expand sidebar" title="Expand sidebar">
<Logo size={28} withWordmark={false} />
</button>
) : (
<NavLink to="/" className="sidebar-brand">
<Logo size={28} withWordmark={false} />
</NavLink>
)}
<button
type="button"
className="icon-btn sidebar-toggle"
onClick={toggle}
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
aria-expanded={!collapsed}
title={collapsed ? "Expand sidebar" : "Collapse sidebar"}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<rect x="3.5" y="4.5" width="17" height="15" rx="2.5" />
<path d="M9 4.5v15" />
<path d={collapsed ? "M13 10l2 2-2 2" : "M15 10l-2 2 2 2"} />
</svg>
</button>
</div>
<nav aria-label="App">
{NAV.map(({ to, label, icon: Icon, end, also }) => (
<NavLink
key={to}
to={to}
end={end}
title={collapsed ? label : undefined}
className={({ isActive }) => `side-link${isActive || also?.some((p) => location.pathname.startsWith(p)) ? " active" : ""}`}
>
<Icon />
<span className="side-label">{label}</span>
</NavLink>
))}
</nav>
<FuelGauge collapsed={collapsed} />
<NavLink to="/app/settings" title={collapsed ? "Settings" : undefined} className={({ isActive }) => `side-link side-settings${isActive ? " active" : ""}`}>
<OrbitIcon />
<span className="side-label">Settings</span>
</NavLink>
<div className="sidebar-user">
<span className="mono muted side-label">{user.email}</span>
<button className="link-btn" onClick={logout} disabled={signingOut} title="Sign out">
Sign out
</button>
</div>
</aside>
<main className="shell-main">
<Outlet />
</main>
</div>
{/* Outside .shell: its children get position: relative, and these are fixed overlays. */}
<NudgeDock />
<UpgradeModal />
</UpgradeProvider>
);
}
|