/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * * Modified by Coffey Labs in 2026 for INBUXA. */ import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import * as LucideIcons from 'lucide-react'; const { Sun, Moon, User, LogOut, Check, Menu, Search, FileCode, Palette, LayoutTemplate } = LucideIcons; import { Button } from '@/components/ui/button'; import { CommandPalette } from '@/components/common/CommandPalette'; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { isPaletteId, PALETTES } from '@/lib/palettes'; import Logo from '@/components/common/Logo'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; import { SOURCE_URL } from '@/lib/sourceDownload'; import { visibleLayouts } from '@/lib/layout'; import { sectionLandingLink } from '@/lib/lastVisited'; import { cn } from '@/lib/utils'; import { isAdminLayout, useUIStore } from '@/stores/uiStore'; import { useAuthStore } from '@/stores/authStore'; import { buildEndSessionUrl, getPostLogoutRedirectUri } from '@/services/auth/oauth'; import { createElement, useEffect, useState } from 'react'; import { useAccountStore } from '@/stores/accountStore'; import { useSchemaStore } from '@/stores/schemaStore'; const IS_MAC = /Mac|iPhone|iPad|iPod/.test(navigator.userAgent); function getIcon(name: string): LucideIcons.LucideIcon { const formatted = name .split('-') .map((s) => s[0].toUpperCase() + s.slice(1)) .join(''); return ((LucideIcons as Record)[formatted] as LucideIcons.LucideIcon) || LucideIcons.Circle; } export function TopBar() { const { t } = useTranslation(); const navigate = useNavigate(); const theme = useUIStore((s) => s.theme); const toggleTheme = useUIStore((s) => s.toggleTheme); const palette = useUIStore((s) => s.palette); const setPalette = useUIStore((s) => s.setPalette); const adminLayout = useUIStore((s) => s.adminLayout); const setAdminLayout = useUIStore((s) => s.setAdminLayout); const toggleSidebar = useUIStore((s) => s.toggleSidebar); const setActiveSection = useUIStore((s) => s.setActiveSection); const activeSection = useUIStore((s) => s.activeSection); const accounts = useAuthStore((s) => s.accounts); const activeAccountId = useAuthStore((s) => s.activeAccountId); const switchAccount = useAuthStore((s) => s.switchAccount); const logout = useAuthStore((s) => s.logout); const edition = useAccountStore((s) => s.edition); const hasObjectPermission = useAccountStore((s) => s.hasObjectPermission); const hasPermission = useAccountStore((s) => s.hasPermission); const schema = useSchemaStore((s) => s.schema); const [upsellOpen, setUpsellOpen] = useState(false); const [paletteOpen, setPaletteOpen] = useState(false); useEffect(() => { function handleGlobalKeyDown(e: KeyboardEvent) { if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'k') { e.preventDefault(); setPaletteOpen((open) => !open); } } document.addEventListener('keydown', handleGlobalKeyDown); return () => document.removeEventListener('keydown', handleGlobalKeyDown); }, []); const navigableLayouts = schema ? visibleLayouts(schema, edition, (prefix) => hasObjectPermission(prefix, 'Get'), hasPermission) : []; return (
{t('version.label', 'inbuxa Admin {{version}}', { version: __APP_VERSION__ })}
{edition !== 'enterprise' && setUpsellOpen(false)} />} {/* INBUXA: the three areas, one click away, where the eye already looks. */} {schema && navigableLayouts.length > 1 && (
{navigableLayouts.map((layout) => { const isActive = layout.name === activeSection; return ( {layout.name} ); })}
)} {schema && navigableLayouts.length > 0 && ( <> {t('sections', 'Sections')} {navigableLayouts.map((layout) => { const Icon = getIcon(layout.icon); return ( { setActiveSection(layout.name); const canGet = (prefix: string) => hasObjectPermission(prefix, 'Get'); const firstLink = sectionLandingLink(schema, layout, edition, canGet, hasPermission); if (firstLink) { navigate(`/${layout.name}/${firstLink}`); } }} > {layout.name} ); })} )} {Object.keys(accounts).length > 0 && ( <> {t('accounts', 'Accounts')} {Object.entries(accounts).map(([id, info]) => ( switchAccount(id)}> {id === activeAccountId && } {info.name || id} ))} )} {/* INBUXA: the shell is the reader's choice, the way the palette is. */} {t('nav.layoutMenu', 'Layout')} isAdminLayout(v) && setAdminLayout(v)} > {t('nav.layoutModern', 'Modern')} {t('nav.layoutLegacy', 'Legacy')}

{adminLayout === 'modern' ? t('nav.layoutModernHint', 'Sections across the top; the page gets the full width.') : t('nav.layoutLegacyHint', 'The sidebar, as the old web UI had it.')}

{/* INBUXA: the same palettes as INBUXA webmail. */} {t('theme.menu', 'Theme')} isPaletteId(v) && setPalette(v)}> {PALETTES.map((p) => ( ))} {t('source.menu', 'Source code (AGPL-3.0)')} { const endSessionEndpoint = useAuthStore.getState().endSessionEndpoint; logout(); if (endSessionEndpoint) { window.location.href = buildEndSessionUrl(endSessionEndpoint, getPostLogoutRedirectUri()); } else { navigate('/login'); } }} > {t('logout', 'Logout')}
); }