gbclient/app/client-nav.tsx

346 lines
12 KiB
TypeScript
Raw Permalink Normal View History

"use client";
import { usePathname, useRouter } from 'next/navigation';
import Image from 'next/image';
import { useRef, useEffect, useState } from 'react';
import { useTheme } from './theme-provider';
import { ChevronLeft, ChevronRight, HardDrive, Terminal } from 'lucide-react';
import './client-nav.css'; // Ensure you have the CSS file for styles
const examples = [
{ name: "Chat", href: "/chat", color: "#25D366" }, // WhatsApp green
{ name: "Paper", href: "/paper", color: "#6366F1" }, // Indigo
{ name: "Mail", href: "/mail", color: "#FFD700" }, // Outlook yellow
{ name: "Calendar", href: "/calendar", color: "#1DB954" }, // Spotify green
{ name: "Meet", href: "/meet", color: "#059669" }, // Google Meet green
{ name: "Drive", href: "/drive", color: "#10B981" }, // Emerald green
{ name: "Editor", href: "/editor", color: "#2563EB" }, // Word blue
{ name: "Player", href: "/player", color: "Yellow" }, // YouTube red
{ name: "Tables", href: "/tables", color: "#8B5CF6" }, // Purple
{ name: "Dashboard", href: "/dashboard", color: "#6366F1" }, // Indigo
{ name: "Sources", href: "/sources", color: "#F59E0B" }, // Amber
{ name: "Settings", href: "/settings", color: "#6B7280" }, // Gray
];
export const Nav = ()=> {
const pathname = usePathname();
const router = useRouter();
const scrollContainerRef = useRef<HTMLDivElement>(null);
const navItemsRefs = useRef<(HTMLButtonElement | null)[]>([]);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [showLoginMenu, setShowLoginMenu] = useState(false);
const [showScrollButtons, setShowScrollButtons] = useState(false);
const loginMenuRef = useRef<HTMLDivElement>(null);
const [showThemeMenu, setShowThemeMenu] = useState(false);
const themeMenuRef = useRef<HTMLDivElement>(null);
const [currentTime, setCurrentTime] = useState(new Date());
const { theme, setTheme, themes } = useTheme();
// Update time every second
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
const formatTime = (date) => {
return date.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
const formatDate = (date) => {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
};
const isActive = (href: string) => {
if (href === '/') return pathname === href;
return pathname.startsWith(href);
};
const handleLogin = () => {
setIsLoggedIn(true);
setShowLoginMenu(false);
};
const handleLogout = () => {
setIsLoggedIn(false);
setShowLoginMenu(false);
};
const checkScrollNeeded = () => {
if (scrollContainerRef.current) {
const container = scrollContainerRef.current;
const isScrollable = container.scrollWidth > container.clientWidth;
setShowScrollButtons(isScrollable);
}
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (loginMenuRef.current && !loginMenuRef.current.contains(event.target as Node)) {
setShowLoginMenu(false);
}
if (themeMenuRef.current && !themeMenuRef.current.contains(event.target as Node)) {
setShowThemeMenu(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
useEffect(() => {
checkScrollNeeded();
window.addEventListener('resize', checkScrollNeeded);
return () => window.removeEventListener('resize', checkScrollNeeded);
}, []);
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js';
script.onload = () => {
const gsap = (window as any).gsap;
gsap.fromTo('.nav-item',
{ opacity: 0, y: -10, scale: 0.9 },
{ opacity: 1, y: 0, scale: 1, duration: 0.6, stagger: 0.1, ease: "back.out(1.7)" }
);
navItemsRefs.current.forEach((item) => {
if (item) {
item.addEventListener('mouseenter', () => {
gsap.to(item, { scale: 1.05, duration: 0.3, ease: "power2.out" });
});
item.addEventListener('mouseleave', () => {
gsap.to(item, { scale: 1, duration: 0.3, ease: "power2.out" });
});
item.addEventListener('click', () => {
gsap.to(item, { scale: 0.95, duration: 0.1, yoyo: true, repeat: 1, ease: "power2.inOut" });
});
}
});
gsap.fromTo('.theme-toggle',
{ opacity: 0, x: 20, scale: 0.8 },
{ opacity: 1, x: 0, scale: 1, duration: 0.8, delay: 0.3, ease: "elastic.out(1, 0.5)" }
);
gsap.fromTo('.login-button',
{ opacity: 0, x: 20, scale: 0.8 },
{ opacity: 1, x: 0, scale: 1, duration: 0.8, delay: 0.4, ease: "elastic.out(1, 0.5)" }
);
};
document.head.appendChild(script);
return () => {
if (document.head.contains(script)) {
document.head.removeChild(script);
}
};
}, []);
const scrollLeft = () => {
scrollContainerRef.current?.scrollBy({ left: -150, behavior: 'smooth' });
};
const scrollRight = () => {
scrollContainerRef.current?.scrollBy({ left: 150, behavior: 'smooth' });
};
const getThemeIcon = () => {
switch(theme.name) {
case 'retrowave': return '🌌';
case 'vapordream': return '🌀';
case 'y2kglow': return '💿';
case 'mellowgold': return '☮️';
case 'arcadeflash': return '🕹️';
case 'polaroidmemories': return '📸';
case 'midcenturymod': return '🪑';
case 'grungeera': return '🎸';
case 'discofever': return '🪩';
case 'saturdaycartoons': return '📺';
case 'oldhollywood': return '🎬';
case 'cyberpunk': return '🤖';
case 'seasidepostcard': return '🏖️';
case 'typewriter': return '⌨️';
case 'jazzage': return '🎷';
case 'xtreegold': return 'X';
default: return '🎨';
}
};
return (
<>
{/* Retro System Status Bar */}
<div className="fixed top-0 left-0 right-0 z-50 bg-gray-800 text-green-400 font-mono border-b border-green-600 text-xs">
<div className="flex items-center justify-between px-4 py-1">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<HardDrive className="w-3 h-3 text-green-400" />
<span className="text-green-300">RETRO NAVIGATOR v4.0</span>
</div>
<div className="flex items-center gap-1">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<span className="text-green-400">READY</span>
</div>
<div className="flex items-center gap-1">
<span className="text-green-300">THEME:</span>
<span className="text-yellow-400">{theme.label}</span>
</div>
</div>
<div className="flex items-center gap-4">
<span className="text-green-300">{formatDate(currentTime)}</span>
<span className="text-green-300">{formatTime(currentTime)}</span>
<div className="flex items-center gap-1">
<Terminal className="w-3 h-3 text-green-400" />
<span className="text-green-400">SYS</span>
</div>
</div>
</div>
</div>
{/* Main Navigation */}
<div className="nav-container" style={{ top: '24px' }}>
<div className="nav-inner">
<div className="nav-content">
<div className="logo-container">
<Image
src="/images/generalbots-logo.svg"
alt="Logo"
width={64}
height={24}
/>
</div>
{showScrollButtons && (
<button
className="w-8 h-8 bg-gray-800 border border-green-600 text-green-400 rounded hover:bg-green-900/30 hover:border-green-500 hover:text-green-300 transition-all flex items-center justify-center flex-shrink-0 mx-1"
onClick={scrollLeft}
aria-label="Scroll left"
>
<ChevronLeft className="w-4 h-4" />
</button>
)}
<div ref={scrollContainerRef} className="nav-scroll">
<div className="nav-items">
{examples.map((example, index) => {
const active = isActive(example.href);
return (
<button
key={example.href}
ref={el => { navItemsRefs.current[index] = el; }}
onClick={() => router.push(example.href)}
className={`nav-item ${active ? 'active' : ''}`}
style={{'--neon-color': example.color} as React.CSSProperties}
onMouseEnter={(e) => {
e.target.style.boxShadow = `0 0 15px ${example.color}60`;
e.target.style.borderColor = example.color;
e.target.style.color = example.color;
e.target.style.textShadow = `0 0 8px ${example.color}80`;
}}
onMouseLeave={(e) => {
if (!active) {
e.target.style.boxShadow = 'none';
e.target.style.borderColor = '';
e.target.style.color = '';
e.target.style.textShadow = 'none';
}
}}
>
{example.name}
<div className="neon-glow"></div>
</button>
);
})}
</div>
</div>
{showScrollButtons && (
<button
className="w-8 h-8 bg-gray-800 border border-green-600 text-green-400 rounded hover:bg-green-900/30 hover:border-green-500 hover:text-green-300 transition-all flex items-center justify-center flex-shrink-0 mx-1"
onClick={scrollRight}
aria-label="Scroll right"
>
<ChevronRight className="w-4 h-4" />
</button>
)}
<div className="auth-controls">
<div className="login-container" ref={loginMenuRef}>
<button
onClick={() => setShowLoginMenu(!showLoginMenu)}
className="login-button"
aria-label={isLoggedIn ? "User menu" : "Login"}
>
{isLoggedIn ? '👤' : '🔐'}
</button>
{showLoginMenu && (
<div className="login-menu">
{!isLoggedIn ? (
<button onClick={handleLogin} className="menu-item">
Login
</button>
) : (
<button onClick={handleLogout} className="menu-item">
Logout
</button>
)}
</div>
)}
</div>
<div className="theme-container" ref={themeMenuRef}>
<button
onClick={() => setShowThemeMenu(!showThemeMenu)}
className="theme-toggle"
aria-label="Change theme"
>
{getThemeIcon()}
</button>
{showThemeMenu && (
<div className="theme-menu">
{themes.map((t) => (
<button
key={t.name}
onClick={() => {
setTheme(t.name);
setShowThemeMenu(false);
}}
className={`theme-menu-item ${
theme.name === t.name ? 'active-theme' : ''
}`}
>
{t.label}
</button>
))}
</div>
)}
</div>
</div>
</div>
</div>
</div>
<div className="nav-spacer" style={{ height: '88px' }}></div>
</>
);
}
export default Nav;