diff --git a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx new file mode 100644 index 0000000..8f5bf75 --- /dev/null +++ b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx @@ -0,0 +1,51 @@ +'use client'; +import { useState, useCallback } from 'react'; +import { PdfViewerWrapper } from './PdfViewerWrapper'; +import { PreparePanel } from './PreparePanel'; + +interface DocumentPageClientProps { + docId: string; + docStatus: string; + defaultEmail: string; + clientName: string; + agentDownloadUrl?: string | null; + signedAt?: Date | null; + clientPropertyAddress?: string | null; +} + +export function DocumentPageClient({ + docId, + docStatus, + defaultEmail, + clientName, + agentDownloadUrl, + signedAt, + clientPropertyAddress, +}: DocumentPageClientProps) { + const [previewToken, setPreviewToken] = useState(null); + + const handleFieldsChanged = useCallback(() => { + setPreviewToken(null); + }, []); + + return ( +
+
+ +
+
+ +
+
+ ); +} 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 d47cd20..70dad79 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 @@ -19,7 +19,7 @@ interface PageInfo { scale: number; } -export function PdfViewer({ docId, docStatus }: { docId: string; docStatus?: string }) { +export function PdfViewer({ docId, docStatus, onFieldsChanged }: { docId: string; docStatus?: string; onFieldsChanged?: () => void }) { const [numPages, setNumPages] = useState(0); const [pageNumber, setPageNumber] = useState(1); const [scale, setScale] = useState(1.0); @@ -69,7 +69,7 @@ export function PdfViewer({ docId, docStatus }: { docId: string; docStatus?: str {/* PDF canvas wrapped in FieldPlacer for drag-and-drop field placement */} - + setNumPages(numPages)} 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 7fcc29f..5862ca4 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 @@ -3,6 +3,6 @@ import dynamic from 'next/dynamic'; const PdfViewer = dynamic(() => import('./PdfViewer').then(m => m.PdfViewer), { ssr: false }); -export function PdfViewerWrapper({ docId, docStatus }: { docId: string; docStatus?: string }) { - return ; +export function PdfViewerWrapper({ docId, docStatus, onFieldsChanged }: { docId: string; docStatus?: string; onFieldsChanged?: () => void }) { + return ; } diff --git a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx index e4bb0b8..2ede9f0 100644 --- a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx +++ b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { TextFillForm } from './TextFillForm'; +import { PreviewModal } from './PreviewModal'; interface PreparePanelProps { docId: string; @@ -11,6 +12,8 @@ interface PreparePanelProps { agentDownloadUrl?: string | null; signedAt?: Date | null; clientPropertyAddress?: string | null; + previewToken: string | null; + onPreviewTokenChange: (token: string | null) => void; } function parseEmails(raw: string | undefined): string[] { @@ -21,7 +24,7 @@ function isValidEmail(email: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } -export function PreparePanel({ docId, defaultEmail, clientName, currentStatus, agentDownloadUrl, signedAt, clientPropertyAddress }: PreparePanelProps) { +export function PreparePanel({ docId, defaultEmail, clientName, currentStatus, agentDownloadUrl, signedAt, clientPropertyAddress, previewToken, onPreviewTokenChange }: PreparePanelProps) { const router = useRouter(); const [recipients, setRecipients] = useState(defaultEmail ?? ''); // Sync if defaultEmail arrives after initial render (streaming / hydration timing) @@ -33,6 +36,8 @@ export function PreparePanel({ docId, defaultEmail, clientName, currentStatus, a ); const [loading, setLoading] = useState(false); const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null); + const [previewBytes, setPreviewBytes] = useState(null); + const [showPreview, setShowPreview] = useState(false); if (currentStatus === 'Signed') { return ( @@ -84,6 +89,36 @@ export function PreparePanel({ docId, defaultEmail, clientName, currentStatus, a ); } + function handleTextFillChange(data: Record) { + setTextFillData(data); + onPreviewTokenChange(null); + } + + async function handlePreview() { + setLoading(true); + setResult(null); + try { + const res = await fetch(`/api/documents/${docId}/preview`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ textFillData }), + }); + if (res.ok) { + const bytes = await res.arrayBuffer(); + setPreviewBytes(bytes); + onPreviewTokenChange(Date.now().toString()); + setShowPreview(true); + } else { + const err = await res.json().catch(() => ({ error: 'Preview failed' })); + setResult({ ok: false, message: err.message ?? err.error ?? 'Preview failed' }); + } + } catch (e) { + setResult({ ok: false, message: String(e) }); + } finally { + setLoading(false); + } + } + async function handlePrepare() { setLoading(true); setResult(null); @@ -152,14 +187,23 @@ export function PreparePanel({ docId, defaultEmail, clientName, currentStatus, a
+ +