Files
server-26/drb-frontend/components/marketing/MarketingHeader.tsx
T
Logan CusanoandClaude Opus 5 be79499635
Build & Deploy / Build & push images (push) Successful in 4m3s
Build & Deploy / Deploy to VM (push) Successful in 1m54s
Build & Deploy / Report a failed deploy (push) Skipped
Give the nav's dead links somewhere to land
Three of the app's routes were referenced but never existed, so the redesign's
navigation pointed at 404s from several directions.

/dashboard was the post-login and fallback redirect target in nine places --
login, onboarding, middleware, the admin/nodes/systems/tokens/settings guards,
and the marketing header -- but app/dashboard/ was never created. Signing in
normally dropped the user on a 404. The real signed-in home is "/", which
app/page.tsx already renders as LiveView for an authed user with an org, and
which the nav labels "Live"; all nine now point there.

Nav also linked /watch and /network, neither of which existed. /watch is the
alerts screen under its redesign name, so it re-exports app/alerts/page.tsx
and /alerts stays reachable for old links. /network is new: the "my equipment"
hub the redesign moved /nodes, /systems and /tokens behind and then never
built, which had left /systems and /tokens with no entry point in the UI at
all. Its hooks all run before the admin/operator guard, per d041c86.

Separately, the admin page's guard read isAdmin without authLoading, so every
cold load of /admin -- typed URL, hard refresh, bookmark -- redirected away
while the Firebase claims were still resolving. Admin was only reachable by
clicking through from an already-mounted page. Now it waits, like every other
guarded route does.

And /incidents no longer lies about an empty list: a failed Firestore query
leaves `incidents` empty just as a quiet night does, and the page was printing
"No incidents recorded yet" over the top of a missing-composite-index error.
useIncidents already returned `error`; the page just ignored it. It now renders
an ErrorBanner instead, so the undeployed indexes in server-26#13 read as a
failure rather than as silence on the radio.

Closes server-26#30, server-26#31. server-26#13 stays open -- the rules and
indexes still have to be pushed to the live project by hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 02:28:02 -04:00

97 lines
3.6 KiB
TypeScript

"use client";
import { useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useAuth } from "@/components/AuthProvider";
import { LinkButton } from "@/components/ui/Button";
const LINKS = [
{ href: "/features", label: "Features" },
{ href: "/pricing", label: "Pricing" },
{ href: "/faq", label: "FAQ" },
];
export function MarketingHeader() {
const pathname = usePathname();
const { user, loading } = useAuth();
const [mobileOpen, setMobileOpen] = useState(false);
return (
<header className="sticky top-0 z-40 border-b border-gray-800 bg-gray-950/95 backdrop-blur">
<div className="max-w-screen-xl mx-auto px-4 md:px-6 py-4 flex items-center gap-6">
<Link href="/" className="flex items-center gap-2 shrink-0 font-mono font-bold text-white tracking-tight">
<span className="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-indigo-600 text-white text-sm">D</span>
DRB
</Link>
<nav className="hidden md:flex items-center gap-6 ml-4">
{LINKS.map(({ href, label }) => (
<Link
key={href}
href={href}
className={`text-sm font-mono transition-colors ${
pathname === href ? "text-white" : "text-gray-400 hover:text-gray-200"
}`}
>
{label}
</Link>
))}
</nav>
<div className="ml-auto hidden md:flex items-center gap-3">
{!loading && user ? (
<LinkButton href="/" size="md">Go to dashboard</LinkButton>
) : (
<>
<LinkButton href="/login" variant="ghost" size="md">Sign in</LinkButton>
<LinkButton href="/login" size="md">Get started</LinkButton>
</>
)}
</div>
<button
onClick={() => setMobileOpen((v) => !v)}
className="md:hidden ml-auto text-gray-400 hover:text-gray-200 transition-colors p-1"
aria-label="Toggle menu"
>
{mobileOpen ? (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
</svg>
) : (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="18" x2="21" y2="18" />
</svg>
)}
</button>
</div>
{mobileOpen && (
<div className="md:hidden border-t border-gray-800 bg-gray-950 px-4 py-3 flex flex-col gap-1">
{LINKS.map(({ href, label }) => (
<Link
key={href}
href={href}
onClick={() => setMobileOpen(false)}
className={`py-2 text-sm font-mono transition-colors ${pathname === href ? "text-white" : "text-gray-400"}`}
>
{label}
</Link>
))}
<div className="border-t border-gray-800 pt-3 mt-2 flex flex-col gap-2">
{!loading && user ? (
<LinkButton href="/" size="md" fullWidth>Go to dashboard</LinkButton>
) : (
<>
<LinkButton href="/login" variant="secondary" size="md" fullWidth>Sign in</LinkButton>
<LinkButton href="/login" size="md" fullWidth>Get started</LinkButton>
</>
)}
</div>
</div>
)}
</header>
);
}