"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")} {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")} )}
); }