From 4cdd9eea80b29c1200c10557dfc7a932a5746f84 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Fri, 20 Mar 2026 11:42:24 -0600 Subject: [PATCH] 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 --- .../[token]/_components/SigningPageClient.tsx | 6 +- .../src/app/sign/[token]/confirmed/page.tsx | 179 +++++++++++++----- 2 files changed, 134 insertions(+), 51 deletions(-) diff --git a/teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx b/teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx index d1121d9..f96e3c2 100644 --- a/teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx +++ b/teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState, useRef, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; import { Document, Page, pdfjs } from 'react-pdf'; import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/TextLayer.css'; @@ -40,6 +41,7 @@ export function SigningPageClient({ documentName, signatureFields, }: SigningPageClientProps) { + const router = useRouter(); const [numPages, setNumPages] = useState(0); // Map from page number (1-indexed) to rendered dimensions const [pageDimensions, setPageDimensions] = useState>({}); @@ -133,10 +135,10 @@ export function SigningPageClient({ body: JSON.stringify({ signatures: signaturesRef.current }), }); if (res.ok) { - window.location.href = `/sign/${token}/confirmed`; + router.push(`/sign/${token}/confirmed`); } else if (res.status === 409) { // Already signed — navigate to confirmed anyway - window.location.href = `/sign/${token}/confirmed`; + router.push(`/sign/${token}/confirmed`); } else { const data = await res.json().catch(() => ({})); alert(`Submission failed: ${(data as { error?: string }).error ?? res.status}. Please try again.`); diff --git a/teressa-copeland-homes/src/app/sign/[token]/confirmed/page.tsx b/teressa-copeland-homes/src/app/sign/[token]/confirmed/page.tsx index b7bd54a..7dfd562 100644 --- a/teressa-copeland-homes/src/app/sign/[token]/confirmed/page.tsx +++ b/teressa-copeland-homes/src/app/sign/[token]/confirmed/page.tsx @@ -1,71 +1,152 @@ -// /sign/[token]/confirmed — shown after a document is successfully signed -// Static success screen; no token re-validation needed (submission already committed) +import { verifySigningToken, createDownloadToken } from '@/lib/signing/token'; +import { db } from '@/lib/db'; +import { signingTokens, documents } from '@/lib/db/schema'; +import { eq } from 'drizzle-orm'; + +interface Props { + params: Promise<{ token: string }>; +} + +export default async function ConfirmedPage({ params }: Props) { + const { token } = await params; + + // Verify signing token to get documentId (allow expired — confirmation page visited after signing) + let documentId: string | null = null; + let jti: string | null = null; + try { + const payload = await verifySigningToken(token); + documentId = payload.documentId; + jti = payload.jti; + } catch { + // Token expired is OK here — signing may have happened right before expiry + // documentId/jti remain null — handled below + } + + if (!documentId) { + return ( +
+ Document not found. +
+ ); + } + + const doc = await db.query.documents.findFirst({ + where: eq(documents.id, documentId), + }); + + // Get signed timestamp from signingTokens.usedAt + let signedAt: Date | null = null; + if (jti) { + const tokenRow = await db.query.signingTokens.findFirst({ + where: eq(signingTokens.jti, jti), + }); + signedAt = tokenRow?.usedAt ?? doc?.signedAt ?? null; + } + + if (!doc || !doc.signedFilePath) { + return ( +
+
+

Document not yet available

+

Please check back shortly.

+
+
+ ); + } + + // Generate 15-minute download token for client copy + const downloadToken = await createDownloadToken(doc.id); + const downloadUrl = `/api/sign/${token}/download?dt=${downloadToken}`; + + const formattedDate = signedAt + ? signedAt.toLocaleString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + hour: 'numeric', + minute: '2-digit', + }) + : 'Just now'; -export default function ConfirmedPage() { return (
-
- {/* Branded header strip */} +
+ {/* Success checkmark */}
-
- Teressa Copeland Homes -
+ ✓
- - {/* Success card */} -
+ You've signed + +

-

-

- Document Signed -

-

- Your signature has been recorded and the document has been securely submitted. - Teressa Copeland will be in touch shortly. -

-
- You may close this window. -
-
+ {doc.name} +

+

+ Signed on {formattedDate} +

+ + Download your copy + +

+ Download link valid for 15 minutes. +

);