cosmo/frontend/src/components/UserAuth.tsx

108 lines
4.2 KiB
TypeScript

import { useState } from 'react';
import { User, LogOut, LayoutDashboard, LogIn } from 'lucide-react';
import { API_BASE_URL } from '../utils/request';
interface UserAuthProps {
user: any;
onOpenAuth: () => void;
onLogout: () => void;
onNavigateToAdmin: () => void;
}
export function UserAuth({ user, onOpenAuth, onLogout, onNavigateToAdmin }: UserAuthProps) {
const [showUserMenu, setShowUserMenu] = useState(false);
if (!user) {
return (
<button
onClick={onOpenAuth}
className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/10 hover:bg-white/20 backdrop-blur-md border border-white/10 text-white font-medium text-sm transition-all duration-200 shadow-lg hover:shadow-blue-500/20 pointer-events-auto"
>
<LogIn size={16} />
<span> / </span>
</button>
);
}
// Helper to get full avatar URL
const getAvatarUrl = () => {
if (!user?.avatar_url) return null;
return `/upload/${user.avatar_url}`;
};
return (
<div className="relative pointer-events-auto">
{/* Overlay for closing menu */}
{showUserMenu && (
<div
className="fixed inset-0 z-40"
onClick={() => setShowUserMenu(false)}
/>
)}
<button
onClick={() => setShowUserMenu(!showUserMenu)}
className="flex items-center gap-3 pl-2 pr-4 py-1.5 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-md border border-white/10 text-white transition-all duration-200 relative z-50"
>
<div className="w-8 h-8 rounded-full overflow-hidden bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center shadow-inner">
{user.avatar_url ? (
<img src={getAvatarUrl()} alt="avatar" className="w-full h-full object-cover" />
) : (
<User size={16} />
)}
</div>
<div className="flex flex-col items-start text-xs">
<span className="text-gray-400 leading-none mb-0.5"></span>
<span className="font-bold leading-none max-w-[80px] truncate">{user.username}</span>
</div>
</button>
{/* Dropdown Menu */}
{showUserMenu && (
<div className="absolute top-full right-0 mt-2 w-48 bg-gray-900/95 backdrop-blur-xl border border-white/10 rounded-xl shadow-2xl overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200 z-50">
<div className="p-4 border-b border-white/10 bg-white/5 flex items-center gap-3">
<div className="w-10 h-10 rounded-full overflow-hidden bg-gradient-to-br from-blue-500 to-purple-600 flex-shrink-0 flex items-center justify-center">
{user.avatar_url ? (
<img src={getAvatarUrl()} alt="avatar" className="w-full h-full object-cover" />
) : (
<User size={20} className="text-white" />
)}
</div>
<div className="overflow-hidden">
<p className="text-sm font-bold text-white truncate">{user.full_name || user.username}</p>
<p className="text-[10px] text-gray-500 truncate">{user.email}</p>
</div>
</div>
<div className="p-1">
<button
onClick={() => {
// Open admin in new tab
window.open('/admin', '_blank');
setShowUserMenu(false);
}}
className="w-full text-left px-3 py-2.5 text-sm text-gray-200 hover:bg-white/10 hover:text-white flex items-center gap-3 transition-colors rounded-lg"
>
<LayoutDashboard size={16} className="text-blue-400" />
</button>
<div className="h-px bg-white/10 my-1 mx-2"></div>
<button
onClick={() => {
onLogout();
setShowUserMenu(false);
}}
className="w-full text-left px-3 py-2.5 text-sm text-red-400 hover:bg-red-500/10 hover:text-red-300 flex items-center gap-3 transition-colors rounded-lg"
>
<LogOut size={16} />
退
</button>
</div>
</div>
)}
</div>
);
}