Files
doc_ai_frontend/src/components/layout/MarketingNavbar.tsx

140 lines
5.0 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
import { useAuth } from '../../contexts/AuthContext';
import AuthModal from '../AuthModal';
export default function MarketingNavbar() {
const { language, setLanguage } = useLanguage();
const { user, signOut } = useAuth();
const location = useLocation();
const isHome = location.pathname === '/';
const [scrolled, setScrolled] = useState(false);
const [activeSection, setActiveSection] = useState('');
const [userMenuOpen, setUserMenuOpen] = useState(false);
const [showAuthModal, setShowAuthModal] = useState(false);
const userMenuRef = useRef<HTMLDivElement>(null);
// Scroll: sticky style + active nav section
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 10);
if (!isHome) return;
let current = '';
document.querySelectorAll('section[id]').forEach((s) => {
if (window.scrollY >= (s as HTMLElement).offsetTop - 100) {
current = s.id;
}
});
setActiveSection(current);
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, [isHome]);
// Close user menu on outside click
useEffect(() => {
const handle = (e: MouseEvent) => {
if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) {
setUserMenuOpen(false);
}
};
document.addEventListener('mousedown', handle);
return () => document.removeEventListener('mousedown', handle);
}, []);
const navLinks = [
{ href: '/', label: language === 'zh' ? '首页' : 'Home' },
{ href: '/docs', label: language === 'zh' ? '文档' : 'Docs' },
{ href: '/blog', label: language === 'zh' ? '博客' : 'Blog' },
];
const anchorLinks = isHome
? [{ href: '#pricing', label: language === 'zh' ? '价格' : 'Pricing' }]
: [];
return (
<>
<nav style={{ opacity: scrolled ? 1 : undefined }}>
<div className="nav-inner">
<Link to="/" className="nav-logo">
<div className="logo-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
<path d="M4 6h16M4 10h10M4 14h12M4 18h8" />
</svg>
</div>
<span style={{ fontFamily: "'Lora', serif" }}>TexPixel</span>
</Link>
<ul className="nav-links">
{navLinks.map((link) => (
<li key={link.href}>
<Link
to={link.href}
className={location.pathname === link.href ? 'active' : ''}
>
{link.label}
</Link>
</li>
))}
{anchorLinks.map((link) => (
<li key={link.href}>
<a
href={link.href}
className={activeSection === link.href.slice(1) ? 'active' : ''}
>
{link.label}
</a>
</li>
))}
</ul>
<div className="nav-right">
<button
className="lang-switch"
onClick={() => setLanguage(language === 'zh' ? 'en' : 'zh')}
>
{language === 'zh' ? 'EN' : '中文'}
</button>
{user ? (
<div className="nav-user" ref={userMenuRef} style={{ position: 'relative' }}>
<div
className="nav-avatar"
onClick={() => setUserMenuOpen((o) => !o)}
style={{ cursor: 'pointer' }}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
<circle cx="12" cy="8" r="4" />
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
</svg>
</div>
{userMenuOpen && (
<div className="nav-user-menu" style={{ display: 'block' }}>
<div className="nav-menu-divider" />
<button
className="nav-menu-item nav-menu-logout"
onClick={() => { signOut(); setUserMenuOpen(false); }}
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left', cursor: 'pointer' }}
>
{language === 'zh' ? '退出登录' : 'Sign Out'}
</button>
</div>
)}
</div>
) : (
<div className="nav-login-btn">
<button className="btn-cta" onClick={() => setShowAuthModal(true)}>
{language === 'zh' ? '登录' : 'Login'}
</button>
</div>
)}
</div>
</div>
</nav>
{showAuthModal && <AuthModal onClose={() => setShowAuthModal(false)} />}
</>
);
}