diff --git a/backlog/features.json b/backlog/features.json index cfb26f9..1f779f0 100644 --- a/backlog/features.json +++ b/backlog/features.json @@ -4623,13 +4623,15 @@ "User toggles notification and marketing preferences", "verify.sh is green" ], - "status": "pending", + "status": "done", "created_at": "2026-08-21", "gates": { - "reviewer": false, - "security": false, - "qa": false - } + "reviewer": true, + "security": true, + "qa": true, + "close": true + }, + "completed_at": "2026-08-21T06:06:31Z" }, { "id": "F-106", diff --git a/project/frontend/src/app/account/page.tsx b/project/frontend/src/app/account/page.tsx index 0dfc598..744975f 100644 --- a/project/frontend/src/app/account/page.tsx +++ b/project/frontend/src/app/account/page.tsx @@ -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(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; + 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() { + {/* Preferences */} +
+

Preferencias

+

Elige qué comunicaciones quieres recibir por email.

+ {prefsMsg && ( +

{prefsMsg}

+ )} +
+ {PREF_ITEMS.map((item) => ( +
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" + > + + {item.title} + {item.desc} + + + + +
+ ))} +
+
+ {/* Addresses */}
diff --git a/project/frontend/src/components/auth/UserMenu.tsx b/project/frontend/src/components/auth/UserMenu.tsx index a21e9a6..055f5bf 100644 --- a/project/frontend/src/components/auth/UserMenu.tsx +++ b/project/frontend/src/components/auth/UserMenu.tsx @@ -11,6 +11,21 @@ export default function UserMenu() { {user.email} + {/* Mi cuenta: gestión de datos, direcciones y preferencias */} +
+ + + + + Mi cuenta + +