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:
parent
b05c4d241b
commit
03a248b0fb
6 changed files with 310 additions and 181 deletions
59
docs/specs/dialogs/product.md
Normal file
59
docs/specs/dialogs/product.md
Normal 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`)
|
||||
Loading…
Add table
Add a link
Reference in a new issue