feat: implementa sistema de logotipo dinâmico

- Adiciona campo 'logo' ao modelo Settings no Prisma
- Atualiza API /api/settings para lidar com upload de logo
- Cria aba Logotipo funcional no admin com upload de imagem
- Atualiza Header para exibir logo dinâmico (fallback para ícone)
- Atualiza Footer para exibir logo dinâmico
- Atualiza Admin Layout para exibir logo dinâmico
- Logo é atualizado em tempo real via evento settings:refresh
This commit is contained in:
Erik
2025-11-29 16:36:25 -03:00
parent cbad251b39
commit e503069a86
6 changed files with 308 additions and 16 deletions

View File

@@ -2,6 +2,7 @@
import { useEffect, useState } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { useLocale } from '@/contexts/LocaleContext';
import { PartnerBadge } from './PartnerBadge';
@@ -13,6 +14,7 @@ type ContactSettings = {
linkedin?: string | null;
facebook?: string | null;
whatsapp?: string | null;
logo?: string | null;
};
export default function Footer() {
@@ -35,7 +37,8 @@ export default function Footer() {
instagram: data.instagram,
linkedin: data.linkedin,
facebook: data.facebook,
whatsapp: data.whatsapp
whatsapp: data.whatsapp,
logo: data.logo
});
}
} catch (error) {
@@ -58,7 +61,18 @@ export default function Footer() {
{/* Brand */}
<div className="col-span-1 md:col-span-1">
<div className="flex items-center gap-2 mb-6">
<i className="ri-building-2-fill text-4xl text-primary"></i>
{contact.logo ? (
<Image
src={contact.logo}
alt="Logo"
width={40}
height={40}
className="object-contain"
unoptimized
/>
) : (
<i className="ri-building-2-fill text-4xl text-primary"></i>
)}
<div className="flex items-center gap-2">
<span className="text-2xl font-bold font-headline">OCCTO</span>
<span className="text-[10px] font-bold text-primary bg-white/10 px-2 py-1 rounded-md uppercase tracking-wider">ENG.</span>

View File

@@ -1,6 +1,7 @@
"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';
@@ -10,6 +11,7 @@ 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);
@@ -30,7 +32,20 @@ export default function Header() {
})
.catch(() => setIsLoggedIn(false));
// Busca o número do WhatsApp do CMS
// 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 => {
@@ -39,6 +54,21 @@ export default function Header() {
}
})
.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
@@ -81,7 +111,18 @@ export default function Header() {
<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">
<i className="ri-building-2-fill text-4xl text-primary group-hover:scale-105 transition-transform"></i>
{logo ? (
<Image
src={logo}
alt="Logo"
width={40}
height={40}
className="object-contain group-hover:scale-105 transition-transform"
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>