photum/frontend/components/Navbar.tsx
yagostn 5f7aaf9b63 feat: melhorias de UI e novas funcionalidades
- Tradução de rotas para português (entrar, cadastro, configuracoes, etc)
- Ajuste de responsividade na página Financeiro (mobile)
- Correção navegação Configurações para usuário CEO/Business Owner
- Modal de gerenciamento de equipe com lista de profissionais
- Exibição de fotógrafos, cinegrafistas e recepcionistas disponíveis por data
- Ajuste de layout da logo nas telas de login e cadastro
- Correção de z-index do header
- Melhoria de espaçamento e padding em cards
2025-12-10 16:53:16 -03:00

759 lines
34 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { UserRole } from "../types";
import { useAuth } from "../contexts/AuthContext";
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 { 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 || "",
});
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: "Gestão de Eventos", path: "painel" },
{ name: "Equipe", path: "equipe" },
{ name: "Gestão de Cursos", path: "cursos" },
{ name: "Aprovação de Cadastros", path: "aprovacao-cadastros" },
{ name: "Financeiro", path: "financeiro" },
];
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" }];
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";
return "";
};
return (
<>
<nav className="fixed w-full z-50 bg-white shadow-sm py-3">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<div
className="flex-shrink-0 flex items-center cursor-pointer"
onClick={() => onNavigate("home")}
>
<img
src="/logo.png"
alt="Photum Formaturas"
className="h-16 md:h-28 mb-0 md:mb-6 w-auto object-contain"
/>
</div>
{/* Desktop Navigation */}
{user && (
<div className="hidden md:flex items-center space-x-6">
{getLinks().map((link) => (
<button
key={link.path}
onClick={() => onNavigate(link.path)}
className={`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 md:flex items-center space-x-4">
{user ? (
<div className="flex items-center gap-3">
<div className="flex flex-col items-end mr-2">
<span className="text-sm font-bold text-brand-black leading-tight">
{user.name}
</span>
<span className="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-9 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={user.avatar}
alt="Avatar"
className="w-full h-full object-cover"
/>
</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={user.avatar}
alt={user.name}
className="w-full h-full object-cover"
/>
</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>
</div>
) : (
<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="md:hidden flex items-center gap-2">
{user ? (
<>
<div className="relative">
<button
onClick={() =>
setIsAccountDropdownOpen(!isAccountDropdownOpen)
}
className="h-10 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={user.avatar}
alt="Avatar"
className="w-full h-full object-cover"
/>
</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={user.avatar}
alt={user.name}
className="w-full h-full object-cover"
/>
</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-2"
>
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</>
) : (
<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="md:hidden absolute top-full left-0 w-full bg-white border-b border-gray-100 shadow-lg fade-in">
<div className="px-4 py-4 space-y-3">
{user &&
getLinks().map((link) => (
<button
key={link.path}
onClick={() => {
onNavigate(link.path);
setIsMobileMenuOpen(false);
}}
className="block w-full text-left 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={user.avatar}
className="w-10 h-10 rounded-full mr-3 border-2 border-gray-200"
alt={user.name}
/>
<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={() => {
setIsEditProfileModalOpen(true);
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>
{/* Modal de Edição de Perfil - Apenas para Fotógrafos e Clientes */}
{isEditProfileModalOpen &&
(user?.role === UserRole.PHOTOGRAPHER ||
user?.role === UserRole.EVENT_OWNER) && (
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[100] p-4 fade-in"
onClick={() => setIsEditProfileModalOpen(false)}
>
<div
className="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 sm:p-8 text-center relative">
<button
onClick={() => setIsEditProfileModalOpen(false)}
className="absolute top-4 right-4 text-white/80 hover:text-white transition-colors"
>
<X size={24} />
</button>
<div className="w-24 h-24 mx-auto mb-4 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-4 border-white/30 overflow-hidden relative group">
<img
src={profileData.avatar}
alt="Avatar"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer">
<Camera size={28} className="text-white" />
</div>
</div>
<h2 className="text-2xl font-bold text-white mb-1">
Editar Perfil
</h2>
<p className="text-white/80 text-sm">
Atualize suas informações pessoais
</p>
</div>
{/* Form */}
<form
className="p-6 sm:p-8 space-y-6"
onSubmit={(e) => {
e.preventDefault();
// Aqui você pode adicionar a lógica para salvar os dados
alert("Perfil atualizado com sucesso!");
setIsEditProfileModalOpen(false);
}}
>
{/* Nome Completo */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nome Completo
</label>
<div className="relative">
<User
size={20}
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"
/>
<input
type="text"
value={profileData.name}
onChange={(e) =>
setProfileData({ ...profileData, name: e.target.value })
}
className="w-full pl-11 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#492E61] focus:border-transparent transition-all"
placeholder="Seu nome completo"
required
/>
</div>
</div>
{/* Email */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Email
</label>
<div className="relative">
<Mail
size={20}
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"
/>
<input
type="email"
value={profileData.email}
onChange={(e) =>
setProfileData({
...profileData,
email: e.target.value,
})
}
className="w-full pl-11 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#492E61] focus:border-transparent transition-all"
placeholder="seu@email.com"
required
/>
</div>
</div>
{/* Telefone */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Telefone
</label>
<div className="relative">
<Phone
size={20}
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"
/>
<input
type="tel"
value={profileData.phone}
onChange={(e) =>
setProfileData({
...profileData,
phone: e.target.value,
})
}
className="w-full pl-11 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#492E61] focus:border-transparent transition-all"
placeholder="(00) 00000-0000"
/>
</div>
</div>
{/* Botões */}
<div className="flex flex-col sm:flex-row gap-3 pt-4 border-t border-gray-200">
<button
type="button"
onClick={() => setIsEditProfileModalOpen(false)}
className="flex-1 px-6 py-3 border border-gray-300 text-gray-700 rounded-xl hover:bg-gray-50 transition-colors font-medium"
>
Cancelar
</button>
<button
type="submit"
className="flex-1 px-6 py-3 bg-[#492E61] text-white rounded-xl hover:bg-[#3a2450] transition-colors font-medium shadow-lg hover:shadow-xl"
>
Salvar Alterações
</button>
</div>
</form>
</div>
</div>
)}
</>
);
};