324 lines
16 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|