Spaces:
Runtime error
Runtime error
File size: 4,624 Bytes
34b0226 013ff92 00a4363 aeea10c 00a4363 aeea10c 00a4363 aeea10c 00a4363 34b0226 00a4363 013ff92 34b0226 00a4363 013ff92 00a4363 aeea10c 8e2a6eb aeea10c 8e2a6eb aeea10c 23cef78 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 119 120 121 122 123 | 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 {
AsteroidIcon,
CometIcon,
ConstellationIcon,
HelmetIcon,
LaunchpadIcon,
LaunchWindowIcon,
OrbitIcon,
PlanetIcon,
RadarIcon,
RocketIcon,
SatelliteDishIcon,
} from "./icons/Icons";
// Theme in the icon, clarity in the label: the plain label always shows.
const NAV = [
{ to: "/app", label: "Mission Control", icon: RadarIcon, end: true },
{ to: "/app/notebooks", label: "Notebooks", icon: PlanetIcon },
{ to: "/app/sources", label: "Sources", icon: SatelliteDishIcon },
{ to: "/app/map", label: "Topic map", icon: ConstellationIcon },
{ to: "/app/voice", label: "Voice profile", icon: HelmetIcon },
{ to: "/app/studio", label: "Video and audio", icon: LaunchWindowIcon },
{ to: "/app/launch-kit", label: "Launch Kit", icon: RocketIcon },
{ to: "/app/launchpad", label: "Launchpad", icon: LaunchpadIcon },
{ to: "/app/archive", label: "Archive", icon: AsteroidIcon },
{ to: "/app/resurface", label: "Resurfacing", icon: CometIcon },
{ to: "/app/settings", label: "Settings", icon: OrbitIcon },
];
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, logout } = useAuth();
const location = useLocation();
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) {
const next = new URLSearchParams({ next: location.pathname + location.search });
return <Navigate to={`/auth?${next}`} replace />;
}
return (
<UpgradeProvider>
<div className={`shell${collapsed ? " collapsed" : ""}`}>
<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 }) => (
<NavLink key={to} to={to} end={end} title={collapsed ? label : undefined} className={({ isActive }) => `side-link${isActive ? " active" : ""}`}>
<Icon />
<span className="side-label">{label}</span>
</NavLink>
))}
</nav>
<FuelGauge collapsed={collapsed} />
<div className="sidebar-user">
<span className="mono muted side-label">{user.email}</span>
<button className="link-btn" onClick={logout} 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>
);
}
|