import { Link } from '@tanstack/react-router'; import { Home, Settings, Construction, ChevronDown, ChevronRight, Building2, Database, BookOpen, CreditCard, Tag, Sparkles, MessageCircle, MessagesSquare, Users } from 'lucide-react'; import { cn } from '@/lib/utils'; import { DashboardUserProfile } from './DashboardUserProfile'; import { NotificationIndicator, LanguageSwitcher } from '@/components/shared'; import { NotificationUrgency, NotificationMessageType } from '@domain/entities'; import { useState, useMemo } from 'react'; import { LogoMinimised } from '@archer/ui/components'; import { useTranslation } from '@/i18n/TranslationProvider'; import { useChannel } from '@/presentation/hooks'; import { useAuthenticatedUser } from '@/features/auth/hooks/useAuthenticatedUser'; import { toEmployeeRole } from '@/presentation/lib/enumGuards'; import { Channel, CPZone, EmployeeRole, canEnterZone } from '@archer/domain'; /** * Sidebar Component * * Left sidebar navigation for the dashboard with: * - TWWIM branding at top * - Notification indicator * - Navigation links with icons * - Active state highlighting * - Hover effects with TWWIM primary color * - Dark slate background (slate-800) */ interface SidebarProps { className?: string; unreadCount?: number; highestUrgency?: NotificationUrgency; messageType?: NotificationMessageType; onNotificationClick?: () => void; } interface NavigationItem { name: string; key?: string; to?: string; icon: React.ComponentType<{ className?: string; size?: number }>; exactMatch?: boolean; submenu?: NavigationItem[]; zone?: CPZone; } const isDev = import.meta.env.DEV; export function Sidebar({ className, unreadCount = 0, highestUrgency = NotificationUrgency.LOW, messageType = NotificationMessageType.INFO, onNotificationClick = () => {}, }: SidebarProps) { const { t } = useTranslation(); const channel = useChannel(); const user = useAuthenticatedUser(); const role = toEmployeeRole(user?.role) ?? EmployeeRole.ADMIN; const navigation: NavigationItem[] = useMemo(() => { // Per-channel UI shape: // - Tenants list: WP plugin embeds a single site, hide the multi-tenant // CRUD list there. Web + Shopify deep-link can manage multiple. // - Subscription / Promotions: Shopify merchants pay through Shopify's // billing API, hide the TWWIM-billing surfaces. WP + Web see them // because billing is direct-to-TWWIM. const showTenants = channel !== Channel.WORDPRESS; const showBillingSurfaces = channel !== Channel.SHOPIFY; const items: NavigationItem[] = [ { name: t('nav.dashboard'), to: '/dashboard', icon: Home, exactMatch: true, zone: CPZone.HOME }, { name: t('nav.intelligence'), to: '/dashboard/intelligence', icon: Sparkles, zone: CPZone.INTELLIGENCE }, ...(showTenants ? [{ name: t('nav.tenants'), to: '/dashboard/tenants', icon: Building2, zone: CPZone.TENANTS }] : []), { name: t('nav.knowledge'), to: '/dashboard/knowledge', icon: BookOpen, zone: CPZone.KNOWLEDGE }, { name: t('nav.conversations'), to: '/dashboard/conversations', icon: MessagesSquare, zone: CPZone.CONVERSATIONS }, { name: t('nav.members'), to: '/dashboard/members', icon: Users, zone: CPZone.MEMBERS }, ...(showBillingSurfaces ? [ { name: t('nav.subscription'), to: '/dashboard/subscription', icon: CreditCard, zone: CPZone.SUBSCRIPTION }, { name: t('nav.promotions'), to: '/dashboard/promotions', icon: Tag, zone: CPZone.PROMOTIONS }, ] : []), { name: t('nav.settings'), key: 'settings', icon: Settings, zone: CPZone.WIDGET_CONFIG, submenu: [ { name: t('nav.widgetConfig'), to: '/dashboard/plugin-settings', icon: MessageCircle, zone: CPZone.WIDGET_CONFIG }, ], }, ...(isDev ? [{ name: t('nav.wip'), key: 'wip', icon: Construction, zone: CPZone.INFO_CENTER, submenu: [ { name: t('nav.infoCenter'), to: '/dashboard/info-center', icon: Database, zone: CPZone.INFO_CENTER }, { name: t('nav.devSettings'), to: '/dashboard/settings', icon: Settings, zone: CPZone.INFO_CENTER }, ], }] : []), ]; return items.filter((item) => !item.zone || canEnterZone(role, item.zone)); }, [t, channel, role]); // Default both Settings and WIP submenus expanded so their children are // immediately visible (and discoverable by e2e selectors). const [expandedMenus, setExpandedMenus] = useState>(new Set(['settings', 'wip'])); const toggleMenu = (name: string) => { setExpandedMenus((prev) => { const next = new Set(prev); if (next.has(name)) { next.delete(name); } else { next.add(name); } return next; }); }; const renderNavItem = (item: NavigationItem, isSubmenu = false) => { const hasSubmenu = item.submenu && item.submenu.length > 0; const trackingKey = item.key || item.name; const isExpanded = expandedMenus.has(trackingKey); const Icon = item.icon; if (hasSubmenu) { // Expandable menu item return (
{isExpanded && (
{item.submenu!.map((subItem) => renderNavItem(subItem, true))}
)}
); } // Regular link item return ( {item.name} ); }; return ( ); }