diff --git a/frontend/prisma/schema.prisma b/frontend/prisma/schema.prisma index 78ed91a..202d063 100644 --- a/frontend/prisma/schema.prisma +++ b/frontend/prisma/schema.prisma @@ -93,6 +93,8 @@ model Settings { id String @id @default(cuid()) showPartnerBadge Boolean @default(false) partnerName String @default("Coca-Cola") + // Logotipo + logo String? // URL do logotipo // Informações de Contato address String? // Endereço completo phone String? // Telefone diff --git a/frontend/src/app/admin/configuracoes/page.tsx b/frontend/src/app/admin/configuracoes/page.tsx index d6edb00..a6ac4ae 100644 --- a/frontend/src/app/admin/configuracoes/page.tsx +++ b/frontend/src/app/admin/configuracoes/page.tsx @@ -1,9 +1,10 @@ "use client"; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useSearchParams } from 'next/navigation'; import { useToast } from '@/contexts/ToastContext'; import { BackupManager } from '@/components/admin/BackupManager'; +import Image from 'next/image'; const PRESET_COLORS = [ { name: 'Laranja (Padrão)', value: '#FF6B35', gradient: 'from-orange-500 to-orange-600' }, @@ -25,6 +26,11 @@ export default function ConfiguracoesPage() { const [customColor, setCustomColor] = useState('#FF6B35'); const [showPartnerBadge, setShowPartnerBadge] = useState(false); const [partnerName, setPartnerName] = useState('Coca-Cola'); + // Campo de logotipo + const [logo, setLogo] = useState(null); + const [logoPreview, setLogoPreview] = useState(null); + const [uploadingLogo, setUploadingLogo] = useState(false); + const logoInputRef = useRef(null); // Campos de contato const [address, setAddress] = useState(''); const [phone, setPhone] = useState(''); @@ -65,6 +71,8 @@ export default function ConfiguracoesPage() { const data = await response.json(); setShowPartnerBadge(data.showPartnerBadge || false); setPartnerName(data.partnerName || 'Coca-Cola'); + setLogo(data.logo || null); + setLogoPreview(data.logo || null); setAddress(data.address || ''); setPhone(data.phone || ''); setEmail(data.email || ''); @@ -78,6 +86,82 @@ export default function ConfiguracoesPage() { } }; + const handleLogoUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + // Validar tipo de arquivo + if (!file.type.startsWith('image/')) { + showError('Por favor, selecione uma imagem válida'); + return; + } + + // Validar tamanho (max 5MB) + if (file.size > 5 * 1024 * 1024) { + showError('A imagem deve ter no máximo 5MB'); + return; + } + + // Preview local + const reader = new FileReader(); + reader.onload = (e) => { + setLogoPreview(e.target?.result as string); + }; + reader.readAsDataURL(file); + + // Upload + setUploadingLogo(true); + try { + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch('/api/upload', { + method: 'POST', + body: formData, + }); + + if (!response.ok) throw new Error('Erro no upload'); + + const data = await response.json(); + setLogo(data.url); + success('Logotipo carregado! Clique em "Salvar" para aplicar.'); + } catch (error) { + showError('Erro ao fazer upload do logotipo'); + setLogoPreview(logo); // Restaurar preview anterior + } finally { + setUploadingLogo(false); + } + }; + + const handleRemoveLogo = () => { + setLogo(null); + setLogoPreview(null); + if (logoInputRef.current) { + logoInputRef.current.value = ''; + } + }; + + const handleSaveLogo = async () => { + setSaving(true); + try { + const response = await fetch('/api/settings', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ logo }) + }); + + if (!response.ok) throw new Error('Erro ao salvar'); + + success('Logotipo salvo com sucesso!'); + // Dispatch event para atualizar em tempo real + window.dispatchEvent(new Event('settings:refresh')); + } catch (error) { + showError('Erro ao salvar logotipo'); + } finally { + setSaving(false); + } + }; + const handleSaveSettings = async () => { try { const response = await fetch('/api/settings', { @@ -460,22 +544,136 @@ export default function ConfiguracoesPage() {

Logotipo

- Configure o logotipo que aparece no cabeçalho e rodapé do site. + Configure o logotipo que aparece no cabeçalho, rodapé e painel administrativo do site.

-
- -
-

- Em Desenvolvimento -

-

- A funcionalidade de upload de logotipo estará disponível em breve. -

+ {/* Current Logo Preview */} +
+ +
+
+ {logoPreview ? ( + Logotipo + ) : ( +
+ + Sem logotipo +
+ )} + {uploadingLogo && ( +
+
+
+ )} +
+ + {/* Logo in Header Preview */} +
+

Prévia no cabeçalho:

+
+ {logoPreview ? ( + Logo Preview + ) : ( + + )} + OCCTO +
+
+ + {/* Upload Section */} +
+ +
+
+ + +
+ + {logoPreview && ( + + )} +
+
+ + {/* Tips */} +
+
+ +
+

+ Dicas para um bom logotipo: +

+
    +
  • • Use imagens com fundo transparente (PNG ou SVG)
  • +
  • • Recomendado: formato horizontal ou quadrado
  • +
  • • Resolução mínima sugerida: 200x100 pixels
  • +
  • • O logotipo será redimensionado automaticamente
  • +
+
+
+
+ + {/* Save Button */} +
)} diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx index 47d3188..37bde8a 100644 --- a/frontend/src/app/admin/layout.tsx +++ b/frontend/src/app/admin/layout.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import Link from 'next/link'; +import Image from 'next/image'; import { usePathname, useRouter } from 'next/navigation'; import { useToast } from '@/contexts/ToastContext'; import { useConfirm } from '@/contexts/ConfirmContext'; @@ -20,6 +21,7 @@ export default function AdminLayout({ }) { const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [user, setUser] = useState<{ name: string; email: string; avatar?: string | null } | null>(null); + const [logo, setLogo] = useState(null); const [isLoading, setIsLoading] = useState(true); const [showAvatarModal, setShowAvatarModal] = useState(false); const [isUploading, setIsUploading] = useState(false); @@ -104,6 +106,27 @@ export default function AdminLayout({ } }; fetchUser(); + + // Buscar logo das configurações + const fetchLogo = async () => { + try { + const response = await fetch('/api/settings'); + if (response.ok) { + const data = await response.json(); + if (data.logo) { + setLogo(data.logo); + } + } + } catch (err) { + console.error('Erro ao buscar logo:', err); + } + }; + fetchLogo(); + + // Listener para atualização em tempo real + const handleSettingsRefresh = () => fetchLogo(); + window.addEventListener('settings:refresh', handleSettingsRefresh); + return () => window.removeEventListener('settings:refresh', handleSettingsRefresh); }, [router]); useEffect(() => { @@ -239,7 +262,18 @@ export default function AdminLayout({