fix: make auth dropdown controlled in React to prevent password manager focus-loss from closing it
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 2m41s
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 2m41s
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { IconUser, IconShield, IconUserCircle, IconSettings2, IconLogout } from "@tabler/icons-react";
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import NotificationPanel from "./NotificationPanel";
|
||||
import ThemeToggle from "./ThemeToggle";
|
||||
import { useSession } from "@/context/session";
|
||||
@@ -11,10 +12,29 @@ import AuthDropdownContent from "./auth/AuthDropdownContent";
|
||||
|
||||
export default function Header() {
|
||||
const { user, setUser } = useSession();
|
||||
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
|
||||
const [isAuthMenuOpen, setIsAuthMenuOpen] = useState(false);
|
||||
|
||||
const userMenuRef = useRef<HTMLDivElement>(null);
|
||||
const authMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) {
|
||||
setIsUserMenuOpen(false);
|
||||
}
|
||||
if (authMenuRef.current && !authMenuRef.current.contains(event.target as Node)) {
|
||||
setIsAuthMenuOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
async function handleLogout() {
|
||||
await logout();
|
||||
setUser(null);
|
||||
setIsUserMenuOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -47,28 +67,31 @@ export default function Header() {
|
||||
<ThemeToggle />
|
||||
|
||||
{user?.id && typeof user.id === "number" ? (
|
||||
<div className="dropdown dropdown-end">
|
||||
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
|
||||
<div ref={userMenuRef} className={`dropdown dropdown-end ${isUserMenuOpen ? "dropdown-open" : ""}`}>
|
||||
<button
|
||||
onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
|
||||
className="btn btn-ghost btn-circle avatar"
|
||||
>
|
||||
<div className="w-10 rounded-full">
|
||||
<IconUser className="m-auto block h-full" />
|
||||
</div>
|
||||
</label>
|
||||
<div tabIndex={0} className="dropdown-content z-50 mt-3 w-80 rounded-box bg-base-100 p-3 shadow-lg">
|
||||
</button>
|
||||
<div className="dropdown-content z-50 mt-3 w-80 rounded-box bg-base-100 p-3 shadow-lg border border-base-300 dark:border-neutral">
|
||||
<div className="px-2 pb-2 font-semibold">{user.name}</div>
|
||||
<ul className="menu menu-sm p-0">
|
||||
<li>
|
||||
<Link href={`/user/${user.name}`}>
|
||||
<Link href={`/user/${user.name}`} onClick={() => setIsUserMenuOpen(false)}>
|
||||
<IconUserCircle size={16} /> Profil
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/settings">
|
||||
<Link href="/settings" onClick={() => setIsUserMenuOpen(false)}>
|
||||
<IconSettings2 size={16} /> Einstellungen
|
||||
</Link>
|
||||
</li>
|
||||
{user.role === "admin" && (
|
||||
<li>
|
||||
<Link href="/admin">
|
||||
<Link href="/admin" onClick={() => setIsUserMenuOpen(false)}>
|
||||
<IconShield size={16} /> Admin-Panel
|
||||
</Link>
|
||||
</li>
|
||||
@@ -90,14 +113,17 @@ export default function Header() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="dropdown dropdown-end">
|
||||
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
|
||||
<div ref={authMenuRef} className={`dropdown dropdown-end ${isAuthMenuOpen ? "dropdown-open" : ""}`}>
|
||||
<button
|
||||
onClick={() => setIsAuthMenuOpen(!isAuthMenuOpen)}
|
||||
className="btn btn-ghost btn-circle avatar"
|
||||
>
|
||||
<div className="w-10 rounded-full">
|
||||
<IconUser className="m-auto block h-full" />
|
||||
</div>
|
||||
</label>
|
||||
<div tabIndex={0} className="dropdown-content z-50 mt-3 w-80 rounded-box bg-base-100 p-4 shadow-lg border border-base-300 dark:border-neutral">
|
||||
<AuthDropdownContent />
|
||||
</button>
|
||||
<div className="dropdown-content z-50 mt-3 w-80 rounded-box bg-base-100 p-4 shadow-lg border border-base-300 dark:border-neutral">
|
||||
<AuthDropdownContent onClose={() => setIsAuthMenuOpen(false)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import Guest from "./Guest";
|
||||
import Login from "./Login";
|
||||
import Register from "./Register";
|
||||
|
||||
export default function AuthDropdownContent() {
|
||||
export default function AuthDropdownContent({ onClose }: { onClose?: () => void }) {
|
||||
const session = useContext(SessionContext);
|
||||
const [activeTab, setActiveTab] = useState<"guest" | "login" | "register">("guest");
|
||||
const [serverMessage, setServerMessage] = useState<string | null>(null);
|
||||
@@ -29,6 +29,9 @@ export default function AuthDropdownContent() {
|
||||
}, []);
|
||||
|
||||
function closeDropdown() {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
if (typeof document !== "undefined" && document.activeElement) {
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user