fix: gộp các nút của thành viên và guest vào một

This commit is contained in:
2026-06-27 15:37:00 +07:00
parent 5f49070a98
commit 400a3d098d
33 changed files with 1142 additions and 250 deletions
@@ -0,0 +1,213 @@
import React, { useState } from 'react';
import { LayoutDashboard, Settings, Compass, ShieldAlert, LogOut, LogIn, Globe, Sun, Moon, Image as ImageIcon } from 'lucide-react';
import { useTranslation } from '@/hooks/useTranslation';
import { useTheme } from '@/hooks/useTheme';
interface MapProfileDropdownProps {
user: any;
onLogout?: () => void;
onGoToDashboard?: () => void;
onOpenSettings: () => void;
onOpenCreateTour: () => void;
onOpenReport: () => void;
onOpenLogin: () => void;
onOpenMyPhotos?: () => void;
onOpenAdmin?: () => void;
}
export const MapProfileDropdown: React.FC<MapProfileDropdownProps> = ({
user,
onLogout,
onGoToDashboard,
onOpenSettings,
onOpenCreateTour,
onOpenReport,
onOpenLogin,
onOpenMyPhotos,
onOpenAdmin,
}) => {
const [isOpen, setIsOpen] = useState(false);
const { lang, changeLanguage } = useTranslation();
const { theme, changeTheme } = useTheme();
const guestToken = localStorage.getItem('guest_token');
const isAuthenticated = !!user && !guestToken;
const renderInitialsAvatar = (name: string) => {
const initials = name
? name.split(' ').map(n => n[0]).slice(0, 2).join('').toUpperCase()
: 'U';
return (
<div className="w-full h-full flex items-center justify-center bg-indigo-600 text-white font-bold text-sm">
{initials}
</div>
);
};
return (
<div className="relative inline-block text-left select-none pointer-events-auto">
{/* TRIGGER BUTTON */}
<button
onClick={() => setIsOpen(!isOpen)}
className="w-11 h-11 rounded-full border-2 border-white dark:border-slate-800 bg-slate-800 flex items-center justify-center overflow-hidden shadow-xl active:scale-95 transition-all duration-150 cursor-pointer"
title="Menu cá nhân"
>
{isAuthenticated ? (
user?.avatar ? (
<img src={user.avatar} alt="Avatar" className="w-full h-full object-cover" />
) : (
renderInitialsAvatar(user?.name || 'User')
)
) : (
<div className="w-full h-full flex items-center justify-center bg-slate-700 text-slate-300 font-bold text-sm">
G
</div>
)}
</button>
{/* DROPDOWN MENU / MOBILE BOTTOM SHEET LAYER */}
{isOpen && (
<>
<div
className="fixed inset-0 z-40 bg-black/50 sm:bg-transparent"
onClick={() => setIsOpen(false)}
/>
<div
className="fixed bottom-0 left-0 right-0 sm:absolute sm:bottom-auto sm:top-14 sm:right-0 sm:left-auto z-50 w-full sm:w-64 bg-slate-900 border-t sm:border border-slate-800 rounded-t-3xl sm:rounded-2xl p-3 sm:p-2 shadow-2xl animate-in slide-in-from-bottom sm:slide-in-from-top-2 duration-300 text-xs text-slate-200"
>
<div className="w-12 h-1 bg-slate-700 rounded-full mx-auto mb-3 sm:hidden" />
{isAuthenticated ? (
<div className="flex flex-col space-y-1">
<div className="px-4 py-2 border-b border-slate-800/60 mb-1 flex items-center gap-3">
<div className="w-8 h-8 rounded-full overflow-hidden shrink-0 border border-slate-700">
{user?.avatar ? (
<img src={user.avatar} alt="Avatar" className="w-full h-full object-cover" />
) : (
renderInitialsAvatar(user?.name || 'User')
)}
</div>
<div className="flex flex-col min-w-0">
<span className="font-bold text-white truncate text-sm">{user?.name}</span>
<span className="text-[10px] text-slate-400 truncate">{user?.email}</span>
</div>
</div>
{user?.isAdmin && (
<button
onClick={() => { setIsOpen(false); onOpenAdmin?.(); }}
className="flex items-center gap-3 w-full px-4 py-3 bg-blue-950/20 hover:bg-slate-805 rounded-xl text-left transition-colors font-bold text-blue-400 cursor-pointer"
>
<Settings className="w-4 h-4 shrink-0 text-blue-400" /> Quản trị hệ thống
</button>
)}
<button
onClick={() => { setIsOpen(false); onGoToDashboard?.(); }}
className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-xl text-left transition-colors font-bold cursor-pointer"
>
<LayoutDashboard className="w-4 h-4 text-blue-400 shrink-0" /> Bảng điều khiển
</button>
<button
onClick={() => { setIsOpen(false); onOpenSettings(); }}
className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-xl text-left transition-colors font-bold cursor-pointer"
>
<Settings className="w-4 h-4 text-emerald-400 shrink-0" /> Cài đt nhân
</button>
<button
onClick={() => { setIsOpen(false); onOpenCreateTour(); }}
className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-xl text-left transition-colors font-bold cursor-pointer"
>
<Compass className="w-4 h-4 text-amber-400 shrink-0" /> Tạo Tour mới
</button>
<button
onClick={() => { setIsOpen(false); onOpenMyPhotos?.(); }}
className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-xl text-left transition-colors font-bold cursor-pointer"
>
<ImageIcon className="w-4 h-4 text-sky-400 shrink-0" /> nh của tôi
</button>
<button
onClick={() => { setIsOpen(false); onOpenReport(); }}
className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-xl text-left transition-colors font-bold cursor-pointer"
>
<ShieldAlert className="w-4 h-4 text-rose-400 shrink-0" /> Báo cáo sai phạm
</button>
<div className="h-[1px] bg-slate-855 my-1 mx-2" />
<button
onClick={() => { setIsOpen(false); onLogout?.(); }}
className="flex items-center gap-3 w-full px-4 py-3 text-rose-400 hover:bg-slate-800 rounded-xl text-left font-black transition-colors cursor-pointer"
>
<LogOut className="w-4 h-4 shrink-0" /> Đăng xuất
</button>
</div>
) : (
<div className="flex flex-col space-y-1">
<div className="px-4 py-1.5 text-[10px] font-black uppercase tracking-wider text-slate-500">Tùy chỉnh nhanh</div>
<div className="px-3 py-2 flex flex-col gap-3 bg-slate-950/40 rounded-xl m-1 border border-slate-850">
<div className="flex justify-between items-center">
<span className="font-semibold flex items-center gap-1.5 text-slate-400">
<Globe className="w-3.5 h-3.5 text-indigo-400" /> Ngôn ngữ:
</span>
<select
value={lang}
onChange={(e) => changeLanguage(e.target.value as any)}
className="bg-slate-800 border border-slate-700 rounded-lg px-2 py-1 text-white text-[11px] font-bold focus:outline-none cursor-pointer"
>
<option value="vi">Tiếng Việt</option>
<option value="en">English</option>
<option value="zh"></option>
</select>
</div>
<div className="flex justify-between items-center">
<span className="font-semibold flex items-center gap-1.5 text-slate-400">
{theme === 'light' ? (
<Sun className="w-3.5 h-3.5 text-amber-500" />
) : (
<Moon className="w-3.5 h-3.5 text-indigo-400" />
)}
Giao diện:
</span>
<select
value={theme}
onChange={(e) => changeTheme(e.target.value as any)}
className="bg-slate-800 border border-slate-700 rounded-lg px-2 py-1 text-white text-[11px] font-bold focus:outline-none cursor-pointer"
>
<option value="dark">Tối</option>
<option value="light">Sáng</option>
<option value="system">Hệ thống</option>
</select>
</div>
</div>
<button
onClick={() => { setIsOpen(false); onOpenMyPhotos?.(); }}
className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-xl text-left transition-colors font-bold cursor-pointer"
>
<ImageIcon className="w-4 h-4 text-sky-400 shrink-0" /> nh của tôi
</button>
<div className="h-[1px] bg-slate-850 my-1 mx-2" />
<button
onClick={() => { setIsOpen(false); onOpenLogin(); }}
className="flex items-center gap-3 w-full px-4 py-3 text-blue-400 hover:bg-slate-800 rounded-xl text-left font-black transition-colors cursor-pointer"
>
<LogIn className="w-4 h-4 shrink-0" /> Đăng / Đăng nhập
</button>
</div>
)}
</div>
</>
)}
</div>
);
};