Server Deploy / deploy (push) Successful in 53s
The console had components but no shell: a brand bar and a nav strip stacked into 100px carrying eight words, no sign-out, no account identity, and an Overview link hardcoded to text-accent so it read as the current page on every screen. Nine pages each hand-rolled their own header. AppBar replaces both bars and derives its active state from usePathname. Settings moves into AccountMenu — it is your password, not a destination — taking appearance with it, which finally sets the data-theme attribute the token blocks have supported in both directions since they were written. That leaves three customer destinations: Overview, People, Billing. PageFrame adds a support rail so a page has a floor, and InstanceRecord replaces InstanceCard with one component that opens and closes: an account with a single instance used to render a third of a row of summary with its substance a click away. It defaults open when the instance is the only one or needs attention. No plan card in the rail: tier, limits and expiry belong to a licence and a licence belongs to one instance, so an account holding a Free cloud instance and a Professional self-hosted one has no single plan. The rail carries only what is account-wide. Tokens and globals.css are untouched — they stay verbatim shared with site/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
4.0 KiB
TypeScript
94 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { AccountMenu } from "@/components/AccountMenu";
|
|
import { EnvBadge } from "@/components/EnvBadge";
|
|
|
|
export type NavLink = { href: string; label: string };
|
|
|
|
/*
|
|
* One masthead in three zones: who you are acting as, where you can go, and
|
|
* which environment you are in.
|
|
*
|
|
* It replaces a brand bar and a separate nav strip. The nav's active state is
|
|
* derived from the pathname rather than hardcoded — the previous customer nav
|
|
* marked Overview as current on every page, including the ones that weren't it.
|
|
*
|
|
* Staff sit on --panel-2 with a chip where the account name goes. web/ is locked
|
|
* to dark so this app defaults to light for the same reason CLAUDE.md gives:
|
|
* telling two consoles apart before you click Reissue. Staff and customer need
|
|
* that distinction from each other too, and one shade plus one chip buys it
|
|
* without a second palette.
|
|
*/
|
|
export function AppBar({
|
|
links,
|
|
context,
|
|
staff = false,
|
|
}: {
|
|
links: NavLink[];
|
|
context?: React.ReactNode;
|
|
staff?: boolean;
|
|
}) {
|
|
const pathname = usePathname();
|
|
|
|
const isCurrent = (href: string) =>
|
|
// The section root matches only exactly; deeper routes match by prefix,
|
|
// so /staff/accounts/:id still lights Accounts while /staff/accounts
|
|
// does not light Operations.
|
|
href === "/" || href === "/staff"
|
|
? pathname === href
|
|
: pathname === href || pathname.startsWith(`${href}/`);
|
|
|
|
return (
|
|
<header className={`border-b border-rule ${staff ? "bg-panel-2" : "bg-panel"}`}>
|
|
<div className="mx-auto grid max-w-rail grid-cols-[auto_1fr_auto] items-center gap-4 px-5 md:gap-7">
|
|
<div className="col-start-1 row-start-1 flex min-w-0 items-center gap-3 py-2.5">
|
|
<span className="flex items-baseline gap-2 text-[1.16rem] font-extrabold tracking-[-0.02em]">
|
|
Vantage
|
|
<span className="font-mono text-[0.72rem] font-normal uppercase tracking-[0.14em] text-ink-3">
|
|
HQ
|
|
</span>
|
|
</span>
|
|
{context && (
|
|
<>
|
|
<span aria-hidden className="hidden h-[22px] w-px bg-rule sm:block" />
|
|
<span className="hidden min-w-0 sm:block">{context}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<nav
|
|
aria-label={staff ? "Staff" : "Account"}
|
|
className="col-span-3 col-start-1 row-start-2 flex items-stretch gap-1 overflow-x-auto border-t border-rule-soft md:col-span-1 md:col-start-2 md:row-start-1 md:border-t-0"
|
|
>
|
|
{links.map((l) => {
|
|
const on = isCurrent(l.href);
|
|
return (
|
|
<Link
|
|
key={l.href}
|
|
href={l.href}
|
|
aria-current={on ? "page" : undefined}
|
|
className={`relative inline-flex shrink-0 items-center px-3 py-2.5 font-mono text-[0.72rem] uppercase tracking-[0.08em] md:py-0 ${
|
|
on
|
|
? "font-bold text-accent after:absolute after:inset-x-3 after:bottom-0 after:h-0.5 after:bg-accent after:content-['']"
|
|
: "text-ink-3 hover:text-ink-2"
|
|
}`}
|
|
>
|
|
{l.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="col-start-3 row-start-1 flex items-center justify-end gap-2.5 py-2.5">
|
|
<span className="hidden sm:block">
|
|
<EnvBadge />
|
|
</span>
|
|
<AccountMenu staff={staff} />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|