WebUI module split; docs

This commit is contained in:
2026-06-24 10:18:47 +02:00
parent 30c11a6dcf
commit 44516a03aa
8 changed files with 558 additions and 74 deletions

View File

@@ -1,26 +1,18 @@
import { Navigate, Route, Routes, useParams } from "react-router-dom";
import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react";
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 type { ApiSettings, AuthInfo, LoginResponse, PlatformModuleInfo } from "./types";
import AppShell from "./layout/AppShell";
import PublicLandingPage from "./features/auth/PublicLandingPage";
import { PermissionBoundary, ResourceAccessBoundary } from "./components/AccessBoundary";
import { PermissionBoundary } from "./components/AccessBoundary";
import { adminReadScopes } from "./utils/permissions";
import { firstAccessibleRoute, navItemsForModules, resolveInstalledWebModules } from "./platform/modules";
import PlaceholderPage from "./features/PlaceholderPage";
import { getCampaign } from "@govoplan/campaign-webui";
import { firstAccessibleRoute, navItemsForModules, resolveInstalledWebModules, routeContributionsForModules } from "./platform/modules";
const DashboardPage = lazy(() => import("./features/dashboard/DashboardPage"));
const CampaignListPage = lazy(() => import("@govoplan/campaign-webui").then((module) => ({ default: module.CampaignListPage })));
const CampaignWorkspace = lazy(() => import("@govoplan/campaign-webui").then((module) => ({ default: module.CampaignWorkspace })));
const SettingsPage = lazy(() => import("./features/settings/SettingsPage"));
const AdminPage = lazy(() => import("./features/admin/AdminPage"));
const TemplatesPage = lazy(() => import("@govoplan/campaign-webui").then((module) => ({ default: module.TemplatesPage })));
const FilesPage = lazy(() => import("@govoplan/files-webui").then((module) => ({ default: module.FilesPage })));
const OperatorQueuePage = lazy(() => import("@govoplan/campaign-webui").then((module) => ({ default: module.OperatorQueuePage })));
const AddressBookPage = lazy(() => import("@govoplan/campaign-webui").then((module) => ({ default: module.AddressBookPage })));
export default function App() {
const [settings, setSettings] = useState<ApiSettings>(() => loadApiSettings());
@@ -30,6 +22,7 @@ export default function App() {
const webModules = useMemo(() => resolveInstalledWebModules(platformModules), [platformModules]);
const navItems = useMemo(() => navItemsForModules(webModules), [webModules]);
const moduleRoutes = useMemo(() => routeContributionsForModules(webModules), [webModules]);
function updateSettings(next: ApiSettings) {
setSettings(next);
@@ -152,33 +145,17 @@ export default function App() {
<Routes key={(auth.active_tenant ?? auth.tenant).id}>
<Route path="/" element={<Navigate to={defaultRoute} replace />} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/campaigns" element={
<PermissionBoundary auth={auth} anyOf={["campaigns:campaign:read"]} fallback={defaultRoute}>
<CampaignListPage settings={settings} />
</PermissionBoundary>
} />
<Route path="/campaigns/:campaignId/*" element={
<PermissionBoundary auth={auth} anyOf={["campaigns:campaign:read"]} fallback={defaultRoute}>
<CampaignResourceRoute settings={settings} auth={auth} />
</PermissionBoundary>
} />
<Route path="/templates" element={<TemplatesPage />} />
<Route path="/files" element={
<PermissionBoundary auth={auth} anyOf={["files:file:read"]} fallback={defaultRoute}>
<FilesPage settings={settings} auth={auth} />
</PermissionBoundary>
} />
<Route path="/address-book" element={<AddressBookPage />} />
<Route path="/operator" element={
<PermissionBoundary auth={auth} anyOf={["campaigns:campaign:queue", "campaigns:campaign:retry", "campaigns:campaign:reconcile", "campaigns:campaign:control", "campaigns:campaign:send"]} fallback={defaultRoute}>
<OperatorQueuePage settings={settings} />
</PermissionBoundary>
} />
<Route path="/reports" element={
<PermissionBoundary auth={auth} anyOf={["campaigns:report:read"]} fallback={defaultRoute}>
<PlaceholderPage title="Reports" />
</PermissionBoundary>
} />
{moduleRoutes.map((route) => (
<Route
key={route.path}
path={route.path}
element={
<PermissionBoundary auth={auth} anyOf={route.anyOf} allOf={route.allOf} fallback={defaultRoute}>
{route.render({ settings, auth })}
</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}>
@@ -191,15 +168,3 @@ export default function App() {
</AppShell>
);
}
function CampaignResourceRoute({ settings, auth }: { settings: ApiSettings; auth: AuthInfo }) {
const { campaignId = "" } = useParams();
const tenantId = (auth.active_tenant ?? auth.tenant).id;
const probe = useCallback(() => getCampaign(settings, campaignId), [settings.apiBaseUrl, settings.apiKey, campaignId, tenantId]);
return (
<ResourceAccessBoundary probe={probe} resetKey={`${tenantId}:${campaignId}`} fallback="/campaigns">
<CampaignWorkspace settings={settings} auth={auth} />
</ResourceAccessBoundary>
);
}