Files
english/src/components/UserMenu.tsx
2026-04-12 18:54:59 +07:00

114 lines
4.3 KiB
TypeScript

import { useState, useEffect, useRef } from 'react'
import { useNavigate } from '@tanstack/react-router'
import { useAuthStore } from '@/store/auth-store'
import { useUser } from '@/hooks/use-auth'
import { useAuthModalStore } from '@/store/auth-modal-store'
/** Avatar circle with first letter of name, deterministic color */
function Avatar({ name }: { name: string }) {
const colors = ['bg-blue-600', 'bg-green-600', 'bg-violet-600', 'bg-rose-600', 'bg-amber-600']
const color = colors[name.charCodeAt(0) % colors.length]
return (
<div className={`w-8 h-8 rounded-full ${color} flex items-center justify-center text-white text-sm font-bold flex-shrink-0`}>
{name.charAt(0).toUpperCase()}
</div>
)
}
export function UserMenu() {
const user = useUser()
const logout = useAuthStore((s) => s.logout)
const openModal = useAuthModalStore((s) => s.open)
const navigate = useNavigate()
const [dropdownOpen, setDropdownOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null)
// Close on outside click
useEffect(() => {
if (!dropdownOpen) return
function handleClick(e: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setDropdownOpen(false)
}
}
document.addEventListener('mousedown', handleClick)
return () => document.removeEventListener('mousedown', handleClick)
}, [dropdownOpen])
async function handleLogout() {
setDropdownOpen(false)
await logout()
navigate({ to: '/' })
}
if (!user) {
return (
<div className="flex items-center gap-2">
<button
onClick={() => openModal('login')}
className="px-3.5 py-1.5 text-sm font-semibold text-slate-600 border border-slate-300 rounded-xl hover:bg-slate-50 transition-colors"
>
Đăng nhập
</button>
<button
onClick={() => openModal('register')}
className="px-3.5 py-1.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition-colors"
>
Đăng
</button>
</div>
)
}
return (
<div className="relative" ref={menuRef}>
<button
onClick={() => setDropdownOpen((o) => !o)}
className="flex items-center gap-2 px-2 py-1 rounded-xl hover:bg-slate-100 transition-colors"
>
<Avatar name={user.name} />
<span className="text-sm font-semibold text-slate-700 max-w-28 truncate hidden sm:block">
{user.name}
</span>
<span className="material-symbols-outlined text-slate-400" style={{ fontSize: 16 }}>
expand_more
</span>
</button>
{dropdownOpen && (
<div className="absolute right-0 top-full mt-2 w-52 bg-white rounded-2xl shadow-lg border border-slate-200 py-1.5 z-50">
<div className="px-4 py-2 border-b border-slate-100 mb-1">
<div className="text-sm font-semibold text-slate-800 truncate">{user.name}</div>
<div className="text-xs text-slate-400 truncate">{user.email}</div>
</div>
<button
onClick={() => { setDropdownOpen(false); alert('Coming soon!') }}
className="w-full flex items-center gap-2.5 px-4 py-2 text-sm text-slate-600 hover:bg-slate-50 transition-colors"
>
<span className="material-symbols-outlined text-slate-400" style={{ fontSize: 18 }}>history</span>
Lịch sử bài thi
</button>
<button
onClick={() => { setDropdownOpen(false); alert('Coming soon!') }}
className="w-full flex items-center gap-2.5 px-4 py-2 text-sm text-slate-600 hover:bg-slate-50 transition-colors"
>
<span className="material-symbols-outlined text-slate-400" style={{ fontSize: 18 }}>edit_note</span>
Lịch sử writing
</button>
<div className="border-t border-slate-100 mt-1 pt-1">
<button
onClick={handleLogout}
className="w-full flex items-center gap-2.5 px-4 py-2 text-sm text-red-600 hover:bg-red-50 transition-colors"
>
<span className="material-symbols-outlined text-red-400" style={{ fontSize: 18 }}>logout</span>
Đăng xuất
</button>
</div>
</div>
)}
</div>
)
}