import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { Bell } from "lucide-react"; // Assume these icons are imported from an icon library import { ChevronDownIcon, GridIcon, HorizontaLDots, ListIcon, PieChartIcon, PlugInIcon, TaskIcon, BoltIcon, DocsIcon, PageIcon, DollarLineIcon, FileIcon, UserIcon, UserCircleIcon, ShootingStarIcon, } from "../icons"; import { useSidebar } from "../context/SidebarContext"; import { useAuthStore } from "../store/authStore"; import { useSettingsStore } from "../store/settingsStore"; import { useModuleStore } from "../store/moduleStore"; type NavItem = { name: string; icon: React.ReactNode; path?: string; subItems?: { name: string; path: string; pro?: boolean; new?: boolean }[]; adminOnly?: boolean; }; type MenuSection = { label: string; items: NavItem[]; }; const AppSidebar: React.FC = () => { const { isExpanded, isMobileOpen, isHovered, setIsHovered, toggleSidebar } = useSidebar(); const location = useLocation(); const { user, isAuthenticated } = useAuthStore(); const { isModuleEnabled, settings: moduleSettings } = useModuleStore(); const [openSubmenu, setOpenSubmenu] = useState<{ sectionIndex: number; itemIndex: number; } | null>(null); const [subMenuHeight, setSubMenuHeight] = useState>( {} ); const subMenuRefs = useRef>({}); const isActive = useCallback( (path: string) => { // Exact match if (location.pathname === path) return true; // For sub-pages, match if pathname starts with the path (except for root) if (path !== '/' && location.pathname.startsWith(path + '/')) return true; return false; }, [location.pathname] ); // Define menu sections with useMemo to prevent recreation on every render // New structure: Dashboard (standalone) → SETUP → WORKFLOW → SETTINGS // Module visibility is controlled by GlobalModuleSettings (Django Admin only) const menuSections: MenuSection[] = useMemo(() => { // SETUP section items - Ordered: Setup Wizard → Sites → Add Keywords → Content Settings → Thinker const setupItems: NavItem[] = []; // Setup Wizard at top - guides users through site setup setupItems.push({ icon: , name: "Setup Wizard", path: "/setup/wizard", }); // Sites is always visible - it's core functionality for managing sites setupItems.push({ icon: , name: "Sites", path: "/sites", }); // Add Keywords second setupItems.push({ icon: , name: "Add Keywords", path: "/setup/add-keywords", }); // Content Settings third - with dropdown setupItems.push({ icon: , name: "Content Settings", subItems: [ { name: "Content Generation", path: "/account/content-settings" }, { name: "Publishing", path: "/account/content-settings/publishing" }, { name: "Image Settings", path: "/account/content-settings/images" }, ], }); // Add Thinker last (admin only - prompts and AI settings) if (isModuleEnabled('thinker')) { setupItems.push({ icon: , name: "Thinker", subItems: [ { name: "Prompts", path: "/thinker/prompts" }, { name: "Author Profiles", path: "/thinker/author-profiles" }, ], adminOnly: true, // Only visible to admin/staff users }); } // WORKFLOW section items (conditionally shown based on global settings) const workflowItems: NavItem[] = []; // Add Planner with dropdown if enabled if (isModuleEnabled('planner')) { workflowItems.push({ icon: , name: "Planner", subItems: [ { name: "Keywords", path: "/planner/keywords" }, { name: "Clusters", path: "/planner/clusters" }, { name: "Ideas", path: "/planner/ideas" }, ], }); } // Add Writer with dropdown if enabled if (isModuleEnabled('writer')) { workflowItems.push({ icon: , name: "Writer", subItems: [ { name: "Content Queue", path: "/writer/tasks" }, { name: "Content Drafts", path: "/writer/content" }, { name: "Content Images", path: "/writer/images" }, { name: "Content Review", path: "/writer/review" }, { name: "Content Approved", path: "/writer/approved" }, ], }); } // Add Automation if enabled (no dropdown - single page) if (isModuleEnabled('automation')) { workflowItems.push({ icon: , name: "Automation", path: "/automation", }); } // Linker and Optimizer removed - not active modules 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: "Notifications", path: "/account/notifications", }, { icon: , name: "Account Settings", subItems: [ { name: "Account", path: "/account/settings" }, { name: "Profile", path: "/account/settings/profile" }, { name: "Team", path: "/account/settings/team" }, ], }, { icon: , name: "Plans & Billing", subItems: [ { name: "Current Plan", path: "/account/plans" }, { name: "Upgrade Plan", path: "/account/plans/upgrade" }, { name: "History", path: "/account/plans/history" }, ], }, { icon: , name: "Usage", subItems: [ { name: "Limits & Usage", path: "/account/usage" }, { name: "Credit History", path: "/account/usage/credits" }, { name: "Activity", path: "/account/usage/activity" }, ], }, { icon: , name: "AI Models", path: "/settings/integration", adminOnly: true, // Only visible to admin/staff users }, ], }, { label: "HELP", items: [ { icon: , name: "Help & Docs", path: "/help", }, ], }, ]; }, [isModuleEnabled, moduleSettings]); // Re-run when settings change // Combine all sections const allSections = useMemo(() => { return menuSections; }, [menuSections]); 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 .filter((nav) => { // Filter out admin-only items for non-admin users if (nav.adminOnly && user?.role !== 'admin' && !user?.is_staff) { return false; } return true; }) .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;