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, TaskIcon, BoltIcon, DocsIcon, ShootingStarIcon, CalendarIcon, TagIcon, } 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 [openSubmenus, setOpenSubmenus] = useState>(new Set()); const [subMenuHeight, setSubMenuHeight] = useState>( {} ); const subMenuRefs = useRef>({}); const submenusInitialized = useRef(false); // Check if a path is active - exact match only for menu items // Prefix matching is only used for parent menus to determine if submenu should be open const isActive = useCallback( (path: string, exactOnly: boolean = false) => { // Exact match always works if (location.pathname === path) return true; // For prefix matching (used by parent menus to check if any child is active) // Skip if exactOnly is requested (for submenu items) if (!exactOnly && path !== '/' && location.pathname.startsWith(path + '/')) { return true; } return false; }, [location.pathname] ); // Check if a submenu item path is active - uses exact match only const isSubItemActive = useCallback( (path: string) => { return location.pathname === path; }, [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 → 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", }); // Keywords Library - Browse and add curated keywords setupItems.push({ icon: , name: "Keywords Library", path: "/keywords-library", }); // Content Settings moved to Site Settings tabs - removed from sidebar // 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" }, ], }); } // Add Publisher (after Writer) - always visible workflowItems.push({ icon: , name: "Publisher", subItems: [ { name: "Content Review", path: "/writer/review" }, { name: "Publish / Schedule", path: "/writer/approved" }, { name: "Content Calendar", path: "/publisher/content-calendar" }, ], }); // Add Automation if enabled (with dropdown) if (isModuleEnabled('automation')) { workflowItems.push({ icon: , name: "Automation", subItems: [ { name: "Overview", path: "/automation/overview" }, { name: "Run Now (Manual)", path: "/automation/run" }, ], }); } // 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, }, ]; }, [isModuleEnabled, moduleSettings]); // Re-run when settings change // Combine all sections const allSections = useMemo(() => { return menuSections; }, [menuSections]); useEffect(() => { if (submenusInitialized.current) return; const initialKeys = new Set(); allSections.forEach((section, sectionIndex) => { section.items.forEach((nav, itemIndex) => { if (nav.subItems) { initialKeys.add(`${sectionIndex}-${itemIndex}`); } }); }); setOpenSubmenus(initialKeys); submenusInitialized.current = true; }, [allSections]); useEffect(() => { const currentPath = location.pathname; allSections.forEach((section, sectionIndex) => { section.items.forEach((nav, itemIndex) => { if (nav.subItems) { const shouldOpen = nav.subItems.some((subItem) => currentPath === subItem.path); if (shouldOpen) { const key = `${sectionIndex}-${itemIndex}`; setOpenSubmenus((prev) => new Set([...prev, key])); } } }); }); }, [location.pathname, allSections]); useEffect(() => { const frameId = requestAnimationFrame(() => { setTimeout(() => { openSubmenus.forEach((key) => { const element = subMenuRefs.current[key]; if (element) { const scrollHeight = element.scrollHeight; if (scrollHeight > 0) { setSubMenuHeight((prevHeights) => { if (prevHeights[key] === scrollHeight) { return prevHeights; } return { ...prevHeights, [key]: scrollHeight, }; }); } } }); }, 50); }); return () => cancelAnimationFrame(frameId); }, [openSubmenus]); const handleSubmenuToggle = (sectionIndex: number, itemIndex: number) => { const key = `${sectionIndex}-${itemIndex}`; setOpenSubmenus((prev) => { const next = new Set(prev); if (next.has(key)) { next.delete(key); } else { next.add(key); } return next; }); }; const renderMenuItems = (items: NavItem[], sectionIndex: number) => (
    {items .filter((nav) => { // Filter out admin-only items for non-admin users // Allow access for: admin role, staff users, or aws-admin account members if (nav.adminOnly) { const isAdmin = user?.role === 'admin' || user?.is_staff === true; const isAwsAdminAccount = user?.account?.name === 'aws-admin' || user?.account?.slug === 'aws-admin'; if (!isAdmin && !isAwsAdminAccount) { return false; } } return true; }) .map((nav, itemIndex) => { // Check if any subitem is active to determine parent active state (uses exact match for subitems) const hasActiveSubItem = nav.subItems?.some(subItem => isSubItemActive(subItem.path)) ?? false; const isSubmenuOpen = openSubmenus.has(`${sectionIndex}-${itemIndex}`); return (
  • {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: isSubmenuOpen ? `${subMenuHeight[`${sectionIndex}-${itemIndex}`]}px` : "0px", }} >
      {nav.subItems.map((subItem) => (
    • {subItem.name} {subItem.new && ( new )} {subItem.pro && ( pro )}
    • ))}
    )}
  • ); })}
); return ( ); }; export default AppSidebar;