--- phase: 20-apply-template-and-portal-nav plan: "01" subsystem: document-template-apply tags: [templates, documents, modal, api] dependency_graph: requires: [phase-18-template-schema-and-crud-api, phase-19-template-editor-ui] provides: [template-apply-to-document, my-templates-tab] affects: [AddDocumentModal, POST /api/documents] tech_stack: added: [] patterns: [snapshot-copy-with-fresh-uuids, role-to-email-mapping, lazy-fetch-on-tab-switch] key_files: created: [] modified: - teressa-copeland-homes/src/app/api/documents/route.ts - teressa-copeland-homes/src/app/portal/_components/AddDocumentModal.tsx decisions: - "documentTemplateId branch placed before fileBuffer and formTemplateId branches — returns early so existing paths are fully unchanged" - "SIGNER_COLORS constant extracted at module level for reuse in role-to-email mapping" - "resolvedFormTemplateId used (not reassigning formTemplateId let var) to avoid confusion with the existing library branch variable" - "handleSwitchToTemplates lazy-fetches only on first click — docTemplatesLoaded flag prevents repeated fetches on tab toggle" - "handleSelectDocTemplate clears selectedTemplate and customFile for mutual exclusivity; handleSelectTemplate now also clears selectedDocTemplate" metrics: duration_minutes: 2 completed_date: "2026-04-06" tasks_completed: 2 files_modified: 2 --- # Phase 20 Plan 01: Apply Template and Portal Nav Summary **One-liner:** Template apply branch in POST /api/documents copies fields with fresh UUIDs and maps signer roles to client contacts; AddDocumentModal gains a My Templates tab with lazy-loaded template list. ## Tasks Completed | # | Task | Commit | Files | |---|------|--------|-------| | 1 | Extend POST /api/documents with documentTemplateId branch | bdf0cb0 | src/app/api/documents/route.ts | | 2 | Add My Templates tab to AddDocumentModal | 2947fa5 | src/app/portal/_components/AddDocumentModal.tsx | ## What Was Built ### Task 1 — POST /api/documents template branch Extended the JSON body path of the POST handler to accept `documentTemplateId`. When provided: 1. Fetches the `documentTemplates` record with its `formTemplate` relation (for the PDF filename) 2. Copies the PDF from `seeds/forms/` using the form template's filename 3. Maps every field to a new object with a fresh `crypto.randomUUID()` — template field IDs never appear in documents 4. Collects unique signer role labels (stored in `signerEmail` on template fields) in order of first appearance 5. Fetches the client record and builds `clientEmails = [client.email, ...contacts[].email]` 6. Maps roles to emails: role[0] → clientEmails[0], role[1] → clientEmails[1], etc.; falls back to the role label if no email at that index 7. Inserts the document with `signatureFields: copiedFields` and `signers: mappedSigners` and returns 201 The existing custom-upload path and form-library path are completely unchanged. ### Task 2 — AddDocumentModal My Templates tab Added a two-tab UI (Forms Library | My Templates) to the AddDocumentModal: - Tab bar uses underline-style active indicator matching project Tailwind conventions - My Templates tab lazy-fetches `GET /api/templates` on first click via `docTemplatesLoaded` flag - Empty state shown when no templates exist; loading state shown while fetching - Template rows show name, form name, and field count - Selecting a template clears `selectedTemplate` and `customFile` (mutual exclusivity) - Selecting a form or uploading a file clears `selectedDocTemplate` - `handleSubmit` has a new top branch: when `selectedDocTemplate` is set, sends `documentTemplateId` to POST /api/documents as JSON - Guard condition and submit button disabled state both updated to include `selectedDocTemplate` - All existing Forms Library and custom upload markup preserved exactly — only wrapped in `activeTab === 'forms'` conditional ## Verification - `npx tsc --noEmit` — zero type errors (both tasks) - `npm run build` — production build succeeds - `documentTemplateId` present in route.ts (4 matches) and AddDocumentModal.tsx (1 match) - `crypto.randomUUID` present in route.ts (2 matches: docId + field copy) - `SIGNER_COLORS` present in route.ts - `activeTab` (5 matches) and `selectedDocTemplate` (6 matches) in AddDocumentModal.tsx ## Deviations from Plan ### Auto-fixed Issues None. ### Plan Adjustments **handleFileChange also clears selectedDocTemplate** — The plan specified mutual exclusivity in `handleSelectTemplate` and `handleSelectDocTemplate`, but `handleFileChange` (custom file picker) also needed to clear `selectedDocTemplate` to maintain full mutual exclusivity across all three selection paths. Added `setSelectedDocTemplate(null)` to `handleFileChange`. This is a completeness fix, not a deviation from intent. **resolvedFormTemplateId instead of reassigning formTemplateId** — The plan showed `formTemplateId = docTemplate.formTemplateId` to override the let variable. Instead used a new `const resolvedFormTemplateId` to avoid modifying a shared variable that existing branches also read. Cleaner and avoids any unintended side effects. ## Known Stubs None — all data paths are fully wired. The template apply branch fetches real data from the database and inserts real documents with copied fields and mapped signers. ## Self-Check: PASSED Files exist: - FOUND: teressa-copeland-homes/src/app/api/documents/route.ts - FOUND: teressa-copeland-homes/src/app/portal/_components/AddDocumentModal.tsx Commits exist: - bdf0cb0 — feat(20-01): extend POST /api/documents with documentTemplateId branch - 2947fa5 — feat(20-01): add My Templates tab to AddDocumentModal