diff --git a/teressa-copeland-homes/src/app/sign/[token]/_components/SignatureModal.tsx b/teressa-copeland-homes/src/app/sign/[token]/_components/SignatureModal.tsx
index 5f49192..6ef886b 100644
--- a/teressa-copeland-homes/src/app/sign/[token]/_components/SignatureModal.tsx
+++ b/teressa-copeland-homes/src/app/sign/[token]/_components/SignatureModal.tsx
@@ -10,9 +10,12 @@ interface SignatureModalProps {
fieldId: string;
onConfirm: (fieldId: string, dataURL: string, save: boolean) => void;
onClose: () => void;
+ title?: string; // defaults to "Add Signature"
}
-export function SignatureModal({ isOpen, fieldId, onConfirm, onClose }: SignatureModalProps) {
+export function SignatureModal({ isOpen, fieldId, onConfirm, onClose, title = 'Add Signature' }: SignatureModalProps) {
+ // Derive button label from title — "Add Initials" → "Apply Initials", otherwise "Apply Signature"
+ const buttonLabel = title.startsWith('Add ') ? title.replace('Add ', 'Apply ') : 'Apply Signature';
const [tab, setTab] = useState<'draw' | 'type' | 'saved'>('draw');
const [typedName, setTypedName] = useState('');
const [saveForLater, setSaveForLater] = useState(false);
@@ -112,7 +115,7 @@ export function SignatureModal({ isOpen, fieldId, onConfirm, onClose }: Signatur
>
{/* Header */}
-
Add Signature
+ {title}
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 aebcd53..99018bf 100644
--- a/teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx
+++ b/teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx
@@ -53,6 +53,7 @@ export function SigningPageClient({
// Modal state
const [modalOpen, setModalOpen] = useState(false);
const [activeFieldId, setActiveFieldId] = useState(null);
+ const [activeFieldType, setActiveFieldType] = useState<'client-signature' | 'initials'>('client-signature');
// Ref for all captured signatures (fieldId -> dataURL)
const signaturesRef = useRef([]);
@@ -87,11 +88,12 @@ export function SigningPageClient({
(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;
+ const ft = getFieldType(field);
+ // Only client-signature and initials require client action
+ if (ft !== 'client-signature' && ft !== 'initials') return;
if (signedFields.has(fieldId)) return;
setActiveFieldId(fieldId);
+ setActiveFieldType(ft as 'client-signature' | 'initials');
setModalOpen(true);
},
[signatureFields, signedFields]
@@ -122,7 +124,9 @@ export function SigningPageClient({
);
const handleJumpToNext = useCallback(() => {
- const nextUnsigned = signatureFields.find((f) => !signedFields.has(f.id));
+ const nextUnsigned = signatureFields.find(
+ (f) => (getFieldType(f) === 'client-signature' || getFieldType(f) === 'initials') && !signedFields.has(f.id)
+ );
if (nextUnsigned) {
document.getElementById(`field-${nextUnsigned.id}`)?.scrollIntoView({
behavior: 'smooth',
@@ -132,10 +136,10 @@ export function SigningPageClient({
}, [signatureFields, signedFields]);
const handleSubmit = useCallback(async () => {
- const clientSigFields = signatureFields.filter(
- (f) => getFieldType(f) === 'client-signature'
+ const requiredFields = signatureFields.filter(
+ (f) => getFieldType(f) === 'client-signature' || getFieldType(f) === 'initials'
);
- if (signedFields.size < clientSigFields.length || submitting) return;
+ if (signedFields.size < requiredFields.length || submitting) return;
setSubmitting(true);
try {
const res = await fetch(`/api/sign/${token}`, {
@@ -205,6 +209,10 @@ export function SigningPageClient({
box-shadow: 0 0 0 3px #3b82f6, 0 0 16px 4px rgba(59,130,246,0.6);
}
}
+ @keyframes pulse-border-purple {
+ 0%, 100% { box-shadow: 0 0 0 2px #7c3aed, 0 0 8px 2px rgba(124,58,237,0.4); }
+ 50% { box-shadow: 0 0 0 3px #7c3aed, 0 0 16px 4px rgba(124,58,237,0.6); }
+ }
.signing-field-signed {
box-shadow: 0 0 0 2px #22c55e !important;
animation: none !important;
@@ -288,19 +296,31 @@ export function SigningPageClient({
{/* Signature field overlays — rendered once page dimensions are known */}
{dims &&
fieldsOnPage.map((field) => {
+ // Only render interactive overlays for client-signature and initials fields
+ // text/checkbox/date are embedded at prepare time — no client interaction needed
+ const ft = getFieldType(field);
+ const isInteractive = ft === 'client-signature' || ft === 'initials';
+ if (!isInteractive) return null;
+
const isSigned = signedFields.has(field.id);
- const overlayStyle = getFieldOverlayStyle(field, dims);
+ const baseStyle = getFieldOverlayStyle(field, dims);
+ const animationStyle: React.CSSProperties = ft === 'initials'
+ ? { animation: 'pulse-border-purple 2s infinite' }
+ : { animation: 'pulse-border 2s infinite' };
+ const fieldOverlayStyle = { ...baseStyle, ...animationStyle };
const sigDataURL = signedFields.get(field.id);
return (
handleFieldClick(field.id)}
role="button"
tabIndex={0}
- aria-label={`Signature field${isSigned ? ' (signed)' : ' — click to sign'}`}
+ aria-label={ft === 'initials'
+ ? `Initials field${isSigned ? ' (initialed)' : ' — click to initial'}`
+ : `Signature field${isSigned ? ' (signed)' : ' — click to sign'}`}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
@@ -308,12 +328,12 @@ export function SigningPageClient({
}
}}
>
- {/* Show signature preview when signed */}
+ {/* Show signature/initials preview when signed */}
{isSigned && sigDataURL && (
/* eslint-disable-next-line @next/next/no-img-element */

getFieldType(f) === 'client-signature').length}
+ total={signatureFields.filter(
+ (f) => getFieldType(f) === 'client-signature' || getFieldType(f) === 'initials'
+ ).length}
signed={signedFields.size}
onJumpToNext={handleJumpToNext}
onSubmit={handleSubmit}
@@ -345,6 +367,7 @@ export function SigningPageClient({
{
setModalOpen(false);