The mobile bottom bar rendered all 15 routes into a single row. On a 390px iPhone that works out at ~17px per tab: the icons overlap, every label truncates to a single letter and the tap targets sit far below the 44px minimum, so the tabs could not be read or hit reliably. Mobile now uses a hamburger in the header that opens a full-screen menu listing every section, grouped (Main / CIPC & BizPortal / SARS / Practice). The panel locks background scroll, closes on Escape or on navigation, and carries the theme toggle so the mobile chrome keeps parity with the desktop cluster. Desktop had the same problem in a different shape. The full 15-link row needed 1752px inside a 1280px container, so the page scrolled sideways even at 1920px (measured scrollWidth 2065 vs a 1920 viewport) and the right-hand cluster was pushed off screen. The inline nav now carries six primary links plus a "More" dropdown, which fits exactly. - NavBar: mobile tab bar replaced by the header menu panel; desktop nav reduced to primary links + More dropdown; grouped list shared by both so the two surfaces cannot drift apart. - RouteChrome: dropped the pb-24 md:pb-0 padding that only existed to clear the old bottom bar. - .gitignore / Workflow/.dockerignore: ignore .next-stale* so a stale build directory cannot bloat the Docker build context.
66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
// RouteChrome
|
|
// -----------
|
|
// Conditionally renders the internal navigation chrome (top NavBar +
|
|
// footer) only on internal routes. Public onboarding routes under
|
|
// `/onboard/*` must not surface the Overview / Directors / Companies /
|
|
// Onboarding links — a client with that URL must not be one tap away
|
|
// from the internal registry.
|
|
//
|
|
// Public route detection: pathname starts with "/onboard". A Client
|
|
// Component is required because Next.js' root layout is a Server
|
|
// Component and `usePathname()` only works client-side. The
|
|
// detection is a single string-prefix check, so the cost is trivial.
|
|
//
|
|
// Initial render: server sends the public chrome (no nav). If the
|
|
// user is actually on an internal route, the client effect flips
|
|
// to internal chrome on hydration. This avoids any chance of the
|
|
// nav flashing into view on the public route.
|
|
|
|
import { usePathname } from "next/navigation";
|
|
import type { ReactNode } from "react";
|
|
import NavBar from "@/components/NavBar";
|
|
|
|
export default function RouteChrome({ children }: { children: ReactNode }) {
|
|
const pathname = usePathname() ?? "/";
|
|
// Public route detection: the client-facing onboarding wizard lives
|
|
// at `/onboard/<token>` (note: NOT `/onboarding` — that's the
|
|
// internal inbox at `/onboarding`). The prefix check must therefore
|
|
// require both "/onboard" and a path separator after it, otherwise
|
|
// "/onboarding" (the inbox) gets treated as public and loses the
|
|
// NavBar + footer.
|
|
const isPublic =
|
|
pathname === "/onboard" ||
|
|
pathname.startsWith("/onboard/") ||
|
|
pathname.startsWith("/register/") ||
|
|
pathname.startsWith("/sign/") ||
|
|
pathname.startsWith("/resign/");
|
|
|
|
if (isPublic) {
|
|
// Public client route. No nav, no footer, full-height canvas.
|
|
return (
|
|
<main id="main" className="min-h-screen">
|
|
{children}
|
|
</main>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<NavBar />
|
|
<main id="main" className="min-h-[60vh]">
|
|
{children}
|
|
</main>
|
|
<footer className="bg-surface-warm border-t border-border mt-16">
|
|
<div className="container-x py-10 flex flex-col gap-2 text-sm text-muted">
|
|
<p className="font-semibold text-fg">Workflow Registry Browser</p>
|
|
<p>
|
|
Read-only client to a self-hosted PocketBase instance. The
|
|
superuser token never leaves the server.
|
|
</p>
|
|
</div>
|
|
</footer>
|
|
</>
|
|
);
|
|
}
|