perfect but the email template has to have an attached pdf where the es

This commit is contained in:
Leon Serfaty G
2025-07-18 03:28:15 +00:00
parent 0b87cca169
commit a8eb3376fc
8 changed files with 468 additions and 33 deletions
+165
View File
@@ -0,0 +1,165 @@
'use server';
import { z } from 'zod';
import nodemailer from 'nodemailer';
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
import db from '@/lib/db';
import { getEmailTemplate } from './email';
import type { FormData } from '@/components/cost-estimator/cost-estimator-form';
const inputSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
customHours: z.number(),
readyMadeHours: z.number(),
formData: z.any(), // Keeping this flexible for now
});
type InputType = z.infer<typeof inputSchema>;
// Helper to get SMTP settings from the database
async function getSmtpSettings() {
const stmt = db.prepare("SELECT key, value FROM settings WHERE key LIKE 'smtp_%'");
const settings = stmt.all() as { key: string, value: string }[];
const config = settings.reduce((acc, setting) => {
acc[setting.key.replace('smtp_', '')] = setting.value;
return acc;
}, {} as Record<string, string>);
return {
host: config.server,
port: parseInt(config.port || '587', 10),
secure: parseInt(config.port || '587', 10) === 465, // true for 465, false for other ports
auth: {
user: config.username,
pass: config.password,
},
};
}
async function createEstimatePdf(data: InputType): Promise<Buffer> {
const { name, customHours, readyMadeHours, formData } = data;
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage();
const { width, height } = page.getSize();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const brandColor = rgb(0.16, 0.47, 0.94); // #2979FF
let y = height - 50;
// Header
page.drawText('EstimateFlow', { x: 50, y, font: boldFont, size: 24, color: brandColor });
y -= 20;
page.drawLine({
start: { x: 50, y: y },
end: { x: width - 50, y: y },
thickness: 2,
color: brandColor,
});
y -= 30;
// Subheader
page.drawText(`Project Estimate for: ${name}`, { x: 50, y, font: boldFont, size: 18 });
y -= 20;
page.drawText(`Date: ${new Date().toLocaleDateString()}`, { x: 50, y, font, size: 12, color: rgb(0.3, 0.3, 0.3) });
y -= 40;
// Summary
page.drawText('Estimate Summary', { x: 50, y, font: boldFont, size: 16 });
y -= 25;
const drawEstimateBox = (title: string, hours: number, description: string, boxY: number) => {
page.drawRectangle({
x: 50,
y: boxY - 80,
width: width - 100,
height: 90,
borderColor: rgb(0.9, 0.9, 0.9),
borderWidth: 1,
color: rgb(0.98, 0.98, 0.98),
borderRadius: 5
});
page.drawText(title, { x: 65, y: boxY, font: boldFont, size: 14 });
page.drawText(`${hours}+ hours`, { x: 65, y: boxY - 30, font: boldFont, size: 28, color: brandColor });
page.drawText(description, { x: 65, y: boxY - 60, font, size: 10, color: rgb(0.4, 0.4, 0.4) });
}
drawEstimateBox('Custom Development', customHours, 'A fully custom solution tailored to your specific needs.', y);
y -= 110;
drawEstimateBox('Ready-Made Tools', readyMadeHours, 'Leveraging pre-built components for a faster turnaround.', y);
y -= 110;
// Disclaimer
page.drawText('Disclaimer:', { x: 50, y, font: boldFont, size: 12 });
y -= 20;
const disclaimer = 'This is a preliminary estimate based on the provided selections. The final cost may vary after a detailed project analysis and scope definition. Please contact us for a comprehensive quote.';
page.drawText(disclaimer, {
x: 50,
y,
font,
size: 10,
color: rgb(0.5, 0.5, 0.5),
lineHeight: 14,
maxWidth: width - 100
});
const pdfBytes = await pdfDoc.save();
return Buffer.from(pdfBytes);
}
export async function sendEstimateEmail(data: InputType): Promise<{ success: boolean, message?: string }> {
const validation = inputSchema.safeParse(data);
if (!validation.success) {
return { success: false, message: 'Invalid data provided.' };
}
try {
const smtpSettings = await getSmtpSettings();
if (!smtpSettings.host || !smtpSettings.auth.user || !smtpSettings.auth.pass) {
return { success: false, message: 'SMTP settings are not configured. Please configure them in the admin panel.' };
}
const transporter = nodemailer.createTransport(smtpSettings);
await transporter.verify();
const pdfBuffer = await createEstimatePdf(validation.data);
const emailTemplate = await getEmailTemplate();
const mailOptions = {
from: `EstimateFlow <${smtpSettings.auth.user}>`,
to: validation.data.email,
subject: emailTemplate.subject.replace('[User Name]', validation.data.name),
html: emailTemplate.body.replace('[User Name]', validation.data.name).replace('[EstimateDetails]', `
<p style="font-size: 16px; line-height: 1.6;">
Please find your detailed project estimate attached to this email as a PDF.
</p>
`),
attachments: [
{
filename: 'EstimateFlow_Project_Estimate.pdf',
content: pdfBuffer,
contentType: 'application/pdf',
},
],
};
await transporter.sendMail(mailOptions);
return { success: true };
} catch (error: any) {
console.error('Failed to send estimate email:', error);
if (error.code === 'ECONNREFUSED') {
return { success: false, message: 'Could not connect to SMTP server. Check server address and port.' };
}
if (error.code === 'EAUTH') {
return { success: false, message: 'SMTP authentication failed. Check username and password.' };
}
return { success: false, message: error.message || 'An unexpected error occurred while sending the email.' };
}
}