"use client";
import {
ChevronDown,
LogIn,
LogOut,
Monitor,
Moon,
Shield,
Sun,
} from "lucide-react";
import { CommandPaletteTrigger } from "@/components/command-palette/command-palette-trigger";
import { AccountSwitcherMenuItems } from "@/components/layout/account-switcher-menu";
import {
isSettingsUtilityDestination,
visibleUtilityDestinations,
} from "@/components/layout/nav-model";
import { SHELL_HEADER_BAND } from "@/components/layout/shell-metrics";
import {
TopBarActionsOutlet,
TopBarContextOutlet,
} from "@/components/layout/top-bar-actions";
import { cn } from "@/lib/utils";
import { Logo } from "@/components/ui/logo";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Skeleton } from "@/components/ui/skeleton";
import Link from "next/link";
import { useAuth, useLogout } from "@/hooks/use-auth";
import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
import { useTheme } from "@/components/providers";
import { useTranslations } from "@/lib/i18n/context";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
function getInitials(name: string): string {
return name
.split(/[\s._-]+/)
.slice(0, 2)
.map((s) => s[0]?.toUpperCase() ?? "")
.join("");
}
export function TopBar() {
const { user, isLoading } = useAuth();
// v1.36.0 — acting on somebody else's record; see the menu below. Read from
// the capability hook, as the sidebar does, so a refused or pending switch
// counts as switched here too and both bars offer the same utilities.
const {
inSharedRecord: sharedRecord,
recordKind,
level,
} = useRecordCapabilities();
const utilityItems = visibleUtilityDestinations({
record: sharedRecord
? {
recordKind,
level,
manageableDomains:
user?.accountAccess?.active?.manageableDomains ?? [],
}
: null,
});
const logout = useLogout();
const { theme, setTheme } = useTheme();
const { t } = useTranslations();
const avatarUrl = user?.avatarUrl ?? null;
const isAdmin = user?.role === "ADMIN";
const themeIcon =
theme === "system" ? (
) : theme === "dark" ? (
) : (
);
return (
{/* Mobile logo */}
HealthLog
{/* Desktop: the page's context, where a page gives one (the Coach's
trail); otherwise an empty spacer. User controls are in the
sidebar. */}
{/* v1.42 — the command palette's door, on every signed-in page: a
magnifier at every width. `ml-auto` keeps it at the trailing end,
just left of any docked panel's strip. */}
{/* Page-owned actions (the Coach's conversations toggle). `ml-auto`
keeps them at the trailing edge, right before the mobile avatar
menu; `empty:hidden` takes the slot out of the row on every page
that leaves it empty, so the header lays out exactly as before. */}
{/* On desktop the last action sits 12 px from the bar's trailing edge
(`md:-mr-3` against `md:px-6`), so a panel toggle stands against the
panel it controls. */}
{/* Mobile-only auth section (desktop uses sidebar user section) */}
{isLoading ? (
) : user ? (
{t("nav.userMenu")}
{avatarUrl && (
)}
{getInitials(user.username)}
{user.username}
{/* The account utilities come from the list the desktop sidebar
reads. Inside somebody else's record Notifications and Admin
drop out — every route behind them refuses under a switch —
and Settings stays only where the record has Settings pages,
pointing at the first one the shell opens. The switcher
below is how the person gets back. */}
{utilityItems.map((item) => {
const Icon = item.icon;
return (
{t(item.tKey)}
);
})}
{!sharedRecord && isAdmin && (
{t("nav.admin")}
)}
{/* v1.4.36 — About moved into the Admin Console
(`/admin/about`). The dropdown entry was redundant
for the small audience that still reaches it (admins
only, on the order of once or twice a year). */}
{/* v1.22.1 — the shared sub-trigger ships `py-1.5` and no
min-height, while every sibling `DropdownMenuItem` carries
`min-h-11 py-2`. Left as-is the Theme row sits a few pixels
shorter than the rows around it and the menu reads as
unevenly spaced. Match the item height here (instance-level
override, so no other dropdown's sub-trigger is touched). */}
{themeIcon}
{t("nav.theme")} setTheme("system")}>
{t("nav.themeSystem")}
{theme === "system" && (
✓
)}
setTheme("dark")}>
{t("nav.themeDark")}
{theme === "dark" && (
✓
)}
setTheme("light")}>
{t("nav.themeLight")}
{theme === "light" && (
✓
)}
{/* v1.36.0 — the same switcher slice the desktop sidebar menu
mounts. One component in both menus: a switcher a person can
reach on their laptop and not on their phone is a record they
can enter and cannot leave. */}
logout.mutate()}
className="text-destructive focus:text-destructive focus:bg-destructive/10 cursor-pointer"
>
{t("nav.logout")}
) : (
{t("nav.login")}
)}