"use client"; import { useState, useCallback } from "react"; import type { Contact, ContactSource } from "@/actions/communications/contacts"; import { getContacts, deleteContact, exportContacts } from "@/actions/communications/contacts"; import { formatDate } from "@/lib/format-date"; const SOURCE_COLORS: Record = { order: "bg-blue-100 text-blue-700", import: "bg-purple-100 text-purple-700", manual: "bg-emerald-100 text-emerald-700", admin: "bg-stone-100 text-stone-600", }; // Icon components const Icons = { users: (className: string) => ( ), download: (className: string) => ( ), search: (className: string) => ( ), trash: (className: string) => ( ), chevronLeft: (className: string) => ( ), chevronRight: (className: string) => ( ), }; export default function ContactListPanel({ initialContacts, initialTotal, brandId, }: { initialContacts: Contact[]; initialTotal: number; brandId: string; }) { const [contacts, setContacts] = useState(initialContacts); const [total, setTotal] = useState(initialTotal); const [search, setSearch] = useState(""); const [sourceFilter, setSourceFilter] = useState(""); const [page, setPage] = useState(0); const [loading, setLoading] = useState(false); const [deleting, setDeleting] = useState(null); const [exporting, setExporting] = useState(false); const limit = 50; const loadPage = useCallback(async (searchVal: string, sourceVal: string, pageNum: number) => { setLoading(true); const result = await getContacts({ brandId, search: searchVal || undefined, source: sourceVal || undefined, limit, offset: pageNum * limit, }); setLoading(false); if (result.success) { setContacts(result.contacts); setTotal(result.total); } }, [brandId]); const handleSearch = (e: React.FormEvent) => { e.preventDefault(); setPage(0); loadPage(search, sourceFilter, 0); }; const handleSourceFilter = (val: ContactSource | "") => { setSourceFilter(val); setPage(0); loadPage(search, val, 0); }; const handlePage = (dir: number) => { const next = page + dir; setPage(next); loadPage(search, sourceFilter, next); }; const handleDelete = async (id: string) => { if (!confirm("Delete this contact?")) return; setDeleting(id); const result = await deleteContact(id); setDeleting(null); if (result.success) { setContacts((prev) => prev.filter((c) => c.id !== id)); setTotal((t) => t - 1); } }; const handleExport = async () => { setExporting(true); const result = await exportContacts({ brandId, brandSlug: "contacts", search: search || undefined, source: sourceFilter || undefined, }); setExporting(false); if (!result.success) { alert("Export failed: " + result.error); return; } const blob = new Blob([result.csv], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = result.filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; return (
{/* Header */}
{Icons.users("w-4 h-4 text-[var(--admin-bg)]")}

Contacts

{total} contact{total !== 1 ? "s" : ""}

{/* Search + filters */}
{Icons.search("h-4 w-4 text-[var(--admin-text-muted)]")}
setSearch(e.target.value)} placeholder="Search email, name, phone..." className="w-full pl-10 pr-3 py-2 text-sm border border-[var(--admin-border)] rounded-lg bg-white text-[var(--admin-text-primary)] focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none" />
{/* Table */} {loading ? (
Loading...
) : contacts.length === 0 ? (
No contacts found
) : ( <> {/* Desktop Table */}
{contacts.map((c) => ( ))}
Name Email Phone Source Email Unsubscribed
{c.full_name || `${c.first_name || ""} ${c.last_name || ""}`.trim() || "—"} {c.email || "—"} {c.phone || "—"} {c.source} {c.email_opt_in ? ( Opted in ) : ( Opted out )} {c.unsubscribed_at ? formatDate(new Date(c.unsubscribed_at)) : "—"}
{/* Mobile cards */}
{contacts.map((c) => (
{c.full_name || `${c.first_name || ""} ${c.last_name || ""}`.trim() || "—"}
{c.email || "—"}
{c.source} {c.phone && {c.phone}} {c.email_opt_in ? ( Opted in ) : ( Opted out )}
))}
{/* Pagination */}
Page {page + 1} — {Math.min((page + 1) * limit, total)} of {total}
)}
); }