Add explicit acting-in-place context

This commit is contained in:
2026-07-31 22:48:07 +02:00
parent e58dcbdd7b
commit 9776f862f8
9 changed files with 450 additions and 4 deletions
+31
View File
@@ -0,0 +1,31 @@
import { apiFetch, type ApiSettings } from "@govoplan/core-webui";
export type ActingContext = {
assignment_id: string;
acting_for_account_id: string;
function_id: string;
organization_unit_id: string;
valid_from?: string | null;
valid_until?: string | null;
};
export type ActingContextList = {
contexts: ActingContext[];
active_assignment_id?: string | null;
};
export function fetchActingContexts(settings: ApiSettings): Promise<ActingContextList> {
return apiFetch<ActingContextList>(settings, "/api/v1/auth/acting-contexts", {
cache: "no-store"
});
}
export function switchActingContext(
settings: ApiSettings,
assignmentId: string | null
): Promise<ActingContextList> {
return apiFetch<ActingContextList>(settings, "/api/v1/auth/switch-acting-context", {
method: "POST",
body: JSON.stringify({ assignment_id: assignmentId })
});
}
@@ -0,0 +1,73 @@
import { useEffect, useState } from "react";
import {
fetchMe,
type ActingContextSelectorProps
} from "@govoplan/core-webui";
import {
fetchActingContexts,
switchActingContext,
type ActingContext
} from "../../api/actingContext";
export default function ActingContextSelector({
settings,
auth,
onAuthChange
}: ActingContextSelectorProps) {
const [contexts, setContexts] = useState<ActingContext[]>([]);
const [activeId, setActiveId] = useState<string>("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
let active = true;
void fetchActingContexts(settings)
.then((response) => {
if (!active) return;
setContexts(response.contexts);
setActiveId(response.active_assignment_id ?? "");
})
.catch((reason: unknown) => {
if (active) setError(reason instanceof Error ? reason.message : "Acting context could not be loaded.");
});
return () => {
active = false;
};
}, [settings.apiBaseUrl, settings.accessToken, settings.apiKey, auth.active_tenant?.id, auth.tenant.id]);
if (!contexts.length && !activeId) return null;
async function selectContext(assignmentId: string) {
setBusy(true);
setError("");
try {
const response = await switchActingContext(settings, assignmentId || null);
setContexts(response.contexts);
setActiveId(response.active_assignment_id ?? "");
onAuthChange(await fetchMe(settings));
} catch (reason) {
setError(reason instanceof Error ? reason.message : "Acting context could not be changed.");
} finally {
setBusy(false);
}
}
return (
<label className="acting-context-selector" title={error || "Select whose authority is represented by this session."}>
<span>Acting as</span>
<select
aria-label="Acting context"
value={activeId}
disabled={busy}
onChange={(event) => void selectContext(event.target.value)}
>
<option value="">Own account</option>
{contexts.map((context) => (
<option key={context.assignment_id} value={context.assignment_id}>
{context.function_id} / {context.organization_unit_id}
</option>
))}
</select>
</label>
);
}
+6 -3
View File
@@ -1,6 +1,7 @@
import { createElement, lazy } from "react";
import type { PlatformRouteContext, PlatformWebModule } from "@govoplan/core-webui";
import type { ActingContextRuntimeUiCapability, PlatformRouteContext, PlatformWebModule } from "@govoplan/core-webui";
import { adminReadScopes } from "@govoplan/core-webui";
import ActingContextSelector from "./features/acting-context/ActingContextSelector";
import { generatedTranslations } from "./i18n/generatedTranslations";
const AdminPage = lazy(() => import("./features/admin/AdminPage"));
@@ -42,8 +43,10 @@ export const accessModule: PlatformWebModule = {
{ to: "/admin", label: "i18n:govoplan-access.admin.4e7afebc", iconName: "admin", anyOf: adminReadScopes, order: 900 }],
routes: [
{ path: "/admin", anyOf: adminReadScopes, order: 900, render: renderAdminRoute }]
{ path: "/admin", anyOf: adminReadScopes, order: 900, render: renderAdminRoute }],
uiCapabilities: {
"access.actingContext": { Selector: ActingContextSelector } satisfies ActingContextRuntimeUiCapability
}
};
export default accessModule;