import { useCallback, useEffect, useState } from 'react' const TOKEN_KEY = 'kairo_auth_token' function apiHeaders(token) { const headers = { 'Content-Type': 'application/json' } if (token) headers['X-Auth-Token'] = token return headers } export default function App() { const [health, setHealth] = useState(null) const [healthError, setHealthError] = useState(null) const [token, setToken] = useState(() => localStorage.getItem(TOKEN_KEY) || '') const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [authError, setAuthError] = useState(null) const [authBusy, setAuthBusy] = useState(false) const [me, setMe] = useState(null) const [context, setContext] = useState(null) useEffect(() => { fetch('/api/health') .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() }) .then(setHealth) .catch((err) => setHealthError(err.message)) }, []) const loadSession = useCallback(async (activeToken) => { if (!activeToken) { setMe(null) setContext(null) return } const meRes = await fetch('/api/me', { headers: apiHeaders(activeToken) }) if (!meRes.ok) { localStorage.removeItem(TOKEN_KEY) setToken('') setMe(null) setContext(null) throw new Error(meRes.status === 401 ? 'Session abgelaufen' : `HTTP ${meRes.status}`) } setMe(await meRes.json()) const ctxRes = await fetch('/api/me/context', { headers: apiHeaders(activeToken) }) if (ctxRes.ok) setContext(await ctxRes.json()) }, []) useEffect(() => { if (!token) return loadSession(token).catch((err) => setAuthError(err.message)) }, [token, loadSession]) async function handleLogin(event) { event.preventDefault() setAuthBusy(true) setAuthError(null) try { const res = await fetch('/api/auth/login', { method: 'POST', headers: apiHeaders(), body: JSON.stringify({ email, password }), }) if (!res.ok) { const body = await res.json().catch(() => ({})) throw new Error(body.detail || `Login fehlgeschlagen (${res.status})`) } const data = await res.json() localStorage.setItem(TOKEN_KEY, data.token) setToken(data.token) setPassword('') } catch (err) { setAuthError(err.message) } finally { setAuthBusy(false) } } async function handleLogout() { setAuthBusy(true) setAuthError(null) try { if (token) { await fetch('/api/auth/logout', { method: 'POST', headers: apiHeaders(token), }) } } catch { /* Session trotzdem lokal entfernen */ } finally { localStorage.removeItem(TOKEN_KEY) setToken('') setMe(null) setContext(null) setAuthBusy(false) } } return (

Jinkendo Kairo

Operativer Program Director — Sprint 0 / AP0.2

Auth, Tenant & Actor Foundation

API Health

{healthError &&

Fehler: {healthError}

} {!healthError && !health &&

Lade …

} {health &&
{JSON.stringify(health, null, 2)}
}

Session

{!token && (
)} {token && me && (

Eingeloggt als {me.display_name} ({me.email})

Portal-Rolle: {me.portal_role} {me.active_tenant_id && ` · Tenant: ${me.active_tenant_id}`}

)} {authError &&

{authError}

}
{token && me && ( <>

/api/me

{JSON.stringify(me, null, 2)}
{context && (

/api/me/context

{JSON.stringify(context, null, 2)}
)} )}
) }