Added ITF form, lineH, & fix

X-Lovable-Edit-ID: edt-a9ce4192-4132-46f7-88b8-c1fb57896273
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 02:42:52 +00:00
co-authored by renee-png
6 changed files with 350 additions and 262 deletions
-248
View File
@@ -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<ClientLite | null>(null);
const [clientId, setClientId] = useState("");
const [, setFirm] = useState<FirmInfo | null>(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 (
<div className="space-y-4">
<Card>
<CardContent className="pt-6 space-y-4">
<ClientHomeownerPicker
clientId={clientId}
homeownerId=""
onClientChange={(id, c) => {
setClientId(id);
setClient(c);
}}
onHomeownerChange={() => {}}
showHomeowner={false}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label>Mailing date</Label>
<Input
type="date"
value={mailingDate}
onChange={(e) => setMailingDate(e.target.value)}
/>
</div>
<div>
<Label>Meeting type</Label>
<Select value={meetingType} onValueChange={setMeetingType}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{MEETING_TYPES.map((t) => (
<SelectItem key={t} value={t}>
{t}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Label>Notice period</Label>
<Select value={noticeDays} onValueChange={setNoticeDays}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{NOTICE_DAYS.map((d) => (
<SelectItem key={d.value} value={d.value}>
{d.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label>Statute</Label>
<Select value={statute} onValueChange={setStatute}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{STATUTES.map((s) => (
<SelectItem key={s.value} value={s.value}>
{s.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label>County</Label>
<Input value={county} onChange={(e) => setCounty(e.target.value)} />
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Label>Signer name</Label>
<Input value={signerName} onChange={(e) => setSignerName(e.target.value)} />
</div>
<div>
<Label>Signer title</Label>
<Input value={signerTitle} onChange={(e) => setSignerTitle(e.target.value)} />
</div>
<div>
<Label>Credentials</Label>
<Input
value={signerCredentials}
onChange={(e) => setSignerCredentials(e.target.value)}
placeholder="e.g. LCAM"
/>
</div>
</div>
<div className="flex justify-end">
<Button onClick={handleExport}>
<FileDown className="h-4 w-4 mr-2" />
Export PDF
</Button>
</div>
</CardContent>
</Card>
</div>
);
}
+72 -11
View File
@@ -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);
@@ -217,6 +227,7 @@ export function CustomFormBuilder() {
const [customDefs, setCustomDefs] = useState<CustomFieldVar[]>([]);
const [fontFamily, setFontFamily] = useState("Bookman Old Style");
const [fontSize, setFontSize] = useState(12);
const [lineHeight, setLineHeight] = useState(1.5);
// Template management
const [templates, setTemplates] = useState<SavedTemplate[]>([]);
@@ -278,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;
@@ -527,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[][] => {
@@ -592,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;
}
@@ -700,11 +742,15 @@ export function CustomFormBuilder() {
const segments = getRenderedSegments();
const children: Array<DocxParagraph | DocxTable> = [];
// 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),
@@ -776,6 +822,7 @@ export function CustomFormBuilder() {
margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [
new DocxParagraph({
spacing: docxLineSpacing,
children: buildDocxRuns(cell?.runs ?? []),
}),
],
@@ -801,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 }),
}),
@@ -867,6 +915,7 @@ export function CustomFormBuilder() {
body_html: editor.getHTML(),
font_family: fontFamily,
font_size_pt: fontSize,
line_height: lineHeight,
signature_blocks: [],
fields: formFields as any,
};
@@ -904,6 +953,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 || "<p></p>");
const loadedFields = Array.isArray(t.fields) ? t.fields : [];
setFormFields(loadedFields);
@@ -1001,7 +1051,7 @@ export function CustomFormBuilder() {
setHomeowner(h);
}}
/>
<div className="grid grid-cols-1 md:grid-cols-[1fr_auto_auto_auto] gap-3 items-end">
<div className="grid grid-cols-1 md:grid-cols-[1fr_auto_auto_auto_auto] gap-3 items-end">
<div>
<Label>Document title</Label>
<Input value={title} onChange={(e) => setTitle(e.target.value)} disabled={hideTitle} />
@@ -1032,6 +1082,17 @@ export function CustomFormBuilder() {
</SelectContent>
</Select>
</div>
<div>
<Label className="text-xs">Line height</Label>
<Select value={String(lineHeight)} onValueChange={(v) => setLineHeight(parseFloat(v))}>
<SelectTrigger className="w-[90px] h-9"><SelectValue /></SelectTrigger>
<SelectContent>
{LINE_HEIGHTS.map((h) => (
<SelectItem key={h.value} value={String(h.value)}>{h.label}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
+270
View File
@@ -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<ClientLite | null>(null);
const [homeowner, setHomeowner] = useState<HomeownerLite | null>(null);
const [clientId, setClientId] = useState("");
const [homeownerId, setHomeownerId] = useState("");
const [firm, setFirm] = useState<FirmInfo | null>(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 (
<div className="space-y-4">
<Card>
<CardContent className="pt-6 space-y-4">
<ClientHomeownerPicker
clientId={clientId}
homeownerId={homeownerId}
onClientChange={(id, c) => {
setClientId(id);
setClient(c);
}}
onHomeownerChange={(id, h) => {
setHomeownerId(id);
setHomeowner(h);
}}
/>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Label>Letter date</Label>
<Input
type="date"
value={letterDate}
onChange={(e) => setLetterDate(e.target.value)}
/>
</div>
<div>
<Label>Cure deadline (auto +45 days)</Label>
<Input value={fmtDateLong(dueDate)} readOnly className="bg-muted" />
</div>
<div>
<Label>Certified mail #</Label>
<Input value={certifiedNo} onChange={(e) => setCertifiedNo(e.target.value)} />
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label>Lien recorded date</Label>
<Input
type="date"
value={lienRecordedDate}
onChange={(e) => setLienRecordedDate(e.target.value)}
/>
</div>
<div>
<Label>Lien book/page (or instrument #)</Label>
<Input
value={lienBookPage}
onChange={(e) => setLienBookPage(e.target.value)}
placeholder="e.g., Book 1234 / Page 567 or Inst # 2024000123"
/>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
<div>
<Label>Assessments</Label>
<Input
type="number"
step="0.01"
value={assessments}
onChange={(e) => setAssessments(e.target.value)}
/>
</div>
<div>
<Label>Interest</Label>
<Input
type="number"
step="0.01"
value={interest}
onChange={(e) => setInterest(e.target.value)}
/>
</div>
<div>
<Label>Late fees</Label>
<Input
type="number"
step="0.01"
value={lateFees}
onChange={(e) => setLateFees(e.target.value)}
/>
</div>
<div>
<Label>Admin/legal</Label>
<Input
type="number"
step="0.01"
value={adminFees}
onChange={(e) => setAdminFees(e.target.value)}
/>
</div>
<div>
<Label>Less payments</Label>
<Input
type="number"
step="0.01"
value={lessPayments}
onChange={(e) => setLessPayments(e.target.value)}
/>
</div>
</div>
<div className="flex items-center justify-between pt-2">
<span className="text-sm font-medium">Total due: ${fmtCurrency(total)}</span>
<Button onClick={handleExport}>
<FileDown className="h-4 w-4 mr-2" />
Export PDF
</Button>
</div>
</CardContent>
</Card>
</div>
);
}
+3
View File
@@ -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
+3 -3
View File
@@ -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() {
<TabsTrigger value="letters">Letters</TabsTrigger>
<TabsTrigger value="nola">NOLA</TabsTrigger>
<TabsTrigger value="itl">ITL</TabsTrigger>
<TabsTrigger value="affidavit">Affidavit</TabsTrigger>
<TabsTrigger value="itf">ITF</TabsTrigger>
<TabsTrigger value="estoppel">Estoppel</TabsTrigger>
</TabsList>
<div className="mt-6">
@@ -37,7 +37,7 @@ function FormsPage() {
<TabsContent value="letters"><LetterGenerator /></TabsContent>
<TabsContent value="nola"><NolaForm /></TabsContent>
<TabsContent value="itl"><ItlForm /></TabsContent>
<TabsContent value="affidavit"><AffidavitForm /></TabsContent>
<TabsContent value="itf"><ItfForm /></TabsContent>
<TabsContent value="estoppel"><EstoppelForm /></TabsContent>
</div>
</Tabs>
@@ -0,0 +1,2 @@
ALTER TABLE public.custom_form_templates
ADD COLUMN IF NOT EXISTS line_height NUMERIC NOT NULL DEFAULT 1.5;