second-brain/client/src/components/layout/MainLayout.tsx

96 lines
5.0 KiB
TypeScript

import { useState, useEffect } from "react";
import { Outlet } from "react-router-dom";
import { Sidebar } from "./Sidebar";
import { SettingsModal } from "../modals/SettingsModal";
export function MainLayout() {
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [user, setUser] = useState<{ displayName: string; photo: string } | null>(null);
useEffect(() => {
fetch("/api/me")
.then(res => res.json())
.then(data => setUser(data))
.catch(err => console.error(err));
}, []);
const handleLogout = () => {
window.location.href = "/auth/logout";
};
return (
<div className="flex h-screen w-full flex-col overflow-hidden bg-background-light dark:bg-black text-zinc-900 dark:text-zinc-100 font-display selection:bg-primary/30">
{/* Top Navigation Bar */}
<header className="fixed top-0 z-50 flex h-16 w-full items-center justify-between border-b border-zinc-200 dark:border-zinc-800 bg-white/80 dark:bg-black/80 px-4 md:px-6 backdrop-blur-md">
<div className="flex items-center gap-3">
{/* Mobile Menu Toggle */}
<button
className="md:hidden p-1 text-zinc-500 hover:text-zinc-900 dark:hover:text-white"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
<span className="material-symbols-outlined">menu</span>
</button>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-white">
<span className="material-symbols-outlined !text-xl">psychology</span>
</div>
<h1 className="text-lg font-bold tracking-tight text-zinc-900 dark:text-white hidden sm:block">Second Brain</h1>
</div>
<div className="flex items-center gap-4">
<div className="hidden md:flex relative">
<span className="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500 !text-lg">search</span>
<input className="h-9 w-64 rounded-lg border-zinc-200 bg-zinc-100 px-10 text-sm focus:border-primary focus:ring-primary dark:border-zinc-800 dark:bg-zinc-900 dark:text-white placeholder-zinc-500" placeholder="Search notes..." type="text" />
</div>
<div className="h-8 w-px bg-zinc-200 dark:bg-zinc-800 mx-2 hidden md:block"></div>
<div className="group relative cursor-pointer">
<div className="flex items-center gap-2 rounded-full border border-zinc-200 p-0.5 dark:border-zinc-800">
<div className="h-8 w-8 overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-800">
{user ? (
<img alt={user.displayName} className="h-full w-full object-cover" src={user.photo || "https://lh3.googleusercontent.com/aida-public/AB6AXuCAJG4ZwrzU3vO8ZTXl_Qn6t57zox5sR-Fpj_Vn86v5gmCouUOEs2pEOj7F_lCZUXN_LeunrolqDev9HBMEmxlaKk6vmUncxIwTZLN8WaDD2cC2QefcnCoOKz7ZLAbtklJiqrHUuqHBkErkY4i9kG6REqojHeHfL4ick60Aa2i-95TrqtVZzaIhabjnSJyGN752oqy-AryELm9M3BdATrjXDpXz6SKH46bfE8vBAkz8TCv0rx71f5rb26Pgsu3Dw-w1c_fnsJwDzlUP"} />
) : (
<div className="h-full w-full bg-zinc-300 animate-pulse" />
)}
</div>
</div>
{/* Hover Dropdown Menu */}
<div className="absolute right-0 top-full mt-2 w-48 scale-95 opacity-0 transition-all group-hover:scale-100 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto">
<div className="rounded-xl border border-zinc-200 bg-white p-1.5 shadow-xl dark:border-zinc-800 dark:bg-zinc-900">
<button onClick={() => setIsSettingsOpen(true)} className="w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 transition-colors">
<span className="material-symbols-outlined !text-lg">settings</span>
Settings
</button>
<div className="my-1 border-t border-zinc-100 dark:border-zinc-800"></div>
<button onClick={handleLogout} className="w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm text-red-600 hover:bg-red-50 dark:hover:bg-red-950/30 transition-colors">
<span className="material-symbols-outlined !text-lg">logout</span>
Sign out
</button>
</div>
</div>
</div>
</div>
</header>
<div className="flex h-full pt-16 relative">
{/* Sidebar */}
<Sidebar
onOpenSettings={() => setIsSettingsOpen(true)}
isOpen={isMobileMenuOpen}
onClose={() => setIsMobileMenuOpen(false)}
/>
{/* Main Content Area */}
<main className="flex-1 overflow-hidden relative bg-white dark:bg-zinc-950">
<Outlet />
</main>
</div>
<SettingsModal
isOpen={isSettingsOpen}
onClose={() => setIsSettingsOpen(false)}
/>
</div >
);
}