mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 23:44:24 +01:00
190 lines
5.3 KiB
TypeScript
190 lines
5.3 KiB
TypeScript
"use client"
|
|
|
|
import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from "react"
|
|
import { apiClient } from "@/lib/api-client"
|
|
import { tokenManager } from "@/lib/token-manager"
|
|
import { usePathname } from "next/navigation"
|
|
|
|
interface Module {
|
|
name: string
|
|
version: string
|
|
description: string
|
|
initialized: boolean
|
|
enabled: boolean
|
|
stats?: any
|
|
}
|
|
|
|
interface ModulesResponse {
|
|
total: number
|
|
modules: Module[]
|
|
module_count: number
|
|
initialized: boolean
|
|
}
|
|
|
|
interface ModulesContextType {
|
|
modules: Module[]
|
|
enabledModules: Set<string>
|
|
isLoading: boolean
|
|
error: string | null
|
|
refreshModules: () => Promise<void>
|
|
isModuleEnabled: (moduleName: string) => boolean
|
|
lastUpdated: Date | null
|
|
}
|
|
|
|
const ModulesContext = createContext<ModulesContextType | undefined>(undefined)
|
|
|
|
export function ModulesProvider({ children }: { children: ReactNode }) {
|
|
const [modules, setModules] = useState<Module[]>([])
|
|
const [enabledModules, setEnabledModules] = useState<Set<string>>(new Set())
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
|
|
const pathname = usePathname()
|
|
|
|
// Check if we're on an auth page
|
|
const isAuthPage = pathname === '/login' || pathname === '/register' || pathname === '/forgot-password'
|
|
|
|
const fetchModules = useCallback(async () => {
|
|
// Don't fetch if we're on an auth page or not authenticated
|
|
if (isAuthPage || !tokenManager.isAuthenticated()) {
|
|
setIsLoading(false)
|
|
return
|
|
}
|
|
|
|
try {
|
|
setIsLoading(true)
|
|
setError(null)
|
|
|
|
const data: ModulesResponse = await apiClient.get("/api-internal/v1/modules/")
|
|
|
|
setModules(data.modules)
|
|
|
|
// Create set of enabled module names for fast lookup
|
|
const enabledSet = new Set(
|
|
data.modules
|
|
.filter(module => module.enabled && module.initialized)
|
|
.map(module => module.name)
|
|
)
|
|
setEnabledModules(enabledSet)
|
|
setLastUpdated(new Date())
|
|
|
|
} catch (err) {
|
|
// Only set error if we're authenticated (to avoid noise on auth pages)
|
|
if (tokenManager.isAuthenticated()) {
|
|
setError(err instanceof Error ? err.message : "Failed to load modules")
|
|
}
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [isAuthPage])
|
|
|
|
const refreshModules = useCallback(async () => {
|
|
await fetchModules()
|
|
}, [fetchModules])
|
|
|
|
const isModuleEnabled = useCallback((moduleName: string): boolean => {
|
|
return enabledModules.has(moduleName)
|
|
}, [enabledModules])
|
|
|
|
useEffect(() => {
|
|
// Only fetch if authenticated and not on auth page
|
|
if (!isAuthPage && tokenManager.isAuthenticated()) {
|
|
fetchModules()
|
|
}
|
|
|
|
// Set up periodic refresh every 30 seconds to catch module state changes
|
|
// But only if authenticated
|
|
let interval: NodeJS.Timeout | null = null
|
|
if (!isAuthPage && tokenManager.isAuthenticated()) {
|
|
interval = setInterval(fetchModules, 30000)
|
|
}
|
|
|
|
return () => {
|
|
if (interval) clearInterval(interval)
|
|
}
|
|
}, [fetchModules, isAuthPage])
|
|
|
|
// Listen for custom events that indicate module state changes
|
|
useEffect(() => {
|
|
const handleModuleStateChange = () => {
|
|
refreshModules()
|
|
}
|
|
|
|
window.addEventListener("moduleStateChanged", handleModuleStateChange)
|
|
|
|
return () => {
|
|
window.removeEventListener("moduleStateChanged", handleModuleStateChange)
|
|
}
|
|
}, [refreshModules])
|
|
|
|
// Listen for authentication changes
|
|
useEffect(() => {
|
|
const handleTokensUpdated = () => {
|
|
// When tokens are updated (user logs in), fetch modules
|
|
if (!isAuthPage) {
|
|
fetchModules()
|
|
}
|
|
}
|
|
|
|
const handleTokensCleared = () => {
|
|
// When tokens are cleared (user logs out), clear modules
|
|
setModules([])
|
|
setEnabledModules(new Set())
|
|
setError(null)
|
|
setLastUpdated(null)
|
|
}
|
|
|
|
tokenManager.on('tokensUpdated', handleTokensUpdated)
|
|
tokenManager.on('tokensCleared', handleTokensCleared)
|
|
tokenManager.on('logout', handleTokensCleared)
|
|
|
|
return () => {
|
|
tokenManager.off('tokensUpdated', handleTokensUpdated)
|
|
tokenManager.off('tokensCleared', handleTokensCleared)
|
|
tokenManager.off('logout', handleTokensCleared)
|
|
}
|
|
}, [fetchModules, isAuthPage])
|
|
|
|
return (
|
|
<ModulesContext.Provider
|
|
value={{
|
|
modules,
|
|
enabledModules,
|
|
isLoading,
|
|
error,
|
|
refreshModules,
|
|
isModuleEnabled,
|
|
lastUpdated,
|
|
}}
|
|
>
|
|
{children}
|
|
</ModulesContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useModules() {
|
|
const context = useContext(ModulesContext)
|
|
if (context === undefined) {
|
|
// During SSR/SSG, return default values instead of throwing
|
|
if (typeof window === "undefined") {
|
|
return {
|
|
modules: [],
|
|
enabledModules: new Set<string>(),
|
|
isLoading: true,
|
|
error: null,
|
|
refreshModules: async () => {},
|
|
isModuleEnabled: () => false,
|
|
lastUpdated: null,
|
|
}
|
|
}
|
|
throw new Error("useModules must be used within a ModulesProvider")
|
|
}
|
|
return context
|
|
}
|
|
|
|
// Helper function to trigger module state refresh from other components
|
|
export function triggerModuleRefresh() {
|
|
if (typeof window !== "undefined") {
|
|
window.dispatchEvent(new CustomEvent("moduleStateChanged"))
|
|
}
|
|
} |