Apply 646095da: Module settings UI fixes with moduleStore

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-23 06:49:38 +00:00
parent 162947f3cc
commit 029c30ae70
7 changed files with 223 additions and 231 deletions

View File

@@ -9,6 +9,7 @@ import { AwsAdminGuard } from "./components/auth/AwsAdminGuard";
import GlobalErrorDisplay from "./components/common/GlobalErrorDisplay";
import LoadingStateMonitor from "./components/common/LoadingStateMonitor";
import { useAuthStore } from "./store/authStore";
import { useModuleStore } from "./store/moduleStore";
// Auth pages - loaded immediately (needed for login)
import SignIn from "./pages/AuthPages/SignIn";
@@ -117,7 +118,13 @@ const Components = lazy(() => import("./pages/Components"));
export default function App() {
// All session validation removed - API interceptor handles authentication
const { isAuthenticated } = useAuthStore();
const { loadModuleSettings } = useModuleStore();
// Load global module settings immediately on mount (public endpoint, no auth required)
useEffect(() => {
loadModuleSettings();
}, [loadModuleSettings]);
return (
<>

View File

@@ -23,12 +23,14 @@ import SidebarWidget from "./SidebarWidget";
import { APP_VERSION } from "../config/version";
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 = {
@@ -40,17 +42,8 @@ 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();
const { isModuleEnabled, settings: moduleSettings } = useModuleStore();
// Show admin menu only for aws-admin account users
const isAwsAdminAccount = Boolean(user?.account?.slug === 'aws-admin');
// 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;
@@ -65,33 +58,9 @@ const AppSidebar: React.FC = () => {
[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
// 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
// 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
// Module visibility is controlled by GlobalModuleSettings (Django Admin only)
const menuSections: MenuSection[] = useMemo(() => {
// SETUP section items (single items, no dropdowns - submenus shown as in-page navigation)
const setupItems: NavItem[] = [
@@ -100,15 +69,19 @@ const AppSidebar: React.FC = () => {
name: "Add Keywords",
path: "/setup/add-keywords",
},
{
];
// Add Sites (Site Builder) if enabled
if (isModuleEnabled('site_builder')) {
setupItems.push({
icon: <GridIcon />,
name: "Sites",
path: "/sites", // Submenus shown as in-page navigation
},
];
});
}
// Add Thinker if enabled (single item, no dropdown)
if (moduleEnabled('thinker')) {
// Add Thinker if enabled
if (isModuleEnabled('thinker')) {
setupItems.push({
icon: <BoltIcon />,
name: "Thinker",
@@ -116,11 +89,11 @@ const AppSidebar: React.FC = () => {
});
}
// WORKFLOW section items (single items, no dropdowns - submenus shown as in-page navigation)
// WORKFLOW section items (conditionally shown based on global settings)
const workflowItems: NavItem[] = [];
// Add Planner if enabled (single item, no dropdown)
if (moduleEnabled('planner')) {
// Add Planner if enabled
if (isModuleEnabled('planner')) {
workflowItems.push({
icon: <ListIcon />,
name: "Planner",
@@ -128,8 +101,8 @@ const AppSidebar: React.FC = () => {
});
}
// Add Writer if enabled (single item, no dropdown)
if (moduleEnabled('writer')) {
// Add Writer if enabled
if (isModuleEnabled('writer')) {
workflowItems.push({
icon: <TaskIcon />,
name: "Writer",
@@ -137,8 +110,8 @@ const AppSidebar: React.FC = () => {
});
}
// Add Automation (always available if Writer is enabled)
if (moduleEnabled('writer')) {
// Add Automation if enabled
if (isModuleEnabled('automation')) {
workflowItems.push({
icon: <BoltIcon />,
name: "Automation",
@@ -146,8 +119,8 @@ const AppSidebar: React.FC = () => {
});
}
// Add Linker if enabled (single item, no dropdown)
if (moduleEnabled('linker')) {
// Add Linker if enabled
if (isModuleEnabled('linker')) {
workflowItems.push({
icon: <PlugInIcon />,
name: "Linker",
@@ -155,8 +128,8 @@ const AppSidebar: React.FC = () => {
});
}
// Add Optimizer if enabled (single item, no dropdown)
if (moduleEnabled('optimizer')) {
// Add Optimizer if enabled
if (isModuleEnabled('optimizer')) {
workflowItems.push({
icon: <BoltIcon />,
name: "Optimizer",
@@ -217,12 +190,10 @@ const AppSidebar: React.FC = () => {
name: "Profile Settings",
path: "/settings/profile",
},
// Integration is admin-only; hide for non-privileged users (handled in render)
{
icon: <PlugInIcon />,
name: "Integration",
name: "AI Model Settings",
path: "/settings/integration",
adminOnly: true,
},
{
icon: <PageIcon />,
@@ -247,34 +218,12 @@ const AppSidebar: React.FC = () => {
],
},
];
}, [moduleEnabled]);
}, [isModuleEnabled, moduleSettings]); // Re-run when settings change
// Admin section - only shown for aws-admin account users
const adminSection: MenuSection = useMemo(() => ({
label: "ADMIN",
items: [
{
icon: <GridIcon />,
name: "System Dashboard",
path: "/admin/dashboard",
},
],
}), []);
// Combine all sections, including admin if user is in aws-admin account
// Combine all sections
const allSections = useMemo(() => {
const baseSections = menuSections.map(section => {
// Filter adminOnly items for non-system users
const filteredItems = section.items.filter((item: any) => {
if ((item as any).adminOnly && !isAwsAdminAccount) return false;
return true;
});
return { ...section, items: filteredItems };
});
return isAwsAdminAccount
? [...baseSections, adminSection]
: baseSections;
}, [isAwsAdminAccount, menuSections, adminSection]);
return menuSections;
}, [menuSections]);
useEffect(() => {
const currentPath = location.pathname;
@@ -355,7 +304,15 @@ const AppSidebar: React.FC = () => {
const renderMenuItems = (items: NavItem[], sectionIndex: number) => (
<ul className="flex flex-col gap-2">
{items.map((nav, itemIndex) => (
{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) => (
<li key={nav.name}>
{nav.subItems ? (
<button

View File

@@ -1879,6 +1879,25 @@ export async function updateModuleSetting(moduleName: string, key: string, data:
});
}
// Global Module Enable Settings (Platform-wide)
export interface GlobalModuleSettings {
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 | null;
updated_at: string | null;
}
export async function fetchGlobalModuleSettings(): Promise<GlobalModuleSettings> {
return fetchAPI('/v1/system/settings/modules/enable/');
}
// Billing API functions
export interface CreditBalance {
credits: number;

View File

@@ -0,0 +1,59 @@
/**
* Module Settings Store (Zustand)
* Manages global module enable/disable state (platform-wide)
* Settings are controlled via Django Admin only - NOT account-specific
* No persistence needed since this is the same for all users
*/
import { create } from 'zustand';
import { fetchGlobalModuleSettings, GlobalModuleSettings } from '../services/api';
interface ModuleState {
settings: GlobalModuleSettings | null;
loading: boolean;
error: string | null;
// Actions
loadModuleSettings: () => Promise<void>;
isModuleEnabled: (moduleName: string) => boolean;
reset: () => void;
}
export const useModuleStore = create<ModuleState>()((set, get) => ({
settings: null,
loading: false,
error: null,
loadModuleSettings: async () => {
set({ loading: true, error: null });
try {
const settings = await fetchGlobalModuleSettings();
console.log('Loaded global module settings:', settings);
set({ settings, loading: false });
} catch (error: any) {
console.error('Failed to load global module settings:', error);
set({
error: error.message || 'Failed to load module settings',
loading: false
});
}
},
isModuleEnabled: (moduleName: string): boolean => {
const { settings } = get();
// Default to true while settings are loading (better UX)
// Once settings load, they will control visibility
if (!settings) {
return true;
}
const fieldName = `${moduleName.toLowerCase()}_enabled` as keyof GlobalModuleSettings;
const enabled = settings[fieldName] === true;
console.log(`Module check for '${moduleName}' (${fieldName}): ${enabled}`);
return enabled;
},
reset: () => {
set({ settings: null, loading: false, error: null });
},
}));