142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import Head from "next/head";
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/router";
|
|
import { ReactNode } from "react";
|
|
import { useAuth } from "../lib/auth-context";
|
|
|
|
const NAV_ITEMS = [
|
|
{ href: "/", label: "Jobs" },
|
|
{ href: "/settings/devices", label: "Devices" },
|
|
{ href: "/settings/members", label: "Members" },
|
|
{ href: "/settings/api-keys", label: "API keys" },
|
|
{ href: "/settings/mqtt-certs", label: "MQTT certs" },
|
|
];
|
|
|
|
export default function Layout({
|
|
children,
|
|
title,
|
|
}: {
|
|
children: ReactNode;
|
|
title?: string;
|
|
}) {
|
|
const { user, memberships, activeOrg, setActiveOrgId, logout } = useAuth();
|
|
const router = useRouter();
|
|
|
|
const isActive = (href: string) =>
|
|
href === "/"
|
|
? router.pathname === "/" || router.pathname.startsWith("/jobs")
|
|
: router.pathname.startsWith(href);
|
|
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
router.push("/login");
|
|
};
|
|
|
|
const navLinks = NAV_ITEMS.map((item) => (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className="ul-shell__nav-link"
|
|
aria-current={isActive(item.href) ? "page" : undefined}
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
));
|
|
|
|
return (
|
|
<>
|
|
<Head>
|
|
<title>{title ? `${title} · UlHub` : "UlHub"}</title>
|
|
</Head>
|
|
<a className="ul-skip-link" href="#main-content">
|
|
Skip to main content
|
|
</a>
|
|
<header className="ul-shell__header">
|
|
<Link href="/" className="ul-shell__brand" aria-label="UlHub jobs home">
|
|
<Image
|
|
className="ul-shell__brand-mark"
|
|
src="/um-trace-mark.png"
|
|
alt=""
|
|
width={40}
|
|
height={40}
|
|
priority
|
|
unoptimized
|
|
/>
|
|
<span>UlHub</span>
|
|
</Link>
|
|
{user && (
|
|
<>
|
|
<nav
|
|
className="ul-shell__nav ul-shell__nav--desktop"
|
|
aria-label="Primary navigation"
|
|
>
|
|
{navLinks}
|
|
</nav>
|
|
<div className="ul-shell__context">
|
|
<div className="ul-org-context">
|
|
<span className="ul-org-context__label" id="active-org-label">
|
|
Active organization
|
|
</span>
|
|
{memberships.length > 1 ? (
|
|
<select
|
|
className="ul-org-context__select"
|
|
aria-labelledby="active-org-label"
|
|
value={activeOrg?.org.id ?? ""}
|
|
onChange={(event) => setActiveOrgId(event.target.value)}
|
|
>
|
|
{memberships.map((membership) => (
|
|
<option key={membership.org.id} value={membership.org.id}>
|
|
{membership.org.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
) : (
|
|
<strong className="ul-org-context__name">
|
|
{activeOrg?.org.name ?? "No organization"}
|
|
</strong>
|
|
)}
|
|
{activeOrg && (
|
|
<span className="ul-org-context__role">
|
|
{activeOrg.role.replace("_", " ")}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<details className="ul-shell__menu">
|
|
<summary>Menu</summary>
|
|
<nav
|
|
className="ul-shell__nav ul-shell__nav--mobile"
|
|
aria-label="Mobile navigation"
|
|
>
|
|
{navLinks}
|
|
<div className="ul-shell__mobile-account">
|
|
<span>{user.email}</span>
|
|
<button
|
|
className="ul-button ul-button--quiet"
|
|
onClick={handleLogout}
|
|
>
|
|
Log out
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
</details>
|
|
<div className="ul-shell__account">
|
|
<span className="ul-shell__email">{user.email}</span>
|
|
<button
|
|
className="ul-button ul-button--quiet"
|
|
onClick={handleLogout}
|
|
>
|
|
Log out
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</header>
|
|
<main id="main-content" className="ul-shell__main" tabIndex={-1}>
|
|
{children}
|
|
</main>
|
|
</>
|
|
);
|
|
}
|