feat(08-02): add type-branching guards to SigningPageClient

- Import getFieldType from @/lib/db/schema
- handleFieldClick returns early for non-client-signature fields (defense-in-depth)
- handleSubmit counts only client-signature fields for completeness check
- SigningProgressBar total reflects client-signature count only
- signatureFields added to handleFieldClick and handleSubmit dependency arrays
This commit is contained in:
Chandler Copeland
2026-03-21 11:52:09 -06:00
parent ea3365feb4
commit 06e477b455

View File

@@ -8,6 +8,7 @@ import 'react-pdf/dist/Page/TextLayer.css';
import { SigningProgressBar } from './SigningProgressBar'; import { SigningProgressBar } from './SigningProgressBar';
import { SignatureModal } from './SignatureModal'; import { SignatureModal } from './SignatureModal';
import type { SignatureFieldData } from '@/lib/db/schema'; import type { SignatureFieldData } from '@/lib/db/schema';
import { getFieldType } from '@/lib/db/schema';
// Worker setup — reuse same pattern as PdfViewerWrapper (no CDN, works in local/Docker) // Worker setup — reuse same pattern as PdfViewerWrapper (no CDN, works in local/Docker)
pdfjs.GlobalWorkerOptions.workerSrc = new URL( pdfjs.GlobalWorkerOptions.workerSrc = new URL(
@@ -84,11 +85,16 @@ export function SigningPageClient({
const handleFieldClick = useCallback( const handleFieldClick = useCallback(
(fieldId: string) => { (fieldId: string) => {
const field = signatureFields.find((f) => f.id === fieldId);
if (!field) return;
// Defense-in-depth: primary protection is the server filter in GET /api/sign/[token]
// Only client-signature fields open the modal; Phase 10 will expand this for initials
if (getFieldType(field) !== 'client-signature') return;
if (signedFields.has(fieldId)) return; if (signedFields.has(fieldId)) return;
setActiveFieldId(fieldId); setActiveFieldId(fieldId);
setModalOpen(true); setModalOpen(true);
}, },
[signedFields] [signatureFields, signedFields]
); );
const handleModalConfirm = useCallback( const handleModalConfirm = useCallback(
@@ -126,7 +132,10 @@ export function SigningPageClient({
}, [signatureFields, signedFields]); }, [signatureFields, signedFields]);
const handleSubmit = useCallback(async () => { const handleSubmit = useCallback(async () => {
if (signedFields.size < signatureFields.length || submitting) return; const clientSigFields = signatureFields.filter(
(f) => getFieldType(f) === 'client-signature'
);
if (signedFields.size < clientSigFields.length || submitting) return;
setSubmitting(true); setSubmitting(true);
try { try {
const res = await fetch(`/api/sign/${token}`, { const res = await fetch(`/api/sign/${token}`, {
@@ -148,7 +157,7 @@ export function SigningPageClient({
alert('Network error. Please check your connection and try again.'); alert('Network error. Please check your connection and try again.');
setSubmitting(false); setSubmitting(false);
} }
}, [signedFields.size, signatureFields.length, submitting, token]); }, [signedFields.size, signatureFields, submitting, token]);
/** /**
* Convert PDF user-space coordinates (bottom-left origin) to screen overlay position. * Convert PDF user-space coordinates (bottom-left origin) to screen overlay position.
@@ -325,7 +334,7 @@ export function SigningPageClient({
{/* Sticky progress bar */} {/* Sticky progress bar */}
<SigningProgressBar <SigningProgressBar
total={signatureFields.length} total={signatureFields.filter((f) => getFieldType(f) === 'client-signature').length}
signed={signedFields.size} signed={signedFields.size}
onJumpToNext={handleJumpToNext} onJumpToNext={handleJumpToNext}
onSubmit={handleSubmit} onSubmit={handleSubmit}