SHL-04: AccountBar zeigt Benutzer, Rolle und Mandant aus der Sitzung
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
patrickandClaude Sonnet 5.5 committed 2026-10-27 22:31:17 +01:00
1 parent 2518160c92
commit bc47c1331b
8 files changed
+95 -14

No files matched your search

+8 -2
View File
@@ -8,6 +8,12 @@ import { authMode, getSession } from "../lib/http";
// sind aber App-spezifisch (eigenes lib/http.ts je App) und duerfen als
// Funktionen nicht von layout.tsx (Server Component) ueber die Props-Grenze
// an eine Client Component gereicht werden -- deshalb hier lokal aufgeloest.
export function AccountBar() {
return <SharedAccountBar visible={authMode() === "token"} onLogout={() => getSession().logout()} />;
async function loadIdentity() {
const s = getSession();
await s.token();
return s.identity();
}
export function AccountBar() {
return <SharedAccountBar visible={authMode() === "token"} onLogout={() => getSession().logout()} loadIdentity={loadIdentity} />;
}
+8 -2
View File
@@ -8,6 +8,12 @@ import { authMode, getSession } from "../lib/http";
// sind aber App-spezifisch (eigenes lib/http.ts je App) und duerfen als
// Funktionen nicht von layout.tsx (Server Component) ueber die Props-Grenze
// an eine Client Component gereicht werden -- deshalb hier lokal aufgeloest.
export function AccountBar() {
return <SharedAccountBar visible={authMode() === "token"} onLogout={() => getSession().logout()} />;
async function loadIdentity() {
const s = getSession();
await s.token();
return s.identity();
}
export function AccountBar() {
return <SharedAccountBar visible={authMode() === "token"} onLogout={() => getSession().logout()} loadIdentity={loadIdentity} />;
}
+8 -2
View File
@@ -8,6 +8,12 @@ import { authMode, getSession } from "../lib/http";
// sind aber App-spezifisch (eigenes lib/http.ts je App) und duerfen als
// Funktionen nicht von layout.tsx (Server Component) ueber die Props-Grenze
// an eine Client Component gereicht werden -- deshalb hier lokal aufgeloest.
export function AccountBar() {
return <SharedAccountBar visible={authMode() === "token"} onLogout={() => getSession().logout()} />;
async function loadIdentity() {
const s = getSession();
await s.token();
return s.identity();
}
export function AccountBar() {
return <SharedAccountBar visible={authMode() === "token"} onLogout={() => getSession().logout()} loadIdentity={loadIdentity} />;
}
+36
View File
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { render, screen } from "@testing-library/react";
import { AccountBar } from "../components/AccountBar";
import { I18nProvider } from "../i18n/i18n";
import { ThemeProvider } from "../theme/ThemeProvider";
function renderBar(props: Partial<React.ComponentProps<typeof AccountBar>>) {
return render(
<ThemeProvider>
<I18nProvider>
<AccountBar visible onLogout={() => {}} {...props} />
</I18nProvider>
</ThemeProvider>,
);
}
describe("AccountBar", () => {
it("zeigt Benutzer, Rolle und Mandant", async () => {
renderBar({ loadIdentity: async () => ({ userId: "u-1", tenant: "t-1", role: "admin" }) });
const el = await screen.findByTestId("account-identity");
expect(el.textContent).toContain("u-1");
expect(el.textContent).toContain("admin");
expect(el.textContent).toContain("t-1");
});
it("zeigt nur Abmelden, wenn die Identitaet nicht ladbar ist", async () => {
renderBar({ loadIdentity: async () => { throw new Error("x"); } });
await screen.findByRole("button");
expect(screen.queryByTestId("account-identity")).toBeNull();
});
it("rendert nichts, wenn nicht sichtbar", () => {
const { container } = renderBar({ visible: false });
expect(container.querySelector("header")).toBeNull();
});
});
+2
View File
@@ -35,6 +35,8 @@ export interface Identity {
role: string;
}
export type IdentityLoader = () => Promise<Identity | null>;
export interface SessionOptions {
coreBase: string;
fetchImpl?: typeof fetch;
+28 -7
View File
@@ -1,21 +1,42 @@
"use client";
import { useEffect, useState } from "react";
import { LogoutButton } from "../auth/LogoutButton";
import type { Identity, IdentityLoader } from "../auth/session";
import { useI18n } from "../i18n/i18n";
export interface AccountBarProps {
visible: boolean;
onLogout: () => void;
loadIdentity?: IdentityLoader;
}
// Ersetzt die in web/dms-browser, web/dms-upload und web/pdf-viewer
// unabhaengig byteidentisch kopierte AccountBar (Fable-Review auf
// Code-Dopplung). visible/onLogout statt direktem lib/http-Import,
// weil jede App ihr eigenes lib/http.ts mit eigenem Session-Handling
// hat -- die Komponente bleibt davon entkoppelt.
export function AccountBar({ visible, onLogout }: AccountBarProps) {
export function AccountBar({ visible, onLogout, loadIdentity }: AccountBarProps) {
const { t } = useI18n();
const [identity, setIdentity] = useState<Identity | null>(null);
useEffect(() => {
if (!visible || !loadIdentity) return;
let cancelled = false;
loadIdentity().then(
(id) => {
if (!cancelled) setIdentity(id);
},
() => {},
);
return () => {
cancelled = true;
};
}, [visible, loadIdentity]);
if (!visible) return null;
return (
<header style={{ display: "flex", justifyContent: "flex-end", padding: "8px 16px" }}>
<header style={{ display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 16, padding: "8px 16px" }}>
{identity && (
<span data-testid="account-identity">
{identity.userId} · {t("shl.auth.role")}: {identity.role} · {t("shl.auth.tenant")}: {identity.tenant}
</span>
)}
<LogoutButton onLogout={onLogout} />
</header>
);
+4
View File
@@ -19,6 +19,8 @@ const baseMessages: MessageBundle = {
"shl.theme.toggleToLight": "Helles Erscheinungsbild",
"shl.theme.toggleToDark": "Dunkles Erscheinungsbild",
"shl.auth.logout": "Abmelden",
"shl.auth.role": "Rolle",
"shl.auth.tenant": "Mandant",
},
en: {
"shl.dialog.close": "Close",
@@ -27,6 +29,8 @@ const baseMessages: MessageBundle = {
"shl.theme.toggleToLight": "Switch to light theme",
"shl.theme.toggleToDark": "Switch to dark theme",
"shl.auth.logout": "Sign out",
"shl.auth.role": "Role",
"shl.auth.tenant": "Tenant",
},
};
+1 -1
View File
@@ -30,7 +30,7 @@ export { TextField, SelectField, CheckboxField } from "./components/FormElements
export type { TextFieldProps, SelectFieldProps, CheckboxFieldProps } from "./components/FormElements";
export { createSession, redirectToLogin, describeHttpError, authMessages, AuthError } from "./auth/session";
export type { Session, SessionOptions, Identity, AuthErrorKind } from "./auth/session";
export type { Session, SessionOptions, Identity, IdentityLoader, AuthErrorKind } from "./auth/session";
export { LogoutButton } from "./auth/LogoutButton";
export type { LogoutButtonProps } from "./auth/LogoutButton";