photum/frontend/components/Navbar.tsx

696 lines
32 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { UserRole } from "../types";
import { useAuth } from "../contexts/AuthContext";
import {
BrowserRouter,
Routes,
Route,
Navigate,
useNavigate,
useLocation,
} from "react-router-dom";
import {
Menu,
X,
LogOut,
User,
Settings,
Camera,
Mail,
Phone,
GraduationCap,
} from "lucide-react";
import { Button } from "./Button";
interface NavbarProps {
onNavigate: (page: string) => void;
currentPage: string;
}
export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
const navigate = useNavigate();
const { user, logout } = useAuth();
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isAccountDropdownOpen, setIsAccountDropdownOpen] = useState(false);
const [isEditProfileModalOpen, setIsEditProfileModalOpen] = useState(false);
const [profileData, setProfileData] = useState({
name: user?.name || "",
email: user?.email || "",
phone: "",
avatar: user?.avatar || "",
});
const [avatarFile, setAvatarFile] = useState<File | null>(null);
const [avatarPreview, setAvatarPreview] = useState<string | null>(null);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (isAccountDropdownOpen) {
const target = event.target as HTMLElement;
if (!target.closest(".relative")) {
setIsAccountDropdownOpen(false);
}
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [isAccountDropdownOpen]);
const getLinks = () => {
if (!user) return [];
switch (user.role) {
case UserRole.SUPERADMIN:
case UserRole.BUSINESS_OWNER:
return [
{ name: "Agenda", path: "painel" },
{ name: "Equipe", path: "equipe" },
{ name: "Cadastro de FOT", path: "cursos" },
{ name: "Aprovação de Cadastros", path: "aprovacao-cadastros" },
{ name: "Códigos de Acesso", path: "codigos-acesso" },
{ name: "Financeiro", path: "financeiro" },
];
case UserRole.AGENDA_VIEWER:
return [
{ name: "Agenda", path: "painel" },
];
case UserRole.EVENT_OWNER:
return [
{ name: "Meus Eventos", path: "painel" },
{ name: "Solicitar Evento", path: "solicitar-evento" },
];
case UserRole.PHOTOGRAPHER:
return [
{ name: "Eventos Designados", path: "painel" },
{ name: "Meus Pagamentos", path: "meus-pagamentos" },
];
default:
return [];
}
};
const getRoleLabel = () => {
if (!user) return "";
if (user.role === UserRole.BUSINESS_OWNER) return "Empresa";
if (user.role === UserRole.EVENT_OWNER) return "Cliente";
if (user.role === UserRole.PHOTOGRAPHER) return "Fotógrafo";
if (user.role === UserRole.SUPERADMIN) return "Super Admin";
if (user.role === UserRole.AGENDA_VIEWER) return "Visualizador";
return "";
};
const getAvatarSrc = (targetUser: { name: string; avatar?: string }) => {
if (targetUser?.avatar && targetUser.avatar.trim() !== "") {
return targetUser.avatar;
}
return `https://ui-avatars.com/api/?name=${encodeURIComponent(
targetUser.name || "User"
)}&background=random&color=fff&size=128`;
};
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
// Validate file size (max 5MB)
if (file.size > 5 * 1024 * 1024) {
alert("A imagem deve ter no máximo 5MB");
return;
}
// Validate file type
if (!file.type.startsWith('image/')) {
alert("Por favor, selecione uma imagem válida");
return;
}
setAvatarFile(file);
// Create preview URL
const reader = new FileReader();
reader.onloadend = () => {
setAvatarPreview(reader.result as string);
};
reader.readAsDataURL(file);
}
};
return (
<>
<nav className="fixed top-0 left-0 w-full z-50 bg-white shadow-sm py-2 sm:py-3">
<div className="max-w-7xl mx-auto px-3 sm:px-4 lg:px-6">
<div className="flex justify-between items-center h-12 sm:h-14 md:h-16">
{/* Logo */}
<div
className="flex-shrink-0 flex items-center cursor-pointer"
onClick={() => navigate("/painel")}
>
<img
src="/logo.png"
alt="Photum Formaturas"
className="h-18 sm:h-30 w-auto max-w-[200px] object-contain mb-4 ml-3"
/>
</div>
{/* Desktop Navigation */}
{user && (
<div className="hidden lg:flex items-center space-x-3 xl:space-x-6">
{getLinks().map((link) => (
<button
key={link.path}
onClick={() => onNavigate(link.path)}
className={`text-xs xl:text-sm font-medium tracking-wide uppercase hover:text-brand-gold transition-colors pb-1 ${currentPage === link.path
? "text-brand-gold border-b-2 border-brand-gold"
: "text-gray-600 border-b-2 border-transparent"
}`}
>
{link.name}
</button>
))}
</div>
)}
{/* Right Side Actions */}
<div className="hidden lg:flex items-center space-x-2 xl:space-x-4">
{user ? (
<div className="flex items-center gap-2 xl:gap-3">
<div className="flex flex-col items-end mr-1 xl:mr-2">
<span className="text-xs xl:text-sm font-bold text-brand-black leading-tight">
{user.name}
</span>
<span className="text-[9px] xl:text-[10px] uppercase tracking-wider text-brand-gold leading-tight">
{getRoleLabel()}
</span>
</div>
{/* Avatar com dropdown */}
<div className="relative">
<button
onClick={() =>
setIsAccountDropdownOpen(!isAccountDropdownOpen)
}
className="h-8 w-8 xl:h-9 xl:w-9 rounded-full bg-gray-100 overflow-hidden border border-gray-200 ring-2 ring-transparent hover:ring-brand-gold transition-all cursor-pointer"
>
<img
src={getAvatarSrc(user)}
alt="Avatar"
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
}}
/>
</button>
{/* Profile Preview Dropdown */}
{isAccountDropdownOpen && (
<div className="absolute right-0 top-full mt-3 w-80 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50 fade-in">
{/* Header com foto e info */}
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 text-center relative">
<div className="w-20 h-20 mx-auto mb-3 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-4 border-white/30 overflow-hidden">
<img
src={getAvatarSrc(user)}
alt={user.name}
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
}}
/>
</div>
<h3 className="text-white font-bold text-lg mb-1">
{user.name}
</h3>
<p className="text-white/90 text-sm mb-1">
{user.email}
</p>
<span className="inline-block px-3 py-1 bg-white/20 backdrop-blur-sm rounded-full text-xs font-medium text-white border border-white/30">
{getRoleLabel()}
</span>
</div>
{/* Menu Items */}
<div className="p-4 space-y-2 bg-gray-50">
{/* Editar Perfil - Apenas para Fotógrafos e Clientes */}
{(user.role === UserRole.PHOTOGRAPHER ||
user.role === UserRole.EVENT_OWNER) && (
<button
onClick={() => {
onNavigate("profile");
setIsAccountDropdownOpen(false);
}}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white transition-colors text-left group"
>
<div className="w-10 h-10 rounded-lg bg-[#492E61]/10 flex items-center justify-center group-hover:bg-[#492E61]/20 transition-colors">
<User size={20} className="text-[#492E61]" />
</div>
<div className="flex-1">
<p className="text-sm font-semibold text-gray-900">
Editar Perfil
</p>
<p className="text-xs text-gray-500">
Atualize suas informações
</p>
</div>
</button>
)}
{/* Configurações - Apenas para CEO e Business Owner */}
{(user.role === UserRole.BUSINESS_OWNER ||
user.role === UserRole.SUPERADMIN) && (
<button
onClick={() => {
onNavigate("configuracoes");
setIsAccountDropdownOpen(false);
}}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white transition-colors text-left group"
>
<div className="w-10 h-10 rounded-lg bg-gray-200 flex items-center justify-center group-hover:bg-gray-300 transition-colors">
<Settings size={20} className="text-gray-600" />
</div>
<div className="flex-1">
<p className="text-sm font-semibold text-gray-900">
Configurações
</p>
<p className="text-xs text-gray-500">
Preferências da conta
</p>
</div>
</button>
)}
{/* Sair */}
<button
onClick={() => {
logout();
setIsAccountDropdownOpen(false);
}}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-red-50 transition-colors text-left group border-t border-gray-200 mt-2 pt-4"
>
<div className="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center group-hover:bg-red-200 transition-colors">
<LogOut size={20} className="text-red-600" />
</div>
<div className="flex-1">
<p className="text-sm font-semibold text-red-600">
Sair da Conta
</p>
<p className="text-xs text-red-400">
Desconectar do sistema
</p>
</div>
</button>
</div>
</div>
)}
</div>
</div>
) : (
!['entrar', 'cadastro', 'cadastro-profissional'].includes(currentPage) && (
<div className="relative">
<button
onClick={() =>
setIsAccountDropdownOpen(!isAccountDropdownOpen)
}
className="flex items-center gap-2 px-4 py-2 rounded-full hover:bg-gray-100 transition-colors shadow-md"
>
<div className="w-10 h-10 rounded-full border-2 border-brand-gold flex items-center justify-center text-brand-gold hover:bg-brand-gold hover:text-white transition-colors">
<User size={24} />
</div>
<div className="text-left hidden lg:block">
<p className="text-xs text-gray-500">Olá, bem-vindo(a)</p>
<p className="text-xs font-semibold text-gray-700">
Entrar/Cadastrar
</p>
</div>
</button>
{/* Dropdown Popup - Responsivo */}
{isAccountDropdownOpen && (
<div className="absolute right-0 lg:left-1/2 lg:-translate-x-1/2 top-full mt-3 w-72 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50 fade-in">
{/* Header com ícone */}
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 text-center">
<div className="w-16 h-16 mx-auto mb-3 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-2 border-white/30">
<User size={32} className="text-white" />
</div>
<p className="text-white/70 text-xs mb-1">
Olá, bem-vindo(a)
</p>
<p className="text-white font-semibold text-base">
Entrar/Cadastrar
</p>
</div>
{/* Botões */}
<div className="p-5 space-y-3 bg-gray-50">
<Button
onClick={() => {
onNavigate("entrar");
setIsAccountDropdownOpen(false);
}}
variant="secondary"
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
>
ENTRAR
</Button>
<Button
onClick={() => {
onNavigate("cadastro");
setIsAccountDropdownOpen(false);
}}
variant="primary"
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
>
Cadastre-se agora
</Button>
</div>
</div>
)}
</div>
)
)}
</div>
{/* Mobile Buttons */}
<div className="lg:hidden flex items-center gap-2">
{user ? (
<>
<div className="relative">
<button
onClick={() =>
setIsAccountDropdownOpen(!isAccountDropdownOpen)
}
className="h-9 w-9 sm:h-10 sm:w-10 rounded-full bg-gray-100 overflow-hidden border border-gray-200 ring-2 ring-transparent active:ring-brand-gold transition-all shadow-md"
>
<img
src={getAvatarSrc(user)}
alt="Avatar"
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
}}
/>
</button>
{/* Profile Preview Dropdown Mobile */}
{isAccountDropdownOpen && (
<div className="absolute right-0 top-full mt-3 w-80 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50 fade-in">
{/* Header com foto e info */}
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 text-center relative">
<div className="w-20 h-20 mx-auto mb-3 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-4 border-white/30 overflow-hidden">
<img
src={getAvatarSrc(user)}
alt={user.name}
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
}}
/>
</div>
<h3 className="text-white font-bold text-lg mb-1">
{user.name}
</h3>
<p className="text-white/90 text-sm mb-1">
{user.email}
</p>
<span className="inline-block px-3 py-1 bg-white/20 backdrop-blur-sm rounded-full text-xs font-medium text-white border border-white/30">
{getRoleLabel()}
</span>
</div>
{/* Menu Items */}
<div className="p-4 space-y-2 bg-gray-50">
{/* Editar Perfil - Apenas para Fotógrafos e Clientes */}
{(user.role === UserRole.PHOTOGRAPHER ||
user.role === UserRole.EVENT_OWNER) && (
<button
onClick={() => {
setIsEditProfileModalOpen(true);
setIsAccountDropdownOpen(false);
}}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white transition-colors text-left group"
>
<div className="w-10 h-10 rounded-lg bg-[#492E61]/10 flex items-center justify-center group-hover:bg-[#492E61]/20 transition-colors">
<User size={20} className="text-[#492E61]" />
</div>
<div className="flex-1">
<p className="text-sm font-semibold text-gray-900">
Editar Perfil
</p>
<p className="text-xs text-gray-500">
Atualize suas informações
</p>
</div>
</button>
)}
{/* Configurações - Apenas para CEO e Business Owner */}
{(user.role === UserRole.BUSINESS_OWNER ||
user.role === UserRole.SUPERADMIN) && (
<button
onClick={() => {
onNavigate("configuracoes");
setIsAccountDropdownOpen(false);
}}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white transition-colors text-left group"
>
<div className="w-10 h-10 rounded-lg bg-gray-200 flex items-center justify-center group-hover:bg-gray-300 transition-colors">
<Settings size={20} className="text-gray-600" />
</div>
<div className="flex-1">
<p className="text-sm font-semibold text-gray-900">
Configurações
</p>
<p className="text-xs text-gray-500">
Preferências da conta
</p>
</div>
</button>
)}
{/* Sair */}
<button
onClick={() => {
logout();
setIsAccountDropdownOpen(false);
}}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-red-50 transition-colors text-left group border-t border-gray-200 mt-2 pt-4"
>
<div className="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center group-hover:bg-red-200 transition-colors">
<LogOut size={20} className="text-red-600" />
</div>
<div className="flex-1">
<p className="text-sm font-semibold text-red-600">
Sair da Conta
</p>
<p className="text-xs text-red-400">
Desconectar do sistema
</p>
</div>
</button>
</div>
</div>
)}
</div>
{/* Menu Hamburguer - Apenas para usuários logados */}
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="text-brand-black hover:text-brand-gold p-1.5 sm:p-2"
>
{isMobileMenuOpen ? (
<X size={22} className="sm:w-6 sm:h-6" />
) : (
<Menu size={22} className="sm:w-6 sm:h-6" />
)}
</button>
</>
) : (
!['entrar', 'cadastro', 'cadastro-profissional'].includes(currentPage) && (
<div className="relative">
<button
onClick={() =>
setIsAccountDropdownOpen(!isAccountDropdownOpen)
}
className="w-10 h-10 rounded-full border-2 border-brand-gold flex items-center justify-center text-brand-gold shadow-md"
>
<User size={20} />
</button>
{/* Dropdown Popup Mobile */}
{isAccountDropdownOpen && (
<div className="absolute right-0 top-full mt-3 w-72 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50 fade-in">
{/* Header com ícone */}
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 text-center">
<div className="w-16 h-16 mx-auto mb-3 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-2 border-white/30">
<User size={32} className="text-white" />
</div>
<p className="text-white/70 text-xs mb-1">
Olá, bem-vindo(a)
</p>
<p className="text-white font-semibold text-base">
Entrar/Cadastrar
</p>
</div>
{/* Botões */}
<div className="p-5 space-y-3 bg-gray-50">
<Button
onClick={() => {
onNavigate("entrar");
setIsAccountDropdownOpen(false);
}}
variant="secondary"
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
>
ENTRAR
</Button>
<Button
onClick={() => {
onNavigate("cadastro");
setIsAccountDropdownOpen(false);
}}
variant="primary"
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
>
Cadastre-se agora
</Button>
</div>
</div>
)}
</div>
)
)}
</div>
</div>
</div>
{/* Mobile Menu */}
{isMobileMenuOpen && (
<div className="lg:hidden absolute top-full left-0 w-full bg-white border-b border-gray-100 shadow-lg fade-in">
<div className="px-3 sm:px-4 py-3 sm:py-4 space-y-2 sm:space-y-3">
{user &&
getLinks().map((link) => (
<button
key={link.path}
onClick={() => {
onNavigate(link.path);
setIsMobileMenuOpen(false);
}}
className="block w-full text-left text-sm sm:text-base font-medium text-gray-700 hover:text-brand-gold py-2 border-b border-gray-50"
>
{link.name}
</button>
))}
<div className="pt-4">
{user ? (
<div className="space-y-3">
{/* Info do usuário */}
<div className="flex items-center justify-between pb-3 border-b border-gray-100">
<div className="flex items-center">
<img
src={getAvatarSrc(user)}
className="w-10 h-10 rounded-full mr-3 border-2 border-gray-200"
alt={user.name}
onError={(e) => {
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
}}
/>
<div>
<span className="font-bold text-sm block text-gray-900">
{user.name}
</span>
<span className="text-xs text-brand-gold font-medium">
{getRoleLabel()}
</span>
</div>
</div>
</div>
{/* Botão Editar Perfil - Apenas para Fotógrafos e Clientes */}
{(user.role === UserRole.PHOTOGRAPHER ||
user.role === UserRole.EVENT_OWNER) && (
<button
onClick={() => {
onNavigate("profile");
setIsMobileMenuOpen(false);
}}
className="w-full flex items-center gap-3 px-4 py-3 bg-[#492E61]/5 hover:bg-[#492E61]/10 rounded-xl transition-colors"
>
<div className="w-10 h-10 rounded-lg bg-[#492E61]/10 flex items-center justify-center">
<User size={20} className="text-[#492E61]" />
</div>
<div className="flex-1 text-left">
<p className="text-sm font-semibold text-gray-900">
Editar Perfil
</p>
<p className="text-xs text-gray-500">
Atualize suas informações
</p>
</div>
</button>
)}
{/* Botão Sair */}
<button
onClick={() => {
logout();
setIsMobileMenuOpen(false);
}}
className="w-full flex items-center gap-3 px-4 py-3 bg-red-50 hover:bg-red-100 rounded-xl transition-colors"
>
<div className="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center">
<LogOut size={20} className="text-red-600" />
</div>
<div className="flex-1 text-left">
<p className="text-sm font-semibold text-red-600">
Sair da Conta
</p>
<p className="text-xs text-red-400">
Desconectar do sistema
</p>
</div>
</button>
</div>
) : (
<div className="flex flex-col gap-2">
<Button
className="w-full rounded-lg"
size="lg"
variant="secondary"
onClick={() => {
onNavigate("entrar");
setIsMobileMenuOpen(false);
}}
>
ENTRAR
</Button>
<Button
className="w-full bg-purple-600 text-white hover:bg-purple-700 focus:ring-purple-500 rounded-lg"
size="lg"
onClick={() => {
onNavigate("cadastro");
setIsMobileMenuOpen(false);
}}
>
Cadastre-se agora
</Button>
</div>
)}
</div>
</div>
</div>
)}
</nav>
</>
);
};