Added filename prompt to forms

X-Lovable-Edit-ID: edt-4c79d619-bfd1-4629-b0b4-e587a8603608
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 19:15:09 +00:00
co-authored by renee-png
10 changed files with 63 additions and 15 deletions
+4 -1
View File
@@ -31,6 +31,7 @@ import {
import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/lib/auth";
import { formatCurrency, formatDate } from "@/lib/format";
import { promptFilename } from "@/lib/prompt-filename";
import {
ArrowLeft,
Calculator,
@@ -544,9 +545,11 @@ export function CollectionDetail({
].join(",")),
].join("\n");
const blob = new Blob([rows], { type: "text/csv" });
const name = promptFilename(`Statement_${ho?.last_name || "homeowner"}`, "csv");
if (!name) return;
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = `Statement_${ho?.last_name || "homeowner"}.csv`;
a.download = `${name}.csv`;
a.click();
};
+11 -6
View File
@@ -91,6 +91,7 @@ import {
} from "docx";
import { toast } from "sonner";
import { supabase } from "@/integrations/supabase/client";
import { promptFilename } from "@/lib/prompt-filename";
// Paragraph extension with indent (px) + caption flag, persisted as inline styles
const PaddedParagraph = Paragraph.extend({
@@ -1026,19 +1027,21 @@ export function CustomFormBuilder() {
y += seg.caption ? 2 : 4;
}
const filenameBase = (renderTitle(ctx) || "Custom_Form").replace(/\s+/g, "_");
const defaultName = (renderTitle(ctx) || "Custom_Form").replace(/\s+/g, "_");
if (saveToCase && selectedCaseId) {
const blob = doc.output("blob");
await savePdfToDocuments({
blob,
caseId: selectedCaseId,
name: `${filenameBase}.pdf`,
name: `${defaultName}.pdf`,
mimeType: "application/pdf",
});
toast.success("Saved to case files");
setSaveToCaseOpen(false);
} else {
doc.save(`${filenameBase}.pdf`);
const name = promptFilename(defaultName, "pdf");
if (!name) return;
doc.save(`${name}.pdf`);
toast.success("PDF downloaded");
}
};
@@ -1236,22 +1239,24 @@ export function CustomFormBuilder() {
});
const blob = await Packer.toBlob(docx);
const filenameBase = (renderTitle(ctx) || "Custom_Form").replace(/\s+/g, "_");
const defaultName = (renderTitle(ctx) || "Custom_Form").replace(/\s+/g, "_");
if (saveToCase && selectedCaseId) {
await savePdfToDocuments({
blob,
caseId: selectedCaseId,
name: `${filenameBase}.docx`,
name: `${defaultName}.docx`,
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
toast.success("Saved to case files");
setSaveToCaseOpen(false);
} else {
const name = promptFilename(defaultName, "docx");
if (!name) return;
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${filenameBase}.docx`;
a.download = `${name}.docx`;
a.click();
URL.revokeObjectURL(url);
toast.success("DOCX downloaded");
+4 -1
View File
@@ -15,6 +15,7 @@ import {
type FirmInfo,
} from "@/lib/forms-shared";
import { fillTokens, loadFormTemplate, type FormTemplateConfig } from "@/lib/form-templates";
import { promptFilename } from "@/lib/prompt-filename";
import { jsPDF } from "jspdf";
import { toast } from "sonner";
@@ -266,7 +267,9 @@ export function EstoppelForm() {
doc.text(al, margin, y);
}
doc.save(`Estoppel_${parcelId || "draft"}_${date}.pdf`);
const name = promptFilename(`Estoppel_${parcelId || "draft"}_${date}`, "pdf");
if (!name) return;
doc.save(`${name}.pdf`);
toast.success("Estoppel PDF downloaded");
};
+4 -1
View File
@@ -16,6 +16,7 @@ import {
type FirmInfo,
} from "@/lib/forms-shared";
import { buildLetterTokens, loadFormTemplate, renderLetterPdf, type FormTemplateConfig } from "@/lib/form-templates";
import { promptFilename } from "@/lib/prompt-filename";
import { toast } from "sonner";
export function ItfForm() {
@@ -106,7 +107,9 @@ export function ItfForm() {
filename: `ITF_${ownerFullName(homeowner) || "draft"}_${letterDate}`,
});
doc.save(`ITF_${ownerFullName(homeowner) || "draft"}_${letterDate}.pdf`);
const name = promptFilename(`ITF_${ownerFullName(homeowner) || "draft"}_${letterDate}`, "pdf");
if (!name) return;
doc.save(`${name}.pdf`);
toast.success("ITF PDF downloaded");
};
+4 -1
View File
@@ -16,6 +16,7 @@ import {
type FirmInfo,
} from "@/lib/forms-shared";
import { buildLetterTokens, loadFormTemplate, renderLetterPdf, type FormTemplateConfig } from "@/lib/form-templates";
import { promptFilename } from "@/lib/prompt-filename";
import { toast } from "sonner";
export function ItlForm() {
@@ -95,7 +96,9 @@ export function ItlForm() {
filename: `ITL_${ownerFullName(homeowner) || "draft"}_${letterDate}`,
});
doc.save(`ITL_${ownerFullName(homeowner) || "draft"}_${letterDate}.pdf`);
const name = promptFilename(`ITL_${ownerFullName(homeowner) || "draft"}_${letterDate}`, "pdf");
if (!name) return;
doc.save(`${name}.pdf`);
toast.success("ITL PDF downloaded");
};
+4 -1
View File
@@ -17,6 +17,7 @@ import {
type FirmInfo,
} from "@/lib/forms-shared";
import { fillTokens, loadFormTemplate, type FormTemplateConfig } from "@/lib/form-templates";
import { promptFilename } from "@/lib/prompt-filename";
import { supabase } from "@/integrations/supabase/client";
import { jsPDF } from "jspdf";
import { toast } from "sonner";
@@ -290,7 +291,9 @@ export function LetterGenerator() {
}
}
doc.save(`Letter_${date}.pdf`);
const name = promptFilename(`Letter_${date}`, "pdf");
if (!name) return;
doc.save(`${name}.pdf`);
toast.success("Letter PDF downloaded");
};
+4 -1
View File
@@ -15,6 +15,7 @@ import {
type FirmInfo,
} from "@/lib/forms-shared";
import { buildLetterTokens, loadFormTemplate, renderLetterPdf, type FormTemplateConfig } from "@/lib/form-templates";
import { promptFilename } from "@/lib/prompt-filename";
import { toast } from "sonner";
interface Item {
@@ -94,7 +95,9 @@ export function NolaForm() {
filename: `NOLA_${ownerFullName(homeowner) || "draft"}_${date}`,
});
doc.save(`NOLA_${ownerFullName(homeowner) || "draft"}_${date}.pdf`);
const name = promptFilename(`NOLA_${ownerFullName(homeowner) || "draft"}_${date}`, "pdf");
if (!name) return;
doc.save(`${name}.pdf`);
toast.success("NOLA PDF downloaded");
};
+17
View File
@@ -0,0 +1,17 @@
/**
* Prompts the user for a filename before download. Returns the chosen base
* name (without extension) sanitized for filesystem use, or null if cancelled.
*
* @param defaultName Suggested base name (no extension)
* @param ext Extension shown to the user, e.g. "pdf", "docx", "csv"
*/
export function promptFilename(defaultName: string, ext: string): string | null {
const sanitize = (s: string) => s.replace(/[\\/:*?"<>|]+/g, "_").trim();
const cleanDefault = sanitize(defaultName).replace(new RegExp(`\\.${ext}$`, "i"), "") || "Document";
const input = typeof window !== "undefined"
? window.prompt(`File name (.${ext})`, cleanDefault)
: cleanDefault;
if (input === null) return null; // user cancelled
const cleaned = sanitize(input).replace(new RegExp(`\\.${ext}$`, "i"), "");
return cleaned || cleanDefault;
}
+7 -2
View File
@@ -22,6 +22,7 @@ import { useAuth } from "@/lib/auth";
import { toast } from "sonner";
import { Download, FileText, Save, Loader2, Plus, Trash2, PenLine, Users, Variable, Copy } from "lucide-react";
import { applyVars, buildChips, buildVarMap, loadPleadingContext, type LoadedContext, type VarChip } from "@/lib/pleading-variables";
import { promptFilename } from "@/lib/prompt-filename";
import { Badge } from "@/components/ui/badge";
export const Route = createFileRoute("/documents/pleading/new")({
@@ -244,11 +245,15 @@ function PleadingNewPage() {
const headerLine2 = `IN AND FOR ${county.toUpperCase()} COUNTY, FLORIDA`;
const onDownload = async () => {
await downloadPleading(input, docName || "Pleading");
const name = promptFilename(docName || "Pleading", "docx");
if (!name) return;
await downloadPleading(input, name);
};
const onDownloadPdf = async () => {
await downloadPleadingPdf(input, docName || "Pleading");
const name = promptFilename(docName || "Pleading", "pdf");
if (!name) return;
await downloadPleadingPdf(input, name);
};
const onSave = async () => {
@@ -12,6 +12,7 @@ import { useAuth } from "@/lib/auth";
import { toast } from "sonner";
import { Download, Loader2, Save, Trash2 } from "lucide-react";
import { downloadGeneric } from "@/lib/docx-pleading";
import { promptFilename } from "@/lib/prompt-filename";
import { Packer } from "docx";
export const Route = createFileRoute("/documents/templates/$templateId")({
@@ -120,9 +121,11 @@ function TemplateDetailPage() {
const fontSizePt: number = tpl?.font_size_pt || 12;
const downloadOnly = async () => {
const name = promptFilename(docName || "Document", "docx");
if (!name) return;
await downloadGeneric(
{ title: isLetter ? undefined : docName, body: merged, fontFamily, fontSizePt },
docName || "Document",
name,
);
};