feat(quick-260917-gyd): Sitzungswaechter erkennt tote Sitzung, Header leitet ab
- auth-actions.ts: fetchSessionState() unterscheidet tote Sitzung (401/403/leere 200-Antwort, Cookie wird geloescht) von API-Ausfall (5xx/Netzwerkfehler/Nicht-JSON, unavailable ohne Redirect); fetchCurrentUser bleibt unveraendert - header.tsx: Waechter im useEffect leitet bei toter Sitzung per Vollnavigation auf /login?next=… um, bleibt bei API-Ausfall still Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,8 @@ import { useTranslations } from 'next-intl';
|
||||
import { TesseraLogo } from '@/components/brand/tessera-logo';
|
||||
import { useSidebarStore } from '@/lib/stores/sidebar-store';
|
||||
import { useAuthStore } from '@/lib/stores/auth-store';
|
||||
import { fetchCurrentUser, logout } from '@/lib/auth-actions';
|
||||
import { fetchSessionState, logout } from '@/lib/auth-actions';
|
||||
import { buildNextParam } from '@/lib/safe-next';
|
||||
import { ThemeToggle } from '@/components/theme-toggle';
|
||||
import { BugReportButton } from '@/components/bug-report/bug-report-button';
|
||||
import { usePathname } from 'next/navigation';
|
||||
@@ -22,23 +23,51 @@ export function Header() {
|
||||
|
||||
const isAdmin = user?.role === 'ADMIN' || user?.role === 'SUPER_ADMIN';
|
||||
|
||||
// Fetch current user on mount to populate auth store
|
||||
// Redirect-Sperre gegen doppelte Navigation (StrictMode-Doppeleffekt,
|
||||
// Effekt-Wiederholungen) — quick-260917-gyd.
|
||||
const redirectedRef = useRef(false);
|
||||
|
||||
// Sitzungswaechter (quick-260917-gyd): Der Header ist der EINZIGE
|
||||
// Waechter, weil er auf jeder Portalseite genau einmal gerendert wird
|
||||
// (AppShell im (portal)-Layout); das (auth)-Layout hat keinen Header,
|
||||
// /login ist oeffentlich (middleware.ts publicRoutes) — Seitenleiste
|
||||
// und Widget-Aufrufe brauchen deshalb keinen eigenen Umbau, ein
|
||||
// Doppel-Redirect ist ausgeschlossen. `authenticated` fuellt den Store,
|
||||
// `unauthenticated` (tote Sitzung, Cookie bereits serverseitig
|
||||
// geloescht) leitet per Vollnavigation zur Anmeldeseite mit `next` auf
|
||||
// die aktuelle Seite, `unavailable` (API-Ausfall) bleibt still.
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
fetchCurrentUser().then((u) => {
|
||||
if (u) {
|
||||
setUser({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
displayName: u.displayName,
|
||||
role: u.role,
|
||||
tenantId: u.tenantId,
|
||||
hasAvatar: u.hasAvatar,
|
||||
accentColor: u.accentColor,
|
||||
});
|
||||
}
|
||||
});
|
||||
if (user) {
|
||||
return;
|
||||
}
|
||||
fetchSessionState().then((state) => {
|
||||
if (state.status === 'authenticated') {
|
||||
const u = state.user;
|
||||
setUser({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
displayName: u.displayName,
|
||||
role: u.role,
|
||||
tenantId: u.tenantId,
|
||||
hasAvatar: u.hasAvatar,
|
||||
accentColor: u.accentColor,
|
||||
});
|
||||
} else if (state.status === 'unauthenticated') {
|
||||
if (redirectedRef.current) {
|
||||
return;
|
||||
}
|
||||
redirectedRef.current = true;
|
||||
const next = buildNextParam(
|
||||
window.location.pathname,
|
||||
window.location.search,
|
||||
);
|
||||
window.location.href = next
|
||||
? `/login?next=${encodeURIComponent(next)}`
|
||||
: '/login';
|
||||
}
|
||||
// 'unavailable' (T-gyd-02): kein Redirect, bisheriges stilles
|
||||
// Verhalten -- ein API-Ausfall darf keine Abmelde-Schleife ausloesen.
|
||||
});
|
||||
}, [user, setUser]);
|
||||
|
||||
// Close dropdown on outside click
|
||||
|
||||
Reference in New Issue
Block a user