"use client"; import { useState, useEffect } from "react"; import { useTranslations } from "next-intl"; import { signOut, useSession } from "next-auth/react"; import { usePathname } from "@/i18n/navigation"; import { Link } from "@/i18n/navigation"; import { SessionProvider } from "next-auth/react"; import type { Session } from "next-auth"; import { LanguageSwitcher } from "@/components/ui/language-switcher"; import { Logo } from "@/components/ui/logo"; function NavBar() { const t = useTranslations("common"); const { data: session } = useSession(); const pathname = usePathname(); const user = (session as any)?.platformUser; const [mobileOpen, setMobileOpen] = useState(false); // Close the mobile menu on any navigation. Without this the panel // would stay open across route changes (the component doesn't // unmount — it lives in the layout). useEffect(() => { setMobileOpen(false); }, [pathname]); // Hide the nav entirely on auth-only routes. These pages have no // session yet — showing "Dashboard" / "Sign Out" is misleading at // best (the buttons would 401 or redirect-loop). Keep this list // narrow and route-exact: anything else we add to the auth flow // (e.g. password reset) needs to be added here too. const isAuthRoute = pathname === "/login" || pathname === "/register"; if (isAuthRoute) return null; // ------------------------------------------------------------------ // Visibility gates — computed once, shared by the desktop nav and the // mobile panel so the two can never diverge. // // - team: owner+platform only AND not a personal account (Bug 8 — // personal accounts have no team). Matches `canMutate` / // `user.isPersonal === false` server-side. // - settings: anyone who can mutate org-level state (owners + platform). // `user`-role customers don't see it (canMutate is false). // - billing / support: any signed-in user (org-scoped server-side). // - admin: platform only. // ------------------------------------------------------------------ const isOwner = user && Array.isArray(user.roles) && user.roles.includes("owner"); const showTeam = !!user && !user.isPersonal && (user.isPlatform || isOwner); const showSettings = !!user && (user.isPlatform || isOwner); const showBilling = !!user; const showSupport = !!user; const showAdmin = !!user?.isPlatform; // Active-state helper. Dashboard/Admin previously used exact `===`, // so sub-routes (/dashboard/new, /admin/billing, …) showed no active // item. startsWith keeps the parent lit on its children too. const isActive = (href: string) => pathname === href || pathname.startsWith(`${href}/`); const links = [ { href: "/dashboard", label: t("dashboard"), show: !!user }, { href: "/team", label: t("team"), show: showTeam }, { href: "/settings", label: t("settings"), show: showSettings }, { href: "/billing", label: t("billing"), show: showBilling }, { href: "/support", label: t("support"), show: showSupport }, { href: "/admin", label: t("admin"), show: showAdmin }, ].filter((l) => l.show); const displayName = user ? user.isPersonal ? user.name || (user.email ? user.email.split("@")[0] : user.orgName) : user.orgName : ""; return (
{/* Logo / brand */}
{/* Brand mark */} {t("appName")} {t("tagline")} {/* Desktop nav links */}
{/* Right side */}
{user && ( {displayName} )} {/* Mobile menu toggle — only shown below the `sm` breakpoint, where the desktop nav and logout button are hidden. */} {user && ( )}
{/* Mobile panel */} {user && mobileOpen && ( )}
); } function NavLink({ href, active, children, }: { href: string; active: boolean; children: React.ReactNode; }) { return ( {children} ); } export function NavShell({ children, session, }: { children: React.ReactNode; // Server-resolved session passed down from the locale layout. Seeding // SessionProvider with it means useSession() is populated on the first // client render, so the nav links render immediately instead of // popping in after the client-side session fetch (CLS / flash). session: Session | null; }) { return (
{children}
); }