"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import type { Campaign, CampaignType, CampaignStatus, AudienceRules } from "@/actions/communications/campaigns"; import { formatDate } from "@/lib/format-date"; import type { Template } from "@/actions/communications/templates"; import type { AudiencePreviewResult } from "@/actions/communications/send"; import { previewCampaignAudience, sendCampaign } from "@/actions/communications/send"; import { upsertCampaign, deleteCampaign } from "@/actions/communications/campaigns"; import { getCommunicationTemplates } from "@/actions/communications/templates"; import { getCommunicationSegments, type Segment } from "@/actions/communications/segments"; const BRAND_ID = process.env.NEXT_PUBLIC_TUXEDO_BRAND_ID ?? "64294306-5f42-463d-a5e8-2ad6c81a96de"; const CAMPAIGN_TYPES: { value: CampaignType; label: string }[] = [ { value: "operational", label: "Operational" }, { value: "marketing", label: "Marketing" }, { value: "transactional", label: "Transactional" }, ]; const STATUS_COLORS: Record = { draft: "bg-zinc-950 text-zinc-300", scheduled: "bg-blue-900/40 text-blue-700", sending: "bg-yellow-100 text-yellow-700", sent: "bg-green-900/40 text-green-400", canceled: "bg-red-900/40 text-red-400", }; export default function CampaignListPanel({ initialCampaigns }: { initialCampaigns: Campaign[] }) { const router = useRouter(); const [campaigns, setCampaigns] = useState(initialCampaigns); const [filterType, setFilterType] = useState("all"); const [filterStatus, setFilterStatus] = useState("all"); const filtered = campaigns.filter((c) => { if (filterType !== "all" && c.campaign_type !== filterType) return false; if (filterStatus !== "all" && c.status !== filterStatus) return false; return true; }); return (

Campaigns

{filtered.length} campaign{filtered.length !== 1 ? "s" : ""}

+ New Campaign
{filtered.length === 0 ? (
No campaigns found
) : (
{filtered.map((c) => ( router.push(`/admin/communications/campaigns/${c.id}`)}> ))}
Name Type Status Created Sent
{c.name} {c.campaign_type} {c.status} {formatDate(new Date(c.created_at))} {c.sent_at ? formatDate(new Date(c.sent_at)) : "—"}
)}
); } // ── Campaign Edit Panel ────────────────────────────────────────────────────── export function CampaignEditPanel({ campaign, templates, mode, brandId, }: { campaign?: Campaign; templates: Template[]; mode: "edit" | "new"; brandId: string; }) { const router = useRouter(); const [saving, setSaving] = useState(false); const [sending, setSending] = useState(false); const [name, setName] = useState(campaign?.name ?? ""); const [subject, setSubject] = useState(campaign?.subject ?? ""); const [bodyText, setBodyText] = useState(campaign?.body_text ?? ""); const [bodyHtml, setBodyHtml] = useState(campaign?.body_html ?? ""); const [templateId, setTemplateId] = useState(campaign?.template_id ?? ""); const [campaignType, setCampaignType] = useState(campaign?.campaign_type ?? "operational"); const [audienceTarget, setAudienceTarget] = useState(campaign?.audience_rules?.target ?? "stop"); const [stopId, setStopId] = useState(campaign?.audience_rules?.stop_id ?? ""); const [dateFrom, setDateFrom] = useState(campaign?.audience_rules?.date_from ?? ""); const [dateTo, setDateTo] = useState(campaign?.audience_rules?.date_to ?? ""); const [scheduleMode, setScheduleMode] = useState<"now" | "later">( campaign?.scheduled_at ? "later" : "now" ); const [scheduledAt, setScheduledAt] = useState( campaign?.scheduled_at ? new Date(campaign.scheduled_at).toISOString().slice(0, 16) : "" ); const [segments, setSegments] = useState([]); const [selectedSegmentId, setSelectedSegmentId] = useState(""); const [preview, setPreview] = useState(null); const [previewLoading, setPreviewLoading] = useState(false); const [error, setError] = useState(""); // Load saved segments useEffect(() => { getCommunicationSegments(brandId).then((result) => { if (result.success) setSegments(result.segments); }); }, []); // When a segment is selected, populate audience fields function applySegment(segmentId: string) { setSelectedSegmentId(segmentId); if (!segmentId) return; const seg = segments.find((s) => s.id === segmentId); if (!seg) return; const rules = seg.rules; if (rules.target) setAudienceTarget(rules.target); if (rules.stop_id) setStopId(rules.stop_id); if (rules.date_from) setDateFrom(rules.date_from); if (rules.date_to) setDateTo(rules.date_to); } const loadPreview = async () => { setPreviewLoading(true); const rules: AudienceRules = { target: audienceTarget as AudienceRules["target"], stop_id: stopId || undefined, date_from: dateFrom || undefined, date_to: dateTo || undefined, }; const result = await previewCampaignAudience(brandId, rules); setPreview(result); setPreviewLoading(false); }; const applyTemplate = (t: Template) => { setSubject(t.subject); setBodyText(t.body_text); setBodyHtml(t.body_html ?? ""); setCampaignType((t.campaign_type as CampaignType) ?? "operational"); }; const handleSave = async () => { setSaving(true); setError(""); const rules: AudienceRules = { target: audienceTarget as AudienceRules["target"], stop_id: stopId || undefined, date_from: dateFrom || undefined, date_to: dateTo || undefined, }; const effectiveScheduledAt = scheduleMode === "later" && scheduledAt ? new Date(scheduledAt).toISOString() : null; const result = await upsertCampaign({ id: campaign?.id, brand_id: brandId, name, subject, body_text: bodyText, body_html: bodyHtml || undefined, template_id: templateId || undefined, campaign_type: campaignType, status: scheduleMode === "later" ? "scheduled" : (campaign?.status ?? "draft"), audience_rules: rules, scheduled_at: effectiveScheduledAt ?? undefined, }); setSaving(false); if (!result.success) { setError(result.error ?? "Failed to save"); return; } router.push("/admin/communications"); router.refresh(); }; const handleSend = async () => { setSending(true); setError(""); if (!campaign?.id) { setError("Save as draft first"); setSending(false); return; } const result = await sendCampaign(campaign.id, brandId); setSending(false); if (!result.success) { setError(result.error ?? "Failed to send"); return; } router.push("/admin/communications"); router.refresh(); }; return (

{mode === "new" ? "New Campaign" : "Edit Campaign"}

{error && (
{error}
)}
{/* Basic info */}
setName(e.target.value)} className="w-full border border-zinc-600 rounded-lg px-3 py-2 text-sm" placeholder="e.g. Tuxedo Stop 5/15 Reminder" />
{/* Audience builder */}

Audience

{segments.length > 0 && (
Saved segment:
)}
{audienceTarget === "stop" && (
setStopId(e.target.value)} className="w-full border border-zinc-600 rounded-lg px-3 py-2 text-sm" placeholder="UUID" />
setDateFrom(e.target.value)} className="w-full border border-zinc-600 rounded-lg px-3 py-2 text-sm" />
setDateTo(e.target.value)} className="w-full border border-zinc-600 rounded-lg px-3 py-2 text-sm" />
)} {preview && (
{preview.count} customers match this audience {preview.sample_customers.length > 0 && (
Sample: {preview.sample_customers.slice(0, 5).map((c) => c.email).join(", ")}
)}
)}
{/* Message content */}

Message Content

setSubject(e.target.value)} className="w-full border border-zinc-600 rounded-lg px-3 py-2 text-sm" placeholder="Email subject line" />