feat: add user dropdown menu with sign-out to top bar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
89955f6db8
commit
a7c6aae8c6
|
|
@ -1,10 +1,53 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useTenantStore } from '@/stores/zustand/tenant-store';
|
||||
import { Search, Bell } from 'lucide-react';
|
||||
import { Search, Bell, LogOut, User, Settings } from 'lucide-react';
|
||||
|
||||
interface StoredUser {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
roles: string[];
|
||||
}
|
||||
|
||||
export function TopBar() {
|
||||
const router = useRouter();
|
||||
const { currentTenant } = useTenantStore();
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [user, setUser] = useState<StoredUser | null>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const raw = localStorage.getItem('user');
|
||||
if (raw) setUser(JSON.parse(raw));
|
||||
} catch { /* ignore */ }
|
||||
}, []);
|
||||
|
||||
// Close menu on outside click
|
||||
useEffect(() => {
|
||||
function handleClick(e: MouseEvent) {
|
||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
||||
setMenuOpen(false);
|
||||
}
|
||||
}
|
||||
if (menuOpen) document.addEventListener('mousedown', handleClick);
|
||||
return () => document.removeEventListener('mousedown', handleClick);
|
||||
}, [menuOpen]);
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('current_tenant');
|
||||
router.replace('/login');
|
||||
};
|
||||
|
||||
const initials = user?.name
|
||||
? user.name.split(' ').map((w) => w[0]).join('').toUpperCase().slice(0, 2)
|
||||
: 'U';
|
||||
|
||||
return (
|
||||
<header className="h-12 border-b bg-card/50 backdrop-blur-sm flex items-center justify-between px-4 shrink-0">
|
||||
|
|
@ -37,13 +80,54 @@ export function TopBar() {
|
|||
{/* Tenant indicator */}
|
||||
<div className="text-xs">
|
||||
<span className="text-muted-foreground">Tenant: </span>
|
||||
<span className="font-medium">{currentTenant?.name || 'Not selected'}</span>
|
||||
<span className="font-medium">{currentTenant?.name || currentTenant?.id || 'Not selected'}</span>
|
||||
</div>
|
||||
|
||||
{/* User avatar */}
|
||||
<button className="w-7 h-7 rounded-full bg-primary/15 border border-primary/20 flex items-center justify-center text-xs font-semibold text-primary hover:bg-primary/25 transition-colors">
|
||||
A
|
||||
</button>
|
||||
{/* User avatar + dropdown */}
|
||||
<div className="relative" ref={menuRef}>
|
||||
<button
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
className="w-7 h-7 rounded-full bg-primary/15 border border-primary/20 flex items-center justify-center text-xs font-semibold text-primary hover:bg-primary/25 transition-colors"
|
||||
>
|
||||
{initials}
|
||||
</button>
|
||||
|
||||
{menuOpen && (
|
||||
<div className="absolute right-0 top-full mt-1.5 w-56 bg-card border rounded-lg shadow-lg py-1 z-50">
|
||||
{/* User info */}
|
||||
<div className="px-3 py-2 border-b">
|
||||
<p className="text-sm font-medium truncate">{user?.name || 'User'}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">{user?.email}</p>
|
||||
</div>
|
||||
|
||||
{/* Menu items */}
|
||||
<button
|
||||
onClick={() => { setMenuOpen(false); router.push('/settings'); }}
|
||||
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||
>
|
||||
<Settings className="h-3.5 w-3.5" />
|
||||
Settings
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setMenuOpen(false); router.push('/users'); }}
|
||||
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||
>
|
||||
<User className="h-3.5 w-3.5" />
|
||||
Users
|
||||
</button>
|
||||
|
||||
<div className="border-t my-1" />
|
||||
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-red-500 hover:bg-red-500/10 transition-colors"
|
||||
>
|
||||
<LogOut className="h-3.5 w-3.5" />
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue