import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Link, useLocation } from "react-router"; // Assume these icons are imported from an icon library import { ChevronDownIcon, GridIcon, HorizontaLDots, ListIcon, PieChartIcon, PlugInIcon, TaskIcon, BoltIcon, DocsIcon, PageIcon, DollarLineIcon, } 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 const menuSections: MenuSection[] = useMemo(() => { const workflowItems: NavItem[] = [ { icon: , name: "Setup", subItems: [ { name: "Sites", path: "/settings/sites" }, { name: "Keywords Opportunities", path: "/planner/keyword-opportunities" }, ], }, ]; // Add Planner if enabled if (moduleEnabled('planner')) { workflowItems.push({ icon: , name: "Planner", subItems: [ { name: "Dashboard", path: "/planner" }, { name: "Keywords", path: "/planner/keywords" }, { name: "Clusters", path: "/planner/clusters" }, { name: "Ideas", path: "/planner/ideas" }, ], }); } // Add Writer if enabled if (moduleEnabled('writer')) { workflowItems.push({ icon: , name: "Writer", subItems: [ { name: "Dashboard", path: "/writer" }, { name: "Tasks", path: "/writer/tasks" }, { name: "Content", path: "/writer/content" }, { name: "Images", path: "/writer/images" }, { name: "Published", path: "/writer/published" }, ], }); } // Add Thinker if enabled if (moduleEnabled('thinker')) { workflowItems.push({ icon: , name: "Thinker", subItems: [ { name: "Dashboard", path: "/thinker" }, { name: "Prompts", path: "/thinker/prompts" }, { name: "Author Profiles", path: "/thinker/author-profiles" }, { name: "Strategies", path: "/thinker/strategies" }, { name: "Image Testing", path: "/thinker/image-testing" }, ], }); } // Add Linker if enabled if (moduleEnabled('linker')) { workflowItems.push({ icon: , name: "Linker", subItems: [ { name: "Dashboard", path: "/linker" }, { name: "Content", path: "/linker/content" }, ], }); } // Add Optimizer if enabled if (moduleEnabled('optimizer')) { workflowItems.push({ icon: , name: "Optimizer", subItems: [ { name: "Dashboard", path: "/optimizer" }, { name: "Content", path: "/optimizer/content" }, ], }); } // Add Automation if enabled if (moduleEnabled('automation')) { workflowItems.push({ icon: , name: "Automation", path: "/automation", }); } return [ { label: "OVERVIEW", items: [ { icon: , name: "Dashboard", path: "/", }, { icon: , name: "Industry / Sectors", path: "/reference/industries", }, ], }, { label: "WORKFLOWS", items: workflowItems, }, { label: "ACCOUNT & SETTINGS", items: [ { icon: , name: "Settings", subItems: [ { name: "General", path: "/settings" }, { name: "Plans", path: "/settings/plans" }, { name: "Integration", path: "/settings/integration" }, { 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" }, ], }, ], }, { label: "HELP", items: [ { 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" }, ], }, { 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;