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