Commit Graph

144 Commits

Author SHA1 Message Date
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
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
Chandler Copeland
05915aa562 fix(05-04): pre-select document client and add manual email entry
- PreparePanel now receives separate assignedClientId (nullable) and
  defaultClientId props so it can distinguish an explicitly locked
  client from just a default
- When document already has assignedClientId: show locked read-only
  display; user cannot change the primary recipient
- When document has no assignedClientId: default to document owner in
  dropdown but allow changing; option to clear and enter email manually
- Added textarea for additional/CC email addresses (comma or newline
  separated) that is always visible for either mode
- POST /api/documents/[id]/prepare now accepts and stores emailAddresses
  array alongside assignedClientId
- Added email_addresses jsonb column to documents table via migration
  0004_military_maximus.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:21:34 -06:00
Chandler Copeland
126e10dc1d fix(05-04): fix signature field placement coordinate math
- Use pageInfo.width/height (authoritative rendered canvas size from
  react-pdf) instead of containerRect.width/height for all coordinate
  math; containerRect dimensions could differ if the wrapper div has
  extra decoration not matching the canvas
- Track containerSize in state (updated via useLayoutEffect when
  pageInfo changes) so renderFields() uses stable values from state
  instead of calling getBoundingClientRect() during render
- Refactor screenToPdfCoords/pdfToScreenCoords to take renderedW/H
  as explicit params instead of a DOMRect, making the contract clearer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:18:23 -06:00
Chandler Copeland
296ef482bb feat(05-03): extend document detail page with PreparePanel
- Fetch allClients in parallel with doc via Promise.all
- Import PreparePanel and render in 1/3 right column of 2/3 + 1/3 grid
- currentClientId defaults to doc.assignedClientId ?? doc.clientId
- asc import added from drizzle-orm for ordered client list
- npm run build is clean with no TypeScript errors
2026-03-20 00:04:55 -06:00
Chandler Copeland
df6eb76bd0 feat(05-03): create TextFillForm and PreparePanel client components
- TextFillForm: key-value pair builder (up to 10 rows, individually removable)
- PreparePanel: client selector + text fill form + Prepare and Send button
- PreparePanel calls POST /api/documents/[id]/prepare and calls router.refresh() on success
- PreparePanel shows read-only message for non-Draft documents
- Rule 1 fix: PdfViewer page.scale -> scale (PageCallback has no .scale property; use state var)
2026-03-20 00:04:08 -06:00
Chandler Copeland
7a367363b1 feat(05-02): extend PdfViewer with pageInfo state and FieldPlacer integration
- Add pageInfo state (PageInfo | null) to track rendered PDF page dimensions
- Wire onLoadSuccess callback on Page to capture originalWidth/Height using Math.max mediaBox pattern
- Import and wrap Document/Page tree inside FieldPlacer component
- Pass docId, pageInfo, currentPage to FieldPlacer for coordinate conversion
- Only scale prop used on Page (not both width+scale — avoids double scaling)
- All existing controls (Prev, Next, Zoom In/Out, Download) preserved unchanged
2026-03-20 00:00:22 -06:00
Chandler Copeland
6069ae5e06 feat(05-02): install dnd-kit and create FieldPlacer component
- Install @dnd-kit/core@^6.3.1 and @dnd-kit/utilities@^3.2.2
- Create FieldPlacer.tsx with DndContext, draggable palette token, droppable PDF zone
- Implements Y-flip coordinate conversion (screenToPdfCoords / pdfToScreenCoords)
- Fetches existing fields from GET /api/documents/[id]/fields on mount
- Persists fields via PUT /api/documents/[id]/fields on every add/remove
- Renders placed fields as absolute-positioned blue-bordered overlays with remove button
- Default field size: 144x36 PDF points (2in x 0.5in at 72 DPI)
2026-03-19 23:59:51 -06:00
Chandler Copeland
34ed0baa43 test(05-01): add unit tests for Y-flip coordinate conversion formula
- Created src/lib/pdf/__tests__/prepare-document.test.ts with 10 test cases
- Tests verify Y-axis flip: screenY=0 → pdfY≈792, screenY=792 → pdfY≈0
- Tests verify scale-invariance at both 1:1 and 50% zoom ratios
- Installed jest, ts-jest, @types/jest and added jest config to package.json
- All 10 tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:55:27 -06:00
Chandler Copeland
c81e8ea838 feat(05-01): add preparePdf utility and fields/prepare API routes
- Installed @cantoo/pdf-lib for server-side PDF mutation
- Created src/lib/pdf/prepare-document.ts with preparePdf function using atomic tmp->rename write pattern
- form.flatten() called before drawing signature rectangles
- Created GET/PUT /api/documents/[id]/fields routes for signature field storage
- Created POST /api/documents/[id]/prepare route that calls preparePdf and transitions status to Sent
- Fixed pre-existing null check error in scripts/debug-inspect2.ts (Rule 3: blocking build)
- Build compiles successfully with 2 new API routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:54:41 -06:00
Chandler Copeland
d67130da20 feat(05-01): extend documents schema with 4 new columns + migration 0003
- Added SignatureFieldData interface export to schema.ts
- Added signatureFields (jsonb), textFillData (jsonb), assignedClientId (text), preparedFilePath (text) nullable columns to documents table
- Added jsonb import to drizzle-orm/pg-core imports
- Generated and applied migration 0003_cool_natasha_romanoff.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:53:12 -06:00
Chandler Copeland
ac5b98fe33 wip: skyslope scraper — fix name extraction via body text parsing, preview+download flow ready 2026-03-19 23:06:17 -06:00
Chandler Copeland
27462a0ebb feat: add Playwright script to scrape SkySlope form libraries into seeds/forms/ 2026-03-19 22:27:41 -06:00
Chandler Copeland
6c5e4afd86 fix(04-03): move ssr:false dynamic import into client wrapper to fix DOMMatrix SSR error 2026-03-19 22:01:58 -06:00
Chandler Copeland
025d9896ed fix(04-03): make back button more prominent with bordered pill style 2026-03-19 21:56:36 -06:00
Chandler Copeland
5c7a0fd061 fix(04-03): style Browse files as a visible button with selected filename display 2026-03-19 21:54:07 -06:00
Chandler Copeland
c1f60cadf6 feat(04-03): add AddDocumentModal, PdfViewer, and document detail page
- Create AddDocumentModal: searchable forms library list + custom file picker
- Wire Add Document button into ClientProfileClient in Documents section header
- Update DocumentsTable: document names now link to /portal/documents/{id}
- Create PdfViewer with page nav, zoom, and download controls (pdfjs worker via import.meta.url)
- Create /portal/documents/[docId] page: server component with auth check, doc/client query
- Add documentsRelations and clientsRelations to schema.ts for db.query with-relations support
- Build verified: /portal/documents/[docId] route present, no errors
2026-03-19 21:44:17 -06:00
Chandler Copeland
63e5888968 feat(04-03): install react-pdf and configure Next.js transpilePackages
- npm install react-pdf (v9+ pulls in pdfjs-dist automatically)
- Add transpilePackages: ['react-pdf', 'pdfjs-dist'] to next.config.ts
- Build verified passing after config change
2026-03-19 21:42:06 -06:00
Chandler Copeland
32e129c097 feat(04-02): create POST /api/documents and GET /api/documents/[id]/file routes
- POST handles both form-data (custom upload) and JSON (template copy) paths
- Copies seed PDF or writes uploaded file to uploads/clients/{clientId}/{uuid}.pdf
- Path traversal guard on destPath before writing
- GET streams PDF bytes with Content-Type: application/pdf
- Path traversal guard on filePath before reading
- Both routes return 401 for unauthenticated requests
2026-03-19 21:36:54 -06:00
Chandler Copeland
e0f180c3d8 feat(04-02): create GET /api/forms-library authenticated template list
- Queries form_templates ordered by name (asc)
- Returns 401 for unauthenticated requests
- Returns JSON array of { id, name, filename } for authenticated agents
2026-03-19 21:36:21 -06:00
Chandler Copeland
f82364d2c7 feat(04-01): create seed-forms script and npm run seed:forms command
- Create scripts/seed-forms.ts: reads seeds/forms/, upserts PDFs into form_templates via onConflictDoUpdate on filename
- Add seed:forms script to package.json with DOTENV_CONFIG_PATH=.env.local prefix
- Empty seeds/forms/ prints guidance message and exits 0 (monthly-sync workflow ready)
2026-03-19 21:33:02 -06:00
Chandler Copeland
bbbbdbed5e feat(04-01): add formTemplates table and extend documents schema
- Add formTemplates table (id text PK, name, filename unique, createdAt, updatedAt)
- Add formTemplateId (nullable FK) and filePath (nullable text) to documents table
- Generate and apply migration 0002_wealthy_zzzax.sql
- Create seeds/forms/.gitkeep to track seed directory in git
2026-03-19 21:32:30 -06:00
Chandler Copeland
b186ac5f38 feat(03-04): add client profile page with edit/delete and documents table
- Create ConfirmDialog component with overlay, title, message, cancel/confirm buttons
- Create ClientProfilePage server component (awaits params Promise — Next.js 16)
- Create ClientProfileClient client component with edit modal and delete confirmation
- Documents section uses DocumentsTable with showClientColumn={false}
- deleteClient called directly from async event handler in client component
2026-03-19 16:58:21 -06:00
Chandler Copeland
3fa2e1c424 feat(03-03): extend seed.ts with client and placeholder document rows
- Added import of clients, documents, inArray from drizzle-orm
- Seeds 2 clients: Sarah Johnson and Mike Torres (onConflictDoNothing)
- Queries back seeded client IDs, then seeds 4 placeholder documents
- Documents cover Signed/Sent/Draft statuses across both clients
- Seed is idempotent via onConflictDoNothing guard
2026-03-19 16:49:29 -06:00
Chandler Copeland
df1924acc4 feat(03-03): add Clients page with card grid and create modal
- ClientCard.tsx: server component with name, email, doc count, last activity; wrapped in Link to /portal/clients/[id]
- ClientModal.tsx: use client component with useActionState from react; supports create/edit modes via bind pattern; closes on success
- ClientsPageClient.tsx: use client wrapper holding isOpen modal state, renders card grid or empty state CTA
- clients/page.tsx: async server component fetching clients with docCount + lastActivity via Drizzle LEFT JOIN + GROUP BY
2026-03-19 16:47:28 -06:00
Chandler Copeland
e55d7a1de5 feat(03-03): add Dashboard page with filterable documents table
- Async server component queries all documents with LEFT JOIN to clients
- Filter state lives in URL (?status=Draft|Sent|Viewed|Signed) via DashboardFilters client component
- Rows filtered in JavaScript after fetch (tiny dataset in Phase 3)
- DashboardFilters extracted to _components/DashboardFilters.tsx (use client + useRouter)
- Displays agent first name extracted from session email
2026-03-19 16:46:26 -06:00
Chandler Copeland
5b87201b28 feat(03-02): add createClient, updateClient, deleteClient server actions
- 'use server' file with Zod validation (name min 1 char, valid email)
- createClient: validate, insert, revalidatePath /portal/clients
- updateClient: bind pattern (id, prevState, formData), revalidates client list + profile
- deleteClient: auth check, delete by id, revalidatePath /portal/clients
- Fixed Zod v4 .issues access (not .errors — v4 API change)
2026-03-19 16:38:49 -06:00
Chandler Copeland
28d42f5d9b feat(03-02): add StatusBadge and DocumentsTable shared portal components
- StatusBadge: color-coded pill for Draft=gray, Sent=blue, Viewed=amber, Signed=green
- DocumentsTable: reusable table with optional Client column, StatusBadge integration
- Date format: toLocaleDateString en-US short month; null sentAt renders as em-dash
2026-03-19 16:37:30 -06:00
Chandler Copeland
9c4caeedba feat(03-02): add portal authenticated layout and PortalNav
- Create portal/(protected)/layout.tsx with auth() check and redirect to /agent/login
- Create PortalNav.tsx as client component with Dashboard/Clients links and active state
- Nav uses usePathname() for active gold underline, LogoutButton for sign-out
- CSS variables --navy/--gold/--cream applied throughout portal shell
2026-03-19 16:37:03 -06:00
Chandler Copeland
00f9c7c9f0 feat(03-01): protect /portal routes and update post-login redirect
- middleware.ts: add /portal/:path* to matcher array
- auth.config.ts: add isPortalRoute check, redirect unauthenticated to /agent/login
- auth.config.ts: change post-login redirect from /agent/dashboard to /portal/dashboard
- agent dashboard page: replace stub with redirect to /portal/dashboard
2026-03-19 16:17:59 -06:00
Chandler Copeland
f8f8b8f4ba feat(03-01): add clients and documents tables to Drizzle schema
- Add pgEnum import and documentStatusEnum (Draft, Sent, Viewed, Signed)
- Add clients table (id, name, email, createdAt, updatedAt)
- Add documents table (id, name, clientId FK, status enum, sentAt, createdAt)
- Generate migration 0001_watery_blindfold.sql and apply to local PostgreSQL
2026-03-19 16:17:26 -06:00
Chandler Copeland
9500f812e0 docs(02-03): complete human verification — phase 2 approved
- All 7 checklist items approved by human reviewer
- Visual fixes applied: hero objectPosition 20%, contact form white inputs, testimonial gold accent bar
- Phase 2 marketing site marked complete (MKTG-01 through MKTG-04 satisfied)
- STATE.md updated: phase 2 complete, 6/6 plans done, progress 40%
2026-03-19 15:19:22 -06:00
Chandler Copeland
a33b4c9520 fix(02-01): restore TestimonialsSection in page.tsx section order
- Add TestimonialsSection import and render between HeroSection and ListingsPlaceholder
- Correct section order: SiteNav → HeroSection → TestimonialsSection → ListingsPlaceholder → ContactSection → SiteFooter
2026-03-19 15:02:19 -06:00
Chandler Copeland
47c6dd9e62 feat(02-02): add ContactSection client component and wire into homepage
- Create src/app/_components/ContactSection.tsx: useActionState form with
  name, email, phone, message fields and gold Send Message button
- Honeypot field (name="website") with display:none, tabIndex=-1, autoComplete="off"
- Success state: form replaced by thank-you message on successful submission
- Error state: inline alert for validation failures
- page.tsx: replace <section id="contact" /> stub with <ContactSection />
- useActionState imported from 'react' (not react-dom, React 19 compatible)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 15:01:36 -06:00
Chandler Copeland
6d701b7faa feat(02-01): add testimonials carousel and wire into homepage
- Create TestimonialsSection with 5 placeholder reviews
- Auto-rotates every 5s, pauses on hover, clearInterval cleanup
- ChevronLeft/ChevronRight arrow controls and dot indicators
- Wire TestimonialsSection into page.tsx between hero and listings
2026-03-19 15:01:07 -06:00
Chandler Copeland
c26a0b1b62 feat(02-01): scaffold marketing site nav, hero, listings, footer
- Add scroll-behavior: smooth and scroll-margin-top to globals.css
- Create SiteNav with sticky navy bar, desktop links, mobile hamburger
- Create HeroSection with split-panel layout and next/image
- Create ListingsPlaceholder with brand navy background and gold CTA
- Create SiteFooter with license number and TODO verify comment
- Update page.tsx to compose all section components
- Install lucide-react for icons

[Rule 1 - Bug] Fixed event handlers in server components — used CSS hover classes instead
2026-03-19 14:59:56 -06:00
Chandler Copeland
39f233dbb4 feat(02-02): install nodemailer and create contact mailer + server action
- Add nodemailer@^7.0.7 and @types/nodemailer (v7 required by next-auth peer dep)
- Create src/lib/contact-mailer.ts: Nodemailer SMTP transporter + sendContactEmail()
- Create src/lib/contact-action.ts: Server Action with Zod validation, honeypot check
- SMTP credentials read from CONTACT_EMAIL_USER/PASS/SMTP_HOST/PORT env vars
- Add CONTACT_* placeholder vars to .env.local (gitignored, for local setup docs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:59:23 -06:00
Chandler Copeland
39af0f19ba fix(auth): resolve middleware Edge Runtime + layout redirect loop
Two bugs:
1. auth.ts imported postgres (Node.js TCP) which crashes in Edge Runtime,
   causing Auth.js to silently fall back to redirecting all requests to login.
   Fix: split into auth.config.ts (Edge-safe, no DB) used by middleware,
   and auth.ts (full, with DB) used by server components.

2. /agent/layout.tsx applied to /agent/login, so unauthenticated login page
   visits redirected to themselves in an infinite loop.
   Fix: moved dashboard + layout into (protected) route group so login page
   has no auth layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:08:04 -06:00
Chandler Copeland
0a75442af3 fix(db): swap @neondatabase/serverless for postgres.js (local dev support)
Neon serverless driver requires remote WebSocket — incompatible with local
PostgreSQL. Replaced with postgres.js (drizzle-orm/postgres-js) in db/index.ts,
scripts/seed.ts, and drizzle.config.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:00:32 -06:00
Chandler Copeland
32dc2d3ee5 feat(01-02): build agent portal layout, dashboard stub, and logout mechanism
- LogoutButton: client component with server action calling signOut to ?signed_out=1
- AgentLayout: defense-in-depth auth() check, header with email and sign-out button
- DashboardPage: belt-and-suspenders auth() check, welcome message with agent email
- HomePage: updated to Teressa Copeland Homes placeholder (Phase 2 marketing site)
- npm run build and npx tsc --noEmit both pass with zero errors
2026-03-19 13:39:25 -06:00
Chandler Copeland
f221597677 feat(01-02): build branded login page with password toggle and error handling
- Split-screen layout: agent photo left (lg+), login card right
- loginAction server action: signIn credentials, redirects on AuthError
- PasswordField client component: show/hide toggle with eye/eye-off SVG
- Signed-out confirmation banner on ?signed_out=1
- Error banner on ?error=invalid with generic message
- Brand colors: gold #C9A84C accent, navy #1B2B4B text, off-white #FAF9F7 bg
- Copied /Users/ccopeland/Downloads/red.jpg to public/red.jpg
2026-03-19 13:38:42 -06:00
Chandler Copeland
e5db79a8f0 feat(01-01): configure Auth.js v5 JWT + Credentials, route protection middleware
- Created src/lib/auth.ts with NextAuth JWT strategy, 7-day rolling session, Credentials provider
- Created src/app/api/auth/[...nextauth]/route.ts with GET/POST handlers and force-dynamic
- Created middleware.ts at project root (not src/) protecting /agent/* routes
- Fixed db/index.ts: lazy Proxy singleton prevents neon() crash during Next.js build
- npm run build passes; /api/auth/[...nextauth] renders as Dynamic route
2026-03-19 13:33:15 -06:00
Chandler Copeland
f46e7027a5 feat(01-01): define DB schema, configure Drizzle Kit, write seed script
- Created src/lib/db/schema.ts with users table (id, email, password_hash, created_at)
- Created src/lib/db/index.ts exporting db singleton via Drizzle + Neon HTTP driver
- Created drizzle.config.ts pointing to schema.ts with postgresql dialect
- Created scripts/seed.ts reading AGENT_EMAIL/AGENT_PASSWORD to create hashed user row
- Generated drizzle/0000_milky_black_cat.sql migration (committed per user decision)
- db:migrate and db:seed pending user Neon database setup
2026-03-19 13:30:52 -06:00
Chandler Copeland
dac1bc817b feat(01-01): scaffold Next.js 16.2.0 project with all Phase 1 dependencies
- Created teressa-copeland-homes with TypeScript, Tailwind, ESLint, App Router, src dir
- Installed next-auth@5.0.0-beta.30 (pinned exact version), drizzle-orm, @neondatabase/serverless
- Installed bcryptjs, @vercel/blob, zod, drizzle-kit, tsx, dotenv
- Added db:generate, db:migrate, db:seed, db:studio npm scripts
- Verified npm run build passes with zero TypeScript errors
2026-03-19 13:29:56 -06:00