Files
octto-engenharia/frontend/src/components/Header.tsx

324 lines
16 KiB
TypeScript

"use client";
import Link from 'next/link';
import Image from 'next/image';
import { useState, useEffect } from 'react';
import { useTheme } from "next-themes";
import { useLocale } from '@/contexts/LocaleContext';
import { localeFlags, localeNames, type Locale } from '@/lib/i18n';
import SearchDropdown from './SearchDropdown';
export default function Header() {
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [logo, setLogo] = useState<string | null>(null);
const { theme, setTheme } = useTheme();
const { locale, setLocale, t } = useLocale();
const [mounted, setMounted] = useState(false);
const [whatsappLink, setWhatsappLink] = useState('https://wa.me/5535988229445');
// Prefixo para links baseado no locale
const prefix = locale === 'pt' ? '' : `/${locale}`;
useEffect(() => {
setMounted(true);
// Verifica se está logado
fetch('/api/auth/me')
.then(res => {
if (res.ok) {
setIsLoggedIn(true);
}
})
.catch(() => setIsLoggedIn(false));
// Busca as configurações (logo e whatsapp)
fetch('/api/settings')
.then(res => res.json())
.then(data => {
if (data.logo) {
setLogo(data.logo);
}
if (data.whatsapp) {
setWhatsappLink(`https://wa.me/${data.whatsapp.replace(/\D/g, '')}`);
}
})
.catch(console.error);
// Busca o número do WhatsApp do CMS (fallback)
fetch('/api/contact-info')
.then(res => res.json())
.then(data => {
if (data.whatsappLink) {
setWhatsappLink(data.whatsappLink);
}
})
.catch(console.error);
// Listener para atualização em tempo real
const handleSettingsRefresh = () => {
fetch('/api/settings')
.then(res => res.json())
.then(data => {
if (data.logo !== undefined) {
setLogo(data.logo);
}
})
.catch(console.error);
};
window.addEventListener('settings:refresh', handleSettingsRefresh);
return () => window.removeEventListener('settings:refresh', handleSettingsRefresh);
}, []);
// Prevent scrolling when mobile menu is open
useEffect(() => {
if (isMobileMenuOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isMobileMenuOpen]);
const toggleTheme = () => {
setTheme(theme === 'dark' ? 'light' : 'dark');
};
return (
<>
{/* Admin Bar - aparece apenas para usuários logados */}
{isLoggedIn && (
<div className="w-full bg-secondary dark:bg-black py-1.5 px-4 z-60 relative">
<div className="container mx-auto flex items-center justify-between">
<div className="flex items-center gap-2 text-xs text-gray-400">
<i className="ri-shield-user-line text-primary"></i>
<span className="hidden sm:inline">Você está logado como administrador</span>
</div>
<Link
href="/admin"
className="flex items-center gap-1.5 text-xs font-medium text-white bg-primary/90 hover:bg-primary px-3 py-1 rounded-full transition-colors cursor-pointer"
>
<i className="ri-dashboard-line"></i>
<span>Painel Admin</span>
</Link>
</div>
</div>
)}
<header className={`w-full bg-white dark:bg-secondary shadow-sm sticky ${isLoggedIn ? 'top-0' : 'top-0'} z-50 transition-colors duration-300`}>
<div className="container mx-auto px-4 h-20 flex items-center justify-between gap-4">
<Link href={`${prefix}/`} className="flex items-center gap-3 shrink-0 group mr-auto z-50 relative">
{logo ? (
<Image
src={logo}
alt="OCCTO Engenharia"
width={150}
height={50}
className="object-contain group-hover:scale-105 transition-transform h-12 w-auto"
unoptimized
/>
) : (
<>
<i className="ri-building-2-fill text-4xl text-primary group-hover:scale-105 transition-transform"></i>
<div className="flex items-center gap-2">
<span className="text-3xl font-bold text-secondary dark:text-white font-headline leading-none">OCCTO</span>
<span className="text-[10px] font-bold text-primary bg-primary/10 px-2 py-1 rounded-md uppercase tracking-wider">ENG.</span>
</div>
</>
)}
</Link>
<div className="hidden md:flex items-center gap-4">
{/* Search Bar with Dropdown */}
<div className="relative">
<div
className={`flex items-center bg-gray-100 dark:bg-white/10 rounded-full transition-all duration-300 ${isSearchOpen ? 'w-64 px-4 py-2' : 'w-10 h-10 justify-center cursor-pointer hover:bg-gray-200 dark:hover:bg-white/20'}`}
onClick={() => !isSearchOpen && setIsSearchOpen(true)}
>
<i className={`ri-search-line text-gray-500 dark:text-gray-300 ${isSearchOpen ? 'mr-2' : 'text-lg'}`}></i>
{isSearchOpen && (
<input
type="text"
placeholder={t('nav.search')}
autoFocus
onBlur={() => setIsSearchOpen(false)}
className="bg-transparent border-none outline-none text-sm w-full text-gray-600 dark:text-gray-200 placeholder-gray-400"
/>
)}
</div>
<SearchDropdown isOpen={isSearchOpen} onClose={() => setIsSearchOpen(false)} />
</div>
<nav className="flex items-center gap-6 mr-4">
<Link href={`${prefix}/`} className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors group">
<i className="ri-home-4-line text-lg group-hover:scale-110 transition-transform"></i>
<span className="hidden lg:inline">{t('nav.home')}</span>
</Link>
<Link href={`${prefix}/servicos`} className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors group">
<i className="ri-tools-line text-lg group-hover:scale-110 transition-transform"></i>
<span className="hidden lg:inline">{t('nav.services')}</span>
</Link>
<Link href={`${prefix}/projetos`} className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors group">
<i className="ri-briefcase-line text-lg group-hover:scale-110 transition-transform"></i>
<span className="hidden lg:inline">{t('nav.projects')}</span>
</Link>
<Link href={`${prefix}/contato`} className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors group">
<i className="ri-mail-send-line text-lg group-hover:scale-110 transition-transform"></i>
<span className="hidden lg:inline">{t('nav.contact')}</span>
</Link>
<Link href={`${prefix}/sobre`} className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors group">
<i className="ri-user-line text-lg group-hover:scale-110 transition-transform"></i>
<span className="hidden lg:inline">{t('nav.about')}</span>
</Link>
</nav>
<div className="shrink-0 ml-2">
<a
href={whatsappLink}
target="_blank"
rel="noopener noreferrer"
className="px-6 py-2.5 bg-primary text-white rounded-lg font-bold hover-primary transition-colors flex items-center gap-2"
>
<i className="ri-whatsapp-line"></i>
<span className="hidden xl:inline">{t('nav.contactUs')}</span>
</a>
</div>
<div className="flex items-center gap-2 pl-4 border-l border-gray-200 dark:border-white/10">
{/* Theme Toggle */}
<button
onClick={toggleTheme}
className="w-10 h-10 rounded-full bg-gray-100 dark:bg-white/10 flex items-center justify-center text-gray-600 dark:text-yellow-400 hover:bg-gray-200 dark:hover:bg-white/20 transition-colors cursor-pointer"
aria-label="Alternar tema"
>
{mounted && theme === 'dark' ? (
<i className="ri-sun-line text-xl"></i>
) : (
<i className="ri-moon-line text-xl"></i>
)}
</button>
{/* Language Dropdown */}
<div className="relative group">
<button
className="h-10 px-3 rounded-full bg-gray-100 dark:bg-white/10 flex items-center justify-center gap-2 text-gray-600 dark:text-white hover:bg-gray-200 dark:hover:bg-white/20 transition-colors font-bold text-sm cursor-pointer"
aria-label="Alterar idioma"
>
<span>{localeFlags[locale]}</span>
<span>{locale.toUpperCase()}</span>
<i className="ri-arrow-down-s-line text-xs opacity-50"></i>
</button>
<div className="absolute top-full right-0 pt-2 w-32 hidden group-hover:block animate-in fade-in slide-in-from-top-2 duration-200">
<div className="bg-white dark:bg-secondary rounded-xl shadow-xl border border-gray-100 dark:border-white/10 overflow-hidden">
<button onClick={() => setLocale('pt')} className="w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-white/5 flex items-center gap-3 transition-colors cursor-pointer">
<span className="text-lg">{localeFlags.pt}</span>
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">{localeNames.pt}</span>
</button>
<button onClick={() => setLocale('en')} className="w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-white/5 flex items-center gap-3 transition-colors cursor-pointer">
<span className="text-lg">{localeFlags.en}</span>
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">{localeNames.en}</span>
</button>
<button onClick={() => setLocale('es')} className="w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-white/5 flex items-center gap-3 transition-colors cursor-pointer">
<span className="text-lg">{localeFlags.es}</span>
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">{localeNames.es}</span>
</button>
</div>
</div>
</div>
</div>
</div>
{/* Mobile Menu Button */}
<button
className="md:hidden text-2xl text-secondary dark:text-white z-50 relative w-10 h-10 flex items-center justify-center cursor-pointer"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
aria-label="Menu"
>
{isMobileMenuOpen ? <i className="ri-close-line"></i> : <i className="ri-menu-line"></i>}
</button>
{/* Mobile Menu Overlay */}
<div className={`fixed inset-0 bg-white dark:bg-secondary z-40 transition-transform duration-300 ease-in-out md:hidden flex flex-col pt-24 px-6 overflow-y-auto ${isMobileMenuOpen ? 'translate-x-0' : 'translate-x-full'}`}>
{/* Mobile Search */}
<div className="mb-6 relative shrink-0">
<i className="ri-search-line absolute left-4 top-1/2 -translate-y-1/2 text-gray-400"></i>
<input
type="text"
placeholder={t('nav.search')}
className="w-full pl-11 pr-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-100 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all"
/>
</div>
<nav className="flex flex-col gap-4 text-base font-medium">
<Link href={`${prefix}/`} onClick={() => setIsMobileMenuOpen(false)} className="flex items-center gap-3 py-2 border-b border-gray-100 dark:border-white/10 text-secondary dark:text-white">
<i className="ri-home-4-line text-primary text-lg"></i>
{t('nav.home')}
</Link>
<Link href={`${prefix}/servicos`} onClick={() => setIsMobileMenuOpen(false)} className="flex items-center gap-3 py-2 border-b border-gray-100 dark:border-white/10 text-secondary dark:text-white">
<i className="ri-tools-line text-primary text-lg"></i>
{t('nav.services')}
</Link>
<Link href={`${prefix}/projetos`} onClick={() => setIsMobileMenuOpen(false)} className="flex items-center gap-3 py-2 border-b border-gray-100 dark:border-white/10 text-secondary dark:text-white">
<i className="ri-briefcase-line text-primary text-lg"></i>
{t('nav.projects')}
</Link>
<Link href={`${prefix}/contato`} onClick={() => setIsMobileMenuOpen(false)} className="flex items-center gap-3 py-2 border-b border-gray-100 dark:border-white/10 text-secondary dark:text-white">
<i className="ri-mail-send-line text-primary text-lg"></i>
{t('nav.contact')}
</Link>
<Link href={`${prefix}/sobre`} onClick={() => setIsMobileMenuOpen(false)} className="flex items-center gap-3 py-2 border-b border-gray-100 dark:border-white/10 text-secondary dark:text-white">
<i className="ri-user-line text-primary text-lg"></i>
{t('nav.about')}
</Link>
</nav>
<div className="mt-6 flex flex-col gap-4 pb-8 shrink-0">
<a
href={whatsappLink}
target="_blank"
rel="noopener noreferrer"
onClick={() => setIsMobileMenuOpen(false)}
className="w-full py-4 bg-primary text-white rounded-xl font-bold text-center flex items-center justify-center gap-2 shadow-lg shadow-primary/20"
>
<i className="ri-whatsapp-line text-xl"></i>
{t('nav.contactUs')}
</a>
<div
className="flex items-center justify-between p-4 bg-gray-50 dark:bg-white/5 rounded-xl cursor-pointer hover:bg-gray-100 dark:hover:bg-white/10 transition-colors"
onClick={toggleTheme}
>
<span className="text-sm font-bold text-gray-500 dark:text-gray-400">{t('nav.theme')}</span>
<button
className="w-10 h-10 rounded-full bg-white dark:bg-white/10 flex items-center justify-center text-gray-600 dark:text-yellow-400 shadow-sm transition-colors"
>
{mounted && theme === 'dark' ? (
<i className="ri-sun-line text-xl"></i>
) : (
<i className="ri-moon-line text-xl"></i>
)}
</button>
</div>
<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-white/5 rounded-xl">
<span className="text-sm font-bold text-gray-500 dark:text-gray-400">{t('nav.language')}</span>
<div className="flex gap-2">
<button onClick={() => setLocale('pt')} className={`w-10 h-10 rounded-lg flex items-center justify-center text-xl cursor-pointer transition-all ${locale === 'pt' ? 'bg-white dark:bg-white/10 shadow-sm scale-110' : 'opacity-50 hover:opacity-100'}`}>{localeFlags.pt}</button>
<button onClick={() => setLocale('en')} className={`w-10 h-10 rounded-lg flex items-center justify-center text-xl cursor-pointer transition-all ${locale === 'en' ? 'bg-white dark:bg-white/10 shadow-sm scale-110' : 'opacity-50 hover:opacity-100'}`}>{localeFlags.en}</button>
<button onClick={() => setLocale('es')} className={`w-10 h-10 rounded-lg flex items-center justify-center text-xl cursor-pointer transition-all ${locale === 'es' ? 'bg-white dark:bg-white/10 shadow-sm scale-110' : 'opacity-50 hover:opacity-100'}`}>{localeFlags.es}</button>
</div>
</div>
</div>
</div>
</div>
</header>
</>
);
}