"use client"; import { useEffect, useRef, useState } from "react"; import { KeyRound, LogOut, Palette, UserCircle2 } from "lucide-react"; import { api } from "@/lib/api-client"; import { Button, Modal } from "@/components/ui"; import { getEffectiveTheme, setTheme, THEMES, type Theme } from "@/lib/theme"; export function ProfileMenu({ username }: { username: string }) { const [open, setOpen] = useState(false); const [showPasswordDialog, setShowPasswordDialog] = useState(false); const [theme, setThemeState] = useState("light"); const menuRef = useRef(null); useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect -- einmalige Übernahme aus localStorage setThemeState(getEffectiveTheme()); }, []); useEffect(() => { function handleClickOutside(e: MouseEvent) { if (menuRef.current && !menuRef.current.contains(e.target as Node)) setOpen(false); } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); function chooseTheme(t: Theme) { setTheme(t); setThemeState(t); } return (
{open && (
{username}
Farbschema
{THEMES.map((t) => ( ))}
} label="Passwort ändern" onClick={() => { setOpen(false); setShowPasswordDialog(true); }} /> } label="Abmelden" onClick={async () => { await api.post("/api/auth/logout"); window.location.href = "/login"; }} />
)} {showPasswordDialog && setShowPasswordDialog(false)} />}
); } function MenuItem({ icon, label, onClick }: { icon: React.ReactNode; label: string; onClick: () => void }) { return ( ); } function ChangePasswordDialog({ onClose }: { onClose: () => void }) { const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [newPasswordConfirm, setNewPasswordConfirm] = useState(""); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const [done, setDone] = useState(false); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(null); if (newPassword !== newPasswordConfirm) { setError("Die neuen Passwörter stimmen nicht überein."); return; } setSaving(true); try { await api.post("/api/auth/change-password", { currentPassword, newPassword }); setDone(true); setTimeout(onClose, 1200); } catch (err) { setError(err instanceof Error ? err.message : "Passwort ändern fehlgeschlagen."); } finally { setSaving(false); } } const inputClass = "w-full rounded-lg border border-border bg-input px-3 py-2 text-sm text-fg shadow-sm focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/25"; // Über die zentrale Modal-Komponente: bringt Esc, Fokus-Falle und aria-modal mit // (SPEZIFIKATION 3.7.6) -- der Dialog war zuvor von Hand gebaut und ignorierte Esc. return (
setCurrentPassword(e.target.value)} className={inputClass} /> setNewPassword(e.target.value)} className={inputClass} /> setNewPasswordConfirm(e.target.value)} className={inputClass} /> {error &&

{error}

} {done &&

Passwort geändert.

}
); }