"use client"; import { useState } from "react"; import { api } from "~/trpc/react"; import { useUrlState } from "~/hooks/useUrlState"; import { useDebouncedCallback } from "use-debounce"; import { SuppressionReason } from "@prisma/client"; import { formatDistanceToNow } from "date-fns"; import { Button } from "@unsend/ui/src/button"; import { Input } from "@unsend/ui/src/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@unsend/ui/src/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@unsend/ui/src/table"; import { Trash2, Download } from "lucide-react"; import RemoveSuppressionDialog from "./remove-suppression"; import Spinner from "@unsend/ui/src/spinner"; const reasonLabels = { HARD_BOUNCE: "Hard Bounce", COMPLAINT: "Complaint", MANUAL: "Manual", } as const; export default function SuppressionList() { const [search, setSearch] = useUrlState("search"); const [reason, setReason] = useUrlState("reason"); const [page, setPage] = useUrlState("page", "1"); const [emailToRemove, setEmailToRemove] = useState(null); const suppressionsQuery = api.suppression.getSuppressions.useQuery({ page: parseInt(page || "1"), limit: 20, search: search || undefined, reason: reason as SuppressionReason | undefined, sortBy: "createdAt", sortOrder: "desc", }); const exportQuery = api.suppression.exportSuppressions.useQuery( { search: search || undefined, reason: reason as SuppressionReason | undefined, }, { enabled: false } ); const utils = api.useUtils(); const removeMutation = api.suppression.removeSuppression.useMutation({ onSuccess: () => { utils.suppression.getSuppressions.invalidate(); utils.suppression.getSuppressionStats.invalidate(); setEmailToRemove(null); }, }); const debouncedSearch = useDebouncedCallback((value: string) => { setSearch(value || null); setPage("1"); }, 1000); const handleReasonFilter = (value: string) => { setReason(value === "all" ? null : value); setPage("1"); }; const handleExport = async () => { const resp = await exportQuery.refetch(); if (resp.data) { const csv = [ "Email,Reason,Created At", ...resp.data.map( (suppression) => `${suppression.email},${suppression.reason},${suppression.createdAt}` ), ].join("\n"); const blob = new Blob([csv], { type: "text/csv" }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `suppressions-${new Date().toISOString().split("T")[0]}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } }; const handleRemove = (email: string) => { setEmailToRemove(email); }; const confirmRemove = () => { if (emailToRemove) { removeMutation.mutate({ email: emailToRemove }); } }; return (
{/* Header and Export */}
{/* Filters */}
debouncedSearch(e.target.value)} />
{" "}
{/* Table */}
Email Reason Added Actions {suppressionsQuery.isLoading ? ( ) : suppressionsQuery.data?.suppressions.length === 0 ? ( No suppressed emails found ) : ( suppressionsQuery.data?.suppressions.map((suppression) => ( {suppression.email}
{reasonLabels[suppression.reason]}
{formatDistanceToNow(new Date(suppression.createdAt), { addSuffix: true, })}
)) )}
{/* Pagination */}
!open && setEmailToRemove(null)} onConfirm={confirmRemove} isLoading={removeMutation.isPending} />
); }