feat(ADM-018): completed feature
This commit is contained in:
109
project/apps/admin/src/app/(dashboard)/settings/page.tsx
Normal file
109
project/apps/admin/src/app/(dashboard)/settings/page.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
'use client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { settingsApi, type StoreSettings } from '@/lib/api-client';
|
||||
|
||||
type FormData = StoreSettings;
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [data, setData] = useState<FormData | null>(null);
|
||||
const [form, setForm] = useState<FormData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [msg, setMsg] = useState('');
|
||||
const [err, setErr] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
settingsApi.get().then(d => {
|
||||
setData(d); setForm(d);
|
||||
}).catch(() => setErr('Error al cargar ajustes')).finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const handleSave = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!form) return;
|
||||
setSaving(true); setErr(''); setMsg('');
|
||||
try {
|
||||
const updated = await settingsApi.update(form);
|
||||
setData(updated); setForm(updated);
|
||||
setMsg('Cambios guardados correctamente');
|
||||
setTimeout(() => setMsg(''), 4000);
|
||||
} catch (er) {
|
||||
setErr(er instanceof Error ? er.message : 'Error al guardar');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const field = (key: keyof FormData, label: string, opts?: { type?: string; placeholder?: string; rows?: number }) => (
|
||||
<div key={key}>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>
|
||||
{opts?.rows ? (
|
||||
<textarea value={form?.[key] ?? ''} onChange={e => setForm(f => f ? { ...f, [key]: e.target.value } : f)}
|
||||
rows={opts.rows} placeholder={opts.placeholder}
|
||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-[#2D6A4F] outline-none resize-none" />
|
||||
) : (
|
||||
<input type={opts?.type ?? 'text'} value={form?.[key] ?? ''}
|
||||
onChange={e => setForm(f => f ? { ...f, [key]: e.target.value } : f)}
|
||||
placeholder={opts?.placeholder} maxLength={key === 'contactAddress' ? 400 : 200}
|
||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-[#2D6A4F] outline-none" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-6 max-w-3xl">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Ajustes de tienda</h1>
|
||||
<p className="text-sm text-gray-500 mt-0.5">Configuración general de la tienda visible para los clientes.</p>
|
||||
</div>
|
||||
|
||||
{msg && <div className="bg-green-50 text-green-700 text-sm px-4 py-3 rounded-xl border border-green-200">{msg}</div>}
|
||||
{err && <div className="bg-red-50 text-red-700 text-sm px-4 py-3 rounded-xl border border-red-200">{err}</div>}
|
||||
|
||||
{loading ? (
|
||||
<div className="bg-white rounded-2xl border border-gray-200 p-12 flex items-center justify-center text-gray-400 text-sm">Cargando...</div>
|
||||
) : (
|
||||
<form onSubmit={handleSave} className="bg-white rounded-2xl border border-gray-200 overflow-hidden">
|
||||
<div className="px-6 py-4 bg-gray-50 border-b border-gray-200">
|
||||
<h2 className="text-base font-semibold text-gray-800">Información general</h2>
|
||||
</div>
|
||||
<div className="p-6 space-y-5">
|
||||
{field('storeName', 'Nombre de la tienda', { placeholder: 'Mercado de Vida' })}
|
||||
{field('storeTagline', 'Eslogan', { placeholder: 'Productos naturales y ecológicos' })}
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 bg-gray-50 border-t border-b border-gray-200">
|
||||
<h2 className="text-base font-semibold text-gray-800">Contacto</h2>
|
||||
</div>
|
||||
<div className="p-6 space-y-5">
|
||||
{field('contactEmail', 'Email de contacto', { type: 'email', placeholder: 'info@mercadodevida.es' })}
|
||||
{field('contactPhone', 'Teléfono', { placeholder: '+34 600 000 000' })}
|
||||
{field('contactAddress', 'Dirección', { placeholder: 'Calle ejemplo, ciudad', rows: 3 })}
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 bg-gray-50 border-t border-b border-gray-200">
|
||||
<h2 className="text-base font-semibold text-gray-800">Redes sociales</h2>
|
||||
</div>
|
||||
<div className="p-6 space-y-5">
|
||||
{field('facebookUrl', 'Facebook', { placeholder: 'https://facebook.com/...' })}
|
||||
{field('instagramUrl', 'Instagram', { placeholder: 'https://instagram.com/...' })}
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 bg-gray-50 border-t border-b border-gray-200">
|
||||
<h2 className="text-base font-semibold text-gray-800">Footer</h2>
|
||||
</div>
|
||||
<div className="p-6 space-y-5">
|
||||
{field('footerText', 'Texto del pie de página', { placeholder: '© 2026 Mercado de Vida...', rows: 2 })}
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-5 bg-gray-50 border-t border-gray-200 flex justify-end">
|
||||
<button type="submit" disabled={saving || !form}
|
||||
className="px-6 py-2.5 bg-[#2D6A4F] hover:bg-[#1B4332] disabled:opacity-50 text-white text-sm font-semibold rounded-xl transition-colors">
|
||||
{saving ? 'Guardando...' : 'Guardar cambios'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user