feat(F-105): completed feature
This commit is contained in:
@@ -11,6 +11,20 @@ interface Profile {
|
||||
phone: string | null;
|
||||
}
|
||||
|
||||
interface Preferences {
|
||||
orderUpdates: boolean;
|
||||
newsletter: boolean;
|
||||
promos: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_PREFS: Preferences = { orderUpdates: true, newsletter: false, promos: false };
|
||||
|
||||
const PREF_ITEMS: Array<{ key: keyof Preferences; title: string; desc: string }> = [
|
||||
{ key: 'orderUpdates', title: 'Estado de mis pedidos', desc: 'Recibe un email cuando tu pedido cambie de estado (pagado, enviado, entregado...).' },
|
||||
{ key: 'newsletter', title: 'Boletín de novedades', desc: 'Nuevos productos, recetas y consejos de vida saludable.' },
|
||||
{ key: 'promos', title: 'Ofertas y promociones', desc: 'Avisos de descuentos y promociones especiales.' },
|
||||
];
|
||||
|
||||
interface Address {
|
||||
id: string;
|
||||
userId: string;
|
||||
@@ -196,6 +210,8 @@ export default function AccountPage() {
|
||||
const [profileForm, setProfileForm] = useState({ displayName: '', phone: '' });
|
||||
const [profileSaving, setProfileSaving] = useState(false);
|
||||
const [profileError, setProfileError] = useState('');
|
||||
const [prefs, setPrefs] = useState<Preferences>(DEFAULT_PREFS);
|
||||
const [prefsMsg, setPrefsMsg] = useState('');
|
||||
const [passwordForm, setPasswordForm] = useState({ currentPassword: '', newPassword: '', confirmPassword: '' });
|
||||
const [passwordSaving, setPasswordSaving] = useState(false);
|
||||
const [passwordMessage, setPasswordMessage] = useState('');
|
||||
@@ -231,6 +247,8 @@ export default function AccountPage() {
|
||||
if (!data) return;
|
||||
setProfile(data);
|
||||
setProfileForm({ displayName: data.displayName ?? '', phone: data.phone ?? '' });
|
||||
const p = (data as Profile & { preferences?: Partial<Preferences> }).preferences;
|
||||
if (p) setPrefs({ ...DEFAULT_PREFS, ...p });
|
||||
})
|
||||
.catch(() => setProfileError('No se pudieron cargar tus datos personales'));
|
||||
}, [user]);
|
||||
@@ -272,6 +290,26 @@ export default function AccountPage() {
|
||||
} finally { setPasswordSaving(false); }
|
||||
};
|
||||
|
||||
const togglePref = async (key: keyof Preferences) => {
|
||||
if (!user) return;
|
||||
const value = !prefs[key];
|
||||
const prev = prefs;
|
||||
setPrefs((p) => ({ ...p, [key]: value }));
|
||||
setPrefsMsg('');
|
||||
try {
|
||||
const res = await fetch(`/api/users/${user.id}`, {
|
||||
method: 'PATCH', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
|
||||
body: JSON.stringify({ preferences: { [key]: value } }),
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
setPrefsMsg('Preferencias actualizadas');
|
||||
setTimeout(() => setPrefsMsg(''), 3000);
|
||||
} catch {
|
||||
setPrefs(prev);
|
||||
setPrefsMsg('Error al guardar las preferencias');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!user || !confirm('¿Eliminar esta dirección?')) return;
|
||||
try {
|
||||
@@ -341,6 +379,42 @@ export default function AccountPage() {
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{/* Preferences */}
|
||||
<section className="rounded-xl border border-stone-200 bg-white p-5">
|
||||
<h2 className="mb-1 text-lg font-semibold text-stone-900">Preferencias</h2>
|
||||
<p className="mb-4 text-sm text-stone-500">Elige qué comunicaciones quieres recibir por email.</p>
|
||||
{prefsMsg && (
|
||||
<p className={`mb-3 text-sm ${prefsMsg.startsWith('Error') ? 'text-red-600' : 'text-green-700'}`}>{prefsMsg}</p>
|
||||
)}
|
||||
<div className="divide-y divide-stone-100">
|
||||
{PREF_ITEMS.map((item) => (
|
||||
<div
|
||||
key={item.key}
|
||||
role="switch"
|
||||
aria-checked={prefs[item.key]}
|
||||
tabIndex={0}
|
||||
onClick={() => togglePref(item.key)}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); togglePref(item.key); } }}
|
||||
className="flex items-start justify-between gap-4 py-3 cursor-pointer select-none"
|
||||
>
|
||||
<span>
|
||||
<span className="block text-sm font-medium text-stone-800">{item.title}</span>
|
||||
<span className="block text-xs text-stone-500 mt-0.5">{item.desc}</span>
|
||||
</span>
|
||||
<span
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors ${
|
||||
prefs[item.key] ? 'bg-[#2D6A4F]' : 'bg-stone-300'
|
||||
}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
prefs[item.key] ? 'translate-x-6' : 'translate-x-1'
|
||||
}`} />
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Addresses */}
|
||||
<section>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -11,6 +11,21 @@ export default function UserMenu() {
|
||||
<span className="text-sm text-gray-600 hidden sm:block">
|
||||
{user.email}
|
||||
</span>
|
||||
{/* Mi cuenta: gestión de datos, direcciones y preferencias */}
|
||||
<div className="relative group">
|
||||
<Link
|
||||
href="/account"
|
||||
aria-label="Mi cuenta"
|
||||
className="flex items-center justify-center w-9 h-9 rounded-full text-[#70ad47] hover:bg-[#70ad47]/10 hover:text-[#5a9040] transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2} aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</Link>
|
||||
<span role="tooltip" className="pointer-events-none absolute right-0 top-full z-10 mt-2 whitespace-nowrap rounded-md bg-gray-900 px-2 py-1 text-xs text-white opacity-0 shadow transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
|
||||
Mi cuenta
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative group">
|
||||
<button
|
||||
onClick={logout}
|
||||
|
||||
Reference in New Issue
Block a user