feat(product-dialog): conform aan dialog-pattern + entity-profile

Story 2 van PBI "Alle dialogen conform docs/patterns/dialog.md".

- lib/schemas/product.ts — gedeeld zod-schema (Dialog API)
- actions/products.ts — createProductAction/updateProductAction returnen
  nu code+fieldErrors voor 422-validatie en code: 403 voor demo/auth
- ProductDialog adopt useDirtyCloseGuard, useDialogSubmitShortcut,
  entityDialog* layout-classes; 422-fieldErrors mappen naar form.setError
- docs/specs/dialogs/product.md — entity-profile

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-04 07:18:39 +02:00
parent b05c4d241b
commit 03a248b0fb
6 changed files with 310 additions and 181 deletions

View file

@ -2,7 +2,7 @@
# Documentation Index
Auto-generated on 2026-05-03 from front-matter and headings.
Auto-generated on 2026-05-04 from front-matter and headings.
## Architecture Decision Records
@ -24,6 +24,7 @@ Auto-generated on 2026-05-03 from front-matter and headings.
| Title | Status | Updated |
|---|---|---|
| [PbiDialog Profiel](./specs/dialogs/pbi.md) | active | 2026-05-03 |
| [ProductDialog Profiel](./specs/dialogs/product.md) | active | 2026-05-04 |
| [StoryDialog Profiel](./specs/dialogs/story.md) | active | 2026-05-03 |
| [TaskDialog Profiel](./specs/dialogs/task.md) | active | 2026-05-03 |
| [Scrum4Me — Functionele Specificatie](./specs/functional.md) | active | 2026-05-03 |

View file

@ -0,0 +1,59 @@
---
title: "ProductDialog Profiel"
status: active
audience: [ai-agent, contributor]
language: nl
last_updated: 2026-05-04
---
# ProductDialog Profiel
> Volgt `docs/patterns/dialog.md`. Dit document beschrijft alleen de afwijkingen en entity-specifieke keuzes.
## Velden
| Veld | Type | Mode | Validatie |
|---|---|---|---|
| `name` | string | both | min 1, max 200 |
| `code` | string? | both | max 20, alleen `[a-zA-Z0-9._-]`; uniek per gebruiker |
| `description` | string? | both | max 4000, markdown vrij |
| `repo_url` | string? \| null | both | https URL, moet beginnen met `https://github.com/` |
| `definition_of_done` | string? | both | max 4000, vrije tekst |
| `auto_pr` | boolean | both | default `false` |
## URL- of state-pattern
- Gekozen: **state-based** (§11.2)
- Reden: dialog leeft binnen één parent-component (`ProductList` op `/dashboard` en de product-actions-bar op `/products/[id]`); deep-linking is niet vereist
- Open-state komt uit `ProductList` (lijst-context) of `EditProductButton` (single-item context)
## Status-veld
N.v.t. — Product heeft geen status-enum. `archived` is een boolean buiten dit dialog (eigen archive-flow).
## Server actions
- `createProductAction(data)` in `actions/products.ts` — context-arg via `revalidatePath('/products')` + `revalidatePath('/dashboard')`
- `updateProductAction(id, data)` in `actions/products.ts` — context-arg via `revalidatePath('/products/${id}')` + `revalidatePath('/dashboard')` + `pg_notify('product_updated')`
- Beide hebben `session.userId`-check, `session.isDemo`-check (laag 2 demo-policy) en `productAccessFilter` voor update
- Resultaat-shape: `{ success: true, productId? }` of `{ error: string, code?: 422|403, fieldErrors?: Record<string, string[]> }`
## Foutcodes
| Code | Wanneer | UI |
|---|---|---|
| 422 | zod-validatie of code-uniqueness | `fieldErrors``form.setError`, geen toast, focus naar eerste error-veld |
| 403 | niet ingelogd, demo-modus, of geen toegang tot product | toast met message |
| 500 | onverwacht | huidige behandeling: error wordt door React opgevangen — laat de form open |
## Speciale gedragingen
- **Custom switch voor `auto_pr`**: native `<button role="switch">` met MD3-kleur-tokens (geen aparte primitive in v1; zou gepromoot moeten worden naar `components/shared/switch.tsx` zodra elders nodig).
- **Code-uniqueness server-side**: bij conflict wordt `fieldErrors.code` gezet; veld krijgt rode rand.
- **`useProductsStore` updates**: na succesvolle save wordt de in-memory store synchroon bijgewerkt zodat de productlijst onmiddellijk reageert (lokaal-first).
## Bewust NIET in v1
- Verwijderen vanuit deze dialog (loopt via `archiveProductAction` op een andere knop)
- Bulk edit
- Members beheren (eigen scherm op `/products/[id]/settings`)