Schedule feed kids/keys are persisted on fetch/play; admin backfill covers live lists and events.
143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useEffect, useState } from "react";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
|
|
/** Volledige desktop-navigatie — logische groepen: overzicht → infra → bibliotheek → live → mensen → systeem */
|
|
const desktopLinks = [
|
|
{ href: "/dashboard", label: "Dashboard" },
|
|
{ href: "/nodes", label: "Nodes" },
|
|
{ href: "/movies", label: "Movies" },
|
|
{ href: "/series", label: "Series" },
|
|
{ href: "/pending", label: "Te koppelen" },
|
|
{ href: "/downloads", label: "Downloads" },
|
|
{ href: "/streams", label: "Streams" },
|
|
{ href: "/live-lists", label: "Live-lijsten" },
|
|
{ href: "/drm", label: "DRM" },
|
|
{ href: "/scripts", label: "Scripts" },
|
|
{ href: "/agent", label: "Agent" },
|
|
{ href: "/viewers", label: "Gebruikers" },
|
|
{ href: "/settings", label: "Instellingen" },
|
|
];
|
|
|
|
/** Bottom-nav (max 6): Series via drawer/desktop */
|
|
const mobileLinks = [
|
|
{ href: "/dashboard", label: "Home", short: "Home" },
|
|
{ href: "/nodes", label: "Nodes", short: "Nodes" },
|
|
{ href: "/movies", label: "Movies", short: "Films" },
|
|
{ href: "/downloads", label: "Downloads", short: "DL" },
|
|
{ href: "/streams", label: "Streams", short: "Live" },
|
|
{ href: "/viewers", label: "Users", short: "Users" },
|
|
];
|
|
|
|
export function Nav() {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setMenuOpen(false);
|
|
}, [pathname]);
|
|
|
|
useEffect(() => {
|
|
if (!menuOpen) return;
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") setMenuOpen(false);
|
|
};
|
|
window.addEventListener("keydown", onKey);
|
|
return () => window.removeEventListener("keydown", onKey);
|
|
}, [menuOpen]);
|
|
|
|
async function logout() {
|
|
await fetch("/api/v1/auth/logout", { method: "POST", credentials: "include" });
|
|
router.push("/");
|
|
}
|
|
|
|
const isActive = (href: string) => pathname === href || pathname.startsWith(`${href}/`);
|
|
|
|
return (
|
|
<>
|
|
<nav className="topnav">
|
|
<Link href="/dashboard" className="brand">
|
|
<span className="brand-mark">VMC</span>
|
|
<span className="brand-text">
|
|
Vonas Media Cluster
|
|
<small>ops console</small>
|
|
</span>
|
|
</Link>
|
|
|
|
<div className="topnav-links topnav-links-desktop">
|
|
{desktopLinks.map((l) => (
|
|
<Link key={l.href} href={l.href} className={isActive(l.href) ? "active" : ""}>
|
|
{l.label}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
<button type="button" className="btn-logout topnav-logout-desktop" onClick={logout}>
|
|
Uitloggen
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
className={`nav-burger${menuOpen ? " open" : ""}`}
|
|
aria-label={menuOpen ? "Menu sluiten" : "Menu openen"}
|
|
aria-expanded={menuOpen}
|
|
onClick={() => setMenuOpen((v) => !v)}
|
|
>
|
|
<span />
|
|
<span />
|
|
<span />
|
|
</button>
|
|
</nav>
|
|
|
|
{menuOpen && (
|
|
<div className="nav-drawer-backdrop" onClick={() => setMenuOpen(false)} aria-hidden />
|
|
)}
|
|
<aside className={`nav-drawer${menuOpen ? " open" : ""}`} aria-hidden={!menuOpen}>
|
|
<div className="nav-drawer-head">
|
|
<span className="page-kicker">Navigatie</span>
|
|
<button type="button" className="btn-logout" onClick={() => setMenuOpen(false)}>
|
|
Sluiten
|
|
</button>
|
|
</div>
|
|
<div className="nav-drawer-links">
|
|
{desktopLinks.map((l) => (
|
|
<Link
|
|
key={l.href}
|
|
href={l.href}
|
|
className={isActive(l.href) ? "active" : ""}
|
|
onClick={() => setMenuOpen(false)}
|
|
>
|
|
{l.label}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
<button type="button" className="btn-logout nav-drawer-logout" onClick={logout}>
|
|
Uitloggen
|
|
</button>
|
|
</aside>
|
|
|
|
<nav className="bottomnav" aria-label="Hoofdnavigatie">
|
|
{mobileLinks.map((l) => (
|
|
<Link key={l.href} href={l.href} className={isActive(l.href) ? "active" : ""}>
|
|
<span className="bottomnav-dot" aria-hidden />
|
|
<span>{l.short}</span>
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const router = useRouter();
|
|
useEffect(() => {
|
|
fetch("/api/v1/auth/me", { credentials: "include" })
|
|
.then((r) => {
|
|
if (!r.ok) router.push("/");
|
|
})
|
|
.catch(() => router.push("/"));
|
|
}, [router]);
|
|
}
|