make the send estimate modal more beautifull and informative, also besid
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
`);
|
||||
|
||||
Reference in New Issue
Block a user