114 lines
4.3 KiB
TypeScript
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 ký
|
|
</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>
|
|
)
|
|
}
|