6.7 KiB
F-067 — Implementer evidence
Scope delivered
Two separate defects surfaced together; both are addressed in this ticket.
-
Product cards left-aligned the image. Every listing page (
/products,/brands/[slug],/categories/[slug],/search, the homeFeaturedProducts, plus the storefront'sProductCard) usedaspect-[5/7] max-h-72which collapses the box to roughly205×288 pxinside a~280 pxcard. The container is left-aligned by default, leaving the right side empty. The fix replaces the constraint withw-full max-h-72, so the box fills the card width and theImage fill + object-containcombo centres the visual. -
Checkout ignored customer saved addresses. The admin
/customers/[id]editor stores addresses viacustomersApi.*which hitsGET /users/:id/addresses(owner-or-admin). The storefront checkout never fetched them: the frontend (where/checkoutis served) had no proxy for the user-address endpoint and the checkout form started empty. The fix adds a thin/api/users/[id]/addressesproxy in the frontend and rewritesCheckoutClientto fetch the addresses on mount, default to theisDefaultone (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.tsxproject/frontend/src/app/brands/[slug]/page.tsxproject/frontend/src/app/search/page.tsxproject/frontend/src/app/categories/[slug]/page.tsxproject/frontend/src/components/home/FeaturedProducts.tsxproject/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
useEffectthat fetches/api/users/${user.id}/addresseswhen the user is logged in and hasrole === 'customer'. The first fetch defaults to the address withisDefault(or the first one if none is marked). The fetched address is mapped into the form viaaddressToForm, which splitsrecipientNameintofirstNameandlastNameand 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
addressToFormto 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.countryis 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 0npm test(backend) — 124 passed, 56 skippedmonolith.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)