diff --git a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx index 3cf6160..6014dbb 100644 --- a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx +++ b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx @@ -168,9 +168,11 @@ interface FieldPlacerProps { aiPlacementKey?: number; signers?: DocumentSigner[]; unassignedFieldIds?: Set; + onPersist?: (fields: SignatureFieldData[]) => Promise | void; + fieldsUrl?: string; } -export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly = false, onFieldsChanged, selectedFieldId, textFillData, onFieldSelect, onFieldValueChange, aiPlacementKey = 0, signers = [], unassignedFieldIds = new Set() }: FieldPlacerProps) { +export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly = false, onFieldsChanged, selectedFieldId, textFillData, onFieldSelect, onFieldValueChange, aiPlacementKey = 0, signers = [], unassignedFieldIds = new Set(), onPersist, fieldsUrl }: FieldPlacerProps) { const [fields, setFields] = useState([]); const [isDraggingToken, setIsDraggingToken] = useState(null); const [activeSignerEmail, setActiveSignerEmail] = useState(null); @@ -221,7 +223,7 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly = useEffect(() => { async function loadFields() { try { - const res = await fetch(`/api/documents/${docId}/fields`); + const res = await fetch(fieldsUrl ?? `/api/documents/${docId}/fields`); if (res.ok) { const data = await res.json(); setFields(data); @@ -231,7 +233,7 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly = } } loadFields(); - }, [docId, aiPlacementKey]); + }, [docId, aiPlacementKey, fieldsUrl]); // Update containerSize whenever pageInfo changes (page load or zoom change) // Use pageInfo.width/height (from react-pdf canvas) as the authoritative rendered size. @@ -321,10 +323,10 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly = const next = [...fields, newField]; setFields(next); - persistFields(docId, next); + if (onPersist) { onPersist(next); } else { persistFields(docId, next); } onFieldsChanged?.(); }, - [fields, pageInfo, currentPage, docId, readOnly, onFieldsChanged, activeSignerEmail], + [fields, pageInfo, currentPage, docId, readOnly, onFieldsChanged, activeSignerEmail, onPersist], ); // --- Move / Resize pointer handlers (event delegation on DroppableZone) --- @@ -505,7 +507,7 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly = }; }); setFields(next); - persistFields(docId, next); + if (onPersist) { onPersist(next); } else { persistFields(docId, next); } onFieldsChanged?.(); } else if (drag.type === 'resize') { const corner = drag.corner ?? 'se'; @@ -572,10 +574,10 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly = return { ...f, x: newPdfX, y: newPdfY, width: newPdfW, height: newPdfH }; }); setFields(next); - persistFields(docId, next); + if (onPersist) { onPersist(next); } else { persistFields(docId, next); } onFieldsChanged?.(); } - }, [docId, onFieldsChanged]); + }, [docId, onFieldsChanged, onPersist]); // Render placed fields for the current page // Uses pageInfo.width/height (not getBoundingClientRect) for consistent coordinate math @@ -742,7 +744,7 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly = e.stopPropagation(); const next = fields.filter((f) => f.id !== field.id); setFields(next); - persistFields(docId, next); + if (onPersist) { onPersist(next); } else { persistFields(docId, next); } onFieldsChanged?.(); }} onPointerDown={(e) => { diff --git a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx index 37f3005..54bec83 100644 --- a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx +++ b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx @@ -4,7 +4,7 @@ import { Document, Page, pdfjs } from 'react-pdf'; import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/TextLayer.css'; import { FieldPlacer } from './FieldPlacer'; -import type { DocumentSigner } from '@/lib/db/schema'; +import type { DocumentSigner, SignatureFieldData } from '@/lib/db/schema'; // Worker setup — must use import.meta.url for local/Docker environments (no CDN) pdfjs.GlobalWorkerOptions.workerSrc = new URL( @@ -31,6 +31,9 @@ export function PdfViewer({ aiPlacementKey, signers, unassignedFieldIds, + onPersist, + fieldsUrl, + fileUrl, }: { docId: string; docStatus?: string; @@ -42,6 +45,9 @@ export function PdfViewer({ aiPlacementKey?: number; signers?: DocumentSigner[]; unassignedFieldIds?: Set; + onPersist?: (fields: SignatureFieldData[]) => Promise | void; + fieldsUrl?: string; + fileUrl?: string; }) { const [numPages, setNumPages] = useState(0); const [pageNumber, setPageNumber] = useState(1); @@ -82,7 +88,7 @@ export function PdfViewer({ {docStatus !== 'Signed' && ( @@ -105,9 +111,11 @@ export function PdfViewer({ aiPlacementKey={aiPlacementKey} signers={signers} unassignedFieldIds={unassignedFieldIds} + onPersist={onPersist} + fieldsUrl={fieldsUrl} > setNumPages(numPages)} className="shadow-lg" > diff --git a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx index ccdb86b..1f77fa2 100644 --- a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx +++ b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx @@ -1,6 +1,6 @@ 'use client'; import dynamic from 'next/dynamic'; -import type { DocumentSigner } from '@/lib/db/schema'; +import type { DocumentSigner, SignatureFieldData } from '@/lib/db/schema'; const PdfViewer = dynamic(() => import('./PdfViewer').then(m => m.PdfViewer), { ssr: false }); @@ -15,6 +15,9 @@ export function PdfViewerWrapper({ aiPlacementKey, signers, unassignedFieldIds, + onPersist, + fieldsUrl, + fileUrl, }: { docId: string; docStatus?: string; @@ -26,6 +29,9 @@ export function PdfViewerWrapper({ aiPlacementKey?: number; signers?: DocumentSigner[]; unassignedFieldIds?: Set; + onPersist?: (fields: SignatureFieldData[]) => Promise | void; + fieldsUrl?: string; + fileUrl?: string; }) { return ( ); } diff --git a/teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx b/teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx index 3889985..7a559ff 100644 --- a/teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx +++ b/teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx @@ -7,6 +7,7 @@ import { LogoutButton } from "@/components/ui/LogoutButton"; const navLinks = [ { href: "/portal/dashboard", label: "Dashboard" }, { href: "/portal/clients", label: "Clients" }, + { href: "/portal/templates", label: "Templates" }, { href: "/portal/profile", label: "Profile" }, ];