Commit Graph

284 Commits

Author SHA1 Message Date
Chandler Copeland
b823ae5c58 feat(07-02): extend PreparePanel with agentDownloadUrl/signedAt props and Signed download section
- Added agentDownloadUrl and signedAt to PreparePanelProps interface (optional, nullable)
- Destructure new props in function signature
- Added Signed status branch: green panel with signed timestamp and Download Signed PDF anchor
- Kept Sent/Viewed branch: gray read-only message
- Draft status: existing prepare form unchanged
- Download is a plain <a href> anchor — no fetch/onClick; browser follows link directly
2026-03-21 10:37:37 -06:00
Chandler Copeland
36069cb1ef docs(07-01): complete agent download token and route plan
- 07-01-SUMMARY.md: execution summary with decisions and file references
- STATE.md: position updated to Phase 7 Plan 1 complete; two decisions logged
- ROADMAP.md: Phase 7 progress updated (1/3 plans complete)
- REQUIREMENTS.md: SIGN-07 and LEGAL-03 marked complete
2026-03-21 10:36:19 -06:00
Chandler Copeland
ebc47ae954 feat(07-01): create GET /api/documents/[id]/download agent download route
- Streams signed PDF via short-lived agent-download JWT (adt query param)
- Returns 401 for missing/expired token, 403 for ID mismatch or path traversal
- Returns 404 for unsigned documents or missing files on disk
- Path traversal guard: absPath.startsWith(UPLOADS_DIR) before readFile
- Token/route ID cross-check: documentId !== id returns 403
- new Uint8Array(fileBuffer) for Next.js 16 TypeScript strict mode compatibility
2026-03-21 10:34:43 -06:00
Chandler Copeland
cd4cb75b60 feat(07-01): add createAgentDownloadToken and verifyAgentDownloadToken
- Appends two new exports to token.ts (existing exports untouched)
- purpose: 'agent-download', 5-min TTL, no DB record
- Mirrors existing createDownloadToken/verifyDownloadToken pattern
2026-03-21 10:33:53 -06:00
Chandler Copeland
9fe7936304 docs(07-audit-trail-and-download): create phase 7 plan
3 plans in 3 sequential waves: agent download token + API route (01),
UI wiring for download button + signedAt column (02), human verification
checkpoint (03). Covers SIGN-07 and LEGAL-03.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:30:05 -06:00
Chandler Copeland
45f49ce498 docs(07): research phase audit-trail-and-download 2026-03-21 10:24:17 -06:00
Chandler Copeland
cf877d7443 wip: 06-signing-flow paused at post-execution bug fixes 2026-03-21 10:16:06 -06:00
Chandler Copeland
bf6d361973 fix(06): log audit events and set Viewed status in signing page server component 2026-03-21 10:15:16 -06:00
Chandler Copeland
1171b2fa86 fix(06): update status to Viewed on link open; serve signedFilePath in agent portal after signing 2026-03-21 10:01:46 -06:00
Chandler Copeland
5aef96786a fix(06): wire /send route after /prepare in PreparePanel — signing email was never being sent 2026-03-21 09:53:38 -06:00
Chandler Copeland
69614cabf9 docs(phase-06): complete phase execution 2026-03-21 09:49:14 -06:00
Chandler Copeland
04c3720096 fix(06): correct clientName in agent notification email 2026-03-21 09:49:06 -06:00
Chandler Copeland
04e3d5cb54 docs(06-06): complete DNS verification plan — LEGAL-04 satisfied, Phase 6 complete
- 06-06-SUMMARY.md: SPF/DKIM/DMARC verified green via Resend for tcopelandhomes.com
- STATE.md: Plan 06 complete, completed_plans 24/24, Phase 6 fully complete
- ROADMAP.md: Phase 6 marked complete (6/6 plans), completed 2026-03-21
- REQUIREMENTS.md: LEGAL-04 marked complete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:23 -06:00
Chandler Copeland
7121279654 feat(06-06): update domain to tcopelandhomes.com, configure Resend SMTP 2026-03-21 09:42:10 -06:00
Chandler Copeland
32ea324504 chore(06-06): automated DNS verification check results
- SPF: MISSING — no TXT records at root domain
- DKIM: MISSING — NXDOMAIN for all common selectors
- DMARC: EXISTS but rua points to hosting default, not teressa@
- SMTP: credentials still placeholder in .env.local
- DNS provider: GoDaddy (ns63/ns64.domaincontrol.com)
2026-03-20 11:46:49 -06:00
Chandler Copeland
119edc2491 docs(06-05): complete confirmation page + download route plan — SUMMARY, STATE, ROADMAP updated
- Post-signing confirmation page with success checkmark, document name, timestamp, 15-min download token
- GET /api/sign/[token]/download streams signedFilePath PDF via short-lived download JWT
- Phase 6 (signing flow) all 5 plans complete
- SIGN-06 requirement marked complete
2026-03-20 11:44:32 -06:00
Chandler Copeland
4cdd9eea80 feat(06-05): confirmation page + router.push redirect after signing
- Rewrite confirmed/page.tsx: verifies signing token, shows document name + signed timestamp + download button
- Generate 15-min download token server-side; pass as dt= query param to /api/sign/[token]/download
- Success checkmark (navy circle + gold checkmark), document name, formatted signed date
- Download link valid for 15 minutes note shown below button
- Update SigningPageClient.tsx: replace window.location.href with router.push for SPA navigation
2026-03-20 11:42:24 -06:00
Chandler Copeland
a276da0da1 feat(06-05): download token utilities + download API route
- Add createDownloadToken and verifyDownloadToken to token.ts (15-min TTL, purpose:'download' claim)
- Create GET /api/sign/[token]/download route: validates dt query param JWT, streams signedFilePath as PDF
- Path traversal guard: signedFilePath must start with UPLOADS_DIR
- Auto-fix: Buffer cast to Uint8Array for Response BodyInit compatibility (Next.js 16 / TypeScript strict)
2026-03-20 11:41:18 -06:00
Chandler Copeland
5c1ea3568e docs(06-04): complete signature modal + submission plan — SUMMARY, STATE, ROADMAP updated
- SignatureModal with Draw/Type/Use Saved tabs (signature_pad DPR scaling)
- POST /api/sign/[token] atomic one-time claim, PDF embed, SHA-256 hash, audit trail
- SIGN-04, SIGN-05, LEGAL-01, LEGAL-02 requirements complete
2026-03-20 11:39:10 -06:00
Chandler Copeland
d445c282c1 feat(06-04): POST /api/sign/[token] atomic submission + confirmed page
- Add POST handler to sign/[token]/route.ts with atomic one-time enforcement
- UPDATE signing_tokens SET usedAt WHERE usedAt IS NULL RETURNING — 0 rows = 409
- Log signature_submitted and pdf_hash_computed audit events
- Merge client dataURLs with server-stored field coordinates (NEVER trust client coords)
- Call embedSignatureInPdf, store pdfHash + signedFilePath in documents table
- Update document status to Signed with signedAt timestamp
- Fire-and-forget sendAgentNotificationEmail (catches errors without failing response)
- Create /sign/[token]/confirmed success page for POST redirect destination
2026-03-20 11:37:00 -06:00
Chandler Copeland
05b5207305 feat(06-04): SignatureModal with Draw/Type/Use Saved tabs + wire SigningPageClient
- Create SignatureModal.tsx with signature_pad Draw tab (devicePixelRatio scaling, touch-none)
- Type tab renders name in Dancing Script cursive via offscreen canvas
- Use Saved tab conditionally shown when localStorage has saved signature
- Save for later checkbox persists drawn/typed sig to localStorage on confirm
- Update SigningPageClient.tsx: import modal, track signedFields as Map<string,string>
- Field overlay shows signature preview image after signing
- handleSubmit POSTs to /api/sign/[token] and redirects to /sign/[token]/confirmed on 200
2026-03-20 11:35:40 -06:00
Chandler Copeland
a3026fb44f docs(06-03): complete signing page plan — SUMMARY, STATE, ROADMAP updated
- 06-03-SUMMARY.md created with decisions, deviations, file list
- STATE.md advanced to Plan 3 complete; two new decisions recorded
- ROADMAP.md phase 6 updated to 3/6 summaries complete
2026-03-20 11:32:40 -06:00
Chandler Copeland
90bd066016 docs(06-02): complete email delivery layer plan
- Add 06-02-SUMMARY.md: SigningRequestEmail, signing-mailer, send route, prepare audit log
- Update STATE.md: Plan 2 complete, decisions logged, session updated
- Update ROADMAP.md: Phase 6 plan progress (2 of 5 summaries)
- Mark SIGN-01 complete in REQUIREMENTS.md
2026-03-20 11:31:35 -06:00
Chandler Copeland
dcf503dfea feat(06-03): signing page — server component, PDF viewer, field overlays, progress bar
- page.tsx: server component validates JWT + one-time-use before rendering any UI
- Three error states (expired/used/invalid) show static pages with no canvas
- SigningPageClientWrapper: dynamic import (ssr:false) for react-pdf browser requirement
- SigningPageClient: full-scroll PDF viewer with pulsing blue field overlays
- Field overlay coordinates convert PDF user-space (bottom-left) to screen (top-left)
- SigningProgressBar: sticky bottom bar with X/Y count + jump-to-next + submit button
- api/sign/[token]/pdf: token-authenticated PDF streaming route (no agent auth)
2026-03-20 11:30:38 -06:00
Chandler Copeland
877ad66ead feat(06-02): POST /api/documents/[id]/send + document_prepared audit log
- Add send/route.ts: creates signing token, sends branded email, logs email_sent, updates status to Sent
- Auth guard returns 401 for unauthenticated requests; 422 if not prepared; 409 if already signed
- Wraps sendMail in try/catch — returns 502 without DB update if email delivery fails
- Add logAuditEvent(document_prepared) to prepare/route.ts after successful PDF preparation
2026-03-20 11:29:54 -06:00
Chandler Copeland
f41db49ff7 feat(06-02): branded signing request email + mailer utilities
- Add SigningRequestEmail.tsx React Email component (navy/gold brand colors, CTA button)
- Add signing-mailer.tsx with sendSigningRequestEmail() and sendAgentNotificationEmail()
- Uses CONTACT_SMTP_* env vars (same SMTP provider as contact form)
- Sender: "Teressa Copeland" <teressa@teressacopelandhomes.com>
2026-03-20 11:29:05 -06:00
Chandler Copeland
e1306dab69 feat(06-03): GET /api/sign/[token] route — token validation + audit logging
- Validates JWT with verifySigningToken(); returns expired/invalid/used/pending
- Checks signingTokens.usedAt for one-time-use enforcement
- Logs link_opened + document_viewed audit events on valid pending access
- Extracts IP from x-forwarded-for/x-real-ip headers for audit trail
- Public route — no auth() import or session required
2026-03-20 11:28:51 -06:00
Chandler Copeland
4bca04f988 docs(06-01): complete signing foundation plan — SUMMARY, STATE, ROADMAP updated
- created 06-01-SUMMARY.md with full task and decision documentation
- STATE.md: advanced to phase 6 plan 1, added 5 signing foundation decisions
- ROADMAP.md: marked 06-01-PLAN.md complete, Signing Flow at 1/6
- REQUIREMENTS.md: marked SIGN-02, LEGAL-01, LEGAL-02 complete
2026-03-20 11:27:07 -06:00
Chandler Copeland
2929581ab9 feat(06-01): create signing utility library (token, audit, embed)
- token.ts: createSigningToken() + verifySigningToken() using jose HS256
- audit.ts: logAuditEvent() inserts typed audit events with server timestamp
- embed-signature.ts: embedSignatureInPdf() embeds PNG sigs, returns SHA-256 hash
- added SIGNING_JWT_SECRET to .env.local (random 32-char base64 secret)
2026-03-20 11:24:56 -06:00
Chandler Copeland
fa68a1bcb4 feat(06-01): install packages + extend schema + generate migration
- installed signature_pad, @react-email/render, @react-email/components
- added signingTokens table (jti pk, documentId, expiresAt, usedAt)
- added auditEvents table with auditEventTypeEnum (6 event types)
- added signedFilePath, pdfHash, signedAt columns to documents table
- generated and applied migration 0005_signing_flow.sql
2026-03-20 11:24:02 -06:00
Chandler Copeland
6cf228c779 docs(06-signing-flow): create phase plan 2026-03-20 11:18:47 -06:00
Chandler Copeland
d049f92c61 docs(06): research signing flow phase 2026-03-20 11:07:33 -06:00
Chandler Copeland
08bf795646 docs(06): capture phase context 2026-03-20 10:55:47 -06:00
Chandler Copeland
d24dd54062 fix(05-04): reject field updates when document status is not Draft 2026-03-20 10:44:21 -06:00
Chandler Copeland
c6f5800394 wip: phase 05-04 paused — readOnly lock not working after Sent transition 2026-03-20 10:42:35 -06:00
Chandler Copeland
bd73f0cc76 feat(05-04): lock field placer to read-only when document is Sent
- Add readOnly prop to FieldPlacer; when true: hide palette, disable all pointer
  events on field boxes, show fields at 60% opacity, suppress delete button and
  all four resize handles
- PdfViewer accepts docStatus prop and derives readOnly={docStatus==='Sent'||'Signed'}
- PdfViewerWrapper forwards docStatus prop to PdfViewer
- page.tsx passes docStatus={doc.status} to PdfViewerWrapper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:36:25 -06:00
Chandler Copeland
08719a6109 feat(05-04): replace single resize handle with 4-corner resize handles
- Add ResizeCorner type ('se' | 'sw' | 'ne' | 'nw')
- handleResizeStart now accepts a corner argument, stored in DraggingState
- Screen-space math: compute start rect (top/left/right/bottom px), apply delta
  per corner, enforce 40px/20px minimums, then convert back to PDF units on pointerup
- renderFields renders four 10x10 blue square handles at each corner with the
  correct CSS resize cursor (nw-resize, ne-resize, sw-resize, se-resize)
- Opposite corner is always the anchor; only the two edges adjacent to the dragged
  corner move

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:36:04 -06:00
Chandler Copeland
51a77ef7d2 feat(05-04): add move and resize to placed signature fields
- Native pointer events on field body for move drag (no dnd-kit conflict)
- 10x10 resize handle in bottom-right corner with se-resize cursor
- Event delegation via DroppableZone onPointerMove/onPointerUp
- DOM mutation during drag for smooth performance; commit to state on pointerUp
- data-no-move attribute prevents resize handle and delete button from triggering move
- draggingRef tracks in-progress drag; activeDragFieldId drives grabbing cursor + box-shadow
- Minimum field size enforced: 40x20 PDF units
2026-03-20 01:02:45 -06:00
Chandler Copeland
3d6f0ea68c fix(05-04): disable DragOverlay drop animation to eliminate snap-back 2026-03-20 01:00:02 -06:00
Chandler Copeland
cbad0a1a34 fix(05-04): remove transform from DraggableToken to prevent snap-back animation 2026-03-20 00:59:27 -06:00
Chandler Copeland
f8897cce34 fix(05-04): sync defaultEmail into state after hydration 2026-03-20 00:55:22 -06:00
Chandler Copeland
0a719c9d60 fix(05-04): fetch client email via direct join, improve recipients hint text 2026-03-20 00:53:46 -06:00
Chandler Copeland
1319d4310e fix(05-04): guard parseEmails against undefined defaultEmail 2026-03-20 00:51:32 -06:00
Chandler Copeland
7f97bbc5e5 fix(05-04): simplify recipients to single pre-filled textarea, remove client dropdown 2026-03-20 00:50:41 -06:00
Chandler Copeland
d669b11a50 docs(05-04): update STATE.md with three additional UI fixes
- Ghost-rect field placement fix (f0ecfd1)
- Editable primary email input fix (73ba6d5)
- Text stamp 60pt/10pt fix (c4e8d01)
2026-03-20 00:43:02 -06:00
Chandler Copeland
c4e8d01784 fix(05-04): move text stamp to 60pt from top, increase font size to 10pt
- Change startY from pageHeight-20 to pageHeight-60 (~0.83 inch from top)
- Increase lineHeight from 12 to 14 for better readability
- Increase stamp font size from 8pt to 10pt for better visibility
2026-03-20 00:42:03 -06:00
Chandler Copeland
73ba6d5a0d fix(05-04): replace locked client display with editable email input
- Remove isLocked variable and read-only div for assigned client
- Add primaryEmail state pre-filled with assigned client's email, user-editable
- Show client name as helper text below the input for reference
- Update buildEmailAddresses() to use primaryEmail when assignedClientId is set
- Update button disabled logic to gate on primaryEmail when assigned client exists
2026-03-20 00:41:49 -06:00
Chandler Copeland
f0ecfd1545 fix(05-04): use ghost rect for field placement, canvas offset for overlays
- Replace cursor+delta calculation with active.rect.current.translated (ghost bounding rect)
- Measure coordinates relative to the inner <canvas> element, not the DroppableZone wrapper
- Add canvasOffset state + useLayoutEffect to offset field overlays by canvas position within wrapper
- Inline PDF coordinate math in handleDragEnd; clamp uses field pixel dimensions
2026-03-20 00:40:18 -06:00
Chandler Copeland
13cdd150f1 docs(05-04): complete pdf-fill-and-field-mapping plan 04 bug fixes
Four post-testing bugs fixed: signature field placement coordinate math,
delete button clickability, client selector pre-selection with manual
email entry, and text fill fallback to drawText for flat PDFs.
SUMMARY.md updated with full bug fix documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:24:50 -06:00
Chandler Copeland
ef10dd5089 fix(05-04): always stamp text fill data into prepared PDF
Strategy A still attempts AcroForm filling by field name (matching
fields in the PDF's form dict). Strategy B is now a mandatory fallback:
any text field entries that did not match an AcroForm field (or when
the PDF has no AcroForm at all) are drawn as 'key: value' text lines
near the top of page 1 using @cantoo/pdf-lib drawText.

This ensures text fill data supplied in the PreparePanel is always
visible in the output PDF regardless of whether the source PDF was
built with interactive form fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:22:17 -06:00