feat(nav): add responsive mobile dropdown menu with avatar and counters
- Hide desktop items on small screens, add Menu/X toggle - Include Matches badge, History, Profile, and Logout - Keep real-time pending matches counter
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import { Video, LogOut, User, History, Users } from 'lucide-react';
|
import { Video, LogOut, User, History, Users, Menu, X } from 'lucide-react';
|
||||||
import Avatar from '../common/Avatar';
|
import Avatar from '../common/Avatar';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { matchesAPI } from '../../services/api';
|
import { matchesAPI } from '../../services/api';
|
||||||
@@ -10,6 +10,7 @@ const Navbar = () => {
|
|||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [pendingMatchesCount, setPendingMatchesCount] = useState(0);
|
const [pendingMatchesCount, setPendingMatchesCount] = useState(0);
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
logout();
|
logout();
|
||||||
@@ -68,8 +69,8 @@ const Navbar = () => {
|
|||||||
<span className="text-xl font-bold text-gray-900">spotlight.cam</span>
|
<span className="text-xl font-bold text-gray-900">spotlight.cam</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Desktop menu */}
|
||||||
<div className="flex items-center space-x-4">
|
<div className="hidden md:flex items-center space-x-4">
|
||||||
<Link
|
<Link
|
||||||
to="/matches"
|
to="/matches"
|
||||||
className="flex items-center space-x-1 px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-primary-600 hover:bg-gray-100 relative"
|
className="flex items-center space-x-1 px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-primary-600 hover:bg-gray-100 relative"
|
||||||
@@ -112,8 +113,70 @@ const Navbar = () => {
|
|||||||
<span>Logout</span>
|
<span>Logout</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile menu button */}
|
||||||
|
<div className="flex md:hidden items-center">
|
||||||
|
<button
|
||||||
|
onClick={() => setMenuOpen((o) => !o)}
|
||||||
|
aria-label="Open menu"
|
||||||
|
className="p-2 rounded-md text-gray-700 hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
{menuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile dropdown */}
|
||||||
|
{menuOpen && (
|
||||||
|
<div className="md:hidden border-t bg-white shadow-sm">
|
||||||
|
<div className="px-4 py-3 flex items-center space-x-3">
|
||||||
|
<Avatar src={user?.avatar} username={user.username} size={32} />
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-sm font-medium text-gray-900">{user.username}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-2 pb-2 space-y-1">
|
||||||
|
<Link
|
||||||
|
to="/matches"
|
||||||
|
onClick={() => setMenuOpen(false)}
|
||||||
|
className="flex items-center justify-between px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<Users className="w-4 h-4" /> Matches
|
||||||
|
</span>
|
||||||
|
{pendingMatchesCount > 0 && (
|
||||||
|
<span className="ml-2 bg-red-500 text-white text-xs rounded-full h-5 px-2 flex items-center justify-center font-semibold">
|
||||||
|
{pendingMatchesCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/history"
|
||||||
|
onClick={() => setMenuOpen(false)}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<History className="w-4 h-4" /> History
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/profile"
|
||||||
|
onClick={() => setMenuOpen(false)}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<User className="w-4 h-4" /> Profile
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
handleLogout();
|
||||||
|
}}
|
||||||
|
className="w-full flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium text-red-600 hover:bg-red-50"
|
||||||
|
>
|
||||||
|
<LogOut className="w-4 h-4" /> Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user