microtecflix/src/components/Navbar.tsx

275 lines
12 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { Play, LogOut, Shield, User, GraduationCap, DollarSign, Bell, ChevronDown, Check, HelpCircle } from 'lucide-react';
import { SubscriptionStatus } from '../types';
interface NavbarProps {
user: {
name: string;
email: string;
role: 'admin' | 'student';
subscriptionStatus: SubscriptionStatus;
avatarUrl?: string;
} | null;
activeView: string;
setView: (view: string) => void;
onLogout: () => void;
settings?: { brandName?: string } | null;
}
export default function Navbar({ user, activeView, setView, onLogout, settings }: NavbarProps) {
const [showNotifications, setShowNotifications] = useState(false);
const [showProfileMenu, setShowProfileMenu] = useState(false);
const [notifications, setNotifications] = useState<any[]>([]);
const notifRef = useRef<HTMLDivElement>(null);
const profileRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Close menus on outside click
const handleClickOutside = (e: MouseEvent) => {
if (notifRef.current && !notifRef.current.contains(e.target as Node)) {
setShowNotifications(false);
}
if (profileRef.current && !profileRef.current.contains(e.target as Node)) {
setShowProfileMenu(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
useEffect(() => {
if (user) {
fetchNotifications();
}
}, [user]);
const fetchNotifications = async () => {
try {
// In a real app we would pass the token or it would be in cookies
// Assuming server uses some auth, for demo we might just fetch
const token = localStorage.getItem('app_token') || localStorage.getItem('admin_token');
if (!token) return;
const res = await fetch('/api/notifications', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
const data = await res.json();
setNotifications(data);
}
} catch (err) {
console.error(err);
}
};
const markAsRead = async (id: string) => {
try {
const token = localStorage.getItem('app_token') || localStorage.getItem('admin_token');
if (!token) return;
await fetch(`/api/notifications/${id}/read`, {
method: 'PUT',
headers: { 'Authorization': `Bearer ${token}` }
});
setNotifications(notifications.map(n => n.id === id ? { ...n, read: true } : n));
} catch (err) {
console.error(err);
}
};
const unreadCount = notifications.filter(n => !n.read).length;
if (!user) return null;
const getStatusColor = (status: SubscriptionStatus) => {
switch (status) {
case 'ACTIVE': return 'bg-emerald-500 text-emerald-950';
case 'TRIAL': return 'bg-amber-500 text-amber-950';
case 'OVERDUE': return 'bg-red-500 text-white';
case 'CANCELED': return 'bg-zinc-600 text-zinc-200';
default: return 'bg-zinc-500 text-white';
}
};
const getStatusLabel = (status: SubscriptionStatus) => {
switch (status) {
case 'ACTIVE': return 'Assinatura Ativa';
case 'TRIAL': return 'Período de Teste';
case 'OVERDUE': return 'Pagamento Pendente';
case 'CANCELED': return 'Assinatura Cancelada';
default: return status;
}
};
const navLinks = (
<>
<button
onClick={() => setView('home')}
className={`font-semibold transition-colors hover:text-[#E50914] whitespace-nowrap flex-shrink-0 ${activeView === 'home' || activeView === 'course-view' ? 'text-white' : 'text-zinc-400'}`}
>
Cursos
</button>
<button
onClick={() => setView('subscription')}
className={`font-semibold transition-colors hover:text-[#E50914] flex items-center space-x-1.5 whitespace-nowrap flex-shrink-0 ${activeView === 'subscription' ? 'text-white' : 'text-zinc-400'}`}
>
<DollarSign className="w-4 h-4 text-[#E50914]" />
<span>Minha Assinatura</span>
</button>
<button
onClick={() => setView('certificates')}
className={`font-semibold transition-colors hover:text-[#E50914] flex items-center space-x-1.5 whitespace-nowrap flex-shrink-0 ${activeView === 'certificates' ? 'text-white' : 'text-zinc-400'}`}
>
<GraduationCap className="w-4 h-4 text-[#E50914]" />
<span>Meus Certificados</span>
</button>
</>
);
return (
<nav className="sticky top-0 z-50 glass-navbar px-4 py-3 md:px-8 flex flex-wrap items-center justify-between gap-y-3">
<div className="flex items-center space-x-8">
{/* LOGO */}
<div className="flex flex-col">
<div
onClick={() => setView('home')}
className="flex items-center space-x-2 cursor-pointer select-none group"
>
<div className="bg-[#E50914] text-white p-1.5 rounded flex items-center justify-center group-hover:scale-105 transition-transform">
<Play className="fill-white w-5 h-5" />
</div>
<span className="font-sans font-black text-xl md:text-2xl tracking-tighter text-[#E50914]">
{(settings?.brandName || 'Plataforma')}<span className="text-white">FLIX</span>
</span>
</div>
{settings?.brandSlogan && (
<span className="text-[10px] text-zinc-400 font-medium ml-10 mt-0.5 max-w-[350px] leading-tight hidden md:block">
{settings.brandSlogan}
</span>
)}
</div>
{/* NAVIGATION LINKS */}
<div className="hidden md:flex items-center space-x-3 lg:space-x-6 text-sm flex-shrink-0">
{navLinks}
</div>
</div>
{/* USER ACTIONS & STATUS */}
<div className="flex items-center space-x-4">
{/* Subscription Status Tag */}
<span
onClick={() => setView('subscription')}
className={`hidden sm:inline-block px-2.5 py-0.5 rounded-full text-xs font-bold tracking-wide uppercase cursor-pointer hover:opacity-90 transition-opacity ${getStatusColor(user.subscriptionStatus)}`}
>
{getStatusLabel(user.subscriptionStatus)}
</span>
{/* User Info & Actions */}
<div className="flex items-center space-x-3 border-l border-zinc-800 pl-4">
{/* Notifications */}
<div className="relative" ref={notifRef}>
<button
onClick={() => setShowNotifications(!showNotifications)}
className="relative w-8 h-8 rounded-full bg-zinc-800 flex items-center justify-center text-zinc-300 hover:bg-zinc-700 transition-colors"
>
<Bell className="w-4 h-4" />
{unreadCount > 0 && (
<span className="absolute -top-1 -right-1 w-3.5 h-3.5 bg-[#E50914] text-white text-[8px] font-bold flex items-center justify-center rounded-full border-2 border-black">
{unreadCount}
</span>
)}
</button>
{showNotifications && (
<div className="absolute right-0 mt-2 w-72 bg-zinc-900 border border-white/10 rounded-xl shadow-2xl overflow-hidden flex flex-col z-50">
<div className="p-3 border-b border-white/10 bg-black/40">
<h4 className="text-xs font-bold text-white uppercase tracking-wider">Notificações</h4>
</div>
<div className="max-h-64 overflow-y-auto">
{notifications.filter(n => !n.read).length === 0 ? (
<div className="p-4 text-center text-zinc-500 text-xs">Nenhuma notificação no momento.</div>
) : (
notifications.filter(n => !n.read).map(n => (
<div
key={n.id}
className={`p-3 border-b border-white/5 text-xs transition-colors bg-white/5 text-white`}
>
<p>{n.message}</p>
<div className="flex items-center justify-between mt-2">
<span className="text-[10px] text-zinc-500">{new Date(n.createdAt).toLocaleDateString('pt-BR')}</span>
<button onClick={() => markAsRead(n.id)} className="text-[10px] text-[#E50914] hover:text-white flex items-center gap-1">
<Check className="w-3 h-3" /> Lida
</button>
</div>
</div>
))
)}
</div>
</div>
)}
</div>
<div className="text-right hidden lg:block">
<p className="text-xs font-medium text-white max-w-[120px] truncate">{user.name}</p>
<p className="text-[10px] text-zinc-500 uppercase font-semibold">{user.role === 'admin' ? 'Administrador' : 'Aluno'}</p>
</div>
{/* Profile Dropdown */}
<div className="relative" ref={profileRef}>
<button
onClick={() => setShowProfileMenu(!showProfileMenu)}
className="w-10 h-10 rounded-full bg-zinc-800 flex items-center justify-center text-zinc-300 hover:bg-zinc-700 transition-colors border border-white/10 overflow-hidden"
>
{user.avatarUrl ? (
<img src={user.avatarUrl} alt="Avatar" className="w-full h-full object-cover" />
) : (
<User className="w-5 h-5" />
)}
</button>
{showProfileMenu && (
<div className="absolute right-0 mt-2 w-48 bg-zinc-900 border border-white/10 rounded-xl shadow-2xl overflow-hidden py-1 z-50">
<button
onClick={() => { setView('profile'); setShowProfileMenu(false); }}
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-zinc-300 hover:bg-zinc-800 hover:text-white transition-colors"
>
<User className="w-4 h-4" />
<span>Meus dados</span>
</button>
<button
onClick={() => { setView('certificates'); setShowProfileMenu(false); }}
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-zinc-300 hover:bg-zinc-800 hover:text-white transition-colors"
>
<GraduationCap className="w-4 h-4" />
<span>Meus Certificados</span>
</button>
<button
onClick={() => { setView('help'); setShowProfileMenu(false); }}
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-zinc-300 hover:bg-zinc-800 hover:text-white transition-colors"
>
<HelpCircle className="w-4 h-4" />
<span>Ajuda</span>
</button>
<div className="border-t border-white/10 my-1"></div>
<button
onClick={() => { onLogout(); setShowProfileMenu(false); }}
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-red-400 hover:bg-red-500/10 transition-colors"
>
<LogOut className="w-4 h-4" />
<span>Sair</span>
</button>
</div>
)}
</div>
</div>
</div>
{/* MOBILE NAVIGATION LINKS */}
<div className="flex md:hidden items-center space-x-6 text-sm overflow-x-auto w-full pt-3 pb-1 border-t border-white/10 scrollbar-hide">
{navLinks}
</div>
</nav>
);
}