diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index eae73415..0ec84998 100644 Binary files a/backend/celerybeat-schedule and b/backend/celerybeat-schedule differ diff --git a/frontend/src/layout/AppSidebar.tsx b/frontend/src/layout/AppSidebar.tsx index c2fadeb4..4f9c2b9e 100644 --- a/frontend/src/layout/AppSidebar.tsx +++ b/frontend/src/layout/AppSidebar.tsx @@ -21,7 +21,6 @@ import SidebarWidget from "./SidebarWidget"; import { APP_VERSION } from "../config/version"; import { useAuthStore } from "../store/authStore"; import { useSettingsStore } from "../store/settingsStore"; -import { isModuleEnabled } from "../config/modules.config"; import ApiStatusIndicator from "../components/sidebar/ApiStatusIndicator"; type NavItem = { @@ -40,7 +39,7 @@ const AppSidebar: React.FC = () => { const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar(); const location = useLocation(); const { user } = useAuthStore(); - const { moduleEnableSettings, isModuleEnabled: checkModuleEnabled } = useSettingsStore(); + const { moduleEnableSettings, isModuleEnabled: checkModuleEnabled, loadModuleEnableSettings, loading: settingsLoading } = useSettingsStore(); // Show admin menu only for users in aws-admin account const isAwsAdminAccount = Boolean( @@ -48,11 +47,11 @@ const AppSidebar: React.FC = () => { user?.role === 'developer' // Also show for developers as fallback ); - // Helper to check if module is enabled - const moduleEnabled = (moduleName: string): boolean => { + // 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; @@ -68,6 +67,15 @@ const AppSidebar: React.FC = () => { [location.pathname] ); + // Load module enable settings on mount (only once) + useEffect(() => { + if (!moduleEnableSettings && !settingsLoading) { + loadModuleEnableSettings().catch((error) => { + console.warn('Failed to load module enable settings:', error); + }); + } + }, []); // Empty dependency array - only run on mount + // Define menu sections with useMemo to prevent recreation on every render // Filter out disabled modules based on module enable settings const menuSections: MenuSection[] = useMemo(() => { @@ -196,7 +204,7 @@ const AppSidebar: React.FC = () => { ], }, ]; - }, [moduleEnableSettings, moduleEnabled]); + }, [moduleEnabled]); // Admin section - only shown for users in aws-admin account const adminSection: MenuSection = useMemo(() => ({ @@ -282,14 +290,6 @@ const AppSidebar: React.FC = () => { : menuSections; }, [isAwsAdminAccount, menuSections, adminSection]); - // Load module enable settings on mount - useEffect(() => { - const { loadModuleEnableSettings } = useSettingsStore.getState(); - if (!moduleEnableSettings) { - loadModuleEnableSettings(); - } - }, [moduleEnableSettings]); - useEffect(() => { const currentPath = location.pathname; let foundMatch = false; @@ -305,9 +305,15 @@ const AppSidebar: React.FC = () => { }); if (shouldOpen) { - setOpenSubmenu({ - sectionIndex, - itemIndex, + setOpenSubmenu((prev) => { + // Only update if different to prevent infinite loops + if (prev?.sectionIndex === sectionIndex && prev?.itemIndex === itemIndex) { + return prev; + } + return { + sectionIndex, + itemIndex, + }; }); foundMatch = true; } @@ -330,10 +336,16 @@ const AppSidebar: React.FC = () => { // scrollHeight should work even when height is 0px due to overflow-hidden const scrollHeight = element.scrollHeight; if (scrollHeight > 0) { - setSubMenuHeight((prevHeights) => ({ - ...prevHeights, - [key]: scrollHeight, - })); + setSubMenuHeight((prevHeights) => { + // Only update if height changed to prevent infinite loops + if (prevHeights[key] === scrollHeight) { + return prevHeights; + } + return { + ...prevHeights, + [key]: scrollHeight, + }; + }); } } }, 50);