"use client"; import { useParams } from "next/navigation"; import { useState } from "react"; import { Download, Loader2 } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Separator } from "~/components/ui/separator"; import { api } from "~/trpc/react"; import { generateInvoicePDF } from "~/lib/pdf-export"; import { toast } from "sonner"; function formatDate(date: Date) { return new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", day: "numeric", }).format(new Date(date)); } function formatCurrency(amount: number, currency = "USD") { return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount); } function StatusPill({ status, dueDate }: { status: string; dueDate: Date }) { const overdue = status === "sent" && new Date(dueDate) < new Date(); const label = overdue ? "Overdue" : status.charAt(0).toUpperCase() + status.slice(1); const cls = overdue ? "bg-red-50 text-red-700 border-red-200" : status === "paid" ? "bg-green-50 text-green-700 border-green-200" : "bg-yellow-50 text-yellow-700 border-yellow-200"; return ( {label} ); } function PublicInvoiceView({ token }: { token: string }) { const [downloading, setDownloading] = useState(false); const { data: invoice, isLoading, error } = api.invoices.getByPublicToken.useQuery({ token }); const handleDownload = async () => { if (!invoice || downloading) return; setDownloading(true); try { await generateInvoicePDF({ invoiceNumber: invoice.invoiceNumber, invoicePrefix: invoice.invoicePrefix, issueDate: new Date(invoice.issueDate), dueDate: new Date(invoice.dueDate), status: invoice.status, totalAmount: invoice.totalAmount, taxRate: invoice.taxRate, currency: invoice.currency ?? "USD", notes: invoice.notes, business: invoice.business, client: invoice.client, items: invoice.items, }); } catch { toast.error("Failed to generate PDF"); } finally { setDownloading(false); } }; if (isLoading) { return (
Invoice not found
This link may have expired or been revoked.
{senderName ?? "Invoice"}
{invoice.business?.email && ({invoice.business.email}
)}{invoice.invoiceNumber}
Issued {formatDate(invoice.issueDate)} · Due {formatDate(invoice.dueDate)}
Bill to
{invoice.client.name}
{invoice.client.email && ({invoice.client.email}
)}{item.description}
{item.hours} hrs @ {formatCurrency(item.rate, invoice.currency ?? "USD")}/hr
{formatCurrency(item.amount, invoice.currency ?? "USD")}
Notes
{invoice.notes}
Powered by beenvoice