feat: CMS com limites de caracteres, traduções auto e painel de notificações
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user