diff --git a/backlog/features.json b/backlog/features.json index d5819e2..dcf2f85 100644 --- a/backlog/features.json +++ b/backlog/features.json @@ -2449,7 +2449,7 @@ "id": "FE-04", "title": "Storefront: Ficha de usuario — direcciones y facturación", "description": "Página /account o /profile donde el usuario puede gestionar sus direcciones de envío (crear/editar/eliminar) y datos de facturación.", - "status": "pending", + "status": "done", "priority": "medium", "phase": "frontend", "type": "feature", @@ -2459,7 +2459,8 @@ "security": false, "qa": false, "close": false - } + }, + "completed_at": "2026-08-17T20:33:40Z" }, { "id": "FIX-09", diff --git a/project/frontend/src/app/account/page.tsx b/project/frontend/src/app/account/page.tsx new file mode 100644 index 0000000..646552d --- /dev/null +++ b/project/frontend/src/app/account/page.tsx @@ -0,0 +1,330 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/contexts/AuthContext'; + +const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000'; + +interface Address { + id: string; + userId: string; + label: string | null; + recipientName: string; + street: string; + city: string; + postalCode: string; + country: string; + isDefault: boolean; + createdAt: string; + updatedAt: string; +} + +const emptyForm = { + label: '', + recipientName: '', + street: '', + city: '', + postalCode: '', + country: 'España', + isDefault: false, +}; + +function AddressForm({ + initial, + userId, + onSaved, + onCancel, +}: { + initial?: Address; + userId: string; + onSaved: () => void; + onCancel: () => void; +}) { + const [form, setForm] = useState(initial ? { + label: initial.label ?? '', + recipientName: initial.recipientName, + street: initial.street, + city: initial.city, + postalCode: initial.postalCode, + country: initial.country, + isDefault: initial.isDefault, + } : { ...emptyForm }); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(''); + + const set = (field: string, value: string | boolean) => + setForm((f) => ({ ...f, [field]: value })); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setSaving(true); + setError(''); + try { + const url = initial + ? `${API}/users/${userId}/addresses/${initial.id}` + : `${API}/users/${userId}/addresses`; + const method = initial ? 'PATCH' : 'POST'; + const res = await fetch(url, { + method, + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ + ...form, + label: form.label || null, + }), + }); + if (!res.ok) throw new Error(`Error ${res.status}`); + onSaved(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error al guardar'); + } finally { + setSaving(false); + } + }; + + return ( +
+ ); +} + +export default function AccountPage() { + const { user, loading: authLoading } = useAuth(); + const router = useRouter(); + const [addresses, setAddresses] = useState([]); + const [loading, setLoading] = useState(true); + const [showForm, setShowForm] = useState(false); + const [editing, setEditing] = useState(null); + const [msg, setMsg] = useState(''); + + useEffect(() => { + if (!authLoading && !user) { + router.push('/auth/login'); + } + }, [user, authLoading, router]); + + const load = useCallback(async () => { + if (!user) return; + setLoading(true); + try { + const res = await fetch(`${API}/users/${user.id}/addresses`, { credentials: 'include' }); + if (res.ok) { + const data = await res.json(); + setAddresses(data.items ?? []); + } + } catch { + // silent + } finally { + setLoading(false); + } + }, [user]); + + useEffect(() => { + load(); + }, [load]); + + const handleDelete = async (id: string) => { + if (!user || !confirm('¿Eliminar esta dirección?')) return; + try { + await fetch(`${API}/users/${user.id}/addresses/${id}`, { method: 'DELETE', credentials: 'include' }); + setMsg('Dirección eliminada'); + setTimeout(() => setMsg(''), 3000); + load(); + } catch { + setMsg('Error al eliminar'); + } + }; + + if (authLoading || !user) { + return{user.email}
+📍
+No tienes direcciones guardadas
+{addr.recipientName}
+{addr.street}
++ {addr.postalCode} {addr.city}, {addr.country} +
+