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 { IconUser, IconShield, IconUserCircle, IconSettings2, IconLogout } from "@tabler/icons-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useState, useEffect, useRef } from "react";
|
||||||
import NotificationPanel from "./NotificationPanel";
|
import NotificationPanel from "./NotificationPanel";
|
||||||
import ThemeToggle from "./ThemeToggle";
|
import ThemeToggle from "./ThemeToggle";
|
||||||
import { useSession } from "@/context/session";
|
import { useSession } from "@/context/session";
|
||||||
@@ -11,10 +12,29 @@ import AuthDropdownContent from "./auth/AuthDropdownContent";
|
|||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { user, setUser } = useSession();
|
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() {
|
async function handleLogout() {
|
||||||
await logout();
|
await logout();
|
||||||
setUser(null);
|
setUser(null);
|
||||||
|
setIsUserMenuOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -47,28 +67,31 @@ export default function Header() {
|
|||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
|
||||||
{user?.id && typeof user.id === "number" ? (
|
{user?.id && typeof user.id === "number" ? (
|
||||||
<div className="dropdown dropdown-end">
|
<div ref={userMenuRef} className={`dropdown dropdown-end ${isUserMenuOpen ? "dropdown-open" : ""}`}>
|
||||||
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
|
<button
|
||||||
|
onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
|
||||||
|
className="btn btn-ghost btn-circle avatar"
|
||||||
|
>
|
||||||
<div className="w-10 rounded-full">
|
<div className="w-10 rounded-full">
|
||||||
<IconUser className="m-auto block h-full" />
|
<IconUser className="m-auto block h-full" />
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</button>
|
||||||
<div tabIndex={0} className="dropdown-content z-50 mt-3 w-80 rounded-box bg-base-100 p-3 shadow-lg">
|
<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>
|
<div className="px-2 pb-2 font-semibold">{user.name}</div>
|
||||||
<ul className="menu menu-sm p-0">
|
<ul className="menu menu-sm p-0">
|
||||||
<li>
|
<li>
|
||||||
<Link href={`/user/${user.name}`}>
|
<Link href={`/user/${user.name}`} onClick={() => setIsUserMenuOpen(false)}>
|
||||||
<IconUserCircle size={16} /> Profil
|
<IconUserCircle size={16} /> Profil
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link href="/settings">
|
<Link href="/settings" onClick={() => setIsUserMenuOpen(false)}>
|
||||||
<IconSettings2 size={16} /> Einstellungen
|
<IconSettings2 size={16} /> Einstellungen
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
{user.role === "admin" && (
|
{user.role === "admin" && (
|
||||||
<li>
|
<li>
|
||||||
<Link href="/admin">
|
<Link href="/admin" onClick={() => setIsUserMenuOpen(false)}>
|
||||||
<IconShield size={16} /> Admin-Panel
|
<IconShield size={16} /> Admin-Panel
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
@@ -90,14 +113,17 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="dropdown dropdown-end">
|
<div ref={authMenuRef} className={`dropdown dropdown-end ${isAuthMenuOpen ? "dropdown-open" : ""}`}>
|
||||||
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
|
<button
|
||||||
|
onClick={() => setIsAuthMenuOpen(!isAuthMenuOpen)}
|
||||||
|
className="btn btn-ghost btn-circle avatar"
|
||||||
|
>
|
||||||
<div className="w-10 rounded-full">
|
<div className="w-10 rounded-full">
|
||||||
<IconUser className="m-auto block h-full" />
|
<IconUser className="m-auto block h-full" />
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</button>
|
||||||
<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">
|
<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 />
|
<AuthDropdownContent onClose={() => setIsAuthMenuOpen(false)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Guest from "./Guest";
|
|||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import Register from "./Register";
|
import Register from "./Register";
|
||||||
|
|
||||||
export default function AuthDropdownContent() {
|
export default function AuthDropdownContent({ onClose }: { onClose?: () => void }) {
|
||||||
const session = useContext(SessionContext);
|
const session = useContext(SessionContext);
|
||||||
const [activeTab, setActiveTab] = useState<"guest" | "login" | "register">("guest");
|
const [activeTab, setActiveTab] = useState<"guest" | "login" | "register">("guest");
|
||||||
const [serverMessage, setServerMessage] = useState<string | null>(null);
|
const [serverMessage, setServerMessage] = useState<string | null>(null);
|
||||||
@@ -29,6 +29,9 @@ export default function AuthDropdownContent() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function closeDropdown() {
|
function closeDropdown() {
|
||||||
|
if (onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
if (typeof document !== "undefined" && document.activeElement) {
|
if (typeof document !== "undefined" && document.activeElement) {
|
||||||
(document.activeElement as HTMLElement).blur();
|
(document.activeElement as HTMLElement).blur();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user