From 5c9ef81aba8507c8cf74a0bd95b6c5e63f8e5959 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Sat, 20 Dec 2025 22:18:32 +0000 Subject: [PATCH] moduels setigns rmeove from frotneend --- .../modules/system/settings_admin.py | 21 +-- .../modules/system/settings_serializers.py | 13 +- .../modules/system/settings_views.py | 152 ++---------------- frontend/src/App.tsx | 112 +++---------- .../src/components/common/ModuleGuard.tsx | 35 +--- frontend/src/config/modules.config.ts | 28 +--- frontend/src/layout/AppSidebar.tsx | 120 +++++--------- frontend/src/pages/Settings/Modules.tsx | 91 ----------- frontend/src/services/api.ts | 39 ----- frontend/src/store/settingsStore.ts | 76 +-------- 10 files changed, 90 insertions(+), 597 deletions(-) delete mode 100644 frontend/src/pages/Settings/Modules.tsx diff --git a/backend/igny8_core/modules/system/settings_admin.py b/backend/igny8_core/modules/system/settings_admin.py index 5b9516b4..d6dacef7 100644 --- a/backend/igny8_core/modules/system/settings_admin.py +++ b/backend/igny8_core/modules/system/settings_admin.py @@ -4,7 +4,7 @@ Settings Models Admin from django.contrib import admin from unfold.admin import ModelAdmin from igny8_core.admin.base import AccountAdminMixin -from .settings_models import SystemSettings, AccountSettings, UserSettings, ModuleSettings, ModuleEnableSettings, AISettings +from .settings_models import SystemSettings, AccountSettings, UserSettings, ModuleSettings, AISettings @admin.register(SystemSettings) @@ -93,19 +93,6 @@ class AISettingsAdmin(AccountAdminMixin, ModelAdmin): get_account_display.short_description = 'Account' -@admin.register(ModuleEnableSettings) -class ModuleEnableSettingsAdmin(AccountAdminMixin, ModelAdmin): - list_display = [ - 'account', - 'planner_enabled', - 'writer_enabled', - 'thinker_enabled', - 'automation_enabled', - 'site_builder_enabled', - 'linker_enabled', - 'optimizer_enabled', - 'publisher_enabled', - ] - list_filter = ['planner_enabled', 'writer_enabled', 'thinker_enabled', 'automation_enabled', 'site_builder_enabled', 'linker_enabled', 'optimizer_enabled', 'publisher_enabled'] - search_fields = ['account__name'] - +# ModuleEnableSettings is DEPRECATED - use GlobalModuleSettings instead +# GlobalModuleSettings is registered in admin.py (platform-wide, singleton) +# Old per-account ModuleEnableSettings model is no longer used diff --git a/backend/igny8_core/modules/system/settings_serializers.py b/backend/igny8_core/modules/system/settings_serializers.py index 414f9cc7..6ae830df 100644 --- a/backend/igny8_core/modules/system/settings_serializers.py +++ b/backend/igny8_core/modules/system/settings_serializers.py @@ -2,7 +2,7 @@ Serializers for Settings Models """ from rest_framework import serializers -from .settings_models import SystemSettings, AccountSettings, UserSettings, ModuleSettings, ModuleEnableSettings, AISettings +from .settings_models import SystemSettings, AccountSettings, UserSettings, ModuleSettings, AISettings from .validators import validate_settings_schema @@ -58,15 +58,8 @@ class ModuleSettingsSerializer(serializers.ModelSerializer): return value -class ModuleEnableSettingsSerializer(serializers.ModelSerializer): - class Meta: - model = ModuleEnableSettings - fields = [ - 'id', 'planner_enabled', 'writer_enabled', 'thinker_enabled', - 'automation_enabled', 'site_builder_enabled', 'linker_enabled', - 'optimizer_enabled', 'publisher_enabled', 'created_at', 'updated_at' - ] - read_only_fields = ['created_at', 'updated_at', 'account'] +# ModuleEnableSettingsSerializer is DEPRECATED - use GlobalModuleSettings instead +# GlobalModuleSettings is managed via Django Admin only (no API serializer needed) class AISettingsSerializer(serializers.ModelSerializer): diff --git a/backend/igny8_core/modules/system/settings_views.py b/backend/igny8_core/modules/system/settings_views.py index 54f2f2c9..313270ef 100644 --- a/backend/igny8_core/modules/system/settings_views.py +++ b/backend/igny8_core/modules/system/settings_views.py @@ -13,10 +13,11 @@ from igny8_core.api.authentication import JWTAuthentication, CSRFExemptSessionAu from igny8_core.api.pagination import CustomPageNumberPagination from igny8_core.api.throttles import DebugScopedRateThrottle from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess, IsAdminOrOwner -from .settings_models import SystemSettings, AccountSettings, UserSettings, ModuleSettings, ModuleEnableSettings, AISettings +from .settings_models import SystemSettings, AccountSettings, UserSettings, ModuleSettings, AISettings +from .global_settings_models import GlobalModuleSettings from .settings_serializers import ( SystemSettingsSerializer, AccountSettingsSerializer, UserSettingsSerializer, - ModuleSettingsSerializer, ModuleEnableSettingsSerializer, AISettingsSerializer + ModuleSettingsSerializer, AISettingsSerializer ) @@ -291,31 +292,20 @@ class ModuleSettingsViewSet(AccountModelViewSet): update=extend_schema(tags=['System']), partial_update=extend_schema(tags=['System']), ) -class ModuleEnableSettingsViewSet(AccountModelViewSet): +class ModuleEnableSettingsViewSet(viewsets.ViewSet): """ ViewSet for GLOBAL module enable/disable settings (read-only). - Returns platform-wide module availability. - Only superadmin can modify via Django Admin. + Returns platform-wide module availability from GlobalModuleSettings singleton. + Only superadmin can modify via Django Admin at /admin/system/globalmodulesettings/. """ - queryset = ModuleEnableSettings.objects.all() - serializer_class = ModuleEnableSettingsSerializer - http_method_names = ['get'] # Read-only authentication_classes = [JWTAuthentication] + permission_classes = [IsAuthenticatedAndActive, HasTenantAccess] throttle_scope = 'system' throttle_classes = [DebugScopedRateThrottle] - def get_permissions(self): - """Read-only for all authenticated users""" - return [IsAuthenticatedAndActive(), HasTenantAccess()] - - def get_queryset(self): - """Return empty queryset (not used - we return global settings)""" - return ModuleEnableSettings.objects.none() - def list(self, request, *args, **kwargs): - """Return global module settings (platform-wide)""" + """Return global module settings (platform-wide, read-only)""" try: - from igny8_core.modules.system.global_settings_models import GlobalModuleSettings global_settings = GlobalModuleSettings.get_instance() data = { @@ -334,134 +324,14 @@ class ModuleEnableSettingsViewSet(AccountModelViewSet): return success_response(data=data, request=request) except Exception as e: return error_response( - error=str(e), + error=f'Failed to load global module settings: {str(e)}', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, request=request ) def retrieve(self, request, pk=None, *args, **kwargs): - """Same as list - return global settings""" - return self.list(request) - return queryset - - @action(detail=False, methods=['get', 'put'], url_path='current', url_name='current') - def get_current(self, request): - """Get or update current account's module enable settings""" - if request.method == 'GET': - return self.list(request) - else: - return self.update(request, pk=None) - - def list(self, request, *args, **kwargs): - """Get or create module enable settings for current account""" - try: - account = getattr(request, 'account', None) - if not account: - user = getattr(request, 'user', None) - if user and hasattr(user, 'account'): - account = user.account - - if not account: - return error_response( - error='Account not found', - status_code=status.HTTP_400_BAD_REQUEST, - request=request - ) - - # Check if table exists (migration might not have been run) - try: - # Get or create settings for account (one per account) - try: - settings = ModuleEnableSettings.objects.get(account=account) - except ModuleEnableSettings.DoesNotExist: - # Create default settings for account - settings = ModuleEnableSettings.objects.create(account=account) - - serializer = self.get_serializer(settings) - return success_response(data=serializer.data, request=request) - except Exception as db_error: - # Check if it's a "table does not exist" error - error_str = str(db_error) - if 'does not exist' in error_str.lower() or 'relation' in error_str.lower(): - import logging - logger = logging.getLogger(__name__) - logger.error(f"ModuleEnableSettings table does not exist. Migration 0007_add_module_enable_settings needs to be run: {error_str}") - return error_response( - error='Module enable settings table not found. Please run migration: python manage.py migrate igny8_core_modules_system 0007', - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - request=request - ) - # Re-raise other database errors - raise - except Exception as e: - import traceback - error_trace = traceback.format_exc() - return error_response( - error=f'Failed to load module enable settings: {str(e)}', - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - request=request - ) - - def retrieve(self, request, pk=None, *args, **kwargs): - """Get module enable settings for current account""" - try: - account = getattr(request, 'account', None) - if not account: - user = getattr(request, 'user', None) - if user: - account = getattr(user, 'account', None) - - if not account: - return error_response( - error='Account not found', - status_code=status.HTTP_400_BAD_REQUEST, - request=request - ) - - # Get or create settings for account - settings, created = ModuleEnableSettings.objects.get_or_create(account=account) - serializer = self.get_serializer(settings) - return success_response(data=serializer.data, request=request) - except Exception as e: - return error_response( - error=f'Failed to load module enable settings: {str(e)}', - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - request=request - ) - - def update(self, request, pk=None): - """Update module enable settings for current account""" - account = getattr(request, 'account', None) - if not account: - user = getattr(request, 'user', None) - if user: - account = getattr(user, 'account', None) - - if not account: - return error_response( - error='Account not found', - status_code=status.HTTP_400_BAD_REQUEST, - request=request - ) - - # Get or create settings for account - settings = ModuleEnableSettings.get_or_create_for_account(account) - serializer = self.get_serializer(settings, data=request.data, partial=True) - - if serializer.is_valid(): - serializer.save() - return success_response(data=serializer.data, request=request) - - return error_response( - error='Validation failed', - errors=serializer.errors, - status_code=status.HTTP_400_BAD_REQUEST, - request=request - ) - - def partial_update(self, request, pk=None): - """Partial update module enable settings""" - return self.update(request, pk) + """Same as list - return global settings (singleton, pk ignored)""" + return self.list(request, *args, **kwargs) @extend_schema_view( diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6d581b39..ec8e292c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,7 +4,6 @@ import { HelmetProvider } from "react-helmet-async"; import AppLayout from "./layout/AppLayout"; import { ScrollToTop } from "./components/common/ScrollToTop"; import ProtectedRoute from "./components/auth/ProtectedRoute"; -import ModuleGuard from "./components/common/ModuleGuard"; import GlobalErrorDisplay from "./components/common/GlobalErrorDisplay"; import LoadingStateMonitor from "./components/common/LoadingStateMonitor"; import { useAuthStore } from "./store/authStore"; @@ -81,7 +80,6 @@ const Users = lazy(() => import("./pages/Settings/Users")); const Subscriptions = lazy(() => import("./pages/Settings/Subscriptions")); const SystemSettings = lazy(() => import("./pages/Settings/System")); const AccountSettings = lazy(() => import("./pages/Settings/Account")); -const ModuleSettings = lazy(() => import("./pages/Settings/Modules")); const AISettings = lazy(() => import("./pages/Settings/AI")); const Plans = lazy(() => import("./pages/Settings/Plans")); const Industries = lazy(() => import("./pages/Settings/Industries")); @@ -142,115 +140,42 @@ export default function App() { {/* Planner Module - Redirect dashboard to keywords */} } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> + } /> + } /> + } /> + } /> {/* Writer Module - Redirect dashboard to tasks */} } /> - - - - } /> + } /> {/* Writer Content Routes - Order matters: list route must come before detail route */} - - - - } /> + } /> {/* Content detail view - matches /writer/content/:id (e.g., /writer/content/10) */} - - - - } /> + } /> } /> - - - - } /> - - - - } /> - - - - } /> + } /> + } /> + } /> {/* Automation Module */} } /> {/* Linker Module - Redirect dashboard to content */} } /> - - - - } /> + } /> {/* Optimizer Module - Redirect dashboard to content */} } /> - - - - } /> - - - - } /> + } /> + } /> - {/* Thinker Module */} {/* Thinker Module - Redirect dashboard to prompts */} } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> + } /> + } /> + } /> + } /> + } /> {/* Billing Module */} } /> @@ -283,7 +208,6 @@ export default function App() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/frontend/src/components/common/ModuleGuard.tsx b/frontend/src/components/common/ModuleGuard.tsx index a33cea96..467087e5 100644 --- a/frontend/src/components/common/ModuleGuard.tsx +++ b/frontend/src/components/common/ModuleGuard.tsx @@ -1,8 +1,4 @@ -import { ReactNode, useEffect } from 'react'; -import { Navigate } from 'react-router-dom'; -import { useSettingsStore } from '../../store/settingsStore'; -import { isModuleEnabled } from '../../config/modules.config'; -import { isUpgradeError } from '../../utils/upgrade'; +import { ReactNode } from 'react'; interface ModuleGuardProps { module: string; @@ -11,31 +7,12 @@ interface ModuleGuardProps { } /** - * ModuleGuard - Protects routes based on module enable status - * Redirects to settings page if module is disabled + * ModuleGuard - DEPRECATED + * Module enable/disable is now controlled ONLY via Django Admin (GlobalModuleSettings) + * This component no longer checks module status - all modules are accessible */ -export default function ModuleGuard({ module, children, redirectTo = '/settings/modules' }: ModuleGuardProps) { - const { moduleEnableSettings, loadModuleEnableSettings, loading } = useSettingsStore(); - - useEffect(() => { - // Load module enable settings if not already loaded - if (!moduleEnableSettings && !loading) { - loadModuleEnableSettings(); - } - }, [moduleEnableSettings, loading, loadModuleEnableSettings]); - - // While loading, show children (optimistic rendering) - if (loading || !moduleEnableSettings) { - return <>{children}; - } - - // Check if module is enabled - const enabled = isModuleEnabled(module, moduleEnableSettings as any); - - if (!enabled) { - return ; - } - +export default function ModuleGuard({ children }: ModuleGuardProps) { + // Module filtering removed - all modules always accessible return <>{children}; } diff --git a/frontend/src/config/modules.config.ts b/frontend/src/config/modules.config.ts index 45d7440d..e57f99df 100644 --- a/frontend/src/config/modules.config.ts +++ b/frontend/src/config/modules.config.ts @@ -78,33 +78,17 @@ export function getModuleConfig(moduleName: string): ModuleConfig | undefined { } /** - * Get all enabled modules + * Get all enabled modules (all modules always enabled) */ -export function getEnabledModules(moduleEnableSettings?: Record): ModuleConfig[] { - return Object.entries(MODULES) - .filter(([key, module]) => { - // If moduleEnableSettings provided, use it; otherwise default to enabled - if (moduleEnableSettings) { - const enabledKey = `${key}_enabled` as keyof typeof moduleEnableSettings; - return moduleEnableSettings[enabledKey] !== false; // Default to true if not set - } - return module.enabled; - }) - .map(([, module]) => module); +export function getEnabledModules(): ModuleConfig[] { + return Object.values(MODULES).filter(module => module.enabled); } /** - * Check if a module is enabled + * Check if a module is enabled (all modules always enabled) */ -export function isModuleEnabled(moduleName: string, moduleEnableSettings?: Record): boolean { +export function isModuleEnabled(moduleName: string): boolean { const module = MODULES[moduleName]; - if (!module) return false; - - if (moduleEnableSettings) { - const enabledKey = `${moduleName}_enabled` as keyof typeof moduleEnableSettings; - return moduleEnableSettings[enabledKey] !== false; // Default to true if not set - } - - return module.enabled; + return module ? module.enabled : false; } diff --git a/frontend/src/layout/AppSidebar.tsx b/frontend/src/layout/AppSidebar.tsx index c148dcda..4e4b8d41 100644 --- a/frontend/src/layout/AppSidebar.tsx +++ b/frontend/src/layout/AppSidebar.tsx @@ -41,14 +41,7 @@ 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(); - // 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; @@ -64,31 +57,10 @@ const AppSidebar: React.FC = () => { ); // 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 - // Skip for non-module pages to reduce unnecessary calls (e.g., account/billing/signup) - const path = location.pathname || ''; - const isModulePage = [ - '/planner', - '/writer', - '/automation', - '/thinker', - '/linker', - '/optimizer', - '/publisher', - '/dashboard', - '/home', - ].some((p) => path.startsWith(p)); - - if (user && isAuthenticated && isModulePage && !moduleEnableSettings && !settingsLoading) { - loadModuleEnableSettings().catch((error) => { - console.warn('Failed to load module enable settings:', error); - }); - } - }, [user, isAuthenticated, location.pathname]); // Only run when user/auth or route changes + // REMOVED: Module enable settings functionality - all modules always shown + // Module enable/disable is now controlled ONLY via Django Admin (GlobalModuleSettings) // 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) @@ -105,62 +77,50 @@ const AppSidebar: React.FC = () => { }, ]; - // 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 - }); - } + // Add Thinker (always shown) + 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) + // WORKFLOW section items (all modules always shown) 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 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 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 Automation + 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 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", - }); - } + // Add Optimizer + workflowItems.push({ + icon: , + name: "Optimizer", + path: "/optimizer/content", + }); return [ // Dashboard is standalone (no section header) @@ -243,7 +203,7 @@ const AppSidebar: React.FC = () => { ], }, ]; - }, [moduleEnabled]); + }, []); // No dependencies - always show all modules // Combine all sections const allSections = useMemo(() => { diff --git a/frontend/src/pages/Settings/Modules.tsx b/frontend/src/pages/Settings/Modules.tsx deleted file mode 100644 index 7952be3b..00000000 --- a/frontend/src/pages/Settings/Modules.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useEffect } from 'react'; -import PageMeta from '../../components/common/PageMeta'; -import { useToast } from '../../components/ui/toast/ToastContainer'; -import { useSettingsStore } from '../../store/settingsStore'; -import { MODULES } from '../../config/modules.config'; -import { Card } from '../../components/ui/card'; -import Switch from '../../components/form/switch/Switch'; - -export default function ModuleSettings() { - const toast = useToast(); - const { - moduleEnableSettings, - loadModuleEnableSettings, - updateModuleEnableSettings, - loading, - } = useSettingsStore(); - - useEffect(() => { - loadModuleEnableSettings(); - }, [loadModuleEnableSettings]); - - const handleToggle = async (moduleName: string, enabled: boolean) => { - try { - const enabledKey = `${moduleName}_enabled` as keyof typeof moduleEnableSettings; - await updateModuleEnableSettings({ - [enabledKey]: enabled, - } as any); - toast.success(`${MODULES[moduleName]?.name || moduleName} ${enabled ? 'enabled' : 'disabled'}`); - } catch (error: any) { - toast.error(`Failed to update module: ${error.message}`); - } - }; - - const getModuleEnabled = (moduleName: string): boolean => { - if (!moduleEnableSettings) return true; // Default to enabled - const enabledKey = `${moduleName}_enabled` as keyof typeof moduleEnableSettings; - return moduleEnableSettings[enabledKey] !== false; - }; - - return ( -
- -
-

Module Settings

-

Enable or disable modules for your account

-
- - {loading ? ( -
-
Loading...
-
- ) : ( - -
- {Object.entries(MODULES).map(([key, module]) => ( -
-
-
{module.icon}
-
-

- {module.name} -

- {module.description && ( -

- {module.description} -

- )} -
-
-
- - {getModuleEnabled(key) ? 'Enabled' : 'Disabled'} - - handleToggle(key, enabled)} - /> -
-
- ))} -
-
- )} -
- ); -} - diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 89bbba46..a25f4858 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1810,20 +1810,6 @@ export async function deleteUserSetting(key: string): Promise { } // Module Settings -export interface ModuleEnableSettings { - id: number; - planner_enabled: boolean; - writer_enabled: boolean; - thinker_enabled: boolean; - automation_enabled: boolean; - site_builder_enabled: boolean; - linker_enabled: boolean; - optimizer_enabled: boolean; - publisher_enabled: boolean; - created_at: string; - updated_at: string; -} - export interface ModuleSetting { id: number; module_name: string; @@ -1834,9 +1820,6 @@ export interface ModuleSetting { updated_at: string; } -// Deduplicate module-enable fetches to prevent 429s for normal users -let moduleEnableSettingsInFlight: Promise | null = null; - export async function fetchModuleSettings(moduleName: string): Promise { // fetchAPI extracts data from unified format {success: true, data: [...]} // So response IS the array, not an object with results @@ -1851,28 +1834,6 @@ export async function createModuleSetting(data: { module_name: string; key: stri }); } -export async function fetchModuleEnableSettings(): Promise { - if (moduleEnableSettingsInFlight) { - return moduleEnableSettingsInFlight; - } - - moduleEnableSettingsInFlight = fetchAPI('/v1/system/settings/modules/enable/'); - try { - const response = await moduleEnableSettingsInFlight; - return response; - } finally { - moduleEnableSettingsInFlight = null; - } -} - -export async function updateModuleEnableSettings(data: Partial): Promise { - const response = await fetchAPI('/v1/system/settings/modules/enable/', { - method: 'PUT', - body: JSON.stringify(data), - }); - return response; -} - export async function updateModuleSetting(moduleName: string, key: string, data: Partial<{ config: Record; is_active: boolean }>): Promise { return fetchAPI(`/v1/system/settings/modules/${key}/?module_name=${moduleName}`, { method: 'PUT', diff --git a/frontend/src/store/settingsStore.ts b/frontend/src/store/settingsStore.ts index 27b8e10b..36e479ff 100644 --- a/frontend/src/store/settingsStore.ts +++ b/frontend/src/store/settingsStore.ts @@ -13,16 +13,13 @@ import { fetchModuleSettings, createModuleSetting, updateModuleSetting, - fetchModuleEnableSettings, - updateModuleEnableSettings, AccountSetting, ModuleSetting, - ModuleEnableSettings, AccountSettingsError, } from '../services/api'; // Version for cache busting - increment when structure changes -const SETTINGS_STORE_VERSION = 2; +const SETTINGS_STORE_VERSION = 4; const getAccountSettingsErrorMessage = (error: AccountSettingsError): string => { switch (error.type) { @@ -38,7 +35,6 @@ const getAccountSettingsErrorMessage = (error: AccountSettingsError): string => interface SettingsState { accountSettings: Record; moduleSettings: Record>; - moduleEnableSettings: ModuleEnableSettings | null; loading: boolean; error: string | null; @@ -48,9 +44,6 @@ interface SettingsState { updateAccountSetting: (key: string, value: any) => Promise; loadModuleSettings: (moduleName: string) => Promise; updateModuleSetting: (moduleName: string, key: string, value: any) => Promise; - loadModuleEnableSettings: () => Promise; - updateModuleEnableSettings: (data: Partial) => Promise; - isModuleEnabled: (moduleName: string) => boolean; reset: () => void; } @@ -59,11 +52,8 @@ export const useSettingsStore = create()( (set, get) => ({ accountSettings: {}, moduleSettings: {}, - moduleEnableSettings: null, loading: false, error: null, - _moduleEnableLastFetched: 0 as number | undefined, - _moduleEnableInFlight: null as Promise | null, loadAccountSettings: async () => { set({ loading: true, error: null }); @@ -183,60 +173,10 @@ export const useSettingsStore = create()( } }, - loadModuleEnableSettings: async () => { - const state = get() as any; - const now = Date.now(); - // Use cached value if fetched within last 60s - if (state.moduleEnableSettings && state._moduleEnableLastFetched && now - state._moduleEnableLastFetched < 60000) { - return; - } - // Coalesce concurrent calls - if (state._moduleEnableInFlight) { - await state._moduleEnableInFlight; - return; - } - set({ loading: true, error: null }); - try { - const inFlight = fetchModuleEnableSettings(); - (state as any)._moduleEnableInFlight = inFlight; - const settings = await inFlight; - set({ moduleEnableSettings: settings, loading: false, _moduleEnableLastFetched: Date.now() }); - } catch (error: any) { - // On 429/403, avoid loops; cache the failure timestamp and do not retry automatically - if (error?.status === 429 || error?.status === 403) { - set({ loading: false, _moduleEnableLastFetched: Date.now() }); - return; - } - set({ error: error.message, loading: false, _moduleEnableLastFetched: Date.now() }); - } finally { - (get() as any)._moduleEnableInFlight = null; - } - }, - - updateModuleEnableSettings: async (data: Partial) => { - set({ loading: true, error: null }); - try { - const settings = await updateModuleEnableSettings(data); - set({ moduleEnableSettings: settings, loading: false }); - } catch (error: any) { - set({ error: error.message, loading: false }); - throw error; - } - }, - - isModuleEnabled: (moduleName: string): boolean => { - const settings = get().moduleEnableSettings; - if (!settings) return true; // Default to enabled if not loaded - - const enabledKey = `${moduleName}_enabled` as keyof ModuleEnableSettings; - return settings[enabledKey] !== false; // Default to true if not set - }, - reset: () => { set({ accountSettings: {}, moduleSettings: {}, - moduleEnableSettings: null, loading: false, error: null, }); @@ -244,23 +184,11 @@ export const useSettingsStore = create()( }), { name: 'settings-storage', - version: SETTINGS_STORE_VERSION, // Add version for cache busting + version: SETTINGS_STORE_VERSION, partialize: (state) => ({ accountSettings: state.accountSettings, moduleSettings: state.moduleSettings, - moduleEnableSettings: state.moduleEnableSettings, }), - // Migrate function to handle version changes - migrate: (persistedState: any, version: number) => { - if (version < SETTINGS_STORE_VERSION) { - // Clear module enable settings on version upgrade - return { - ...persistedState, - moduleEnableSettings: null, - }; - } - return persistedState; - }, } ) );