Release v0.1.2
This commit is contained in:
@@ -2,13 +2,16 @@ 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 { loadApiSettings, saveApiSettings } from "./api/client";
|
||||
import { AUTH_REQUIRED_EVENT, loadApiSettings, saveApiSettings, type AuthRequiredEventDetail } from "./api/client";
|
||||
import type { ApiSettings, AuthInfo, LoginResponse, PlatformModuleInfo } 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 { PlatformModulesProvider } from "./platform/ModuleContext";
|
||||
import { UnsavedChangesProvider } from "./components/UnsavedChangesGuard";
|
||||
|
||||
const DashboardPage = lazy(() => import("./features/dashboard/DashboardPage"));
|
||||
const SettingsPage = lazy(() => import("./features/settings/SettingsPage"));
|
||||
@@ -19,6 +22,7 @@ export default function App() {
|
||||
const [auth, setAuth] = useState<AuthInfo | null>(null);
|
||||
const [checkingSession, setCheckingSession] = useState(true);
|
||||
const [platformModules, setPlatformModules] = useState<PlatformModuleInfo[] | null>(null);
|
||||
const [reloginMessage, setReloginMessage] = useState("");
|
||||
|
||||
const webModules = useMemo(() => resolveInstalledWebModules(platformModules), [platformModules]);
|
||||
const navItems = useMemo(() => navItemsForModules(webModules), [webModules]);
|
||||
@@ -38,22 +42,39 @@ export default function App() {
|
||||
}
|
||||
}
|
||||
|
||||
function handlePublicLogin(response: LoginResponse) {
|
||||
function authFromLoginResponse(response: LoginResponse): AuthInfo {
|
||||
const active = response.active_tenant ?? response.tenant;
|
||||
updateAuth(
|
||||
{
|
||||
user: response.user,
|
||||
tenant: active,
|
||||
active_tenant: active,
|
||||
tenants: response.tenants ?? [active],
|
||||
scopes: response.scopes,
|
||||
roles: response.roles,
|
||||
groups: response.groups
|
||||
},
|
||||
""
|
||||
);
|
||||
return {
|
||||
user: response.user,
|
||||
tenant: active,
|
||||
active_tenant: active,
|
||||
tenants: response.tenants ?? [active],
|
||||
scopes: response.scopes,
|
||||
roles: response.roles,
|
||||
groups: response.groups
|
||||
};
|
||||
}
|
||||
|
||||
function handlePublicLogin(response: LoginResponse) {
|
||||
updateAuth(authFromLoginResponse(response), "");
|
||||
}
|
||||
|
||||
function handleRelogin(response: LoginResponse) {
|
||||
updateAuth(authFromLoginResponse(response), "");
|
||||
setReloginMessage("");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function handleAuthRequired(event: Event) {
|
||||
if (!auth) return;
|
||||
const detail = (event as CustomEvent<AuthRequiredEventDetail>).detail;
|
||||
setReloginMessage(detail?.message || "Your session has expired. Sign in again to continue.");
|
||||
}
|
||||
|
||||
window.addEventListener(AUTH_REQUIRED_EVENT, handleAuthRequired);
|
||||
return () => window.removeEventListener(AUTH_REQUIRED_EVENT, handleAuthRequired);
|
||||
}, [auth]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setCheckingSession(true);
|
||||
@@ -117,7 +138,9 @@ export default function App() {
|
||||
|
||||
if (checkingSession) {
|
||||
return (
|
||||
<AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems}>
|
||||
<PlatformModulesProvider modules={webModules}>
|
||||
<UnsavedChangesProvider>
|
||||
<AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems}>
|
||||
<div className="public-landing">
|
||||
<section className="public-card">
|
||||
<div className="public-kicker">GovOPlaN</div>
|
||||
@@ -125,26 +148,34 @@ export default function App() {
|
||||
<p>Please wait while the local session is verified.</p>
|
||||
</section>
|
||||
</div>
|
||||
</AppShell>
|
||||
</AppShell>
|
||||
</UnsavedChangesProvider>
|
||||
</PlatformModulesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
if (!auth) {
|
||||
return (
|
||||
<AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems}>
|
||||
<PublicLandingPage settings={settings} onLogin={handlePublicLogin} />
|
||||
</AppShell>
|
||||
<PlatformModulesProvider modules={webModules}>
|
||||
<UnsavedChangesProvider>
|
||||
<AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems}>
|
||||
<PublicLandingPage settings={settings} onLogin={handlePublicLogin} />
|
||||
</AppShell>
|
||||
</UnsavedChangesProvider>
|
||||
</PlatformModulesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const defaultRoute = firstAccessibleRoute(auth, webModules);
|
||||
|
||||
return (
|
||||
<AppShell settings={settings} auth={auth} onSettingsChange={updateSettings} onAuthChange={updateAuth} navItems={navItems}>
|
||||
<PlatformModulesProvider modules={webModules}>
|
||||
<UnsavedChangesProvider>
|
||||
<AppShell settings={settings} auth={auth} onSettingsChange={updateSettings} onAuthChange={updateAuth} navItems={navItems}>
|
||||
<Suspense fallback={<div className="content-pad"><p className="muted">Loading module...</p></div>}>
|
||||
<Routes key={(auth.active_tenant ?? auth.tenant).id}>
|
||||
<Route path="/" element={<Navigate to={defaultRoute} replace />} />
|
||||
<Route path="/dashboard" element={<DashboardPage />} />
|
||||
<Route path="/dashboard" element={<DashboardPage settings={settings} />} />
|
||||
{moduleRoutes.map((route) => (
|
||||
<Route
|
||||
key={route.path}
|
||||
@@ -165,6 +196,17 @@ export default function App() {
|
||||
<Route path="*" element={<Navigate to={defaultRoute} replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</AppShell>
|
||||
{reloginMessage && (
|
||||
<LoginModal
|
||||
settings={settings}
|
||||
title="Session expired"
|
||||
message={reloginMessage}
|
||||
onClose={() => setReloginMessage("")}
|
||||
onLogin={handleRelogin}
|
||||
/>
|
||||
)}
|
||||
</AppShell>
|
||||
</UnsavedChangesProvider>
|
||||
</PlatformModulesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user