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, UserIcon, UserCircleIcon, } 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: "Add Keywords", path: "/setup/add-keywords", }, { icon: , name: "Sites", path: "/sites", // 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/tasks", // Default to tasks, submenus shown as in-page navigation }); } // Add Automation (always available if Writer is enabled) if (moduleEnabled('writer')) { workflowItems.push({ icon: , name: "Automation", path: "/automation", }); } // 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: "/", }, ], }, { label: "SETUP", items: setupItems, }, { label: "WORKFLOW", items: workflowItems, }, { label: "ACCOUNT", items: [ { icon: , name: "Account Settings", path: "/account/settings", }, { icon: , name: "Plans & Billing", path: "/account/billing", }, { icon: , name: "Team Management", path: "/account/team", }, { icon: , name: "Usage & Analytics", path: "/account/usage", }, ], }, { label: "SETTINGS", items: [ { icon: , name: "Profile Settings", path: "/settings/profile", }, { icon: , name: "Integration", path: "/settings/integration", }, { icon: , name: "Publishing", path: "/settings/publishing", }, { icon: , name: "Import / Export", path: "/settings/import-export", }, ], }, { label: "HELP & DOCS", 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: "System Dashboard", path: "/admin/dashboard", }, { icon: , name: "Account Management", subItems: [ { name: "All Accounts", path: "/admin/accounts" }, { name: "Subscriptions", path: "/admin/subscriptions" }, { name: "Account Limits", path: "/admin/account-limits" }, ], }, { icon: , name: "Billing Administration", subItems: [ { name: "Billing Overview", path: "/admin/billing" }, { name: "Invoices", path: "/admin/invoices" }, { name: "Payments", path: "/admin/payments" }, { name: "Credit Costs Config", path: "/admin/credit-costs" }, { name: "Credit Packages", path: "/admin/credit-packages" }, ], }, { icon: , name: "User Administration", subItems: [ { name: "All Users", path: "/admin/users" }, { name: "Roles & Permissions", path: "/admin/roles" }, { name: "Activity Logs", path: "/admin/activity-logs" }, ], }, { icon: , name: "System Configuration", subItems: [ { name: "System Settings", path: "/admin/system-settings" }, { name: "AI Settings", path: "/admin/ai-settings" }, { name: "Module Settings", path: "/admin/module-settings" }, { name: "Integration Settings", path: "/admin/integration-settings" }, ], }, { icon: , name: "Monitoring", subItems: [ { name: "System Health", path: "/settings/status" }, { name: "API Monitor", path: "/settings/api-monitor" }, { name: "Debug Status", path: "/settings/debug-status" }, ], }, { icon: , name: "Developer Tools", subItems: [ { name: "Function Testing", path: "/admin/function-testing" }, { name: "System Testing", path: "/admin/system-testing" }, { name: "UI Elements", path: "/admin/ui-elements" }, ], }, ], }), []); // 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;