avatar menu closes properly

This commit is contained in:
jackiettran
2025-12-30 23:25:50 -05:00
parent 1b4e86be29
commit 3ff98fbe1e

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import React, { useState, useEffect, useRef, useCallback } from "react";
import { Link, useNavigate, useLocation } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
import { useSocket } from "../contexts/SocketContext";
import { rentalAPI, messageAPI } from "../services/api";
@@ -9,9 +9,55 @@ const Navbar: React.FC = () => {
const { user, logout, openAuthModal } = useAuth();
const { onNewMessage, onMessageRead } = useSocket();
const navigate = useNavigate();
const location = useLocation();
const [searchTerm, setSearchTerm] = useState("");
const [pendingRequestsCount, setPendingRequestsCount] = useState(0);
const [unreadMessagesCount, setUnreadMessagesCount] = useState(0);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const dropdownRef = useRef<HTMLLIElement>(null);
const mobileMenuRef = useRef<HTMLDivElement>(null);
const mobileToggleRef = useRef<HTMLButtonElement>(null);
const closeDropdown = useCallback(() => {
setIsDropdownOpen(false);
}, []);
const closeMobileMenu = useCallback(() => {
setIsMobileMenuOpen(false);
}, []);
// Close menus when route changes
useEffect(() => {
closeDropdown();
closeMobileMenu();
}, [location.pathname, closeDropdown, closeMobileMenu]);
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
// Desktop dropdown
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsDropdownOpen(false);
}
// Mobile menu
if (
isMobileMenuOpen &&
mobileMenuRef.current &&
mobileToggleRef.current &&
!mobileMenuRef.current.contains(event.target as Node) &&
!mobileToggleRef.current.contains(event.target as Node)
) {
setIsMobileMenuOpen(false);
}
};
document.addEventListener("click", handleClickOutside, true);
return () => {
document.removeEventListener("click", handleClickOutside, true);
};
}, [isMobileMenuOpen]);
// Fetch pending rental requests count when user logs in
useEffect(() => {
@@ -109,31 +155,16 @@ const Navbar: React.FC = () => {
<>
<style>
{`
@media (max-width: 991.98px) {
#navbarNav {
position: absolute;
top: calc(100% - 8px);
right: 0.5rem;
left: auto;
z-index: 1000;
min-width: 10rem;
padding: 0;
background-color: #fff;
border: 1px solid rgba(0,0,0,.15);
border-radius: 0.375rem;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,.175);
}
#navbarNav .mobile-menu {
padding: 0;
}
#navbarNav .dropdown-item {
padding: 0.25rem 1rem;
}
#navbarNav .dropdown-divider {
margin: 0.5rem 0;
border-top: 1px solid rgba(0,0,0,.15);
opacity: 1;
}
#navbarNav .mobile-menu {
padding: 0;
}
#navbarNav .dropdown-item {
padding: 0.25rem 1rem;
}
#navbarNav .dropdown-divider {
margin: 0.5rem 0;
border-top: 1px solid rgba(0,0,0,.15);
opacity: 1;
}
`}
</style>
@@ -170,13 +201,16 @@ const Navbar: React.FC = () => {
{/* Mobile menu toggle */}
<button
className="navbar-toggler border-0 p-0"
ref={mobileToggleRef}
className="navbar-toggler border-0 p-0 d-lg-none"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-expanded={isMobileMenuOpen}
aria-label="Toggle navigation"
onClick={(e) => {
e.stopPropagation();
setIsMobileMenuOpen((prev) => !prev);
}}
>
{user ? (
<span
@@ -219,7 +253,25 @@ const Navbar: React.FC = () => {
></i>
)}
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<div
ref={mobileMenuRef}
id="navbarNav"
className="d-lg-none"
style={{
display: isMobileMenuOpen ? "block" : "none",
position: "absolute",
top: "calc(100% - 8px)",
right: "0.5rem",
left: "auto",
zIndex: 1000,
minWidth: "10rem",
padding: 0,
backgroundColor: "#fff",
border: "1px solid rgba(0,0,0,.15)",
borderRadius: "0.375rem",
boxShadow: "0 0.5rem 1rem rgba(0,0,0,.175)",
}}
>
<div
className="d-flex align-items-center justify-content-center justify-content-lg-end w-100 py-3 py-lg-0 mobile-menu"
>
@@ -228,17 +280,17 @@ const Navbar: React.FC = () => {
<>
{/* Mobile menu - show items directly */}
<li className="d-lg-none">
<Link className="dropdown-item" to="/profile">
<Link className="dropdown-item" to="/profile" onClick={closeMobileMenu}>
<i className="bi bi-person me-2"></i>Profile
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/renting">
<Link className="dropdown-item" to="/renting" onClick={closeMobileMenu}>
<i className="bi bi-calendar-check me-2"></i>Renting
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/owning">
<Link className="dropdown-item" to="/owning" onClick={closeMobileMenu}>
<i className="bi bi-list-ul me-2"></i>Owning
{pendingRequestsCount > 0 && (
<span className="badge bg-danger rounded-pill ms-2">
@@ -248,17 +300,17 @@ const Navbar: React.FC = () => {
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/forum">
<Link className="dropdown-item" to="/forum" onClick={closeMobileMenu}>
<i className="bi bi-chat-dots me-2"></i>Forum
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/earnings">
<Link className="dropdown-item" to="/earnings" onClick={closeMobileMenu}>
<i className="bi bi-cash-coin me-2"></i>Earnings
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/messages">
<Link className="dropdown-item" to="/messages" onClick={closeMobileMenu}>
<i className="bi bi-envelope me-2"></i>Messages
{unreadMessagesCount > 0 && (
<span className="badge bg-danger rounded-pill ms-2">
@@ -273,21 +325,26 @@ const Navbar: React.FC = () => {
<li className="d-lg-none">
<button
className="dropdown-item"
onClick={handleLogout}
onClick={() => {
closeMobileMenu();
handleLogout();
}}
>
<i className="bi bi-box-arrow-right me-2"></i>Logout
</button>
</li>
{/* Desktop dropdown */}
<li className="nav-item dropdown d-none d-lg-block">
<a
className="nav-link dropdown-toggle"
href="#"
id="navbarDropdown"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
<li className="nav-item d-none d-lg-block" ref={dropdownRef} style={{ position: "relative" }}>
<button
type="button"
className="nav-link btn btn-link p-0"
aria-expanded={isDropdownOpen}
onClick={(e) => {
e.stopPropagation();
setIsDropdownOpen((prev) => !prev);
}}
style={{ cursor: "pointer", border: "none", background: "none" }}
>
<span
style={{
@@ -322,24 +379,30 @@ const Navbar: React.FC = () => {
)}
<Avatar user={user} size="sm" />
</span>
</a>
</button>
{isDropdownOpen && (
<ul
className="dropdown-menu"
aria-labelledby="navbarDropdown"
className="dropdown-menu show"
style={{
position: "absolute",
right: 0,
top: "100%",
zIndex: 1000,
}}
>
<li>
<Link className="dropdown-item" to="/profile">
<Link className="dropdown-item" to="/profile" onClick={() => setIsDropdownOpen(false)}>
<i className="bi bi-person me-2"></i>Profile
</Link>
</li>
<li>
<Link className="dropdown-item" to="/renting">
<Link className="dropdown-item" to="/renting" onClick={() => setIsDropdownOpen(false)}>
<i className="bi bi-calendar-check me-2"></i>
Renting
</Link>
</li>
<li>
<Link className="dropdown-item" to="/owning">
<Link className="dropdown-item" to="/owning" onClick={() => setIsDropdownOpen(false)}>
<i className="bi bi-list-ul me-2"></i>Owning
{pendingRequestsCount > 0 && (
<span className="badge bg-danger rounded-pill ms-2">
@@ -349,19 +412,19 @@ const Navbar: React.FC = () => {
</Link>
</li>
<li>
<Link className="dropdown-item" to="/forum">
<Link className="dropdown-item" to="/forum" onClick={() => setIsDropdownOpen(false)}>
<i className="bi bi-chat-dots me-2"></i>
Forum
</Link>
</li>
<li>
<Link className="dropdown-item" to="/earnings">
<Link className="dropdown-item" to="/earnings" onClick={() => setIsDropdownOpen(false)}>
<i className="bi bi-cash-coin me-2"></i>
Earnings
</Link>
</li>
<li>
<Link className="dropdown-item" to="/messages">
<Link className="dropdown-item" to="/messages" onClick={() => setIsDropdownOpen(false)}>
<i className="bi bi-envelope me-2"></i>Messages
{unreadMessagesCount > 0 && (
<span className="badge bg-danger rounded-pill ms-2">
@@ -376,20 +439,24 @@ const Navbar: React.FC = () => {
<li>
<button
className="dropdown-item"
onClick={handleLogout}
onClick={() => {
setIsDropdownOpen(false);
handleLogout();
}}
>
<i className="bi bi-box-arrow-right me-2"></i>
Logout
</button>
</li>
</ul>
)}
</li>
</>
) : (
<>
{/* Mobile menu items */}
<li className="d-lg-none">
<Link className="dropdown-item" to="/forum">
<Link className="dropdown-item" to="/forum" onClick={closeMobileMenu}>
<i className="bi bi-chat-dots me-2"></i>Forum
</Link>
</li>
@@ -397,7 +464,10 @@ const Navbar: React.FC = () => {
<button
className="dropdown-item fw-bold"
style={{ color: "#000000" }}
onClick={() => openAuthModal("login")}
onClick={() => {
closeMobileMenu();
openAuthModal("login");
}}
>
<i className="bi bi-box-arrow-in-right me-2"></i>Login or Sign Up
</button>