Files
mercadodevida/work/artifacts/F-064/implementer.md
2026-08-19 17:09:37 +02:00

4.3 KiB

F-064 — Implementer evidence

Scope delivered

The product description in the admin (ProductEditor) was edited through a plain <textarea>, which forced admins to write raw HTML by hand, and the public renderers treated product.description as a plain string. Both frontend pages (/products/[slug]) and the storefront product page (/productos/[slug]) now render the description as HTML, and the admin uses the same Lexical editor that the CMS already uses (F-062).

Changes

Admin

project/apps/admin/src/features/products/components/ProductEditor.tsx

  • Imported the Lexical editor from @/features/cms/components/LexicalEditor.
  • Replaced the description <textarea> with <LexicalEditor value={desc} onChange={setDesc} />.
  • desc continues to live in the existing form state, gets included in the save payload, and is sent to PATCH /api/products/:id exactly as before. The only difference is that what gets sent is now HTML produced by the WYSIWYG instead of raw text from the textarea.

Public renderers

project/frontend/src/app/products/[slug]/page.tsx

- <p className="text-gray-600 leading-relaxed">{product.description}</p>
+ <div
+   className="text-gray-600 leading-relaxed prose prose-sm max-w-none"
+   dangerouslySetInnerHTML={{ __html: product.description }}
+ />

project/storefront/src/app/productos/[slug]/page.tsx

- <p classNameName="text-lg leading-8 text-stone-700">
-   {product.description ?? 'Producto del catálogo público de mercadodevida.'}
- </p>
+ <p classNameName="text-lg leading-8 text-stone-700">
+   {product.description ? (
+     <span dangerouslySetInnerHTML={{ __html: product.description }} />
+   ) : (
+     'Producto del catálogo público de mercadodevida.'
+   )}
+ </p>

The fallback to a literal Spanish sentence is preserved on the storefront when the description is empty; the frontend already had its own copy in ContentPage.

Acceptance traceability

Acceptance criterion How it is met
Admin product editor description is the Lexical WYSIWYG with toolbar ProductEditor.tsx imports LexicalEditor from @/features/cms/components and mounts it for the description field.
Saving the product persists the HTML body produced by the editor The save payload already includes description; the editor writes HTML to desc on every change.
Public product pages render the description HTML safely Both public renderers use dangerouslySetInnerHTML for the description. Plain-text descriptions render as a single text node, identical to the previous behaviour.
Existing plain text descriptions still display product.description is still a string field on the backend; passing plain text through dangerouslySetInnerHTML is a no-op.
verify.sh is green Exit 0.

Manual verification

$ curl -X PATCH http://192.168.18.93:3004/api/products/13a65dc0-… \
    -H 'Content-Type: application/json' -b /tmp/admin_cookies.txt \
    -d '{"description":"<p>A great product with <strong>bold</strong> and <em>italic</em> text.</p><ul><li>Item 1</li><li>Item 2</li></ul>"}'
{ "description": "<p>A great product with <strong>bold</strong> …", … }

$ curl http://192.168.18.93:3003/products/proteina-guisante-ecologica
<div class="text-gray-600 leading-relaxed prose prose-sm max-w-none">
  <p>A great product with <strong>bold</strong> and <em>italic</em> text.</p>
  <ul><li>Item 1</li><li>Item 2</li></ul>
</div>

$ curl http://192.168.18.93:3005/productos/proteina-guisante-ecologica
<p class="text-lg leading-8 text-stone-700">
  <span><p>A great product with …

The description was restored to the original plain text after the test.

Build verification

  • npx tsc --noEmit (admin / frontend / storefront) — exit 0
  • npm run typecheck (backend) — exit 0
  • Admin build (monolith.sh prod restart admin) → MDVCmsEditor chunk present in the new build
  • Admin /products/<id> responds 200
  • ./scripts/verify.sh — exit 0

Files touched

project/apps/admin/src/features/products/components/ProductEditor.tsx (textarea → LexicalEditor)
project/frontend/src/app/products/[slug]/page.tsx                         (description → dangerouslySetInnerHTML)
project/storefront/src/app/productos/[slug]/page.tsx                      (description → dangerouslySetInnerHTML)