- SUMMARY.md documents list page, editor page, TemplatePageClient, TemplatePanel - STATE.md: plan advanced to 2, progress 97%, 2 decisions logged - ROADMAP.md: phase 19 updated (2/3 plans complete) - REQUIREMENTS.md: TMPL-05 through TMPL-09 marked complete
6.1 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 19-template-editor-ui | 02 | template-editor-ui |
|
|
|
|
|
|
Phase 19 Plan 02: Template Editor UI Summary
One-liner
Template editor UI with list page, create modal, TemplatePageClient state owner, and TemplatePanel with role management, AI auto-place, and save — all wired to the Phase 18 CRUD API and Phase 19-01 PdfViewerWrapper abstractions.
What Was Built
Task 1: Templates list page (/portal/templates)
page.tsx (server component) queries documentTemplates LEFT JOINed with formTemplates, filtered WHERE archivedAt IS NULL, ordered by updatedAt DESC. Also fetches all formTemplates for the create modal picker. Renders TemplatesListClient.
TemplatesListClient.tsx (client component):
- Page heading "Templates" with subtitle showing count
- "+ New Template" button (gold
#C9A84C) - Empty state: "No templates yet" with body copy and CTA
- List rows: name (navy/600), form name (gray-500), field count, last-updated date. Row hover
#F0EDE8. Click navigates viauseRouter().push(). - Create modal: overlay + white card, template name input + form select dropdown, POSTs to
/api/templates, navigates to/portal/templates/${id}on 201 success.
Task 2: Template editor pages
[id]/page.tsx (server component): queries with with: { formTemplate: true }, calls notFound() on missing/archived. Passes templateId, templateName, formName, initialFields to TemplatePageClient.
TemplatePageClient.tsx (client state owner):
deriveRolesFromFields()extracts signer roles from existingfield.signerEmailvalues; falls back to[Buyer, Seller]defaults if none.textFillDatainitialized fromfield.hintvalues on existing fields.handlePersistmergestextFillData[id]intofield.hintforf.type === 'text'before PATCHing.handleAiAutoPlacePOSTs to/api/templates/[id]/ai-prepareand incrementsaiPlacementKeyto reload FieldPlacer.handleRenameRole/handleRemoveRolefetch current fields, updatesignerEmail, and PATCH — then incrementaiPlacementKeyto reload.handleSavePATCHes the template name (fields already persisted viaonPersiston each drag/drop/delete/resize).- PdfViewerWrapper receives
fieldsUrl=/api/templates/[id]/fieldsandfileUrl=/api/templates/[id]/file— operates in template mode.
TemplatePanel.tsx (right panel, 280px sticky):
- Template name: inline
<input>with border-bottom style;onBlurPATCHes name immediately. - "Signers / Roles" section: color-dot pills, click-to-rename inline input (Enter/blur commits, Escape cancels),
×remove button withConfirmDialogconfirmation. - Add role: text input + "Add" button + preset chips (Buyer, Co-Buyer, Seller, Co-Seller — filtered to exclude already-present roles).
- "AI Auto-place Fields" button (navy
#1B2B4B): CSS spinner + "Placing..." loading state, red error text below. - "Save Template" button (gold
#C9A84C): "Saving..." at 0.7 opacity, "Saved" in green#059669for 3s on success.
Verification
npx tsc --noEmit: exits 0 (no errors)npm run build: succeeds —/portal/templatesand/portal/templates/[id]listed as dynamic routes
Deviations from Plan
None — plan executed exactly as written, with one minor UX simplification:
ConfirmDialog shown for all role removals (not just when fields > 0): The plan specifies fetching field count to conditionally show the dialog. Simplified to always show ConfirmDialog on role removal — avoids a round-trip fetch purely for conditional UI logic, and the dialog message is safe to show generically ("will unassign its field(s)"). This is strictly better UX (no flicker from async check) and consistent with the ConfirmDialog pattern elsewhere.
Known Stubs
None — all data flows are wired. List page reads from DB. Editor page reads template + renders PDF via /api/templates/[id]/file. Fields load from /api/templates/[id]/fields. Persist writes to /api/templates/[id] via PATCH.