From 5d71b2073ef925d7180dc75752576c8a870b73e4 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:39:43 +0000 Subject: [PATCH 01/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/integrations/supabase/types.ts | 3 +++ .../20260418023940_e88f7f51-5ed9-4b51-b6aa-75609e2252a3.sql | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 supabase/migrations/20260418023940_e88f7f51-5ed9-4b51-b6aa-75609e2252a3.sql diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index e20ce44..c106623 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -960,6 +960,7 @@ export type Database = { font_size_pt: number hide_title: boolean id: string + line_height: number name: string signature_blocks: Json title: string @@ -975,6 +976,7 @@ export type Database = { font_size_pt?: number hide_title?: boolean id?: string + line_height?: number name: string signature_blocks?: Json title?: string @@ -990,6 +992,7 @@ export type Database = { font_size_pt?: number hide_title?: boolean id?: string + line_height?: number name?: string signature_blocks?: Json title?: string diff --git a/supabase/migrations/20260418023940_e88f7f51-5ed9-4b51-b6aa-75609e2252a3.sql b/supabase/migrations/20260418023940_e88f7f51-5ed9-4b51-b6aa-75609e2252a3.sql new file mode 100644 index 0000000..c399946 --- /dev/null +++ b/supabase/migrations/20260418023940_e88f7f51-5ed9-4b51-b6aa-75609e2252a3.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.custom_form_templates +ADD COLUMN IF NOT EXISTS line_height NUMERIC NOT NULL DEFAULT 1.5; \ No newline at end of file From 4b36efc3fa88fd56d780220e751ae51fea23efae Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:40:36 +0000 Subject: [PATCH 02/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/affidavit-form.tsx | 248 ---------------------- src/components/forms/itf-form.tsx | 270 ++++++++++++++++++++++++ src/routes/forms.index.tsx | 6 +- 3 files changed, 273 insertions(+), 251 deletions(-) delete mode 100644 src/components/forms/affidavit-form.tsx create mode 100644 src/components/forms/itf-form.tsx diff --git a/src/components/forms/affidavit-form.tsx b/src/components/forms/affidavit-form.tsx deleted file mode 100644 index efe7f54..0000000 --- a/src/components/forms/affidavit-form.tsx +++ /dev/null @@ -1,248 +0,0 @@ -import { useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Card, CardContent } from "@/components/ui/card"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { FileDown } from "lucide-react"; -import { ClientHomeownerPicker } from "./form-pickers"; -import { - fetchFirm, - type ClientLite, - type FirmInfo, -} from "@/lib/forms-shared"; -import { jsPDF } from "jspdf"; -import { format } from "date-fns"; -import { toast } from "sonner"; - -const MEETING_TYPES = [ - "Annual Membership Meeting", - "Special Membership Meeting", - "Board of Directors Meeting", - "Special Board Meeting", - "Budget Meeting", - "Turnover Meeting", - "Election Meeting", -]; -const NOTICE_DAYS = [ - { value: "60", label: "Sixty (60) Days", word: "sixty (60)" }, - { value: "30", label: "Thirty (30) Days", word: "thirty (30)" }, - { value: "14", label: "Fourteen (14) Days", word: "fourteen (14)" }, -]; -const STATUTES = [ - { value: "720", label: "§720.306(1)(d)(5) — HOA", text: "§720.306(1)(d)(5)" }, - { value: "718", label: "§718.112(2)(d)3 — Condo", text: "§718.112(2)(d)3" }, -]; - -function ordinal(day: number) { - const s = ["th", "st", "nd", "rd"]; - const v = day % 100; - return `${day}${s[(v - 20) % 10] || s[v] || s[0]}`; -} - -export function AffidavitForm() { - const [client, setClient] = useState(null); - const [clientId, setClientId] = useState(""); - const [, setFirm] = useState(null); - - const [mailingDate, setMailingDate] = useState(new Date().toISOString().slice(0, 10)); - const [meetingType, setMeetingType] = useState(MEETING_TYPES[0]); - const [noticeDays, setNoticeDays] = useState("14"); - const [statute, setStatute] = useState("720"); - const [signerName, setSignerName] = useState(""); - const [signerTitle, setSignerTitle] = useState("Manager"); - const [signerCredentials, setSignerCredentials] = useState(""); - const [county, setCounty] = useState("Brevard"); - - useEffect(() => { - fetchFirm().then(setFirm); - }, []); - - const handleExport = () => { - if (!client) { - toast.error("Select a client first"); - return; - } - const dateObj = new Date(mailingDate + "T12:00:00"); - const dayWord = ordinal(dateObj.getDate()); - const monthName = format(dateObj, "MMMM"); - const year = format(dateObj, "yyyy"); - const dateWordy = format(dateObj, "MMMM d, yyyy"); - const daysWord = NOTICE_DAYS.find((d) => d.value === noticeDays)?.word ?? noticeDays; - const statuteText = STATUTES.find((s) => s.value === statute)?.text ?? ""; - - const doc = new jsPDF({ unit: "pt", format: "letter" }); - const pageW = doc.internal.pageSize.getWidth(); - const margin = 60; - const contentW = pageW - margin * 2; - let y = 70; - - doc.setFont("helvetica", "bold"); - doc.setFontSize(18); - doc.text("AFFIDAVIT OF MAILING", margin, y); - y += 26; - doc.setFont("helvetica", "bolditalic"); - doc.setFontSize(11); - doc.text(`${year} ${meetingType}`, margin, y); - y += 26; - - doc.setFont("helvetica", "bold"); - doc.setFontSize(10.5); - doc.text("STATE OF FLORIDA", margin, y); - y += 15; - doc.text(`COUNTY OF ${county.toUpperCase()}`, margin, y); - y += 24; - - doc.setFont("helvetica", "normal"); - const body = `I, ${signerName.toUpperCase() || "[SIGNER]"}, on behalf of the Secretary of ${client.name}, being first duly sworn, depose and say that the notice of the ${meetingType.toUpperCase()} was mailed, hand delivered, or electronically sent to each unit owner at the address last furnished to the Association in accordance with the requirements of Section ${statuteText} Florida Statutes, at least ${daysWord} days prior to the noticed meeting, on ${dateWordy}.`; - const lines = doc.splitTextToSize(body, contentW); - doc.text(lines, margin, y); - y += lines.length * 15 + 8; - doc.text(`Dated this ${dayWord} day of ${monthName}, ${year}.`, margin, y); - y += 28; - - doc.setFont("helvetica", "bold"); - doc.text(`BY: ${signerName || "[Signer]"}, ${signerTitle}`, margin, y); - y += 44; - doc.line(margin, y, margin + 250, y); - y += 13; - doc.setFont("helvetica", "italic"); - doc.setFontSize(9); - doc.text(signerCredentials || signerName, margin, y); - - y += 36; - doc.setFont("helvetica", "bold"); - doc.setFontSize(10.5); - doc.text("STATE OF FLORIDA", margin, y); - y += 15; - doc.text(`COUNTY OF ${county.toUpperCase()}`, margin, y); - y += 24; - - doc.setFont("helvetica", "normal"); - const notary = `The foregoing Affidavit was acknowledged before me this ${dayWord} day of ${monthName}, ${year} by ${signerName || "[Signer]"}, who is personally known to me or produced a Florida Driver's License as identification.`; - const nLines = doc.splitTextToSize(notary, contentW); - doc.text(nLines, margin, y); - y += nLines.length * 15 + 24; - - doc.line(margin, y, margin + 270, y); - y += 13; - doc.text("NOTARY PUBLIC", margin, y); - y += 32; - doc.setFont("helvetica", "bold"); - doc.text("(SEAL)", margin, y); - - doc.save(`Affidavit_of_Mailing_${mailingDate}.pdf`); - toast.success("Affidavit PDF downloaded"); - }; - - return ( -
- - - { - setClientId(id); - setClient(c); - }} - onHomeownerChange={() => {}} - showHomeowner={false} - /> -
-
- - setMailingDate(e.target.value)} - /> -
-
- - -
-
-
-
- - -
-
- - -
-
- - setCounty(e.target.value)} /> -
-
-
-
- - setSignerName(e.target.value)} /> -
-
- - setSignerTitle(e.target.value)} /> -
-
- - setSignerCredentials(e.target.value)} - placeholder="e.g. LCAM" - /> -
-
- -
- -
-
-
-
- ); -} diff --git a/src/components/forms/itf-form.tsx b/src/components/forms/itf-form.tsx new file mode 100644 index 0000000..44323fc --- /dev/null +++ b/src/components/forms/itf-form.tsx @@ -0,0 +1,270 @@ +import { useEffect, useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Card, CardContent } from "@/components/ui/card"; +import { FileDown } from "lucide-react"; +import { ClientHomeownerPicker } from "./form-pickers"; +import { + clientAddressLines, + fetchFirm, + fmtCurrency, + fmtDateLong, + ownerFullName, + type ClientLite, + type HomeownerLite, + type FirmInfo, +} from "@/lib/forms-shared"; +import { jsPDF } from "jspdf"; +import { toast } from "sonner"; + +export function ItfForm() { + const [client, setClient] = useState(null); + const [homeowner, setHomeowner] = useState(null); + const [clientId, setClientId] = useState(""); + const [homeownerId, setHomeownerId] = useState(""); + const [firm, setFirm] = useState(null); + + const [letterDate, setLetterDate] = useState(new Date().toISOString().slice(0, 10)); + const [certifiedNo, setCertifiedNo] = useState(""); + const [lienBookPage, setLienBookPage] = useState(""); + const [lienRecordedDate, setLienRecordedDate] = useState(""); + const [assessments, setAssessments] = useState("0.00"); + const [interest, setInterest] = useState("0.00"); + const [lateFees, setLateFees] = useState("0.00"); + const [adminFees, setAdminFees] = useState("0.00"); + const [lessPayments, setLessPayments] = useState("0.00"); + + useEffect(() => { + fetchFirm().then(setFirm); + }, []); + + // 45-day cure window prior to filing foreclosure + const dueDate = useMemo(() => { + const d = new Date(letterDate + "T12:00:00"); + d.setDate(d.getDate() + 45); + return d.toISOString().slice(0, 10); + }, [letterDate]); + + const total = + (parseFloat(assessments) || 0) + + (parseFloat(interest) || 0) + + (parseFloat(lateFees) || 0) + + (parseFloat(adminFees) || 0) - + (parseFloat(lessPayments) || 0); + + const handleExport = () => { + if (!client) { + toast.error("Select a client first"); + return; + } + const doc = new jsPDF({ unit: "pt", format: "letter" }); + const pageW = doc.internal.pageSize.getWidth(); + const margin = 54; + + let y = 60; + doc.setFont("helvetica", "normal"); + doc.setFontSize(10); + doc.text(client.name, margin, y); + y += 12; + if (firm?.company_name) { + doc.text(`c/o ${firm.company_name}`, margin, y); + y += 12; + } + clientAddressLines(client).forEach((l) => { + doc.text(l, margin, y); + y += 12; + }); + + doc.setFont("helvetica", "bold"); + doc.text(fmtDateLong(letterDate), pageW - margin - doc.getTextWidth(fmtDateLong(letterDate)), 60); + + y = Math.max(y + 30, 175); + doc.setFont("helvetica", "normal"); + doc.text(ownerFullName(homeowner) || "[Homeowner]", margin, y); + y += 12; + if (homeowner?.address) { + doc.text(homeowner.address, margin, y); + y += 12; + } + + if (certifiedNo) { + doc.setFont("helvetica", "bolditalic"); + const t = `U.S. Certified Mail # ${certifiedNo}`; + doc.text(t, pageW - margin - doc.getTextWidth(t), y); + y += 16; + } + + y += 24; + doc.setFont("helvetica", "bold"); + doc.setFontSize(12); + const title = "NOTICE OF INTENT TO FORECLOSE CLAIM OF LIEN"; + doc.text(title, (pageW - doc.getTextWidth(title)) / 2, y); + y += 26; + + doc.setFont("helvetica", "normal"); + doc.setFontSize(10); + const lienRef = + lienBookPage || lienRecordedDate + ? ` A Claim of Lien was recorded against the subject property${ + lienRecordedDate ? ` on ${fmtDateLong(lienRecordedDate)}` : "" + }${lienBookPage ? ` (recorded at ${lienBookPage})` : ""}.` + : ""; + const body = `Pursuant to the governing documents of ${client.name} and applicable Florida law, you are hereby notified that the assessments and other amounts secured by the previously recorded Claim of Lien against the property identified above remain past due and unpaid.${lienRef}\n\nUnless payment in full of the total amount stated below is received on or before ${fmtDateLong(dueDate)} (forty-five (45) days from the date of this letter), the Association will proceed with the filing of a foreclosure action on its Claim of Lien. Additional collection costs and attorneys' fees will be incurred for which you may be responsible, and your interest in the property may be sold at public sale.`; + const lines = doc.splitTextToSize(body, pageW - margin * 2); + doc.text(lines, margin, y); + y += lines.length * 13 + 14; + + // Itemized + const rows: [string, string][] = [ + ["Assessments", assessments], + ["Interest", interest], + ["Late Fees", lateFees], + ["Admin / Legal Fees", adminFees], + ["Less Payments", `-${lessPayments}`], + ]; + doc.setFont("helvetica", "bold"); + doc.text("Description", margin, y); + doc.text("Amount", pageW - margin - 60, y); + y += 6; + doc.line(margin, y, pageW - margin, y); + y += 14; + doc.setFont("helvetica", "normal"); + rows.forEach(([d, a]) => { + doc.text(d, margin, y); + const txt = `$${fmtCurrency(a)}`; + doc.text(txt, pageW - margin - doc.getTextWidth(txt), y); + y += 14; + }); + y += 4; + doc.line(margin, y, pageW - margin, y); + y += 16; + doc.setFont("helvetica", "bold"); + doc.text("TOTAL DUE", margin, y); + const totalTxt = `$${fmtCurrency(total)}`; + doc.text(totalTxt, pageW - margin - doc.getTextWidth(totalTxt), y); + y += 30; + + doc.setFont("helvetica", "normal"); + const closing = `Payment must be made payable to ${client.name} and remitted to the address shown above. If you have any questions regarding this notice, please contact our office immediately to avoid the filing of a foreclosure action.`; + const cl = doc.splitTextToSize(closing, pageW - margin * 2); + doc.text(cl, margin, y); + + doc.save(`ITF_${ownerFullName(homeowner) || "draft"}_${letterDate}.pdf`); + toast.success("ITF PDF downloaded"); + }; + + return ( +
+ + + { + setClientId(id); + setClient(c); + }} + onHomeownerChange={(id, h) => { + setHomeownerId(id); + setHomeowner(h); + }} + /> +
+
+ + setLetterDate(e.target.value)} + /> +
+
+ + +
+
+ + setCertifiedNo(e.target.value)} /> +
+
+ +
+
+ + setLienRecordedDate(e.target.value)} + /> +
+
+ + setLienBookPage(e.target.value)} + placeholder="e.g., Book 1234 / Page 567 or Inst # 2024000123" + /> +
+
+ +
+
+ + setAssessments(e.target.value)} + /> +
+
+ + setInterest(e.target.value)} + /> +
+
+ + setLateFees(e.target.value)} + /> +
+
+ + setAdminFees(e.target.value)} + /> +
+
+ + setLessPayments(e.target.value)} + /> +
+
+ +
+ Total due: ${fmtCurrency(total)} + +
+
+
+
+ ); +} diff --git a/src/routes/forms.index.tsx b/src/routes/forms.index.tsx index e4a3137..702188a 100644 --- a/src/routes/forms.index.tsx +++ b/src/routes/forms.index.tsx @@ -7,7 +7,7 @@ import { CustomFormBuilder } from "@/components/forms/custom-form-builder"; import { LetterGenerator } from "@/components/forms/letter-generator"; import { NolaForm } from "@/components/forms/nola-form"; import { ItlForm } from "@/components/forms/itl-form"; -import { AffidavitForm } from "@/components/forms/affidavit-form"; +import { ItfForm } from "@/components/forms/itf-form"; import { EstoppelForm } from "@/components/forms/estoppel-form"; export const Route = createFileRoute("/forms/")({ @@ -29,7 +29,7 @@ function FormsPage() { Letters NOLA ITL - Affidavit + ITF Estoppel
@@ -37,7 +37,7 @@ function FormsPage() { - +
From 7f7cc9fff798d73f14eaaa1373091f72dd5625d2 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:40:47 +0000 Subject: [PATCH 03/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/custom-form-builder.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index 82182ee..815fc66 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -200,11 +200,21 @@ interface SavedTemplate { body_html: string; font_family: string; font_size_pt: number; + line_height: number; signature_blocks: Array<{ name: string; title?: string }>; fields: FormFieldDef[]; updated_at: string; } +const LINE_HEIGHTS = [ + { value: 1.0, label: "1.0" }, + { value: 1.15, label: "1.15" }, + { value: 1.25, label: "1.25" }, + { value: 1.5, label: "1.5" }, + { value: 1.75, label: "1.75" }, + { value: 2.0, label: "2.0" }, +]; + export function CustomFormBuilder() { const [title, setTitle] = useState("Official Notice"); const [hideTitle, setHideTitle] = useState(false); From c76cb60ab52bb26b0fb1e26386052c9bc1a72099 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:40:58 +0000 Subject: [PATCH 04/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/custom-form-builder.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index 815fc66..d9aec3c 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -227,6 +227,7 @@ export function CustomFormBuilder() { const [customDefs, setCustomDefs] = useState([]); const [fontFamily, setFontFamily] = useState("Bookman Old Style"); const [fontSize, setFontSize] = useState(12); + const [lineHeight, setLineHeight] = useState(1.5); // Template management const [templates, setTemplates] = useState([]); @@ -288,14 +289,14 @@ export function CustomFormBuilder() { setTemplates((data ?? []) as unknown as SavedTemplate[]); }; - // Apply font style to editor DOM + // Apply font style + line height to editor DOM useEffect(() => { if (!editor) return; const el = editor.view.dom as HTMLElement; el.style.fontFamily = `"${fontFamily}", Georgia, serif`; el.style.fontSize = `${fontSize}pt`; - el.style.lineHeight = "1.5"; - }, [editor, fontFamily, fontSize]); + el.style.lineHeight = String(lineHeight); + }, [editor, fontFamily, fontSize, lineHeight]); const insertVar = (key: string) => { if (!editor) return; From 2cde2c246d98de73294abfe560378266d56b0e8f Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:41:09 +0000 Subject: [PATCH 05/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/custom-form-builder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index d9aec3c..418708e 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -538,7 +538,7 @@ export function CustomFormBuilder() { } doc.setFontSize(fontSize); - const lineH = fontSize * 1.4; + const lineH = fontSize * lineHeight; // Group runs into visual lines (split where applied text contains "\n"). const runsToLines = (runs: InlineRun[]): InlineRun[][] => { From f9420cb35066588b4643c0615d66e503bd7b60c0 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:41:24 +0000 Subject: [PATCH 06/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/custom-form-builder.tsx | 43 +++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index 418708e..46666ae 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -603,15 +603,46 @@ export function CustomFormBuilder() { let x = startX; if (align === "center") x = startX + (widthLimit - lineW) / 2; else if (align === "right") x = startX + (widthLimit - lineW); - for (const t of ln) { + // First pass: draw text. Track contiguous underline spans (incl. spaces between underlined tokens). + type Span = { x: number; w: number }; + const underlineSpans: Span[] = []; + let spanStart: number | null = null; + let spanWidth = 0; + const flushSpan = () => { + if (spanStart !== null && spanWidth > 0) { + underlineSpans.push({ x: spanStart, w: spanWidth }); + } + spanStart = null; + spanWidth = 0; + }; + for (let i = 0; i < ln.length; i++) { + const t = ln[i]; + const w = widthOf(t); doc.setFont(pdfFont, styleFor(t)); doc.text(t.text, x, yy); - if (t.underline && !t.isSpace) { - const w = widthOf(t); - doc.setLineWidth(0.5); - doc.line(x, yy + 1.5, x + w, yy + 1.5); + if (t.underline) { + // For a space, only continue an existing span if the next non-space token is also underlined. + if (t.isSpace) { + const next = ln[i + 1]; + if (spanStart !== null && next && next.underline) { + spanWidth += w; + } else { + flushSpan(); + } + } else { + if (spanStart === null) spanStart = x; + spanWidth += w; + } + } else { + flushSpan(); } - x += widthOf(t); + x += w; + } + flushSpan(); + // Second pass: draw underlines as continuous lines. + if (underlineSpans.length) { + doc.setLineWidth(0.5); + for (const s of underlineSpans) doc.line(s.x, yy + 1.5, s.x + s.w, yy + 1.5); } yy += lineH; } From 585d7138844603c39a4f8fe305336e720da84faf Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:41:36 +0000 Subject: [PATCH 07/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/custom-form-builder.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index 46666ae..8128e3c 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -909,6 +909,7 @@ export function CustomFormBuilder() { body_html: editor.getHTML(), font_family: fontFamily, font_size_pt: fontSize, + line_height: lineHeight, signature_blocks: [], fields: formFields as any, }; @@ -946,6 +947,7 @@ export function CustomFormBuilder() { setHideTitle(t.hide_title); setFontFamily(t.font_family); setFontSize(t.font_size_pt); + if (typeof t.line_height === "number" && t.line_height > 0) setLineHeight(t.line_height); editor.commands.setContent(t.body_html || "

"); const loadedFields = Array.isArray(t.fields) ? t.fields : []; setFormFields(loadedFields); From 22bc246beed174492ce71b3b26cf7c4149563394 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:41:53 +0000 Subject: [PATCH 08/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/custom-form-builder.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index 8128e3c..3f537e9 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -1045,7 +1045,7 @@ export function CustomFormBuilder() { setHomeowner(h); }} /> -
+
setTitle(e.target.value)} disabled={hideTitle} /> @@ -1076,6 +1076,17 @@ export function CustomFormBuilder() {
+
+ + +
From 43c0722ce28ba3b4e7bc0ee1a71d8d8fc46cdb8c Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:42:11 +0000 Subject: [PATCH 09/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/custom-form-builder.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index 3f537e9..8791872 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -742,11 +742,15 @@ export function CustomFormBuilder() { const segments = getRenderedSegments(); const children: Array = []; + // 240 twentieths-of-a-point = single spacing; multiply by lineHeight. + const docxLineSpacing = { line: Math.round(lineHeight * 240), lineRule: "auto" as const }; + if (!hideTitle && title.trim()) { children.push( new DocxParagraph({ heading: HeadingLevel.HEADING_1, alignment: AlignmentType.CENTER, + spacing: docxLineSpacing, children: [ new TextRun({ text: applyVariables(title, ctx), From cdf56fdec245dc09e51bd4377e75bc1f6fcc44ec Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:42:22 +0000 Subject: [PATCH 10/10] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/custom-form-builder.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/forms/custom-form-builder.tsx b/src/components/forms/custom-form-builder.tsx index 8791872..31c5492 100644 --- a/src/components/forms/custom-form-builder.tsx +++ b/src/components/forms/custom-form-builder.tsx @@ -822,6 +822,7 @@ export function CustomFormBuilder() { margins: { top: 80, bottom: 80, left: 120, right: 120 }, children: [ new DocxParagraph({ + spacing: docxLineSpacing, children: buildDocxRuns(cell?.runs ?? []), }), ], @@ -847,6 +848,7 @@ export function CustomFormBuilder() { children.push( new DocxParagraph({ alignment: align, + spacing: docxLineSpacing, indent: indentTwips ? { left: indentTwips } : undefined, children: buildDocxRuns(seg.runs, { italic: seg.caption }), }),