moduel setgins fixed
This commit is contained in:
@@ -7,6 +7,7 @@ import ProtectedRoute from "./components/auth/ProtectedRoute";
|
||||
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";
|
||||
@@ -111,7 +112,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 (
|
||||
<>
|
||||
|
||||
@@ -23,6 +23,7 @@ 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;
|
||||
@@ -41,6 +42,7 @@ const AppSidebar: React.FC = () => {
|
||||
const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar();
|
||||
const location = useLocation();
|
||||
const { user, isAuthenticated } = useAuthStore();
|
||||
const { isModuleEnabled, settings: moduleSettings } = useModuleStore();
|
||||
|
||||
const [openSubmenu, setOpenSubmenu] = useState<{
|
||||
sectionIndex: number;
|
||||
@@ -56,12 +58,9 @@ const AppSidebar: React.FC = () => {
|
||||
[location.pathname]
|
||||
);
|
||||
|
||||
// Load module enable settings on mount (only once) - but only if user is authenticated
|
||||
// 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
|
||||
// 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[] = [
|
||||
@@ -70,57 +69,73 @@ 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 (always shown)
|
||||
setupItems.push({
|
||||
icon: <BoltIcon />,
|
||||
name: "Thinker",
|
||||
path: "/thinker/prompts", // Default to prompts, submenus shown as in-page navigation
|
||||
});
|
||||
// Add Thinker if enabled
|
||||
if (isModuleEnabled('thinker')) {
|
||||
setupItems.push({
|
||||
icon: <BoltIcon />,
|
||||
name: "Thinker",
|
||||
path: "/thinker/prompts", // Default to prompts, submenus shown as in-page navigation
|
||||
});
|
||||
}
|
||||
|
||||
// WORKFLOW section items (all modules always shown)
|
||||
// WORKFLOW section items (conditionally shown based on global settings)
|
||||
const workflowItems: NavItem[] = [];
|
||||
|
||||
// Add Planner
|
||||
workflowItems.push({
|
||||
icon: <ListIcon />,
|
||||
name: "Planner",
|
||||
path: "/planner/keywords", // Default to keywords, submenus shown as in-page navigation
|
||||
});
|
||||
// Add Planner if enabled
|
||||
if (isModuleEnabled('planner')) {
|
||||
workflowItems.push({
|
||||
icon: <ListIcon />,
|
||||
name: "Planner",
|
||||
path: "/planner/keywords", // Default to keywords, submenus shown as in-page navigation
|
||||
});
|
||||
}
|
||||
|
||||
// Add Writer
|
||||
workflowItems.push({
|
||||
icon: <TaskIcon />,
|
||||
name: "Writer",
|
||||
path: "/writer/tasks", // Default to tasks, submenus shown as in-page navigation
|
||||
});
|
||||
// Add Writer if enabled
|
||||
if (isModuleEnabled('writer')) {
|
||||
workflowItems.push({
|
||||
icon: <TaskIcon />,
|
||||
name: "Writer",
|
||||
path: "/writer/tasks", // Default to tasks, submenus shown as in-page navigation
|
||||
});
|
||||
}
|
||||
|
||||
// Add Automation
|
||||
workflowItems.push({
|
||||
icon: <BoltIcon />,
|
||||
name: "Automation",
|
||||
path: "/automation",
|
||||
});
|
||||
// Add Automation if enabled
|
||||
if (isModuleEnabled('automation')) {
|
||||
workflowItems.push({
|
||||
icon: <BoltIcon />,
|
||||
name: "Automation",
|
||||
path: "/automation",
|
||||
});
|
||||
}
|
||||
|
||||
// Add Linker
|
||||
workflowItems.push({
|
||||
icon: <PlugInIcon />,
|
||||
name: "Linker",
|
||||
path: "/linker/content",
|
||||
});
|
||||
// Add Linker if enabled
|
||||
if (isModuleEnabled('linker')) {
|
||||
workflowItems.push({
|
||||
icon: <PlugInIcon />,
|
||||
name: "Linker",
|
||||
path: "/linker/content",
|
||||
});
|
||||
}
|
||||
|
||||
// Add Optimizer
|
||||
workflowItems.push({
|
||||
icon: <BoltIcon />,
|
||||
name: "Optimizer",
|
||||
path: "/optimizer/content",
|
||||
});
|
||||
// Add Optimizer if enabled
|
||||
if (isModuleEnabled('optimizer')) {
|
||||
workflowItems.push({
|
||||
icon: <BoltIcon />,
|
||||
name: "Optimizer",
|
||||
path: "/optimizer/content",
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
// Dashboard is standalone (no section header)
|
||||
@@ -203,7 +218,7 @@ const AppSidebar: React.FC = () => {
|
||||
],
|
||||
},
|
||||
];
|
||||
}, []); // No dependencies - always show all modules
|
||||
}, [isModuleEnabled, moduleSettings]); // Re-run when settings change
|
||||
|
||||
// Combine all sections
|
||||
const allSections = useMemo(() => {
|
||||
|
||||
@@ -1841,6 +1841,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;
|
||||
|
||||
59
frontend/src/store/moduleStore.ts
Normal file
59
frontend/src/store/moduleStore.ts
Normal 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 });
|
||||
},
|
||||
}));
|
||||
69
frontend/test-module-settings.html
Normal file
69
frontend/test-module-settings.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Module Settings Test</title>
|
||||
<style>
|
||||
body { font-family: Arial; padding: 20px; }
|
||||
.success { color: green; }
|
||||
.error { color: red; }
|
||||
.info { color: blue; }
|
||||
pre { background: #f5f5f5; padding: 10px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Module Settings API Test</h1>
|
||||
<button onclick="testAPI()">Test API Endpoint</button>
|
||||
<div id="result"></div>
|
||||
|
||||
<script>
|
||||
async function testAPI() {
|
||||
const resultDiv = document.getElementById('result');
|
||||
resultDiv.innerHTML = '<p class="info">Testing...</p>';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.igny8.com/v1/system/settings/modules/enable/', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_TOKEN_HERE' // Replace with actual token
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
resultDiv.innerHTML = `
|
||||
<h2 class="success">✓ Success!</h2>
|
||||
<h3>Raw Response:</h3>
|
||||
<pre>${JSON.stringify(data, null, 2)}</pre>
|
||||
|
||||
<h3>Module Status:</h3>
|
||||
<ul>
|
||||
<li>Planner: ${data.data?.planner_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Writer: ${data.data?.writer_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Thinker: ${data.data?.thinker_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Automation: ${data.data?.automation_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Site Builder: ${data.data?.site_builder_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Linker: ${data.data?.linker_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Optimizer: ${data.data?.optimizer_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Publisher: ${data.data?.publisher_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
</ul>
|
||||
`;
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `
|
||||
<h2 class="error">✗ Error</h2>
|
||||
<p>${error.message}</p>
|
||||
<p class="info">Note: This test requires authentication. Open browser console in your app and run:</p>
|
||||
<pre>
|
||||
// In browser console on your app:
|
||||
fetch('/v1/system/settings/modules/enable/')
|
||||
.then(r => r.json())
|
||||
.then(data => console.log(data))
|
||||
</pre>
|
||||
`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user