- Integração Mapbox GL JS para seleção interativa de localização - Mapa arrastável com pin para localização exata - Geocoding e reverse geocoding automático - Busca de endereços com autocomplete - Campos editáveis que atualizam mapa automaticamente - Token configurado via variável de ambiente (.env.local) - Sistema de upload de fotos de fotógrafos - Upload via input de arquivo (substituiu URL) - Preview automático com FileReader API - Botão para remover foto selecionada - Placeholder com ícone de câmera - Remoção de funcionalidades de uploads/álbuns - Removida página Albums.tsx - Removido sistema de attachments - Removida aba Inspiração para empresas - Criada página Inspiração com galeria de exemplo - Melhorias de responsividade - Cards do mapa adaptados para mobile - Texto e padding reduzidos em telas pequenas - Arquivos de configuração - .env.example criado - vite-env.d.ts para tipagem - MAPBOX_SETUP.md com instruções - Footer atualizado com serviços universitários
215 lines
8.5 KiB
TypeScript
215 lines
8.5 KiB
TypeScript
|
|
import React, { useState, useEffect } from 'react';
|
|
import { UserRole } from '../types';
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
import { Menu, X, LogOut, User } 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);
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
setIsScrolled(window.scrollY > 20);
|
|
};
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
const getLinks = () => {
|
|
if (!user) return [];
|
|
|
|
switch (user.role) {
|
|
case UserRole.SUPERADMIN:
|
|
case UserRole.BUSINESS_OWNER:
|
|
return [
|
|
{ name: 'Gestão de Eventos', path: 'dashboard' },
|
|
{ name: 'Equipe & Fotógrafos', path: 'team' },
|
|
{ name: 'Financeiro', path: 'finance' },
|
|
{ name: 'Configurações', path: 'settings' }
|
|
];
|
|
case UserRole.EVENT_OWNER:
|
|
return [
|
|
{ name: 'Meus Eventos', path: 'dashboard' },
|
|
{ name: 'Solicitar Evento', path: 'request-event' }
|
|
];
|
|
case UserRole.PHOTOGRAPHER:
|
|
return [
|
|
{ name: 'Eventos Designados', path: 'dashboard' },
|
|
{ name: 'Agenda', path: 'calendar' }
|
|
];
|
|
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-30 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 space-x-4 pl-4 border-l border-gray-200">
|
|
<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>
|
|
<div 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">
|
|
<img src={user.avatar} alt="Avatar" className="w-full h-full object-cover" />
|
|
</div>
|
|
<button onClick={logout} className="text-gray-400 hover:text-red-500 transition-colors p-1" title="Sair">
|
|
<LogOut size={18} />
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="relative">
|
|
<button
|
|
onClick={() => setIsAccountDropdownOpen(!isAccountDropdownOpen)}
|
|
className="flex items-center gap-2 p-2 rounded-full hover:bg-gray-100 transition-colors"
|
|
>
|
|
<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-sm font-medium text-brand-black">Entrar / Cadastrar</p>
|
|
</div>
|
|
</button>
|
|
|
|
{/* Dropdown Popup - Centralizado */}
|
|
{isAccountDropdownOpen && (
|
|
<div className="absolute left-1/2 -translate-x-1/2 top-full mt-2 w-64 bg-white rounded-xl shadow-xl border border-gray-200 overflow-hidden z-50 fade-in">
|
|
<div className="p-4 space-y-3">
|
|
<Button
|
|
onClick={() => {
|
|
onNavigate('login');
|
|
setIsAccountDropdownOpen(false);
|
|
}}
|
|
variant="secondary"
|
|
className="w-full rounded-xl"
|
|
>
|
|
ENTRAR
|
|
</Button>
|
|
|
|
<Button
|
|
onClick={() => {
|
|
onNavigate('register');
|
|
setIsAccountDropdownOpen(false);
|
|
}}
|
|
className="w-full bg-purple-600 text-white hover:bg-purple-700 focus:ring-purple-500 rounded-xl"
|
|
>
|
|
Cadastre-se agora
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Mobile Button */}
|
|
<div className="md:hidden flex items-center">
|
|
<button
|
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
className="text-brand-black hover:text-brand-gold p-2"
|
|
>
|
|
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
|
</button>
|
|
</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="flex items-center justify-between">
|
|
<div className="flex items-center">
|
|
<img src={user.avatar} className="w-8 h-8 rounded-full mr-2" />
|
|
<div>
|
|
<span className="font-bold text-sm block">{user.name}</span>
|
|
<span className="text-xs text-brand-gold">{getRoleLabel()}</span>
|
|
</div>
|
|
</div>
|
|
<Button variant="ghost" size="sm" onClick={logout}>Sair</Button>
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-col gap-2">
|
|
<Button className="w-full rounded-lg" size="lg" variant="secondary" onClick={() => { onNavigate('login'); 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('register'); setIsMobileMenuOpen(false); }}>
|
|
Cadastre-se agora
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</nav>
|
|
);
|
|
};
|