2025-08-27 22:57:52 +08:00

267 lines
9.6 KiB
TypeScript

"use client";
import '../pages/style/top-bar.css';
import { Button } from '@/components/ui/button';
import { GradientText } from '@/components/ui/gradient-text';
import { useTheme } from 'next-themes';
import {
Sun,
Moon,
User,
Sparkles,
LogOut,
} from 'lucide-react';
import { motion } from 'framer-motion';
import ReactDOM from 'react-dom';
import { useRouter } from 'next/navigation';
import React, { useRef, useEffect, useLayoutEffect } from 'react';
import { logoutUser } from '@/lib/auth';
interface User {
id: string;
name: string;
email: string;
avatar: string;
}
export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onToggleSidebar: () => void }) {
const router = useRouter();
const [isOpen, setIsOpen] = React.useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const currentUser: User = JSON.parse(localStorage.getItem('currentUser') || '{}');
const [mounted, setMounted] = React.useState(false);
useLayoutEffect(() => {
console.log('Setting mounted state');
setMounted(true);
return () => console.log('Cleanup mounted effect');
}, []);
// 处理点击事件
useEffect(() => {
if (!isOpen) return;
let isClickStartedInside = false;
const handleMouseDown = (event: MouseEvent) => {
const target = event.target as Node;
isClickStartedInside = !!(
menuRef.current?.contains(target) ||
buttonRef.current?.contains(target)
);
};
const handleMouseUp = (event: MouseEvent) => {
const target = event.target as Node;
const isClickEndedInside = !!(
menuRef.current?.contains(target) ||
buttonRef.current?.contains(target)
);
// 只有当点击开始和结束都在外部时才关闭
if (!isClickStartedInside && !isClickEndedInside) {
setIsOpen(false);
}
isClickStartedInside = false;
};
// 在冒泡阶段处理事件
document.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousedown', handleMouseDown);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isOpen]);
const handleAnimationEnd = (event: React.AnimationEvent<HTMLDivElement>) => {
const element = event.currentTarget;
element.classList.remove('on');
};
const handleMouseEnter = (event: React.MouseEvent<HTMLDivElement>) => {
const element = event.currentTarget;
element.classList.add('on');
};
return (
<div className="fixed right-0 top-0 left-0 h-16 header z-[999]" style={{ isolation: 'isolate' }}>
<div className="h-full flex items-center justify-between pr-6 pl-6">
<div className="flex items-center space-x-4">
<div
className={`flex items-center cursor-pointer space-x-1 link-logo roll event-on`}
onClick={() => router.push('/')}
onMouseEnter={handleMouseEnter}
onAnimationEnd={handleAnimationEnd}
>
<span className="translate">
<span>
<h1 className="logo text-2xl font-bold">
<GradientText
text="MovieFlow"
startPercentage={30}
endPercentage={70}
/>
</h1>
</span>
<span>
<h1 className="logo text-2xl font-bold">
<GradientText
text="MovieFlow"
startPercentage={30}
endPercentage={70}
/>
</h1>
</span>
</span>
{/* beta标签 */}
<div className="relative transform translate-y-[-1px]">
<span className="inline-flex items-center px-1.5 py-0.5 text-[10px] font-semibold tracking-wider text-[rgb(212 202 202)] border border-[rgba(106,244,249,0.2)] rounded-full shadow-[0_0_10px_rgba(106,244,249,0.1)]">
Beta
</span>
</div>
</div>
</div>
<div className="flex items-center space-x-4">
{/* Pricing Link */}
<Button
variant="ghost"
size="sm"
onClick={() => router.push('/pricing')}
className="text-gray-300 hover:text-white"
>
Pricing
</Button>
{/* Notifications */}
{/* <Button variant="ghost" size="sm" onClick={() => setOpenModal(true)}>
<Bell className="h-4 w-4" />
</Button> */}
{/* Theme Toggle */}
{/* <Button
variant="ghost"
size="sm"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button> */}
{/* User Menu */}
<div className="relative" style={{ isolation: 'isolate' }}>
<Button
ref={buttonRef}
variant="ghost"
size="sm"
onClick={() => {
console.log('Button clicked, current isOpen:', isOpen);
setIsOpen(!isOpen);
}}
data-alt="user-menu-trigger"
>
<User className="h-4 w-4" />
</Button>
{mounted && isOpen ? ReactDOM.createPortal(
<motion.div
ref={menuRef}
initial={{ opacity: 0, scale: 0.95, y: -20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: -20 }}
transition={{ duration: 0.2 }}
style={{
position: 'fixed',
top: '4rem',
right: '1rem',
width: '18rem',
zIndex: 9999
}}
className="bg-[#1E1E1E] rounded-lg shadow-lg overflow-hidden"
data-alt="user-menu-dropdown"
onClick={(e) => e.stopPropagation()}
>
{/* User Info */}
<div className="p-4">
<div className="flex items-center space-x-3">
<div className="h-10 w-10 rounded-full bg-[#1E4D3E] flex items-center justify-center text-white font-semibold">
{currentUser.name ? currentUser.name.charAt(0) : ''}
</div>
<div className='flex-1'>
<p className="text-sm font-medium">{currentUser.name}</p>
<p className="text-xs text-gray-500">{currentUser.email}</p>
</div>
<div
className='cursor-pointer hover:text-red-400 transition-colors duration-200'
onClick={() => {
logoutUser();
}}
title="退出登录"
>
<LogOut className="h-4 w-4" />
</div>
</div>
</div>
{/* AI Points */}
<div className="px-4 py-3 flex items-center justify-between">
<div className="flex items-center space-x-2">
<Sparkles className="h-4 w-4" />
<span className="text-white underline text-sm">100 credits</span>
</div>
<Button
variant="outline"
size="sm"
className="text-white border-white hover:bg-white/10 rounded-full px-8"
onClick={() => router.push('/pricing')}
>
Upgrade
</Button>
</div>
{/* Menu Items */}
<div className="p-2">
{/* <motion.button
whileHover={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
className="w-full flex items-center space-x-2 px-3 py-2 rounded-md text-sm text-white"
onClick={() => router.push('/my-library')}
data-alt="my-library-button"
>
<Library className="h-4 w-4" />
<span>My Library</span>
</motion.button>
<motion.button
whileHover={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
className="w-full flex items-center space-x-2 px-3 py-2 rounded-md text-sm text-white"
onClick={() => {
// 处理退出登录
setIsOpen(false);
}}
data-alt="logout-button"
>
<LogOut className="h-4 w-4" />
<span>Logout</span>
</motion.button> */}
{/* Footer */}
<div className="mt-4 px-3 py-2 text-xs text-gray-400 text-center">
<div>Privacy Policy · Terms of Service</div>
<div>250819215404 | 2025/8/20 06:00:50</div>
</div>
</div>
</motion.div>
, document.body)
: null}
</div>
</div>
</div>
</div>
);
}