// UserGreeting.tsx 'use client'; import { useEffect, useState, useRef, useCallback } from 'react'; import { Button } from './Button'; import { useCurrentUser } from './AuthContext'; import { useFade } from '@/app/providers/FadeContext'; import { writeLogoutNotice } from '@/lib/logoutNotice'; function capitalize(name: string) { return name.charAt(0).toUpperCase() + name.slice(1); } function getGreeting() { const hour = new Date().getHours(); if (hour < 11) return 'Morgen'; if (hour < 18) return 'Tag'; return 'Abend'; } function formatTimeLeft(seconds: number): string { const m = Math.floor(seconds / 60); const s = seconds % 60; const paddedSeconds = s.toString().padStart(2, '0'); return `${m}m ${paddedSeconds}s`; } // Texte zentral halten, falls mehrfach benötigt const LOGOUT_REASON_MANUAL = 'Du hast dich abgemeldet.'; const LOGOUT_REASON_INACTIVE = 'Du wurdest wegen Inaktivität automatisch abgemeldet.'; const LOGOUT_REASON_EXPIRED = 'Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.'; export default function UserGreeting() { const { user, logout, tokenExpiresAt } = useCurrentUser(); const INACTIVITY_LIMIT = 5 * 60; // 5 Minuten in Sekunden const [timeLeft, setTimeLeft] = useState(INACTIVITY_LIMIT); const intervalRef = useRef(null); const refreshedRef = useRef(false); const cachedUsername = typeof window !== 'undefined' ? sessionStorage.getItem('username') : null; const [username, setUsername] = useState(cachedUsername ?? ''); const [isClient, setIsClient] = useState(false); const fade = useFade(); const doLogoutWithNotice = useCallback( (noticeReason: 'manual' | 'timeout' | 'expired', message: string) => { // 1) persistent Notice schreiben writeLogoutNotice({ reason: noticeReason, message }); // 2) Context-Logout (räumt User + Token) logout(message); // 3) Navigation fade('/login'); }, [logout, fade] ); const handleLogout = async () => { doLogoutWithNotice('manual', LOGOUT_REASON_MANUAL); }; const refreshToken = useCallback(async () => { try { const res = await fetch(`/api/refresh-token`, { method: 'POST', credentials: 'include', }); if (res.ok) { setTimeLeft(INACTIVITY_LIMIT); // Reset auf volle Zeit sessionStorage.setItem('tokenIssuedAt', `${Date.now()}`); refreshedRef.current = false; } else { console.warn('Token refresh failed'); doLogoutWithNotice('expired', LOGOUT_REASON_EXPIRED); } } catch (err) { console.error('Error refreshing token', err); // Netzwerkfehler beim Refresh -> wir loggen besser aus, damit User nicht "hängen" bleibt doLogoutWithNotice('expired', LOGOUT_REASON_EXPIRED); } }, [INACTIVITY_LIMIT, doLogoutWithNotice]); // ⏳ Countdown useEffect(() => { intervalRef.current = setInterval(() => { setTimeLeft(prev => { if (prev <= 1) { doLogoutWithNotice('timeout', LOGOUT_REASON_INACTIVE); return 0; } // Token bald ablaufend? Refresh const now = Date.now(); if ( tokenExpiresAt && tokenExpiresAt - now <= 30_000 && !refreshedRef.current ) { refreshedRef.current = true; refreshToken(); } return prev - 1; }); }, 1000); return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; }, [refreshToken, tokenExpiresAt, doLogoutWithNotice]); // 🖱 Aktivität zurücksetzen useEffect(() => { const reset = () => setTimeLeft(INACTIVITY_LIMIT); const events = ['mousemove', 'keydown', 'scroll', 'click', 'touchstart']; events.forEach(e => window.addEventListener(e, reset, { passive: true })); return () => { events.forEach(e => window.removeEventListener(e, reset)); }; }, [INACTIVITY_LIMIT]); useEffect(() => { if (user?.username) { setUsername(user.username); sessionStorage.setItem('username', user.username); } }, [user?.username]); useEffect(() => { setIsClient(true); }, []); const percent = (timeLeft / INACTIVITY_LIMIT) * 100; if (!isClient) return null; return (
{username ? `Guten ${getGreeting()}, ${capitalize(username)}!` : null}
); }