make the send estimate modal more beautifull and informative, also besid

This commit is contained in:
Leon Serfaty G
2025-07-18 04:46:40 +00:00
parent 2d2d283588
commit d21fc7fb52
5 changed files with 88 additions and 43 deletions
+6 -2
View File
@@ -39,13 +39,14 @@ export default function LeadsPage() {
}, []); }, []);
const handleExportCsv = () => { const handleExportCsv = () => {
const headers = ['Name', 'Email', 'Date Submitted']; const headers = ['Name', 'Email', 'Phone', 'Date Submitted'];
const csvContent = [ const csvContent = [
headers.join(','), headers.join(','),
...leads.map(lead => ...leads.map(lead =>
[ [
`"${lead.name}"`, `"${lead.name}"`,
`"${lead.email}"`, `"${lead.email}"`,
`"${lead.phone || ''}"`,
`"${format(new Date(lead.createdAt), 'PPP p')}"` `"${format(new Date(lead.createdAt), 'PPP p')}"`
].join(',') ].join(',')
) )
@@ -85,6 +86,7 @@ export default function LeadsPage() {
<TableRow> <TableRow>
<TableHead>Name</TableHead> <TableHead>Name</TableHead>
<TableHead>Email</TableHead> <TableHead>Email</TableHead>
<TableHead>Phone</TableHead>
<TableHead className="text-right">Date Submitted</TableHead> <TableHead className="text-right">Date Submitted</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -95,6 +97,7 @@ export default function LeadsPage() {
<TableRow key={i}> <TableRow key={i}>
<TableCell><Skeleton className="h-5 w-24" /></TableCell> <TableCell><Skeleton className="h-5 w-24" /></TableCell>
<TableCell><Skeleton className="h-5 w-40" /></TableCell> <TableCell><Skeleton className="h-5 w-40" /></TableCell>
<TableCell><Skeleton className="h-5 w-32" /></TableCell>
<TableCell className="text-right"><Skeleton className="h-5 w-32 ml-auto" /></TableCell> <TableCell className="text-right"><Skeleton className="h-5 w-32 ml-auto" /></TableCell>
</TableRow> </TableRow>
))} ))}
@@ -104,6 +107,7 @@ export default function LeadsPage() {
<TableRow key={lead.id}> <TableRow key={lead.id}>
<TableCell className="font-medium">{lead.name}</TableCell> <TableCell className="font-medium">{lead.name}</TableCell>
<TableCell>{lead.email}</TableCell> <TableCell>{lead.email}</TableCell>
<TableCell>{lead.phone || 'N/A'}</TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
{format(new Date(lead.createdAt), 'PPP p')} {format(new Date(lead.createdAt), 'PPP p')}
</TableCell> </TableCell>
@@ -111,7 +115,7 @@ export default function LeadsPage() {
)) ))
) : ( ) : (
<TableRow> <TableRow>
<TableCell colSpan={3} className="text-center h-24"> <TableCell colSpan={4} className="text-center h-24">
No leads yet. No leads yet.
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -3,8 +3,8 @@
import type { FormData } from './cost-estimator-form'; import type { FormData } from './cost-estimator-form';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Info, Mail } from 'lucide-react'; import { Info, Mail, User, Phone, Briefcase, Clock } from 'lucide-react';
import React, { useState, useTransition } from 'react'; import React, { useState, useTransition } from 'react';
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'; import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { import {
@@ -20,6 +20,7 @@ import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { useToast } from '@/hooks/use-toast'; import { useToast } from '@/hooks/use-toast';
import { sendEstimateEmail } from '@/lib/actions/send-estimate'; import { sendEstimateEmail } from '@/lib/actions/send-estimate';
import { Separator } from '../ui/separator';
type Step11Props = { type Step11Props = {
onReset: () => void; onReset: () => void;
@@ -123,12 +124,13 @@ export function Step11Results({ onReset, customHours, readyMadeHours, formData }
const form = new FormData(event.currentTarget); const form = new FormData(event.currentTarget);
const name = form.get('name') as string; const name = form.get('name') as string;
const email = form.get('email') as string; const email = form.get('email') as string;
const phone = form.get('phone') as string;
if (!name || !email) { if (!name || !email) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Missing Information", title: "Missing Information",
description: "Please enter both your name and email.", description: "Please enter your name and email.",
}); });
return; return;
} }
@@ -137,6 +139,7 @@ export function Step11Results({ onReset, customHours, readyMadeHours, formData }
const result = await sendEstimateEmail({ const result = await sendEstimateEmail({
name, name,
email, email,
phone,
customHours, customHours,
readyMadeHours, readyMadeHours,
formData, formData,
@@ -212,37 +215,66 @@ export function Step11Results({ onReset, customHours, readyMadeHours, formData }
<DialogTrigger asChild> <DialogTrigger asChild>
<Button size="lg" variant="outline"> <Button size="lg" variant="outline">
<Mail className="mr-2 h-4 w-4" /> <Mail className="mr-2 h-4 w-4" />
Send Estimate on Email Send Estimate to Email
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="sm:max-w-md">
<form onSubmit={handleSubmit}> <DialogHeader>
<DialogHeader> <DialogTitle className="font-headline text-2xl">Get Your Detailed Estimate</DialogTitle>
<DialogTitle>Send Estimate</DialogTitle> <DialogDescription>
<DialogDescription> Fill out the form below and we'll email you a PDF copy of your estimate.
Enter your name and email to receive the estimate PDF. </DialogDescription>
</DialogDescription> </DialogHeader>
</DialogHeader>
<div className="grid gap-4 py-4"> <div className="space-y-4 py-2">
<div className="grid grid-cols-4 items-center gap-4"> <Card className="bg-muted/50 border-dashed">
<Label htmlFor="name" className="text-right"> <CardHeader className="flex-row items-center gap-4 space-y-0 p-4">
Name <Briefcase className="h-8 w-8 text-primary" />
</Label> <CardTitle className="text-lg font-medium">Estimate Summary</CardTitle>
<Input id="name" name="name" className="col-span-3" required /> </CardHeader>
</div> <CardContent className="p-4 pt-0 space-y-2 text-sm">
<div className="grid grid-cols-4 items-center gap-4"> <div className="flex justify-between items-center">
<Label htmlFor="email" className="text-right"> <span className="text-muted-foreground flex items-center gap-2"><Clock className="h-4 w-4" /> Custom Development</span>
Email <span className="font-bold">{customHours}+ hours</span>
</Label> </div>
<Input id="email" name="email" type="email" className="col-span-3" required /> <div className="flex justify-between items-center">
</div> <span className="text-muted-foreground flex items-center gap-2"><Clock className="h-4 w-4" /> Ready-Made Tools</span>
</div> <span className="font-bold">{readyMadeHours}+ hours</span>
<DialogFooter> </div>
<Button type="submit" disabled={isPending}> </CardContent>
{isPending ? "Sending..." : "Send to Email"} </Card>
</Button>
</DialogFooter> <Separator />
</form>
<form id="send-estimate-form" onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Full Name</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input id="name" name="name" placeholder="John Doe" className="pl-10" required />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email Address</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input id="email" name="email" type="email" placeholder="john.doe@example.com" className="pl-10" required />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="phone">Phone Number (Optional)</Label>
<div className="relative">
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input id="phone" name="phone" type="tel" placeholder="(123) 456-7890" className="pl-10" />
</div>
</div>
</form>
</div>
<DialogFooter>
<Button type="submit" form="send-estimate-form" className="w-full" size="lg" disabled={isPending}>
{isPending ? "Sending..." : "Send to Email"}
</Button>
</DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<Button onClick={onReset} size="lg" variant="link" className="text-muted-foreground"> <Button onClick={onReset} size="lg" variant="link" className="text-muted-foreground">
+2 -1
View File
@@ -7,13 +7,14 @@ export type Lead = {
id: number; id: number;
name: string; name: string;
email: string; email: string;
phone: string | null;
createdAt: string; createdAt: string;
}; };
export async function getLeads(): Promise<Lead[]> { export async function getLeads(): Promise<Lead[]> {
try { try {
const stmt = db.prepare( const stmt = db.prepare(
'SELECT id, name, email, createdAt FROM leads ORDER BY createdAt DESC' 'SELECT id, name, email, phone, createdAt FROM leads ORDER BY createdAt DESC'
); );
const leads = stmt.all() as Lead[]; const leads = stmt.all() as Lead[];
return leads; return leads;
+15 -8
View File
@@ -11,6 +11,7 @@ import type { FormData } from '@/components/cost-estimator/cost-estimator-form';
const inputSchema = z.object({ const inputSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
email: z.string().email(), email: z.string().email(),
phone: z.string().optional(),
customHours: z.number(), customHours: z.number(),
readyMadeHours: z.number(), readyMadeHours: z.number(),
formData: z.any(), // Keeping this flexible for now formData: z.any(), // Keeping this flexible for now
@@ -39,7 +40,7 @@ async function getSmtpSettings() {
async function createEstimatePdf(data: InputType): Promise<Buffer> { async function createEstimatePdf(data: InputType): Promise<Buffer> {
const { name, customHours, readyMadeHours, formData } = data; const { name, email, phone, customHours, readyMadeHours, formData } = data;
const pdfDoc = await PDFDocument.create(); const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage(); const page = pdfDoc.addPage();
const { width, height } = page.getSize(); const { width, height } = page.getSize();
@@ -61,10 +62,16 @@ async function createEstimatePdf(data: InputType): Promise<Buffer> {
y -= 30; y -= 30;
// Subheader // Subheader
page.drawText(`Project Estimate for: ${name}`, { x: 50, y, font: boldFont, size: 18 }); page.drawText('Project Estimate For:', { x: 50, y, font: boldFont, size: 18 });
y -= 25;
page.drawText(name, { x: 50, y, font: font, size: 14 });
y -= 20; y -= 20;
page.drawText(`Date: ${new Date().toLocaleDateString()}`, { x: 50, y, font, size: 12, color: rgb(0.3, 0.3, 0.3) }); page.drawText(email, { x: 50, y, font: font, size: 12, color: rgb(0.3, 0.3, 0.3) });
y -= 40; y -= 18;
if (phone) {
page.drawText(phone, { x: 50, y, font: font, size: 12, color: rgb(0.3, 0.3, 0.3) });
}
y -= 30;
// Summary // Summary
page.drawText('Estimate Summary', { x: 50, y, font: boldFont, size: 16 }); page.drawText('Estimate Summary', { x: 50, y, font: boldFont, size: 16 });
@@ -109,10 +116,10 @@ async function createEstimatePdf(data: InputType): Promise<Buffer> {
return Buffer.from(pdfBytes); return Buffer.from(pdfBytes);
} }
async function saveLead(name: string, email: string) { async function saveLead(name: string, email: string, phone?: string) {
try { try {
const stmt = db.prepare('INSERT INTO leads (name, email) VALUES (?, ?)'); const stmt = db.prepare('INSERT INTO leads (name, email, phone) VALUES (?, ?, ?)');
stmt.run(name, email); stmt.run(name, email, phone || null);
console.log(`Lead saved: ${name}, ${email}`); console.log(`Lead saved: ${name}, ${email}`);
} catch (error) { } catch (error) {
console.error('Failed to save lead:', error); console.error('Failed to save lead:', error);
@@ -160,7 +167,7 @@ export async function sendEstimateEmail(data: InputType): Promise<{ success: boo
await transporter.sendMail(mailOptions); await transporter.sendMail(mailOptions);
// Save lead after email is sent successfully // Save lead after email is sent successfully
await saveLead(validation.data.name, validation.data.email); await saveLead(validation.data.name, validation.data.email, validation.data.phone);
return { success: true }; return { success: true };
+1
View File
@@ -34,6 +34,7 @@ db.exec(`
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
email TEXT NOT NULL, email TEXT NOT NULL,
phone TEXT,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
) )
`); `);