feat: add email message field to invoices and update related components
This commit is contained in:
@@ -7,6 +7,16 @@ import { env } from "~/env";
|
||||
import { generateInvoicePDFBlob } from "~/lib/pdf-export";
|
||||
import { generateInvoiceEmailTemplate } from "~/lib/email-templates";
|
||||
|
||||
function plainTextToHtml(value: string) {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
.replace(/\n/g, "<br>");
|
||||
}
|
||||
|
||||
export const emailRouter = createTRPCRouter({
|
||||
sendInvoice: protectedProcedure
|
||||
.input(
|
||||
@@ -106,7 +116,6 @@ export const emailRouter = createTRPCRouter({
|
||||
totalAmount: invoice.totalAmount,
|
||||
taxRate: invoice.taxRate,
|
||||
currency: invoice.currency,
|
||||
notes: invoice.notes,
|
||||
client: {
|
||||
name: invoice.client.name,
|
||||
email: invoice.client.email,
|
||||
@@ -115,7 +124,11 @@ export const emailRouter = createTRPCRouter({
|
||||
items: invoice.items,
|
||||
},
|
||||
customContent: input.customContent,
|
||||
customMessage: input.customMessage,
|
||||
customMessage:
|
||||
input.customMessage ??
|
||||
(invoice.emailMessage
|
||||
? plainTextToHtml(invoice.emailMessage)
|
||||
: undefined),
|
||||
userName,
|
||||
userEmail,
|
||||
baseUrl: process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000",
|
||||
|
||||
@@ -29,6 +29,7 @@ const createInvoiceSchema = z.object({
|
||||
dueDate: z.date(),
|
||||
status: z.enum(["draft", "sent", "paid"]).default("draft"),
|
||||
notes: z.string().optional().or(z.literal("")),
|
||||
emailMessage: z.string().optional().or(z.literal("")),
|
||||
taxRate: z.number().min(0).max(100).default(0),
|
||||
currency: z.string().length(3).default("USD"),
|
||||
items: z.array(invoiceItemSchema).min(1, "At least one item is required"),
|
||||
@@ -156,6 +157,8 @@ export const invoicesRouter = createTRPCRouter({
|
||||
? null
|
||||
: invoiceData.businessId,
|
||||
notes: invoiceData.notes === "" ? null : invoiceData.notes,
|
||||
emailMessage:
|
||||
invoiceData.emailMessage === "" ? null : invoiceData.emailMessage,
|
||||
};
|
||||
|
||||
// Verify business exists and belongs to user (if provided)
|
||||
@@ -263,6 +266,14 @@ export const invoicesRouter = createTRPCRouter({
|
||||
...(invoiceData.notes !== undefined
|
||||
? { notes: invoiceData.notes === "" ? null : invoiceData.notes }
|
||||
: {}),
|
||||
...(invoiceData.emailMessage !== undefined
|
||||
? {
|
||||
emailMessage:
|
||||
invoiceData.emailMessage === ""
|
||||
? null
|
||||
: invoiceData.emailMessage,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
// Verify invoice exists and belongs to user
|
||||
|
||||
@@ -94,6 +94,7 @@ const InvoiceBackupSchema = z.object({
|
||||
totalAmount: z.number().default(0),
|
||||
taxRate: z.number().default(0),
|
||||
notes: z.string().optional(),
|
||||
emailMessage: z.string().optional(),
|
||||
items: z.array(InvoiceItemBackupSchema),
|
||||
});
|
||||
|
||||
@@ -562,6 +563,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
totalAmount: invoice.totalAmount,
|
||||
taxRate: invoice.taxRate,
|
||||
notes: invoice.notes ?? undefined,
|
||||
emailMessage: invoice.emailMessage ?? undefined,
|
||||
items: invoice.items,
|
||||
})),
|
||||
};
|
||||
@@ -641,6 +643,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
totalAmount: invoiceData.totalAmount,
|
||||
taxRate: invoiceData.taxRate,
|
||||
notes: invoiceData.notes,
|
||||
emailMessage: invoiceData.emailMessage,
|
||||
createdById: userId,
|
||||
})
|
||||
.returning({ id: invoices.id });
|
||||
|
||||
@@ -237,6 +237,9 @@ async function isMigrationApplied(client: Pool, tag: string): Promise<boolean> {
|
||||
"pdfTemplate",
|
||||
);
|
||||
}
|
||||
if (tag === "0007_invoice_email_message") {
|
||||
return columnExists(client, "public", "beenvoice_invoice", "emailMessage");
|
||||
}
|
||||
// Unknown migration — assume not applied so it runs
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -320,6 +320,7 @@ export const invoices = createTable(
|
||||
totalAmount: d.real().notNull().default(0),
|
||||
taxRate: d.real().notNull().default(0.0),
|
||||
notes: d.varchar({ length: 1000 }),
|
||||
emailMessage: d.varchar({ length: 2000 }),
|
||||
currency: d.varchar({ length: 3 }).default("USD").notNull(),
|
||||
createdById: d
|
||||
.varchar({ length: 255 })
|
||||
|
||||
Reference in New Issue
Block a user