"use client"; import { Building2, Check, ChevronsUpDown, LogOut, Moon, Plus, Search, Settings as SettingsIcon, Sun, } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useTheme } from "next-themes"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useCommandPalette } from "@/components/command-palette"; import { CreateClinicForm } from "@/components/clinic/create-clinic-form"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Dialog, DialogDescription, DialogHeader, DialogPanel, DialogPopup, DialogTitle, } from "@/components/ui/dialog"; import { Kbd, KbdGroup } from "@/components/ui/kbd"; import { Menu, MenuGroup, MenuGroupLabel, MenuItem, MenuPopup, MenuSeparator, MenuShortcut, MenuSub, MenuSubPopup, MenuSubTrigger, MenuTrigger, } from "@/components/ui/menu"; import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar, } from "@/components/ui/sidebar"; import { authClient } from "@/lib/auth-client"; import { useActiveRole } from "@/lib/roles"; import { notify } from "@/lib/toast"; // Open-source repo (placeholder). const REPO_URL = "https://github.com/temetro/temetro"; function initialsFromName(name: string): string { const letters = name .split(/\s+/) .map((w) => w[0]) .filter(Boolean) .join("") .slice(0, 2); return (letters || "?").toUpperCase(); } function GitHubIcon({ className }: { className?: string }) { return ( ); } // The sidebar's single footer row. Its menu also hosts the command-palette // shortcut hint (below Theme) and a clinic switcher submenu (below that) whose // popup reveals the active clinic's details on hover. export function NavUser() { const { t } = useTranslation(); const { isMobile, state } = useSidebar(); const isCollapsed = state === "collapsed"; const router = useRouter(); const { resolvedTheme, setTheme } = useTheme(); const isDark = resolvedTheme === "dark"; const { data } = authClient.useSession(); const { open: openCommand } = useCommandPalette(); const { data: orgs } = authClient.useListOrganizations(); const { data: activeOrg } = authClient.useActiveOrganization(); const role = useActiveRole(); // Only clinic owners/admins may spin up additional clinics. New users with no // clinic still onboard via /onboarding (a separate flow). const canCreateClinic = role === "owner" || role === "admin"; const [createOpen, setCreateOpen] = useState(false); const name = data?.user?.name ?? t("userMenu.defaultName"); const email = data?.user?.email ?? ""; // Admin-provisioned staff sign in by username and may only have a synthetic // `username@slug.temetro.local` address โ€” show their @username instead of an // email that looks broken. const username = data?.user?.displayUsername ?? data?.user?.username ?? null; const isSyntheticEmail = /@.+\.temetro\.local$/i.test(email); const secondary = !email || isSyntheticEmail ? (username ? `@${username}` : email) : email; const initials = initialsFromName(name); const activeName = activeOrg?.name ?? t("userMenu.selectClinic"); const setActive = async (organizationId: string) => { if (organizationId === activeOrg?.id) return; await authClient.organization.setActive({ organizationId }); }; const signOut = async () => { const { error } = await authClient.signOut(); if (error) { notify.error( t("userMenu.signOutFailed"), error.message ?? t("userMenu.tryAgain"), ); return; } notify.success(t("userMenu.signedOut")); router.push("/login"); }; return ( } > {initials} {!isCollapsed && ( <>
{name} {secondary}
)}
{initials}
{name} {secondary}
}> {t("userMenu.settings")} } > {t("userMenu.docs")} setTheme(isDark ? "light" : "dark")} > {isDark ? : } {t("userMenu.theme")} {isDark ? t("userMenu.dark") : t("userMenu.light")} {/* Command palette: hint + shortcut, sits below Theme. */} {t("userMenu.search")} โŒ˜ K {/* Clinic switcher: hover reveals the active clinic's details. */} {activeName}

{activeOrg?.name ?? t("userMenu.noClinic")}

{activeOrg?.slug ? `/${activeOrg.slug}` : "โ€”"} ยท{" "} {t("userMenu.clinicCount", { count: orgs?.length ?? 0 })}

{t("userMenu.switchClinic")} {(orgs ?? []).map((org) => ( setActive(org.id)} >
{org.name} {org.id === activeOrg?.id && }
))}
{canCreateClinic && ( <> setCreateOpen(true)} > {t("userMenu.createClinic")} )}
{t("userMenu.logout")}
{/* Create a new clinic */} {t("userMenu.createDialogTitle")} {t("userMenu.createDialogDescription")} setCreateOpen(false)} />
); }