refactor: Remove contact page from admin, redirect to settings tab
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useToast } from '@/contexts/ToastContext';
|
||||
import { BackupManager } from '@/components/admin/BackupManager';
|
||||
|
||||
@@ -17,7 +18,9 @@ const PRESET_COLORS = [
|
||||
|
||||
export default function ConfiguracoesPage() {
|
||||
const { success, error: showError } = useToast();
|
||||
const [activeTab, setActiveTab] = useState<'personalizacao' | 'logotipo' | 'contato' | 'backup'>('personalizacao');
|
||||
const searchParams = useSearchParams();
|
||||
const tabFromUrl = searchParams.get('tab') as 'personalizacao' | 'logotipo' | 'contato' | 'backup' | null;
|
||||
const [activeTab, setActiveTab] = useState<'personalizacao' | 'logotipo' | 'contato' | 'backup'>(tabFromUrl || 'personalizacao');
|
||||
const [primaryColor, setPrimaryColor] = useState('#FF6B35');
|
||||
const [customColor, setCustomColor] = useState('#FF6B35');
|
||||
const [showPartnerBadge, setShowPartnerBadge] = useState(false);
|
||||
|
||||
@@ -1,716 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useToast } from '@/contexts/ToastContext';
|
||||
import { CharLimitBadge } from '@/components/admin/CharLimitBadge';
|
||||
|
||||
const AVAILABLE_ICONS = [
|
||||
// Pessoas e Equipe
|
||||
{ value: 'ri-team-line', label: 'Equipe', category: 'pessoas' },
|
||||
{ value: 'ri-user-star-line', label: 'Destaque', category: 'pessoas' },
|
||||
{ value: 'ri-user-follow-line', label: 'Seguir', category: 'pessoas' },
|
||||
{ value: 'ri-group-line', label: 'Grupo', category: 'pessoas' },
|
||||
|
||||
// Segurança
|
||||
{ value: 'ri-shield-check-line', label: 'Segurança', category: 'segurança' },
|
||||
{ value: 'ri-shield-star-line', label: 'Proteção Premium', category: 'segurança' },
|
||||
{ value: 'ri-lock-line', label: 'Cadeado', category: 'segurança' },
|
||||
{ value: 'ri-hard-hat-line', label: 'Capacete', category: 'segurança' },
|
||||
|
||||
// Serviços
|
||||
{ value: 'ri-service-line', label: 'Atendimento', category: 'serviço' },
|
||||
{ value: 'ri-customer-service-line', label: 'Suporte', category: 'serviço' },
|
||||
{ value: 'ri-tools-line', label: 'Ferramentas', category: 'serviço' },
|
||||
{ value: 'ri-settings-3-line', label: 'Engrenagem', category: 'serviço' },
|
||||
|
||||
// Transporte
|
||||
{ value: 'ri-car-line', label: 'Veículo', category: 'transporte' },
|
||||
{ value: 'ri-truck-line', label: 'Caminhão', category: 'transporte' },
|
||||
{ value: 'ri-bus-line', label: 'Ônibus', category: 'transporte' },
|
||||
{ value: 'ri-motorbike-line', label: 'Moto', category: 'transporte' },
|
||||
|
||||
// Documentos
|
||||
{ value: 'ri-file-list-3-line', label: 'Documentos', category: 'documentos' },
|
||||
{ value: 'ri-file-text-line', label: 'Arquivo', category: 'documentos' },
|
||||
{ value: 'ri-clipboard-line', label: 'Prancheta', category: 'documentos' },
|
||||
{ value: 'ri-contract-line', label: 'Contrato', category: 'documentos' },
|
||||
|
||||
// Conquistas
|
||||
{ value: 'ri-award-line', label: 'Prêmio', category: 'conquista' },
|
||||
{ value: 'ri-trophy-line', label: 'Troféu', category: 'conquista' },
|
||||
{ value: 'ri-medal-line', label: 'Medalha', category: 'conquista' },
|
||||
{ value: 'ri-vip-crown-line', label: 'Coroa', category: 'conquista' },
|
||||
|
||||
// Inovação
|
||||
{ value: 'ri-lightbulb-line', label: 'Ideia', category: 'inovação' },
|
||||
{ value: 'ri-flashlight-line', label: 'Lanterna', category: 'inovação' },
|
||||
{ value: 'ri-rocket-line', label: 'Foguete', category: 'inovação' },
|
||||
{ value: 'ri-flask-line', label: 'Experimento', category: 'inovação' },
|
||||
|
||||
// Status
|
||||
{ value: 'ri-checkbox-circle-line', label: 'Confirmado', category: 'status' },
|
||||
{ value: 'ri-check-double-line', label: 'Verificado', category: 'status' },
|
||||
{ value: 'ri-star-line', label: 'Estrela', category: 'status' },
|
||||
{ value: 'ri-thumb-up-line', label: 'Aprovado', category: 'status' },
|
||||
|
||||
// Dados
|
||||
{ value: 'ri-pie-chart-line', label: 'Gráfico Pizza', category: 'dados' },
|
||||
{ value: 'ri-bar-chart-line', label: 'Gráfico Barras', category: 'dados' },
|
||||
{ value: 'ri-line-chart-line', label: 'Gráfico Linha', category: 'dados' },
|
||||
{ value: 'ri-dashboard-line', label: 'Dashboard', category: 'dados' },
|
||||
|
||||
// Performance
|
||||
{ value: 'ri-speed-line', label: 'Velocidade', category: 'performance' },
|
||||
{ value: 'ri-timer-line', label: 'Cronômetro', category: 'performance' },
|
||||
{ value: 'ri-time-line', label: 'Relógio', category: 'performance' },
|
||||
{ value: 'ri-pulse-line', label: 'Pulso', category: 'performance' },
|
||||
|
||||
// Negócios
|
||||
{ value: 'ri-building-line', label: 'Empresa', category: 'negócios' },
|
||||
{ value: 'ri-briefcase-line', label: 'Maleta', category: 'negócios' },
|
||||
{ value: 'ri-money-dollar-circle-line', label: 'Dinheiro', category: 'negócios' },
|
||||
{ value: 'ri-hand-coin-line', label: 'Pagamento', category: 'negócios' },
|
||||
|
||||
// Cálculo
|
||||
{ value: 'ri-calculator-line', label: 'Calculadora', category: 'cálculo' },
|
||||
{ value: 'ri-percent-line', label: 'Porcentagem', category: 'cálculo' },
|
||||
{ value: 'ri-functions', label: 'Funções', category: 'cálculo' },
|
||||
|
||||
// Comunicação
|
||||
{ value: 'ri-message-3-line', label: 'Mensagem', category: 'comunicação' },
|
||||
{ value: 'ri-chat-3-line', label: 'Chat', category: 'comunicação' },
|
||||
{ value: 'ri-phone-line', label: 'Telefone', category: 'comunicação' },
|
||||
{ value: 'ri-mail-line', label: 'Email', category: 'comunicação' },
|
||||
{ value: 'ri-whatsapp-line', label: 'WhatsApp', category: 'comunicação' },
|
||||
{ value: 'ri-mail-send-line', label: 'Enviar Email', category: 'comunicação' },
|
||||
|
||||
// Localização
|
||||
{ value: 'ri-map-pin-line', label: 'Localização', category: 'local' },
|
||||
{ value: 'ri-navigation-line', label: 'Navegação', category: 'local' },
|
||||
{ value: 'ri-roadster-line', label: 'Estrada', category: 'local' },
|
||||
{ value: 'ri-compass-line', label: 'Bússola', category: 'local' },
|
||||
];
|
||||
|
||||
interface IconSelectorProps {
|
||||
value: string;
|
||||
onChange: (icon: string) => void;
|
||||
label: string;
|
||||
}
|
||||
|
||||
function IconSelector({ value, onChange, label }: IconSelectorProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const filteredIcons = AVAILABLE_ICONS.filter(icon =>
|
||||
icon.label.toLowerCase().includes(search.toLowerCase()) ||
|
||||
icon.category.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
{label}
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="w-full px-4 py-3 bg-white dark:bg-secondary border border-gray-200 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 flex items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<i className={`${value} text-2xl text-primary`}></i>
|
||||
<span className="text-sm">{AVAILABLE_ICONS.find(i => i.value === value)?.label || 'Selecionar ícone'}</span>
|
||||
</div>
|
||||
<i className={`ri-arrow-${isOpen ? 'up' : 'down'}-s-line text-gray-400`}></i>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute z-50 mt-2 w-full bg-white dark:bg-secondary border border-gray-200 dark:border-white/10 rounded-xl shadow-xl">
|
||||
<div className="p-3 border-b border-gray-200 dark:border-white/10">
|
||||
<div className="relative">
|
||||
<i className="ri-search-line absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Buscar ícone..."
|
||||
className="w-full pl-10 pr-4 py-2 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg text-sm focus:outline-none focus:border-primary"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2 grid grid-cols-4 gap-2 max-h-64 overflow-y-auto">
|
||||
{filteredIcons.map((icon) => (
|
||||
<button
|
||||
key={icon.value}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onChange(icon.value);
|
||||
setIsOpen(false);
|
||||
setSearch('');
|
||||
}}
|
||||
className={`p-3 rounded-lg flex flex-col items-center gap-1 transition-all ${
|
||||
value === icon.value
|
||||
? 'bg-primary text-white'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-white/5 text-gray-700 dark:text-gray-300'
|
||||
}`}
|
||||
title={icon.label}
|
||||
>
|
||||
<i className={`${icon.value} text-2xl`}></i>
|
||||
<span className="text-[10px] text-center leading-tight">{icon.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const CONTACT_TEXT_LIMITS = {
|
||||
hero: { pretitle: 32, title: 70, subtitle: 200 },
|
||||
info: {
|
||||
title: 36,
|
||||
subtitle: 80,
|
||||
description: 200,
|
||||
itemTitle: 40,
|
||||
itemDescription: 160,
|
||||
link: 120,
|
||||
linkText: 32,
|
||||
},
|
||||
} as const;
|
||||
|
||||
type LabelWithLimitProps = {
|
||||
label: string;
|
||||
value?: string;
|
||||
limit: number;
|
||||
};
|
||||
|
||||
function LabelWithLimit({ label, value, limit }: LabelWithLimitProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between mb-2 gap-4">
|
||||
<span className="block text-sm font-bold text-gray-700 dark:text-gray-300">{label}</span>
|
||||
<CharLimitBadge value={value || ''} limit={limit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ContactInfo {
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
link: string;
|
||||
linkText: string;
|
||||
}
|
||||
|
||||
interface ContactContent {
|
||||
hero: {
|
||||
pretitle: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
};
|
||||
info: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
description: string;
|
||||
items: ContactInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
export default function EditContactPage() {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [initialLoading, setInitialLoading] = useState(true);
|
||||
const [activeTab, setActiveTab] = useState('hero');
|
||||
const { success, error: showError } = useToast();
|
||||
|
||||
const scrollToPreview = (sectionId: string) => {
|
||||
const element = document.getElementById(`preview-${sectionId}`);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab);
|
||||
setTimeout(() => scrollToPreview(tab), 100);
|
||||
};
|
||||
|
||||
const [formData, setFormData] = useState<ContactContent>({
|
||||
hero: {
|
||||
pretitle: 'Fale Conosco',
|
||||
title: 'Entre em Contato',
|
||||
subtitle: 'Nossa equipe está pronta para atender você e transformar suas ideias em realidade.'
|
||||
},
|
||||
info: {
|
||||
title: 'Informações de Contato',
|
||||
subtitle: 'Estamos à disposição',
|
||||
description: 'Estamos à disposição para atender sua empresa com a excelência técnica que seu projeto exige.',
|
||||
items: [
|
||||
{
|
||||
icon: 'ri-whatsapp-line',
|
||||
title: 'WhatsApp',
|
||||
description: 'Atendimento rápido e direto',
|
||||
link: 'https://wa.me/5527999999999',
|
||||
linkText: '(27) 99999-9999'
|
||||
},
|
||||
{
|
||||
icon: 'ri-mail-send-line',
|
||||
title: 'E-mail',
|
||||
description: 'Envie sua mensagem',
|
||||
link: 'mailto:contato@octto.com.br',
|
||||
linkText: 'contato@octto.com.br'
|
||||
},
|
||||
{
|
||||
icon: 'ri-map-pin-line',
|
||||
title: 'Endereço',
|
||||
description: 'Av. Nossa Senhora da Penha, 1234\nSanta Lúcia, Vitória - ES\nCEP: 29056-000',
|
||||
link: 'https://maps.google.com',
|
||||
linkText: 'Ver no mapa'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchPageContent();
|
||||
}, []);
|
||||
|
||||
const fetchPageContent = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/pages/contact');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.content) {
|
||||
setFormData(prevData => ({
|
||||
hero: data.content.hero || prevData.hero,
|
||||
info: data.content.info || prevData.info
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Nenhum conteúdo salvo ainda, usando padrão');
|
||||
} finally {
|
||||
setInitialLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/pages/contact', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content: formData })
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Erro ao salvar');
|
||||
|
||||
await response.json();
|
||||
success('Conteúdo salvo com sucesso!');
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new Event('translation:refresh'));
|
||||
}
|
||||
} catch (err) {
|
||||
showError('Erro ao salvar alterações');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (initialLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx global>{`
|
||||
main { padding: 0 !important; }
|
||||
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
||||
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
`}</style>
|
||||
<div className="fixed top-20 bottom-0 left-64 right-0 flex gap-0 bg-gray-50 dark:bg-tertiary">
|
||||
{/* Formulário de Edição - Coluna Esquerda 30% */}
|
||||
<div className="w-[30%] shrink-0 overflow-y-auto bg-white dark:bg-secondary relative">
|
||||
<div className="absolute top-0 right-0 bottom-0 w-px bg-gray-200 dark:bg-white/10"></div>
|
||||
|
||||
<div className="p-6 border-b border-gray-200 dark:border-white/10">
|
||||
<h1 className="text-2xl font-bold">Editar Página Contato</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Personalize informações de contato
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Navigation Tabs */}
|
||||
<div className="relative border-b border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const container = document.getElementById('tabs-container');
|
||||
if (container) container.scrollLeft -= 200;
|
||||
}}
|
||||
className="absolute left-0 z-10 w-10 h-full bg-white dark:bg-secondary border-r border-gray-200 dark:border-white/10 flex items-center justify-center hover:bg-gray-50 dark:hover:bg-white/5 transition-all cursor-pointer"
|
||||
>
|
||||
<i className="ri-arrow-left-s-line text-xl text-gray-600 dark:text-gray-400"></i>
|
||||
</button>
|
||||
|
||||
<div id="tabs-container" className="flex gap-2 p-4 overflow-x-auto scrollbar-hide scroll-smooth px-12">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleTabChange('hero')}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap transition-colors cursor-pointer ${
|
||||
activeTab === 'hero'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-background hover:bg-muted'
|
||||
}`}
|
||||
>
|
||||
Banner
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleTabChange('info')}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap transition-colors cursor-pointer ${
|
||||
activeTab === 'info'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-background hover:bg-muted'
|
||||
}`}
|
||||
>
|
||||
Informações (3)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const container = document.getElementById('tabs-container');
|
||||
if (container) container.scrollLeft += 200;
|
||||
}}
|
||||
className="absolute right-0 z-10 w-10 h-full bg-white dark:bg-secondary border-l border-gray-200 dark:border-white/10 flex items-center justify-center hover:bg-gray-50 dark:hover:bg-white/5 transition-all cursor-pointer"
|
||||
>
|
||||
<i className="ri-arrow-right-s-line text-xl text-gray-600 dark:text-gray-400"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6 pb-20">
|
||||
|
||||
{/* Hero Section */}
|
||||
{activeTab === 'hero' && (
|
||||
<div className="bg-white dark:bg-secondary p-8 rounded-2xl border border-gray-200 dark:border-white/10 shadow-sm">
|
||||
<h2 className="text-xl font-bold text-secondary dark:text-white mb-6 flex items-center gap-2">
|
||||
<i className="ri-layout-top-line text-primary"></i>
|
||||
Banner Principal
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Pré-título"
|
||||
value={formData.hero.pretitle}
|
||||
limit={CONTACT_TEXT_LIMITS.hero.pretitle}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.hero.pretitle}
|
||||
onChange={(e) => setFormData({...formData, hero: {...formData.hero, pretitle: e.target.value}})}
|
||||
maxLength={CONTACT_TEXT_LIMITS.hero.pretitle}
|
||||
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 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>
|
||||
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Título Principal"
|
||||
value={formData.hero.title}
|
||||
limit={CONTACT_TEXT_LIMITS.hero.title}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.hero.title}
|
||||
onChange={(e) => setFormData({...formData, hero: {...formData.hero, title: e.target.value}})}
|
||||
maxLength={CONTACT_TEXT_LIMITS.hero.title}
|
||||
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 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>
|
||||
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Subtítulo"
|
||||
value={formData.hero.subtitle}
|
||||
limit={CONTACT_TEXT_LIMITS.hero.subtitle}
|
||||
/>
|
||||
<textarea
|
||||
value={formData.hero.subtitle}
|
||||
onChange={(e) => setFormData({...formData, hero: {...formData.hero, subtitle: e.target.value}})}
|
||||
rows={2}
|
||||
maxLength={CONTACT_TEXT_LIMITS.hero.subtitle}
|
||||
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 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 resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Section */}
|
||||
{activeTab === 'info' && (
|
||||
<div className="bg-white dark:bg-secondary p-8 rounded-2xl border border-gray-200 dark:border-white/10 shadow-sm">
|
||||
<h2 className="text-xl font-bold text-secondary dark:text-white mb-6 flex items-center gap-2">
|
||||
<i className="ri-information-line text-primary"></i>
|
||||
Informações de Contato
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 mb-6">
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Pré-título"
|
||||
value={formData.info.title}
|
||||
limit={CONTACT_TEXT_LIMITS.info.title}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.info.title}
|
||||
onChange={(e) => setFormData({...formData, info: {...formData.info, title: e.target.value}})}
|
||||
maxLength={CONTACT_TEXT_LIMITS.info.title}
|
||||
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 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>
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Título da Seção"
|
||||
value={formData.info.subtitle}
|
||||
limit={CONTACT_TEXT_LIMITS.info.subtitle}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.info.subtitle}
|
||||
onChange={(e) => setFormData({...formData, info: {...formData.info, subtitle: e.target.value}})}
|
||||
maxLength={CONTACT_TEXT_LIMITS.info.subtitle}
|
||||
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 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>
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Descrição"
|
||||
value={formData.info.description}
|
||||
limit={CONTACT_TEXT_LIMITS.info.description}
|
||||
/>
|
||||
<textarea
|
||||
value={formData.info.description}
|
||||
onChange={(e) => setFormData({...formData, info: {...formData.info, description: e.target.value}})}
|
||||
rows={2}
|
||||
maxLength={CONTACT_TEXT_LIMITS.info.description}
|
||||
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 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 resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{formData.info.items.map((item, index) => (
|
||||
<div key={index} className="p-6 bg-gray-50 dark:bg-white/5 rounded-xl border border-gray-200 dark:border-white/10">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-bold text-gray-900 dark:text-white">Contato {index + 1}</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<IconSelector
|
||||
label="Ícone"
|
||||
value={item.icon}
|
||||
onChange={(icon) => {
|
||||
const newItems = [...formData.info.items];
|
||||
newItems[index].icon = icon;
|
||||
setFormData({...formData, info: {...formData.info, items: newItems}});
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Título"
|
||||
value={item.title}
|
||||
limit={CONTACT_TEXT_LIMITS.info.itemTitle}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={item.title}
|
||||
onChange={(e) => {
|
||||
const newItems = [...formData.info.items];
|
||||
newItems[index].title = e.target.value;
|
||||
setFormData({...formData, info: {...formData.info, items: newItems}});
|
||||
}}
|
||||
maxLength={CONTACT_TEXT_LIMITS.info.itemTitle}
|
||||
className="w-full px-4 py-3 bg-white dark:bg-secondary border border-gray-200 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>
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Descrição"
|
||||
value={item.description}
|
||||
limit={CONTACT_TEXT_LIMITS.info.itemDescription}
|
||||
/>
|
||||
<textarea
|
||||
value={item.description}
|
||||
onChange={(e) => {
|
||||
const newItems = [...formData.info.items];
|
||||
newItems[index].description = e.target.value;
|
||||
setFormData({...formData, info: {...formData.info, items: newItems}});
|
||||
}}
|
||||
rows={3}
|
||||
maxLength={CONTACT_TEXT_LIMITS.info.itemDescription}
|
||||
className="w-full px-4 py-3 bg-white dark:bg-secondary border border-gray-200 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 resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Link"
|
||||
value={item.link}
|
||||
limit={CONTACT_TEXT_LIMITS.info.link}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={item.link}
|
||||
onChange={(e) => {
|
||||
const newItems = [...formData.info.items];
|
||||
newItems[index].link = e.target.value;
|
||||
setFormData({...formData, info: {...formData.info, items: newItems}});
|
||||
}}
|
||||
placeholder="https://..."
|
||||
maxLength={CONTACT_TEXT_LIMITS.info.link}
|
||||
className="w-full px-4 py-3 bg-white dark:bg-secondary border border-gray-200 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>
|
||||
<div>
|
||||
<LabelWithLimit
|
||||
label="Texto do Link"
|
||||
value={item.linkText}
|
||||
limit={CONTACT_TEXT_LIMITS.info.linkText}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={item.linkText}
|
||||
onChange={(e) => {
|
||||
const newItems = [...formData.info.items];
|
||||
newItems[index].linkText = e.target.value;
|
||||
setFormData({...formData, info: {...formData.info, items: newItems}});
|
||||
}}
|
||||
maxLength={CONTACT_TEXT_LIMITS.info.linkText}
|
||||
className="w-full px-4 py-3 bg-white dark:bg-secondary border border-gray-200 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>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="fixed bottom-0 left-64 flex items-center justify-end gap-4 p-4 bg-white dark:bg-secondary border-t border-gray-200 dark:border-white/10 shadow-lg z-20" style={{ width: 'calc((100vw - 256px) * 0.3)' }}>
|
||||
<Link
|
||||
href="/admin/paginas"
|
||||
className="px-6 py-2.5 border border-gray-200 dark:border-white/10 text-gray-600 dark:text-gray-300 rounded-xl font-bold hover:bg-gray-50 dark:hover:bg-white/5 transition-colors text-sm"
|
||||
>
|
||||
Cancelar
|
||||
</Link>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="px-6 py-2.5 bg-primary text-white rounded-xl font-bold hover-primary transition-colors shadow-lg shadow-primary/20 flex items-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed cursor-pointer text-sm"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<i className="ri-loader-4-line animate-spin"></i>
|
||||
Salvando...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="ri-save-line"></i>
|
||||
Salvar
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Preview em Tempo Real - Coluna Direita Grande */}
|
||||
<div className="flex-1 overflow-y-auto bg-white dark:bg-secondary">
|
||||
<div className="sticky top-0 z-10 p-4 bg-gray-50 dark:bg-white/5 border-b border-gray-200 dark:border-white/10">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<i className="ri-eye-line text-primary"></i>
|
||||
Preview em Tempo Real
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">Visualização aproximada da página pública</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/contato"
|
||||
target="_blank"
|
||||
className="px-4 py-2 bg-white dark:bg-secondary border border-gray-200 dark:border-white/10 text-gray-700 dark:text-gray-300 rounded-lg font-medium hover:bg-gray-50 dark:hover:bg-white/5 transition-colors flex items-center gap-2 text-sm cursor-pointer"
|
||||
>
|
||||
<i className="ri-external-link-line"></i>
|
||||
Ver Página Real
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-8">
|
||||
{/* Hero Preview */}
|
||||
{activeTab === 'hero' && (
|
||||
<div id="preview-hero" className="space-y-4">
|
||||
<div className="inline-flex items-center gap-2 bg-primary/20 backdrop-blur-sm border border-primary/30 rounded-full px-4 py-1">
|
||||
<span className="w-2 h-2 rounded-full bg-primary animate-pulse"></span>
|
||||
<span className="text-sm font-bold text-primary uppercase tracking-wider">{formData.hero.pretitle}</span>
|
||||
</div>
|
||||
<h1 className="text-5xl font-bold font-headline text-secondary dark:text-white leading-tight">
|
||||
{formData.hero.title}
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-2xl leading-relaxed">
|
||||
{formData.hero.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Preview */}
|
||||
{activeTab === 'info' && (
|
||||
<div id="preview-info" className="space-y-8">
|
||||
<div>
|
||||
<h2 className="text-primary font-bold tracking-wider uppercase mb-3">{formData.info.title}</h2>
|
||||
<h3 className="text-3xl font-bold font-headline text-secondary dark:text-white mb-6">{formData.info.subtitle}</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 text-lg leading-relaxed">
|
||||
{formData.info.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{formData.info.items.map((item, index) => (
|
||||
<div key={index} className="group bg-gray-50 dark:bg-white/5 p-6 rounded-2xl border border-gray-100 dark:border-white/10 hover:border-primary/50 transition-colors">
|
||||
<div className="flex items-start gap-5">
|
||||
<div className="w-14 h-14 bg-white dark:bg-white/10 rounded-xl flex items-center justify-center text-primary shadow-sm">
|
||||
<i className={`${item.icon} text-3xl`}></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-xl font-bold font-headline text-secondary dark:text-white mb-2">{item.title}</h4>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-3 text-sm whitespace-pre-line">{item.description}</p>
|
||||
<a href={item.link} className="inline-flex items-center gap-2 text-primary font-bold hover:gap-3 transition-all">
|
||||
{item.linkText} <i className="ri-arrow-right-line"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -29,12 +29,6 @@ export default function PagesList() {
|
||||
desc: 'História da empresa, missão, visão e valores.',
|
||||
icon: 'ri-team-line'
|
||||
},
|
||||
{
|
||||
title: 'Contato',
|
||||
slug: 'contato',
|
||||
desc: 'Endereço, telefones, emails e horário de funcionamento.',
|
||||
icon: 'ri-contacts-book-line'
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@@ -97,13 +91,37 @@ export default function PagesList() {
|
||||
|
||||
<Link
|
||||
href={`/admin/paginas/${pageDef.slug}`}
|
||||
className="block w-full py-3 text-center rounded-xl border border-gray-200 dark:border-white/10 font-bold text-gray-600 dark:text-gray-300 hover:bg-primary hover:text-white hover:border-primary transition-all"
|
||||
className="block w-full py-3 text-center rounded-xl border border-gray-200 dark:border-white/10 font-bold text-gray-600 dark:text-gray-300 hover:bg-primary hover:text-white hover:border-primary transition-all cursor-pointer"
|
||||
>
|
||||
Editar Conteúdo
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Card especial para Contato - redireciona para Configurações */}
|
||||
<div className="bg-white dark:bg-secondary rounded-2xl border border-gray-200 dark:border-white/10 p-6 shadow-sm hover:shadow-md transition-all group">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="w-12 h-12 rounded-xl bg-blue-500/10 text-blue-500 flex items-center justify-center text-2xl group-hover:scale-110 transition-transform">
|
||||
<i className="ri-contacts-book-line"></i>
|
||||
</div>
|
||||
<span className="text-xs font-medium px-2 py-1 rounded-lg text-blue-600 bg-blue-100 dark:bg-blue-900/30">
|
||||
<i className="ri-settings-3-line mr-1"></i>
|
||||
Configurações
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-bold text-secondary dark:text-white mb-2">Contato</h3>
|
||||
<p className="text-gray-500 dark:text-gray-400 text-sm mb-6 h-10">Endereço, telefones, emails e redes sociais.</p>
|
||||
|
||||
<Link
|
||||
href="/admin/configuracoes?tab=contato"
|
||||
className="block w-full py-3 text-center rounded-xl border border-blue-200 dark:border-blue-800 font-bold text-blue-600 dark:text-blue-400 hover:bg-blue-500 hover:text-white hover:border-blue-500 transition-all cursor-pointer"
|
||||
>
|
||||
<i className="ri-settings-3-line mr-2"></i>
|
||||
Ir para Configurações
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user