128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
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 { LanguageSwitcher } from "@/components/ui/language-switcher";
|
|
|
|
function NavBar() {
|
|
const t = useTranslations("common");
|
|
const { data: session } = useSession();
|
|
const pathname = usePathname();
|
|
const user = (session as any)?.platformUser;
|
|
|
|
const isLogin = pathname === "/login";
|
|
if (isLogin) return null;
|
|
|
|
return (
|
|
<header className="sticky top-0 z-50 border-b border-border bg-surface-1/80 backdrop-blur-md">
|
|
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-5">
|
|
{/* Logo / brand */}
|
|
<div className="flex items-center gap-6">
|
|
<Link href="/dashboard" className="flex items-center gap-2.5 group">
|
|
{/* Geometric mark */}
|
|
<div className="relative h-7 w-7">
|
|
<div className="absolute inset-0 rounded-md bg-accent/20 group-hover:bg-accent/30 transition-colors" />
|
|
<div className="absolute inset-[3px] rounded-sm bg-accent" />
|
|
</div>
|
|
<span className="font-display text-base font-semibold tracking-tight text-text-primary">
|
|
{t("appName")}
|
|
</span>
|
|
<span className="hidden sm:inline text-[11px] font-medium tracking-widest uppercase text-text-muted">
|
|
{t("tagline")}
|
|
</span>
|
|
</Link>
|
|
|
|
{/* Nav links */}
|
|
<nav className="hidden sm:flex items-center gap-1 ml-2">
|
|
<NavLink href="/dashboard" active={pathname === "/dashboard"}>
|
|
{t("dashboard")}
|
|
</NavLink>
|
|
{/* Slice 7: /team is owner+platform only AND personal
|
|
accounts are excluded — they have no team to manage
|
|
(Bug 8). Match server-side gates (`canMutate`,
|
|
`user.isPersonal === false`). The roles array carries
|
|
either "owner" or "user" for customer sessions;
|
|
isPlatform covers the platform side. */}
|
|
{user &&
|
|
!user.isPersonal &&
|
|
(user.isPlatform ||
|
|
(Array.isArray(user.roles) && user.roles.includes("owner"))) && (
|
|
<NavLink href="/team" active={pathname === "/team"}>
|
|
{t("team")}
|
|
</NavLink>
|
|
)}
|
|
{user?.isPlatform && (
|
|
<NavLink href="/admin" active={pathname === "/admin"}>
|
|
{t("admin")}
|
|
</NavLink>
|
|
)}
|
|
</nav>
|
|
</div>
|
|
|
|
{/* Right side */}
|
|
<div className="flex items-center gap-4">
|
|
{user && (
|
|
// For personal accounts the orgName is opaque
|
|
// ("personal-3f2a8b1c") or a synthetic legacy
|
|
// "Name (Personal)" — neither is what we want in the nav.
|
|
// Show the user's display name instead. The detection logic
|
|
// and fallback chain live in `lib/personal-org.ts`; keeping
|
|
// a thin inline branch here avoids importing a server-only
|
|
// helper into a client component.
|
|
<span className="hidden md:inline text-xs text-text-secondary font-mono">
|
|
{user.isPersonal
|
|
? user.name || (user.email ? user.email.split("@")[0] : user.orgName)
|
|
: user.orgName}
|
|
</span>
|
|
)}
|
|
<LanguageSwitcher />
|
|
<button
|
|
onClick={() => signOut({ callbackUrl: "/login" })}
|
|
className="text-xs font-medium text-text-secondary hover:text-error transition-colors cursor-pointer"
|
|
>
|
|
{t("logout")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|
|
|
|
function NavLink({
|
|
href,
|
|
active,
|
|
children,
|
|
}: {
|
|
href: string;
|
|
active: boolean;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className={`
|
|
px-3 py-1.5 rounded-md text-sm font-medium transition-colors
|
|
${
|
|
active
|
|
? "bg-surface-3 text-text-primary"
|
|
: "text-text-secondary hover:text-text-primary hover:bg-surface-2"
|
|
}
|
|
`}
|
|
>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export function NavShell({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<SessionProvider>
|
|
<NavBar />
|
|
<main className="mx-auto max-w-6xl px-5 py-8">{children}</main>
|
|
</SessionProvider>
|
|
);
|
|
}
|