feat: CMS com limites de caracteres, traduções auto e painel de notificações

This commit is contained in:
Erik
2025-11-27 12:05:23 -03:00
parent ea0c4ac5a6
commit 6e32ffdc95
40 changed files with 3665 additions and 278 deletions

View File

@@ -4,6 +4,7 @@ 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';
// Ícones pré-definidos para seleção
const AVAILABLE_ICONS = [
@@ -171,6 +172,27 @@ function IconSelector({ value, onChange, label }: IconSelectorProps) {
);
}
const ABOUT_TEXT_LIMITS = {
hero: { title: 70, subtitle: 200 },
history: { title: 36, subtitle: 80, paragraph: 320 },
values: { title: 36, subtitle: 80, itemTitle: 40, itemDescription: 140 },
} 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 ValueItem {
icon: string;
title: string;
@@ -273,7 +295,11 @@ export default function EditAboutPage() {
if (!response.ok) throw new Error('Erro ao salvar');
success('Conteúdo da página Sobre atualizado com sucesso!');
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 {
@@ -383,21 +409,31 @@ export default function EditAboutPage() {
<div className="grid grid-cols-1 gap-6">
<div>
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Título Principal</label>
<LabelWithLimit
label="Título Principal"
value={formData.hero.title}
limit={ABOUT_TEXT_LIMITS.hero.title}
/>
<input
type="text"
value={formData.hero.title}
onChange={(e) => setFormData({...formData, hero: {...formData.hero, title: e.target.value}})}
maxLength={ABOUT_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>
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Subtítulo</label>
<LabelWithLimit
label="Subtítulo"
value={formData.hero.subtitle}
limit={ABOUT_TEXT_LIMITS.hero.subtitle}
/>
<textarea
value={formData.hero.subtitle}
onChange={(e) => setFormData({...formData, hero: {...formData.hero, subtitle: e.target.value}})}
rows={2}
maxLength={ABOUT_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>
@@ -415,41 +451,61 @@ export default function EditAboutPage() {
<div className="grid grid-cols-1 gap-6">
<div>
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Pré-título</label>
<LabelWithLimit
label="Pré-título"
value={formData.history.title}
limit={ABOUT_TEXT_LIMITS.history.title}
/>
<input
type="text"
value={formData.history.title}
onChange={(e) => setFormData({...formData, history: {...formData.history, title: e.target.value}})}
maxLength={ABOUT_TEXT_LIMITS.history.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>
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Título</label>
<LabelWithLimit
label="Título"
value={formData.history.subtitle}
limit={ABOUT_TEXT_LIMITS.history.subtitle}
/>
<input
type="text"
value={formData.history.subtitle}
onChange={(e) => setFormData({...formData, history: {...formData.history, subtitle: e.target.value}})}
maxLength={ABOUT_TEXT_LIMITS.history.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>
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Parágrafo 1</label>
<LabelWithLimit
label="Parágrafo 1"
value={formData.history.paragraph1}
limit={ABOUT_TEXT_LIMITS.history.paragraph}
/>
<textarea
value={formData.history.paragraph1}
onChange={(e) => setFormData({...formData, history: {...formData.history, paragraph1: e.target.value}})}
rows={4}
maxLength={ABOUT_TEXT_LIMITS.history.paragraph}
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>
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Parágrafo 2</label>
<LabelWithLimit
label="Parágrafo 2"
value={formData.history.paragraph2}
limit={ABOUT_TEXT_LIMITS.history.paragraph}
/>
<textarea
value={formData.history.paragraph2}
onChange={(e) => setFormData({...formData, history: {...formData.history, paragraph2: e.target.value}})}
rows={4}
maxLength={ABOUT_TEXT_LIMITS.history.paragraph}
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>
@@ -467,20 +523,30 @@ export default function EditAboutPage() {
<div className="grid grid-cols-1 gap-6 mb-6">
<div>
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Pré-título</label>
<LabelWithLimit
label="Pré-título"
value={formData.values.title}
limit={ABOUT_TEXT_LIMITS.values.title}
/>
<input
type="text"
value={formData.values.title}
onChange={(e) => setFormData({...formData, values: {...formData.values, title: e.target.value}})}
maxLength={ABOUT_TEXT_LIMITS.values.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>
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Título da Seção</label>
<LabelWithLimit
label="Título da Seção"
value={formData.values.subtitle}
limit={ABOUT_TEXT_LIMITS.values.subtitle}
/>
<input
type="text"
value={formData.values.subtitle}
onChange={(e) => setFormData({...formData, values: {...formData.values, subtitle: e.target.value}})}
maxLength={ABOUT_TEXT_LIMITS.values.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>
@@ -503,7 +569,11 @@ export default function EditAboutPage() {
}}
/>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Título</label>
<LabelWithLimit
label="Título"
value={item.title}
limit={ABOUT_TEXT_LIMITS.values.itemTitle}
/>
<input
type="text"
value={item.title}
@@ -512,11 +582,16 @@ export default function EditAboutPage() {
newItems[index].title = e.target.value;
setFormData({...formData, values: {...formData.values, items: newItems}});
}}
maxLength={ABOUT_TEXT_LIMITS.values.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>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Descrição</label>
<LabelWithLimit
label="Descrição"
value={item.description}
limit={ABOUT_TEXT_LIMITS.values.itemDescription}
/>
<textarea
value={item.description}
onChange={(e) => {
@@ -525,6 +600,7 @@ export default function EditAboutPage() {
setFormData({...formData, values: {...formData.values, items: newItems}});
}}
rows={2}
maxLength={ABOUT_TEXT_LIMITS.values.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>