veza/apps/web/src/components/layout/Navbar.tsx
senke dd6333d540 ui(tokens): migrate kodo-cyan to primary (51 files, 88 instances)
Replace legacy text-kodo-cyan/border-kodo-cyan/bg-kodo-cyan with semantic
text-primary/border-primary/bg-primary across 51 components.

The brand primary color now uses the design system token, enabling proper
theme adaptation. Covers UI primitives, search, dashboard, chat, playlists,
settings, social, marketplace, and auth components.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 00:19:12 +01:00

261 lines
9.2 KiB
TypeScript

import React, { useState } from 'react';
import {
Menu,
Palette,
Zap,
ChevronDown,
LogOut,
Settings,
User,
ShoppingCart,
Search,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useCartStore } from '../../stores/cartStore';
import { useTheme } from '../theme/ThemeProvider';
import { Notification } from '../../types';
import { NotificationBell } from '../notifications/NotificationBell';
interface NavbarProps {
onNavigate: (viewId: string) => void;
onLogout: () => void;
}
const mockNotifications: Notification[] = [
{
id: '1',
type: 'info',
title: 'System Update',
message: 'System Update v2.0 Live',
timestamp: '2m',
read: false,
},
{
id: '2',
type: 'like',
title: 'New Like',
message: 'Neon_Dev liked your track',
timestamp: '15m',
read: false,
actionUrl: '/track/1',
},
{
id: '3',
type: 'follow',
title: 'New Follower',
message: 'Skrillex started following you',
timestamp: '1h',
read: true,
actionUrl: '/u/skrillex',
},
];
export const Navbar: React.FC<NavbarProps> = ({ onNavigate, onLogout }) => {
const { theme, setTheme } = useTheme();
const toggleTheme = () => {
setTheme(theme === 'dark' ? 'light' : 'dark');
};
// Selector ensures we re-render only when the calculated count changes
const itemCount = useCartStore((state) => state.getItemCount());
const [showUserMenu, setShowUserMenu] = useState(false);
const [notifications, setNotifications] =
useState<Notification[]>(mockNotifications);
const handleMarkAllRead = () => {
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
};
const handleRead = (id: string) => {
setNotifications((prev) =>
prev.map((n) => (n.id === id ? { ...n, read: true } : n)),
);
};
return (
<>
{/* Backdrop for closing menus - Lower z-index than Navbar (z-40) but higher than content */}
{showUserMenu && (
<div
className="fixed inset-0 z-[35] bg-transparent cursor-default"
onClick={() => setShowUserMenu(false)}
/>
)}
<nav
role="navigation"
aria-label="Main Navigation"
className="fixed top-0 left-0 right-0 h-16 bg-background/80 backdrop-blur-md border-b border-border/40 z-40 flex items-center justify-between px-6 lg:px-8"
>
{/* Brand & Mobile Menu */}
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm" className="lg:hidden p-1">
<Menu className="w-5 h-5" />
</Button>
<div
className="flex items-center gap-4 cursor-pointer"
onClick={() => onNavigate('dashboard')}
>
<div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center shadow-lg shadow-border/20">
<span className="font-display font-bold text-primary-foreground text-lg">
V
</span>
</div>
<div className="hidden sm:flex flex-col justify-center">
<span className="font-display font-bold text-base tracking-wide text-foreground leading-none">
VEZA
</span>
<span className="text-xs text-muted-foreground font-medium tracking-widest uppercase leading-none mt-1">
Spectre Astral
</span>
</div>
</div>
</div>
{/* Center Search (Hidden on Mobile) */}
<div className="hidden md:flex flex-1 max-w-md mx-8 relative">
<Input
placeholder="Search platform..."
icon={<Search className="w-4 h-4" />}
/>
<div className="absolute right-0 top-1/2 -translate-y-1/2 pr-3 flex gap-2">
<span className="px-1.5 py-0.5 bg-muted rounded text-xs text-muted-foreground font-mono border border-white/5">
CMD+K
</span>
</div>
</div>
{/* Right Actions */}
<div className="flex items-center gap-4 md:gap-6">
{/* Pro Badge */}
<div className="hidden xl:flex items-center gap-4 border-r border-border/50 pr-6">
<div className="text-right">
<div className="text-xs text-foreground font-medium">
Pro Plan
</div>
<div className="text-xs text-muted-foreground">
Valid until Dec 31
</div>
</div>
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center">
<Zap className="w-4 h-4 text-warning fill-current" />
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={toggleTheme}
title={`Theme: ${theme}`}
className="hidden sm:flex"
>
<Palette className="w-5 h-5" />
</Button>
{/* Cart Trigger */}
<Button
variant="ghost"
size="sm"
className="relative hidden sm:flex"
onClick={() => onNavigate('cart')}
>
<ShoppingCart className="w-5 h-5" />
{itemCount > 0 && (
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-primary rounded-full border border-kodo-void"></span>
)}
</Button>
{/* Notification Center */}
<NotificationBell
notifications={notifications}
onMarkAllRead={handleMarkAllRead}
onRead={handleRead}
onViewAll={() => onNavigate('notifications')}
/>
{/* User Menu */}
<div className="relative z-50">
<div
className="flex items-center gap-2 cursor-pointer group select-none"
onClick={() => setShowUserMenu(!showUserMenu)}
>
<div className="w-9 h-9 rounded-full bg-border p-px hover:ring-2 hover:ring-border transition-all">
<div className="w-full h-full rounded-full overflow-hidden">
<img
src="https://picsum.photos/100/100"
alt="Avatar"
className="w-full h-full object-cover"
/>
</div>
</div>
<ChevronDown
className={`w-4 h-4 text-white group-hover:text-primary transition-transform duration-[var(--duration-fast)] ${showUserMenu ? 'rotate-180' : ''}`}
/>
</div>
{showUserMenu && (
<div className="absolute top-full right-0 mt-4 w-56 bg-card border border-border rounded-xl shadow-2xl overflow-hidden animate-fadeIn origin-top-right ring-1 ring-white/5 flex flex-col">
<div className="px-4 py-4 border-b border-border/30 mb-1 bg-muted/50">
<p className="text-sm font-bold text-white">Cyber_Producer</p>
<p className="text-xs text-muted-foreground">Pro Plan</p>
</div>
<Button
variant="ghost"
onClick={() => {
onNavigate('profile');
setShowUserMenu(false);
}}
className="w-full justify-start px-4 py-2.5 text-sm text-foreground hover:bg-muted hover:text-foreground gap-4"
>
<User className="w-4 h-4" /> My Profile
</Button>
<Button
variant="ghost"
onClick={() => {
onNavigate('studio/go-live');
setShowUserMenu(false);
}}
className="w-full justify-start px-4 py-2.5 text-sm text-foreground hover:bg-muted hover:text-foreground gap-4"
>
<Zap className="w-4 h-4 text-destructive" /> Go Live
</Button>
<Button
variant="ghost"
onClick={() => {
onNavigate('purchases');
setShowUserMenu(false);
}}
className="w-full justify-start px-4 py-2.5 text-sm text-foreground hover:bg-muted hover:text-foreground gap-4"
>
<ShoppingCart className="w-4 h-4" /> Purchases
</Button>
<Button
variant="ghost"
onClick={() => {
onNavigate('settings');
setShowUserMenu(false);
}}
className="w-full justify-start px-4 py-2.5 text-sm text-foreground hover:bg-muted hover:text-foreground gap-4"
>
<Settings className="w-4 h-4" /> Settings
</Button>
<div className="h-px bg-border/30 my-1 mx-2"></div>
<Button
variant="ghost"
onClick={() => {
onLogout();
setShowUserMenu(false);
}}
className="w-full justify-start px-4 py-2.5 text-sm text-destructive hover:bg-destructive/10 rounded-lg gap-4"
>
<LogOut className="w-4 h-4" /> Sign Out
</Button>
</div>
)}
</div>
</div>
</nav>
</>
);
};