diff --git a/apps/web/src/app/(dashboard)/emails/email-list.tsx b/apps/web/src/app/(dashboard)/emails/email-list.tsx index 645b283..751c16e 100644 --- a/apps/web/src/app/(dashboard)/emails/email-list.tsx +++ b/apps/web/src/app/(dashboard)/emails/email-list.tsx @@ -16,6 +16,7 @@ import { MailSearch, MailWarning, MailX, + Download, } from "lucide-react"; import { formatDate, formatDistanceToNow } from "date-fns"; import { EmailStatus } from "@prisma/client"; @@ -74,6 +75,16 @@ export default function EmailsList() { apiId: apiId, }); + const exportQuery = api.email.exportEmails.useQuery( + { + status: status?.toUpperCase() as EmailStatus, + domain: domainId, + search, + apiId: apiId, + }, + { enabled: false }, + ); + const { data: domainsQuery } = api.domain.domains.useQuery(); const { data: apiKeysQuery } = api.apiKey.getApiKeys.useQuery(); @@ -99,9 +110,43 @@ export default function EmailsList() { setSearch(value); }, 1000); + const handleExport = async () => { + try { + const resp = await exportQuery.refetch(); + if (!resp.data) return; + + const escape = (val: unknown) => { + const s = String(val ?? ""); + const startsRisky = /^\s*[=+\-@]/.test(s); + const safe = (startsRisky ? "'" : "") + s.replace(/"/g, '""'); + return /[",\r\n]/.test(safe) ? `"${safe}"` : safe; + }; + + const header = ["To", "Status", "Subject", "Sent At"].join(","); + const rows = resp.data.map((e) => + [e.to, e.status, e.subject, e.sentAt].map(escape).join(","), + ); + const csv = [header, ...rows].join("\n"); + + const blob = new Blob(["\uFEFF" + csv], { + type: "text/csv;charset=utf-8", + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `emails-${new Date().toISOString().split("T")[0]}.csv`; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + } catch (err) { + console.error("Export failed", err); + } + }; + return (