'use client'; import { useEffect, useRef, useState } from 'react'; import Link from 'next/link'; 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 { 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 { HEADER_CENTER_SLOT_ID } from '@/components/layout/header-slot'; import { usePathname } from 'next/navigation'; export function Header() { const t = useTranslations(); const tHeader = useTranslations('header'); const setMobileOpen = useSidebarStore((s) => s.setMobileOpen); const { user, setUser, avatarVersion } = useAuthStore(); const [dropdownOpen, setDropdownOpen] = useState(false); const dropdownRef = useRef(null); const pathname = usePathname(); const isAdmin = user?.role === 'ADMIN' || user?.role === 'SUPER_ADMIN'; // 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) { 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 useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setDropdownOpen(false); } } if (dropdownOpen) { document.addEventListener('mousedown', handleClickOutside); } return () => document.removeEventListener('mousedown', handleClickOutside); }, [dropdownOpen]); const [avatarError, setAvatarError] = useState(false); const userInitial = user ? (user.displayName ?? user.username).charAt(0).toUpperCase() : '?'; const roleLabel = user ? tHeader(`role.${user.role}`) : ''; return (
{/* Left: Logo + Mobile hamburger */}
{/* Mitte: Einhaengepunkt fuer die Dashboard-Reiter (quick-260924-h7x). `DashboardTabs` rendert per Portal hierher; der Punkt muss deshalb im React-Baum der Kopfzeile LEER bleiben. Auf der Startseite entfaellt der Text „Startseite“, auf allen anderen Seiten bleibt er wie bisher. `min-w-0` laesst die Mitte schrumpfen, damit Logo und Aktionen rechts auch bei schmalem Fenster Platz behalten. */}
{pathname !== '/' && ( {tHeader('breadcrumb.home')} )}
{/* Right: Actions */}
{/* User avatar dropdown */}
{/* Dropdown menu */} {dropdownOpen && (
{/* User info */}

{user?.displayName ?? user?.username ?? '...'}

{user && ( {roleLabel} )}
{/* Settings link */}
setDropdownOpen(false)} className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-foreground hover:bg-muted transition-colors" > {tHeader('settings')}
{/* Administration link */} {isAdmin && (
setDropdownOpen(false)} className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors ${ pathname.startsWith('/admin') ? 'bg-muted text-foreground font-medium' : 'text-foreground hover:bg-muted' }`} > {tHeader('admin.label')}
)} {/* Logout button */}
)}
); }