diff --git a/src/platform/env.js b/src/platform/env.js index 187e0f3..60b1266 100644 --- a/src/platform/env.js +++ b/src/platform/env.js @@ -84,6 +84,7 @@ export const CONFIG_KEYS = { BRAND_LOGO: 'BRAND_LOGO', THEME_COLOR: 'THEME_COLOR', UI_SHELL: 'UI_SHELL', + INITIAL_ROUTE: 'INITIAL_ROUTE', STORAGE_BACKEND: 'STORAGE_BACKEND', API_BASE_URL: 'API_BASE_URL', MODULES: 'MODULES', @@ -152,6 +153,7 @@ export function initEnv(appConfig) { BRAND_LOGO: appConfig.brand_logo || appConfig.brandLogo || appConfig.icons?.[0]?.src || '/favicon.svg', THEME_COLOR: appConfig.theme_color || appConfig.themeColor || '#000000', UI_SHELL: appConfig.ui_shell || appConfig.uiShell || 'EmptyShell', + INITIAL_ROUTE: appConfig.initial_route || appConfig.initialRoute || appConfig.ui?.initial_route || appConfig.ui?.initialRoute || '/home', STORAGE_BACKEND: appConfig.storage?.backend || 'localStorage', API_BASE_URL: resolvedApiBaseURL, MODULES: appConfig.modules || [], diff --git a/src/ui/App.jsx b/src/ui/App.jsx index b402e4a..70e6cab 100644 --- a/src/ui/App.jsx +++ b/src/ui/App.jsx @@ -6,7 +6,13 @@ import React, { createContext, useContext, useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { TamaguiProvider, Theme, createTamagui, YStack } from 'tamagui'; -import { clearPWACache, getServiceWorkerStatus } from '../platform/sw-register.js'; +import { + clearPWACache, + clearAllCaches, + clearAllStorage, + getServiceWorkerStatus, + unregisterAllServiceWorkers +} from '../platform/sw-register.js'; import { getProvider } from '../platform/storage.js'; import * as apiClient from '../platform/api.js'; import * as storageModuleRef from '../platform/storage.js'; @@ -14,6 +20,7 @@ import * as menuRef from '../platform/menu.js'; import * as envModuleRef from '../platform/env.js'; import { getConfig, setConfig, CONFIG_KEYS, createLogger, startTrace, isDevelopment } from '../platform/env.js'; import { EmptyShell, LandingShell, DashboardShell, AppInfo, Router } from './components/index.js'; +import { resolveRegisteredShell } from './components/shell-registry.js'; import { LoginPage } from '../security/pages/LoginPage.jsx'; import { getStyleTheme, DEFAULT_STYLE_THEME, normalizeStyleThemeName, setActiveStyleThemeName } from './styles/index.js'; import { THEME_MODE_CONFIG_KEY, THEME_NAME_CONFIG_KEY, THEME_MODES, themeManager } from './theme-controller.js'; @@ -48,8 +55,11 @@ function resolveShellComponent(shellName = 'EmptyShell') { case 'dashboardshell': return DashboardShell; case 'emptyshell': - default: return EmptyShell; + default: { + const registeredShell = resolveRegisteredShell(shellName); + return registeredShell || EmptyShell; + } } } @@ -84,6 +94,9 @@ function App({ const [menuItems, setMenuItems] = useState([]); const [initialized, setInitialized] = useState(false); const [ShellComponent, setShellComponent] = useState(() => resolveShellComponent(initialProfile?.ui_shell ?? 'EmptyShell')); + const [initialRoute, setInitialRoute] = useState( + initialProfile?.initial_route ?? initialProfile?.initialRoute ?? initialProfile?.ui?.initial_route ?? initialProfile?.ui?.initialRoute ?? '/home' + ); const [bootResult, setBootResult] = useState(null); const [bootModeOverride, setBootModeOverride] = useState(null); @@ -282,6 +295,9 @@ function App({ const Shell = resolveShellComponent(shellName); setShellComponent(() => Shell); appLogger.log(`Using shell: ${shellName}`); + + const configuredInitialRoute = await services.env.getConfig(CONFIG_KEYS.INITIAL_ROUTE, '/home'); + setInitialRoute(configuredInitialRoute || '/home'); // Get menu items from primary menu if (services.menu) { @@ -405,7 +421,7 @@ function App({ appContent = ; } else if (!shouldRenderBootScreen && !shouldHoldDuringInit) { appContent = ( - + {/* Declarative route registration (commented out - routes now registered programmatically via modules) @@ -520,18 +536,9 @@ if (typeof window !== 'undefined') { window.clearPWACache = clearPWACache; window.__PWA_UTILS__ = { clearPWACache, - clearAllCaches: async () => { - const { clearAllCaches } = await import('../platform/sw-register.js'); - return clearAllCaches(); - }, - unregisterAllServiceWorkers: async () => { - const { unregisterAllServiceWorkers } = await import('../platform/sw-register.js'); - return unregisterAllServiceWorkers(); - }, - clearAllStorage: async () => { - const { clearAllStorage } = await import('../platform/sw-register.js'); - return clearAllStorage(); - } + clearAllCaches, + unregisterAllServiceWorkers, + clearAllStorage }; console.log('💡 PWA Utilities available:'); diff --git a/src/ui/components/IconMapper.jsx b/src/ui/components/IconMapper.jsx index 618eec2..44c2e32 100644 --- a/src/ui/components/IconMapper.jsx +++ b/src/ui/components/IconMapper.jsx @@ -394,6 +394,7 @@ const iconMap = { 'wifi': wrap(WifiHigh, 'WifiHigh'), 'wifi-off': wrap(WifiSlash, 'WifiSlash'), 'lightning': wrap(Lightning, 'Lightning'), + 'game': wrap(Lightning, 'Lightning'), // ── Volume ───────────────────────────────────────────────────────────── 'volume-up': wrap(SpeakerHigh, 'SpeakerHigh'), diff --git a/src/ui/components/index.js b/src/ui/components/index.js index 7ca9a8f..d01e562 100644 --- a/src/ui/components/index.js +++ b/src/ui/components/index.js @@ -25,6 +25,7 @@ export { Panel, default as PanelDefault } from './Panel.jsx'; export { SettingsPanel, default as SettingsPanelDefault } from './SettingsPanel.jsx'; export { GeneralConfig, default as GeneralConfigDefault } from './GeneralConfig.jsx'; export { IdentityConfig, default as IdentityConfigDefault } from './IdentityConfig.jsx'; +export { registerShell, unregisterShell, resolveRegisteredShell, listRegisteredShells, clearRegisteredShells } from './shell-registry.js'; export * from './grid/index.js'; export { getTypographyRoleProps, getStyleTypography, TYPOGRAPHY_ROLE_KEYS } from '../styles/index.js'; diff --git a/src/ui/components/shell-registry.js b/src/ui/components/shell-registry.js new file mode 100644 index 0000000..25bad64 --- /dev/null +++ b/src/ui/components/shell-registry.js @@ -0,0 +1,49 @@ +const SHELL_REGISTRY_KEY = '__bface_shell_registry__'; + +function getShellRegistry() { + const scope = typeof globalThis !== 'undefined' ? globalThis : window; + if (!scope[SHELL_REGISTRY_KEY]) { + scope[SHELL_REGISTRY_KEY] = new Map(); + } + return scope[SHELL_REGISTRY_KEY]; +} + +function normalizeShellName(name = '') { + return String(name).trim().toLowerCase(); +} + +export function registerShell(name, component) { + const key = normalizeShellName(name); + if (!key || typeof component !== 'function') { + return false; + } + + getShellRegistry().set(key, component); + return true; +} + +export function unregisterShell(name) { + const key = normalizeShellName(name); + if (!key) { + return false; + } + + return getShellRegistry().delete(key); +} + +export function resolveRegisteredShell(name) { + const key = normalizeShellName(name); + if (!key) { + return null; + } + + return getShellRegistry().get(key) || null; +} + +export function listRegisteredShells() { + return Array.from(getShellRegistry().keys()); +} + +export function clearRegisteredShells() { + getShellRegistry().clear(); +}