Release v0.1.6
This commit is contained in:
@@ -1,30 +1,32 @@
|
||||
import { Navigate, Route, Routes } from "react-router-dom";
|
||||
import { lazy, Suspense, useEffect, useMemo, useState } from "react";
|
||||
import { fetchMe } from "./api/auth";
|
||||
import { fetchPlatformModules } from "./api/platform";
|
||||
import { fetchPlatformModules, fetchPlatformStatus } from "./api/platform";
|
||||
import { AUTH_REQUIRED_EVENT, loadApiSettings, saveApiSettings, type AuthRequiredEventDetail } from "./api/client";
|
||||
import type { ApiSettings, AuthInfo, LoginResponse, PlatformModuleInfo } from "./types";
|
||||
import type { ApiSettings, AuthInfo, LoginResponse, PlatformModuleInfo, PlatformWebModule } from "./types";
|
||||
import AppShell from "./layout/AppShell";
|
||||
import PublicLandingPage from "./features/auth/PublicLandingPage";
|
||||
import LoginModal from "./features/auth/LoginModal";
|
||||
import { PermissionBoundary } from "./components/AccessBoundary";
|
||||
import { adminReadScopes } from "./utils/permissions";
|
||||
import { firstAccessibleRoute, navItemsForModules, resolveInstalledWebModules, routeContributionsForModules } from "./platform/modules";
|
||||
import { firstAccessibleRoute, loadRemoteWebModules, navItemsForModules, resolveInstalledWebModules, routeContributionsForModules } from "./platform/modules";
|
||||
import { PlatformModulesProvider } from "./platform/ModuleContext";
|
||||
import { PLATFORM_MODULES_CHANGED_EVENT } from "./platform/moduleEvents";
|
||||
import { UnsavedChangesProvider } from "./components/UnsavedChangesGuard";
|
||||
|
||||
const DashboardPage = lazy(() => import("./features/dashboard/DashboardPage"));
|
||||
const SettingsPage = lazy(() => import("./features/settings/SettingsPage"));
|
||||
const AdminPage = lazy(() => import("./features/admin/AdminPage"));
|
||||
|
||||
export default function App() {
|
||||
const [settings, setSettings] = useState<ApiSettings>(() => loadApiSettings());
|
||||
const [auth, setAuth] = useState<AuthInfo | null>(null);
|
||||
const [checkingSession, setCheckingSession] = useState(true);
|
||||
const [platformModules, setPlatformModules] = useState<PlatformModuleInfo[] | null>(null);
|
||||
const [remoteWebModules, setRemoteWebModules] = useState<PlatformWebModule[]>([]);
|
||||
const [maintenanceMode, setMaintenanceMode] = useState<{ enabled: boolean; message?: string | null }>({ enabled: false, message: null });
|
||||
const [reloginMessage, setReloginMessage] = useState("");
|
||||
|
||||
const webModules = useMemo(() => resolveInstalledWebModules(platformModules), [platformModules]);
|
||||
const localWebModules = useMemo(() => resolveInstalledWebModules(platformModules), [platformModules]);
|
||||
const webModules = useMemo(() => mergeWebModules(localWebModules, remoteWebModules), [localWebModules, remoteWebModules]);
|
||||
const navItems = useMemo(() => navItemsForModules(webModules), [webModules]);
|
||||
const moduleRoutes = useMemo(() => routeContributionsForModules(webModules), [webModules]);
|
||||
|
||||
@@ -75,6 +77,18 @@ export default function App() {
|
||||
return () => window.removeEventListener(AUTH_REQUIRED_EVENT, handleAuthRequired);
|
||||
}, [auth]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
fetchPlatformStatus(settings)
|
||||
.then((response) => {
|
||||
if (!cancelled) setMaintenanceMode(response.maintenance_mode);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setMaintenanceMode({ enabled: false, message: null });
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [settings.apiBaseUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setCheckingSession(true);
|
||||
@@ -87,6 +101,7 @@ export default function App() {
|
||||
saveApiSettings(cleared);
|
||||
setAuth(null);
|
||||
setPlatformModules(null);
|
||||
setRemoteWebModules([]);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -99,13 +114,52 @@ export default function App() {
|
||||
if (!auth) return;
|
||||
|
||||
let cancelled = false;
|
||||
fetchPlatformModules(settings)
|
||||
.then((response) => { if (!cancelled) setPlatformModules(response.modules); })
|
||||
.catch(() => { if (!cancelled) setPlatformModules(null); });
|
||||
let inFlight = false;
|
||||
let lastRefreshAt = 0;
|
||||
function loadModules() {
|
||||
inFlight = true;
|
||||
lastRefreshAt = Date.now();
|
||||
return fetchPlatformModules(settings)
|
||||
.then((response) => { if (!cancelled) setPlatformModules(response.modules); })
|
||||
.catch(() => { if (!cancelled) setPlatformModules(null); })
|
||||
.finally(() => { inFlight = false; });
|
||||
}
|
||||
|
||||
return () => { cancelled = true; };
|
||||
void loadModules();
|
||||
|
||||
function handleModulesChanged() {
|
||||
void loadModules();
|
||||
}
|
||||
|
||||
function refreshVisibleModules() {
|
||||
if (document.visibilityState === "hidden" || inFlight) return;
|
||||
if (Date.now() - lastRefreshAt < 5_000) return;
|
||||
void loadModules();
|
||||
}
|
||||
|
||||
window.addEventListener(PLATFORM_MODULES_CHANGED_EVENT, handleModulesChanged);
|
||||
window.addEventListener("focus", refreshVisibleModules);
|
||||
document.addEventListener("visibilitychange", refreshVisibleModules);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.removeEventListener(PLATFORM_MODULES_CHANGED_EVENT, handleModulesChanged);
|
||||
window.removeEventListener("focus", refreshVisibleModules);
|
||||
document.removeEventListener("visibilitychange", refreshVisibleModules);
|
||||
};
|
||||
}, [auth?.user.id, auth?.active_tenant?.id, auth?.tenant.id, settings.apiBaseUrl, settings.apiKey]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (!auth || !platformModules?.length) {
|
||||
setRemoteWebModules([]);
|
||||
return () => { cancelled = true; };
|
||||
}
|
||||
loadRemoteWebModules(platformModules, localWebModules)
|
||||
.then((modules) => { if (!cancelled) setRemoteWebModules(modules); })
|
||||
.catch(() => { if (!cancelled) setRemoteWebModules([]); });
|
||||
return () => { cancelled = true; };
|
||||
}, [auth?.user.id, auth?.active_tenant?.id, auth?.tenant.id, platformModules, localWebModules]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!auth) return;
|
||||
|
||||
@@ -159,7 +213,7 @@ export default function App() {
|
||||
<PlatformModulesProvider modules={webModules}>
|
||||
<UnsavedChangesProvider>
|
||||
<AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems}>
|
||||
<PublicLandingPage settings={settings} onLogin={handlePublicLogin} />
|
||||
<PublicLandingPage settings={settings} maintenanceMode={maintenanceMode} onLogin={handlePublicLogin} />
|
||||
</AppShell>
|
||||
</UnsavedChangesProvider>
|
||||
</PlatformModulesProvider>
|
||||
@@ -182,17 +236,12 @@ export default function App() {
|
||||
path={route.path}
|
||||
element={
|
||||
<PermissionBoundary auth={auth} anyOf={route.anyOf} allOf={route.allOf} fallback={defaultRoute}>
|
||||
{route.render({ settings, auth })}
|
||||
{route.render({ settings, auth, onAuthChange: updateAuth })}
|
||||
</PermissionBoundary>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<Route path="/settings" element={<SettingsPage settings={settings} auth={auth} onSettingsChange={updateSettings} onAuthChange={updateAuth} />} />
|
||||
<Route path="/admin" element={
|
||||
<PermissionBoundary auth={auth} anyOf={adminReadScopes} fallback={defaultRoute}>
|
||||
<AdminPage settings={settings} auth={auth} onAuthChange={updateAuth} />
|
||||
</PermissionBoundary>
|
||||
} />
|
||||
<Route path="*" element={<Navigate to={defaultRoute} replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
@@ -210,3 +259,16 @@ export default function App() {
|
||||
</PlatformModulesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function mergeWebModules(localModules: PlatformWebModule[], remoteModules: PlatformWebModule[]): PlatformWebModule[] {
|
||||
if (remoteModules.length === 0) return localModules;
|
||||
const seen = new Set(localModules.map((module) => module.id));
|
||||
return [
|
||||
...localModules,
|
||||
...remoteModules.filter((module) => {
|
||||
if (seen.has(module.id)) return false;
|
||||
seen.add(module.id);
|
||||
return true;
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user