"use client"; import { useState } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "~/components/ui/dialog"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; import { Button } from "~/components/ui/button"; import { EmailComposer } from "./email-composer"; import { EmailPreview } from "./email-preview"; import { toast } from "sonner"; import { api } from "~/trpc/react"; import { Send, Loader2, Eye, Edit3, CheckCircle, AlertTriangle, Mail, } from "lucide-react"; import { Alert, AlertDescription } from "~/components/ui/alert"; interface SendEmailDialogProps { invoiceId: string; trigger: React.ReactNode; invoice?: { id: string; invoiceNumber: string; issueDate: Date; dueDate: Date; status: string; taxRate: number; currency?: string | null; notes?: string | null; client?: { name: string; email: string | null; }; business?: { name: string; email: string | null; }; items?: Array<{ id: string; date?: Date; description?: string; hours: number; rate: number; amount?: number; }>; }; onEmailSent?: () => void; } export function SendEmailDialog({ invoiceId, trigger, invoice, onEmailSent, }: SendEmailDialogProps) { const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState("compose"); const [isSending, setIsSending] = useState(false); const [isConfirming, setIsConfirming] = useState(false); // Email content state const [subject, setSubject] = useState(() => invoice ? `Invoice ${invoice.invoiceNumber} from ${invoice.business?.name ?? "Your Business"}` : "Invoice from Your Business", ); const [ccEmail, setCcEmail] = useState(""); const [bccEmail, setBccEmail] = useState(""); const [customMessage, setCustomMessage] = useState(""); const [emailContent, setEmailContent] = useState(() => { const getTimeOfDayGreeting = () => { const hour = new Date().getHours(); if (hour < 12) return "Good morning"; if (hour < 17) return "Good afternoon"; return "Good evening"; }; const formatDate = (date: Date) => { return new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", day: "numeric", }).format(new Date(date)); }; if (!invoice) return ""; const businessName = invoice.business?.name ?? "Your Business"; const issueDate = formatDate(invoice.issueDate); // Calculate total from items const subtotal = invoice.items?.reduce((sum, item) => sum + item.hours * item.rate, 0) ?? 0; const taxAmount = subtotal * (invoice.taxRate / 100); const total = subtotal + taxAmount; return `

${getTimeOfDayGreeting()},

I hope this email finds you well. Please find attached invoice ${invoice.invoiceNumber} dated ${issueDate}.

The invoice details are as follows:

Please let me know if you have any questions or need any clarification regarding this invoice. I appreciate your prompt attention to this matter.

Thank you for your business!

Best regards,
${businessName}

`; }); // Get utils for cache invalidation const utils = api.useUtils(); // Email sending mutation const sendEmailMutation = api.email.sendInvoice.useMutation({ onSuccess: (data) => { toast.success("Email sent successfully!", { description: data.message, duration: 5000, }); // Reset state and close dialog setIsOpen(false); setActiveTab("compose"); setIsSending(false); setIsConfirming(false); // Refresh invoice data void utils.invoices.getById.invalidate({ id: invoiceId }); // Callback for parent component onEmailSent?.(); }, onError: (error) => { console.error("Email send error:", error); let errorMessage = "Failed to send invoice email"; let errorDescription = error.message; if (error.message.includes("Invalid recipient")) { errorMessage = "Invalid Email Address"; errorDescription = "Please check the client's email address and try again."; } else if (error.message.includes("domain not verified")) { errorMessage = "Email Configuration Issue"; errorDescription = "Please contact support to configure email sending."; } else if (error.message.includes("rate limit")) { errorMessage = "Too Many Emails"; errorDescription = "Please wait a moment before sending another email."; } else if (error.message.includes("no email address")) { errorMessage = "No Email Address"; errorDescription = "This client doesn't have an email address on file."; } toast.error(errorMessage, { description: errorDescription, duration: 6000, }); setIsSending(false); setIsConfirming(false); }, }); const handleSendEmail = async () => { if (!invoice?.client?.email || invoice.client.email.trim() === "") { toast.error("No email address", { description: "This client doesn't have an email address on file.", }); return; } if (!subject.trim()) { toast.error("Subject required", { description: "Please enter an email subject before sending.", }); return; } if (!emailContent.trim()) { toast.error("Message required", { description: "Please enter an email message before sending.", }); return; } setIsSending(true); try { // Use the enhanced API with custom subject and content await sendEmailMutation.mutateAsync({ invoiceId, customSubject: subject, customContent: emailContent, customMessage: customMessage.trim() || undefined, useHtml: true, ccEmails: ccEmail.trim() || undefined, bccEmails: bccEmail.trim() || undefined, }); } catch (error) { // Error handling is done in the mutation's onError console.error("Send email error:", error); } }; const handleConfirmSend = () => { setIsConfirming(true); setActiveTab("confirm"); }; const fromEmail = invoice?.business?.email ?? "noreply@yourdomain.com"; const toEmail = invoice?.client?.email ?? ""; const canSend = !isSending && subject.trim() && emailContent.trim() && toEmail && toEmail.trim() !== ""; return ( {trigger} Send Invoice Email Compose and preview your invoice email before sending to{" "} {invoice?.client?.name ?? "client"}. {/* Warning for missing email */} {(!toEmail || toEmail.trim() === "") && ( This client doesn't have an email address. Please add an email address to the client before sending the invoice. )} {/* Branded Template Info */} Professional Email Template: Your email will be sent using a beautifully designed, beenvoice-branded template with proper fonts and styling. Any custom content you add will be incorporated into the professional template automatically. Compose Preview Confirm
You're about to send this email to{" "} {toEmail}. The invoice PDF will be automatically attached. {invoice?.status === "draft" && ( This invoice is currently in draft{" "} status. Sending it will automatically change the status to{" "} sent. )}
{activeTab === "compose" && ( )} {activeTab === "preview" && ( <> )} {activeTab === "confirm" && ( <> )}
); }