Patchpass V1
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { ReactNode, useState } from "react";
|
||||
import { Link, NavLink, useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { NotificationBell } from "./NotificationBell";
|
||||
|
||||
const navItems = [
|
||||
{ to: "/", label: "Dashboard", end: true },
|
||||
{ to: "/requests", label: "Requests" },
|
||||
{ to: "/agents", label: "Agents" },
|
||||
{ to: "/settings", label: "Settings" },
|
||||
];
|
||||
|
||||
export function Layout({ children }: { children: ReactNode }) {
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<header className="sticky top-0 z-30 border-b border-border bg-bg/80 backdrop-blur">
|
||||
<div className="mx-auto flex max-w-6xl items-center justify-between px-4 py-3">
|
||||
<div className="flex items-center gap-6">
|
||||
<Link to="/" className="flex items-center gap-2">
|
||||
<span className="text-xl">✅</span>
|
||||
<span className="text-lg font-bold tracking-tight">
|
||||
Patch<span className="text-primary">Pass</span>
|
||||
</span>
|
||||
</Link>
|
||||
<nav className="hidden items-center gap-1 md:flex">
|
||||
{navItems.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
end={item.end}
|
||||
className={({ isActive }) =>
|
||||
`rounded-lg px-3 py-1.5 text-sm font-medium transition ${
|
||||
isActive ? "bg-surface-raised text-text" : "text-muted hover:text-text"
|
||||
}`
|
||||
}
|
||||
>
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
{user?.role === "ADMIN" && (
|
||||
<NavLink
|
||||
to="/admin"
|
||||
className={({ isActive }) =>
|
||||
`rounded-lg px-3 py-1.5 text-sm font-medium transition ${
|
||||
isActive ? "bg-surface-raised text-accent" : "text-accent/70 hover:text-accent"
|
||||
}`
|
||||
}
|
||||
>
|
||||
Admin
|
||||
</NavLink>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<NotificationBell />
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setMenuOpen((o) => !o)}
|
||||
className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm hover:bg-surface-raised"
|
||||
>
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-primary-dim/50 text-xs font-semibold text-primary">
|
||||
{user?.display_name?.[0]?.toUpperCase()}
|
||||
</span>
|
||||
<span className="hidden text-text sm:inline">{user?.display_name}</span>
|
||||
</button>
|
||||
{menuOpen && (
|
||||
<div
|
||||
className="animate-fade-in absolute right-0 mt-2 w-44 rounded-xl border border-border-strong bg-surface py-1 shadow-2xl"
|
||||
onMouseLeave={() => setMenuOpen(false)}
|
||||
>
|
||||
<Link
|
||||
to="/settings"
|
||||
className="block px-4 py-2 text-sm text-muted hover:bg-surface-raised hover:text-text"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
<Link
|
||||
to="/notifications"
|
||||
className="block px-4 py-2 text-sm text-muted hover:bg-surface-raised hover:text-text md:hidden"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
Notifications
|
||||
</Link>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await logout();
|
||||
navigate("/login");
|
||||
}}
|
||||
className="block w-full px-4 py-2 text-left text-sm text-rejected hover:bg-surface-raised"
|
||||
>
|
||||
Log out
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* mobile nav */}
|
||||
<nav className="flex items-center gap-1 overflow-x-auto border-t border-border px-4 py-2 md:hidden">
|
||||
{navItems.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
end={item.end}
|
||||
className={({ isActive }) =>
|
||||
`whitespace-nowrap rounded-lg px-3 py-1.5 text-sm font-medium ${
|
||||
isActive ? "bg-surface-raised text-text" : "text-muted"
|
||||
}`
|
||||
}
|
||||
>
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
{user?.role === "ADMIN" && (
|
||||
<NavLink to="/admin" className="whitespace-nowrap rounded-lg px-3 py-1.5 text-sm text-accent">
|
||||
Admin
|
||||
</NavLink>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main className="mx-auto max-w-6xl px-4 py-8">{children}</main>
|
||||
|
||||
<footer className="mx-auto max-w-6xl px-4 py-8 text-center text-xs text-faint">
|
||||
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||
<Link to="/privacy" className="hover:text-muted">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
<span>·</span>
|
||||
<Link to="/terms" className="hover:text-muted">
|
||||
Terms of Service
|
||||
</Link>
|
||||
<span>·</span>
|
||||
<span>PatchPass — review changes, approve intent, let agents proceed.</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user