phase 2
This commit is contained in:
113
src/components/UserMenu.tsx
Normal file
113
src/components/UserMenu.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user