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 headers = ['Name', 'Email', 'Date Submitted'];
const headers = ['Name', 'Email', 'Phone', 'Date Submitted'];
const csvContent = [
headers.join(','),
...leads.map(lead =>
[
`"${lead.name}"`,
`"${lead.email}"`,
`"${lead.phone || ''}"`,
`"${format(new Date(lead.createdAt), 'PPP p')}"`
].join(',')
)
@@ -85,6 +86,7 @@ export default function LeadsPage() {
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Phone</TableHead>
<TableHead className="text-right">Date Submitted</TableHead>
</TableRow>
</TableHeader>
@@ -95,6 +97,7 @@ export default function LeadsPage() {
<TableRow key={i}>
<TableCell><Skeleton className="h-5 w-24" /></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>
</TableRow>
))}
@@ -104,6 +107,7 @@ export default function LeadsPage() {
<TableRow key={lead.id}>
<TableCell className="font-medium">{lead.name}</TableCell>
<TableCell>{lead.email}</TableCell>
<TableCell>{lead.phone || 'N/A'}</TableCell>
<TableCell className="text-right">
{format(new Date(lead.createdAt), 'PPP p')}
</TableCell>
@@ -111,7 +115,7 @@ export default function LeadsPage() {
))
) : (
<TableRow>
<TableCell colSpan={3} className="text-center h-24">
<TableCell colSpan={4} className="text-center h-24">
No leads yet.
</TableCell>
</TableRow>
@@ -3,8 +3,8 @@
import type { FormData } from './cost-estimator-form';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Info, Mail } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Info, Mail, User, Phone, Briefcase, Clock } from 'lucide-react';
import React, { useState, useTransition } from 'react';
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import {
@@ -20,6 +20,7 @@ import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { useToast } from '@/hooks/use-toast';
import { sendEstimateEmail } from '@/lib/actions/send-estimate';
import { Separator } from '../ui/separator';
type Step11Props = {
onReset: () => void;
@@ -123,12 +124,13 @@ export function Step11Results({ onReset, customHours, readyMadeHours, formData }
const form = new FormData(event.currentTarget);
const name = form.get('name') as string;
const email = form.get('email') as string;
const phone = form.get('phone') as string;
if (!name || !email) {
toast({
variant: "destructive",
title: "Missing Information",
description: "Please enter both your name and email.",
description: "Please enter your name and email.",
});
return;
}
@@ -137,6 +139,7 @@ export function Step11Results({ onReset, customHours, readyMadeHours, formData }
const result = await sendEstimateEmail({
name,
email,
phone,
customHours,
readyMadeHours,
formData,
@@ -212,37 +215,66 @@ export function Step11Results({ onReset, customHours, readyMadeHours, formData }
<DialogTrigger asChild>
<Button size="lg" variant="outline">
<Mail className="mr-2 h-4 w-4" />
Send Estimate on Email
Send Estimate to Email
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<form onSubmit={handleSubmit}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Send Estimate</DialogTitle>
<DialogTitle className="font-headline text-2xl">Get Your Detailed Estimate</DialogTitle>
<DialogDescription>
Enter your name and email to receive the estimate PDF.
Fill out the form below and we'll email you a PDF copy of your estimate.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Name
</Label>
<Input id="name" name="name" className="col-span-3" required />
<div className="space-y-4 py-2">
<Card className="bg-muted/50 border-dashed">
<CardHeader className="flex-row items-center gap-4 space-y-0 p-4">
<Briefcase className="h-8 w-8 text-primary" />
<CardTitle className="text-lg font-medium">Estimate Summary</CardTitle>
</CardHeader>
<CardContent className="p-4 pt-0 space-y-2 text-sm">
<div className="flex justify-between items-center">
<span className="text-muted-foreground flex items-center gap-2"><Clock className="h-4 w-4" /> Custom Development</span>
<span className="font-bold">{customHours}+ hours</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="email" className="text-right">
Email
</Label>
<Input id="email" name="email" type="email" className="col-span-3" required />
<div className="flex justify-between items-center">
<span className="text-muted-foreground flex items-center gap-2"><Clock className="h-4 w-4" /> Ready-Made Tools</span>
<span className="font-bold">{readyMadeHours}+ hours</span>
</div>
</CardContent>
</Card>
<Separator />
<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" disabled={isPending}>
<Button type="submit" form="send-estimate-form" className="w-full" size="lg" disabled={isPending}>
{isPending ? "Sending..." : "Send to Email"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<Button onClick={onReset} size="lg" variant="link" className="text-muted-foreground">
+2 -1
View File
@@ -7,13 +7,14 @@ export type Lead = {
id: number;
name: string;
email: string;
phone: string | null;
createdAt: string;
};
export async function getLeads(): Promise<Lead[]> {
try {
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[];
return leads;
+15 -8
View File
@@ -11,6 +11,7 @@ import type { FormData } from '@/components/cost-estimator/cost-estimator-form';
const inputSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
phone: z.string().optional(),
customHours: z.number(),
readyMadeHours: z.number(),
formData: z.any(), // Keeping this flexible for now
@@ -39,7 +40,7 @@ async function getSmtpSettings() {
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 page = pdfDoc.addPage();
const { width, height } = page.getSize();
@@ -61,10 +62,16 @@ async function createEstimatePdf(data: InputType): Promise<Buffer> {
y -= 30;
// 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;
page.drawText(`Date: ${new Date().toLocaleDateString()}`, { x: 50, y, font, size: 12, color: rgb(0.3, 0.3, 0.3) });
y -= 40;
page.drawText(email, { x: 50, y, font: font, size: 12, color: rgb(0.3, 0.3, 0.3) });
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
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);
}
async function saveLead(name: string, email: string) {
async function saveLead(name: string, email: string, phone?: string) {
try {
const stmt = db.prepare('INSERT INTO leads (name, email) VALUES (?, ?)');
stmt.run(name, email);
const stmt = db.prepare('INSERT INTO leads (name, email, phone) VALUES (?, ?, ?)');
stmt.run(name, email, phone || null);
console.log(`Lead saved: ${name}, ${email}`);
} catch (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);
// 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 };
+1
View File
@@ -34,6 +34,7 @@ db.exec(`
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);