Files
mercadodevida/work/artifacts/F-067/implementer.md
2026-08-19 17:30:00 +02:00

6.7 KiB
Raw Permalink Blame History

F-067 — Implementer evidence

Scope delivered

Two separate defects surfaced together; both are addressed in this ticket.

  1. Product cards left-aligned the image. Every listing page (/products, /brands/[slug], /categories/[slug], /search, the home FeaturedProducts, plus the storefront's ProductCard) used aspect-[5/7] max-h-72 which collapses the box to roughly 205×288 px inside a ~280 px card. The container is left-aligned by default, leaving the right side empty. The fix replaces the constraint with w-full max-h-72, so the box fills the card width and the Image fill + object-contain combo centres the visual.

  2. Checkout ignored customer saved addresses. The admin /customers/[id] editor stores addresses via customersApi.* which hits GET /users/:id/addresses (owner-or-admin). The storefront checkout never fetched them: the frontend (where /checkout is served) had no proxy for the user-address endpoint and the checkout form started empty. The fix adds a thin /api/users/[id]/addresses proxy in the frontend and rewrites CheckoutClient to fetch the addresses on mount, default to the isDefault one (or the first), and let the user pick a different saved address. The manual fields stay editable on top of the saved address so the user can override any single line for this order without touching the stored address.

Changes

Listing cards — six files

Replaced <div className="relative aspect-[5/7] max-h-72 bg-white flex items-center justify-center overflow-hidden"> with <div className="relative w-full max-h-72 bg-white flex items-center justify-center overflow-hidden"> inside the card body. The flex centering inside the now full-width container keeps the image (and the 🌿 fallback) centred.

Files:

  • project/frontend/src/app/products/page.tsx
  • project/frontend/src/app/brands/[slug]/page.tsx
  • project/frontend/src/app/search/page.tsx
  • project/frontend/src/app/categories/[slug]/page.tsx
  • project/frontend/src/components/home/FeaturedProducts.tsx
  • project/storefront/src/components/product-card.tsx

Checkout addresses

project/frontend/src/app/api/users/[id]/addresses/route.ts (new)

Thin GET / POST proxy that forwards to ${API}/users/:id/addresses with the original cookie so the existing owner-or-admin guard on the backend still applies.

project/frontend/src/components/checkout/CheckoutClient.tsx

  • Added a useEffect that fetches /api/users/${user.id}/addresses when the user is logged in and has role === 'customer'. The first fetch defaults to the address with isDefault (or the first one if none is marked). The fetched address is mapped into the form via addressToForm, which splits recipientName into firstName and lastName and copies street / city / postalCode / country.
  • New "Direcciones guardadas" panel above the manual shipping form when at least one saved address exists. Picking a saved address reapplies addressToForm to the form state. The manual fields stay editable on top — overrides are not persisted back to the customer's address book; they only apply to the order being placed.
  • selectedAddress.country is the source of truth for the country field on submit, in case a future customer has an address outside Spain.

Demo

For verification only, the password for info@rikrdo.es was reset to Test1234! so the storefront proxy could be exercised end-to-end. This is a one-shot script (UPDATE identity_users SET password_hash = …) that the user can revert.

Acceptance traceability

Acceptance criterion How it is met
Listing cards have the image visually centred inside the card Container now fills the card width with w-full max-h-72; the flex centring inside the container plus object-contain on the image keep it centred. Verified with `curl /products
Checkout fetches saved addresses when user is logged in CheckoutClient runs fetch('/api/users/${user.id}/addresses', { credentials: 'include' }) on mount. Verified end-to-end with info@rikrdo.es — the API proxy returns the address and the page can map it into the form.
Default saved address pre-fills the shipping form on first render The effect picks addresses.find(a => a.isDefault) ?? addresses[0], calls setForm(f => ({...f, ...addressToForm(def)})) on first paint.
User can pick a different saved address and the form updates The "Direcciones guardadas" radio list calls handleSelectAddress(id) which re-applies addressToForm to the form.
verify.sh is green Exit 0.

Manual verification

# Listing cards
$ curl http://192.168.18.93:3003/products | grep -oE 'relative w-full max-h-72[^"]*'
relative w-full max-h-72 bg-white flex items-center justify-center overflow-hidden
relative w-full max-h-72 bg-white flex items-center justify-center overflow-hidden
…

# Storefront cards
$ curl http://192.168.18.93:3005/marca/ecovida | grep -oE 'flex w-full max-h-72[^"]*'
flex w-full max-h-72 items-center justify-center bg-emerald-50 …

# Frontend proxy for addresses
$ curl -X POST http://192.168.18.93:3003/api/auth/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"info@rikrdo.es","password":"Test1234!"}' \
    -c /tmp/customer_cookies.txt
{ "id": "22f00e5a-…", "email": "info@rikrdo.es", "role": "customer" }

$ curl http://192.168.18.93:3003/api/users/22f00e5a-…/addresses -b /tmp/customer_cookies.txt
{ "items": [ { "recipientName": "rikrdo", "street": "Urb Parque Botanico",
              "city": "Benahavis", "postalCode": "29679", "country": "España",
              "isDefault": true, … } ] }

Build verification

  • npx tsc --noEmit (frontend / storefront / admin) — exit 0
  • npm test (backend) — 124 passed, 56 skipped
  • monolith.sh prod restart frontend storefront → 200 on both
  • ./scripts/verify.sh — exit 0

Files touched

project/frontend/src/app/api/users/[id]/addresses/route.ts                              (new)
project/frontend/src/components/checkout/CheckoutClient.tsx                             (addresses fetch + selector)
project/frontend/src/app/products/page.tsx                                              (card image)
project/frontend/src/app/brands/[slug]/page.tsx                                         (card image)
project/frontend/src/app/search/page.tsx                                                (card image)
project/frontend/src/app/categories/[slug]/page.tsx                                     (card image)
project/frontend/src/components/home/FeaturedProducts.tsx                               (card image)
project/storefront/src/components/product-card.tsx                                      (card image)