import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Link, useLocation } from "react-router-dom"; // Assume these icons are imported from an icon library import { ChevronDownIcon, GridIcon, HorizontaLDots, ListIcon, PieChartIcon, PlugInIcon, TaskIcon, BoltIcon, DocsIcon, PageIcon, DollarLineIcon, FileIcon, } from "../icons"; import { useSidebar } from "../context/SidebarContext"; import SidebarWidget from "./SidebarWidget"; import { APP_VERSION } from "../config/version"; import { useAuthStore } from "../store/authStore"; import { useSettingsStore } from "../store/settingsStore"; import ApiStatusIndicator from "../components/sidebar/ApiStatusIndicator"; type NavItem = { name: string; icon: React.ReactNode; path?: string; subItems?: { name: string; path: string; pro?: boolean; new?: boolean }[]; }; type MenuSection = { label: string; items: NavItem[]; }; const AppSidebar: React.FC = () => { const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar(); const location = useLocation(); const { user, isAuthenticated } = useAuthStore(); const { moduleEnableSettings, isModuleEnabled: checkModuleEnabled, loadModuleEnableSettings, loading: settingsLoading } = useSettingsStore(); // Show admin menu only for users in aws-admin account const isAwsAdminAccount = Boolean( user?.account?.slug === 'aws-admin' || user?.role === 'developer' // Also show for developers as fallback ); // Helper to check if module is enabled - memoized to prevent infinite loops const moduleEnabled = useCallback((moduleName: string): boolean => { if (!moduleEnableSettings) return true; // Default to enabled if not loaded return checkModuleEnabled(moduleName); }, [moduleEnableSettings, checkModuleEnabled]); const [openSubmenu, setOpenSubmenu] = useState<{ sectionIndex: number; itemIndex: number; } | null>(null); const [subMenuHeight, setSubMenuHeight] = useState>( {} ); const subMenuRefs = useRef>({}); const isActive = useCallback( (path: string) => location.pathname === path, [location.pathname] ); // Load module enable settings on mount (only once) - but only if user is authenticated useEffect(() => { // Only load if user is authenticated and settings aren't already loaded if (user && isAuthenticated && !moduleEnableSettings && !settingsLoading) { loadModuleEnableSettings().catch((error) => { console.warn('Failed to load module enable settings:', error); }); } }, [user, isAuthenticated]); // Only run when user/auth state changes // Define menu sections with useMemo to prevent recreation on every render // Filter out disabled modules based on module enable settings // New structure: Dashboard (standalone) → SETUP → WORKFLOW → SETTINGS const menuSections: MenuSection[] = useMemo(() => { // SETUP section items (single items, no dropdowns - submenus shown as in-page navigation) const setupItems: NavItem[] = [ { icon: , name: "Industry, Sectors & Keywords", path: "/setup/industries-sectors-keywords", // Merged page }, { icon: , name: "Sites", path: "/sites", // Submenus shown as in-page navigation }, ]; // Add Automation if enabled (single item, no dropdown) if (moduleEnabled('automation')) { setupItems.push({ icon: , name: "Automation", path: "/automation/rules", // Default to rules, submenus shown as in-page navigation }); } // Add Thinker if enabled (single item, no dropdown) if (moduleEnabled('thinker')) { setupItems.push({ icon: , name: "Thinker", path: "/thinker/prompts", // Default to prompts, submenus shown as in-page navigation }); } // WORKFLOW section items (single items, no dropdowns - submenus shown as in-page navigation) const workflowItems: NavItem[] = []; // Add Planner if enabled (single item, no dropdown) if (moduleEnabled('planner')) { workflowItems.push({ icon: , name: "Planner", path: "/planner/keywords", // Default to keywords, submenus shown as in-page navigation }); } // Add Writer if enabled (single item, no dropdown) if (moduleEnabled('writer')) { workflowItems.push({ icon: , name: "Writer", path: "/writer/content", // Default to content, submenus shown as in-page navigation }); } // Add Content Manager (always enabled - single item, no dropdown) workflowItems.push({ icon: , name: "Content Manager", path: "/content-manager", // Default to all content, submenus shown as in-page navigation }); // Add Linker if enabled (single item, no dropdown) if (moduleEnabled('linker')) { workflowItems.push({ icon: , name: "Linker", path: "/linker/content", }); } // Add Optimizer if enabled (single item, no dropdown) if (moduleEnabled('optimizer')) { workflowItems.push({ icon: , name: "Optimizer", path: "/optimizer/content", }); } return [ // Dashboard is standalone (no section header) { label: "", // Empty label for standalone Dashboard items: [ { icon: , name: "Dashboard", path: "/", }, { icon: , name: "AI Control Hub", path: "/ai-control-hub", }, ], }, { label: "SETUP", items: setupItems, }, { label: "WORKFLOW", items: workflowItems, }, { label: "SETTINGS", items: [ { icon: , name: "Settings", subItems: [ { name: "General", path: "/settings" }, { name: "Plans", path: "/settings/plans" }, { name: "Integration", path: "/settings/integration" }, { name: "Publishing", path: "/settings/publishing" }, { name: "Import / Export", path: "/settings/import-export" }, ], }, { icon: , name: "Billing", subItems: [ { name: "Credits", path: "/billing/credits" }, { name: "Transactions", path: "/billing/transactions" }, { name: "Usage", path: "/billing/usage" }, ], }, { icon: , name: "Help & Documentation", path: "/help", }, ], }, ]; }, [moduleEnabled]); // Admin section - only shown for users in aws-admin account const adminSection: MenuSection = useMemo(() => ({ label: "ADMIN", items: [ { icon: , name: "User Management", subItems: [ { name: "Users", path: "/settings/users" }, { name: "Subscriptions", path: "/settings/subscriptions" }, ], }, { icon: , name: "Configuration", subItems: [ { name: "System Settings", path: "/settings/system" }, { name: "Account Settings", path: "/settings/account" }, { name: "Module Settings", path: "/settings/modules" }, ], }, { icon: , name: "AI Controls", subItems: [ { name: "AI Settings", path: "/settings/ai" }, ], }, { icon: , name: "System Health", subItems: [ { name: "Status", path: "/settings/status" }, { name: "API Monitor", path: "/settings/api-monitor" }, { name: "Debug Status", path: "/settings/debug-status" }, ], }, { icon: , name: "Testing Tools", subItems: [ { name: "Function Testing", path: "/help/function-testing" }, { name: "System Testing", path: "/help/system-testing" }, ], }, { icon: , name: "UI Elements", subItems: [ { name: "Alerts", path: "/ui-elements/alerts" }, { name: "Avatar", path: "/ui-elements/avatars" }, { name: "Badge", path: "/ui-elements/badges" }, { name: "Breadcrumb", path: "/ui-elements/breadcrumb" }, { name: "Buttons", path: "/ui-elements/buttons" }, { name: "Buttons Group", path: "/ui-elements/buttons-group" }, { name: "Cards", path: "/ui-elements/cards" }, { name: "Carousel", path: "/ui-elements/carousel" }, { name: "Dropdowns", path: "/ui-elements/dropdowns" }, { name: "Images", path: "/ui-elements/images" }, { name: "Links", path: "/ui-elements/links" }, { name: "List", path: "/ui-elements/list" }, { name: "Modals", path: "/ui-elements/modals" }, { name: "Notification", path: "/ui-elements/notifications" }, { name: "Pagination", path: "/ui-elements/pagination" }, { name: "Popovers", path: "/ui-elements/popovers" }, { name: "Pricing Table", path: "/ui-elements/pricing-table" }, { name: "Progressbar", path: "/ui-elements/progressbar" }, { name: "Ribbons", path: "/ui-elements/ribbons" }, { name: "Spinners", path: "/ui-elements/spinners" }, { name: "Tabs", path: "/ui-elements/tabs" }, { name: "Tooltips", path: "/ui-elements/tooltips" }, { name: "Videos", path: "/ui-elements/videos" }, { name: "Components", path: "/components" }, ], }, ], }), []); // Combine all sections, including admin if user is in aws-admin account const allSections = useMemo(() => { return isAwsAdminAccount ? [...menuSections, adminSection] : menuSections; }, [isAwsAdminAccount, menuSections, adminSection]); useEffect(() => { const currentPath = location.pathname; let foundMatch = false; // Find the matching submenu for the current path allSections.forEach((section, sectionIndex) => { section.items.forEach((nav, itemIndex) => { if (nav.subItems && !foundMatch) { const shouldOpen = nav.subItems.some((subItem) => { if (currentPath === subItem.path) return true; if (subItem.path !== '/' && currentPath.startsWith(subItem.path + '/')) return true; return false; }); if (shouldOpen) { setOpenSubmenu((prev) => { // Only update if different to prevent infinite loops if (prev?.sectionIndex === sectionIndex && prev?.itemIndex === itemIndex) { return prev; } return { sectionIndex, itemIndex, }; }); foundMatch = true; } } }); }); // If no match found and we're not on a submenu path, don't change the state // This allows manual toggles to persist }, [location.pathname, allSections]); useEffect(() => { if (openSubmenu !== null) { const key = `${openSubmenu.sectionIndex}-${openSubmenu.itemIndex}`; // Use requestAnimationFrame and setTimeout to ensure DOM is ready const frameId = requestAnimationFrame(() => { setTimeout(() => { const element = subMenuRefs.current[key]; if (element) { // scrollHeight should work even when height is 0px due to overflow-hidden const scrollHeight = element.scrollHeight; if (scrollHeight > 0) { setSubMenuHeight((prevHeights) => { // Only update if height changed to prevent infinite loops if (prevHeights[key] === scrollHeight) { return prevHeights; } return { ...prevHeights, [key]: scrollHeight, }; }); } } }, 50); }); return () => cancelAnimationFrame(frameId); } }, [openSubmenu]); const handleSubmenuToggle = (sectionIndex: number, itemIndex: number) => { setOpenSubmenu((prevOpenSubmenu) => { if ( prevOpenSubmenu && prevOpenSubmenu.sectionIndex === sectionIndex && prevOpenSubmenu.itemIndex === itemIndex ) { return null; } return { sectionIndex, itemIndex }; }); }; const renderMenuItems = (items: NavItem[], sectionIndex: number) => (
    {items.map((nav, itemIndex) => (
  • {nav.subItems ? ( ) : ( nav.path && ( {nav.icon} {(isExpanded || isHovered || isMobileOpen) && ( {nav.name} )} ) )} {nav.subItems && (isExpanded || isHovered || isMobileOpen) && (
    { subMenuRefs.current[`${sectionIndex}-${itemIndex}`] = el; }} className="overflow-hidden transition-all duration-300" style={{ height: openSubmenu?.sectionIndex === sectionIndex && openSubmenu?.itemIndex === itemIndex ? `${subMenuHeight[`${sectionIndex}-${itemIndex}`]}px` : "0px", }} >
      {nav.subItems.map((subItem) => (
    • {subItem.name} {subItem.new && ( new )} {subItem.pro && ( pro )}
    • ))}
    )}
  • ))}
); return ( ); }; export default AppSidebar;