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

101 lines
4.3 KiB
Markdown

# 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`
```diff
- <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`
```diff
- <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)
```