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
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:
1 parent
2518160c92
commit
bc47c1331b
8 files changed
+95
-14
No files matched your search
@@ -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,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,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} />;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -35,6 +35,8 @@ export interface Identity {
|
||||
role: string;
|
||||
}
|
||||
|
||||
export type IdentityLoader = () => Promise<Identity | null>;
|
||||
|
||||
export interface SessionOptions {
|
||||
coreBase: string;
|
||||
fetchImpl?: typeof fetch;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
|
||||
Reference in new issue
Block a user