modul-e / src /pages /CampaignDetail.tsx
sitcod3's picture
Upload src/pages/CampaignDetail.tsx with huggingface_hub
ffb0c52 verified
Raw History Blame Contribute Delete
32.5 kB
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { apiClient } from "../lib/apiClient";
import { useParams, Link } from "react-router-dom";
import {
ArrowLeft,
Megaphone,
Layers,
Mail,
Users,
BarChart2,
Settings as SettingsIcon,
Plus,
Trash2,
Play,
Pause,
Clock,
CheckCircle2,
Sparkles,
Download,
Send,
Split,
RefreshCw
} from "lucide-react";
import { useState } from "react";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
import StatusBadge from "../components/StatusBadge";
import StatCard from "../components/StatCard";
import RichTextEditor from "../components/RichTextEditor";
import FormDrawer from "../components/FormDrawer";
import CsvExportButton from "../components/CsvExportButton";
import toast from "react-hot-toast";
export default function CampaignDetail() {
const { id } = useParams<{ id: string }>();
const queryClient = useQueryClient();
const [activeTab, setActiveTab] = useState<"overview" | "sequences" | "accounts" | "leads" | "analytics" | "settings">("overview");
const [isAddLeadOpen, setIsAddLeadOpen] = useState(false);
const [selectedVariantTab, setSelectedVariantTab] = useState<Record<string, number>>({});
const { data: campaign, isLoading, isError, refetch: refetchCampaign } = useQuery({
queryKey: ["campaign", id],
queryFn: async () => {
const res = await apiClient.get(`/campaigns/${id}`);
return res.data;
},
enabled: Boolean(id),
});
const { data: sequences = [], refetch: refetchSequences } = useQuery({
queryKey: ["campaign-sequences", id],
queryFn: async () => {
const res = await apiClient.get(`/campaigns/${id}/sequences`);
return res.data;
},
enabled: Boolean(id),
});
const { data: campaignLeads = [], refetch: refetchLeads } = useQuery({
queryKey: ["campaign-leads", id],
queryFn: async () => {
const res = await apiClient.get(`/campaigns/${id}/leads`);
return res.data;
},
enabled: Boolean(id),
});
const { data: assignedAccounts = [], refetch: refetchAccounts } = useQuery({
queryKey: ["campaign-accounts", id],
queryFn: async () => {
const res = await apiClient.get(`/campaigns/${id}/email-accounts`);
return res.data;
},
enabled: Boolean(id),
});
const { data: allEmailAccounts = [] } = useQuery({
queryKey: ["email-accounts"],
queryFn: async () => {
const res = await apiClient.get("/email-accounts");
return res.data;
},
});
const { data: analyticsHistory = [] } = useQuery({
queryKey: ["campaign-analytics-date", id],
queryFn: async () => {
const res = await apiClient.get(`/campaigns/${id}/analytics-by-date`);
return res.data;
},
enabled: Boolean(id),
});
// Save sequences mutation
const saveSequencesMutation = useMutation({
mutationFn: async (updatedSequences: any[]) => {
const res = await apiClient.post(`/campaigns/${id}/sequences`, { sequences: updatedSequences });
return res.data;
},
onSuccess: () => {
toast.success("Sequence saved successfully");
refetchSequences();
queryClient.invalidateQueries({ queryKey: ["campaign", id] });
},
onError: (err: any) => {
toast.error(err.response?.data?.error || "Failed to save sequences");
},
});
const addSequenceStep = () => {
const nextSeqNum = (sequences.length || 0) + 1;
const newStep = {
id: "seq_" + Math.random().toString(36).substring(2, 7),
seq_number: nextSeqNum,
delay_days: nextSeqNum === 1 ? 0 : 3,
subject: `Follow-up #${nextSeqNum - 1}: Re: {{company_name}}`,
email_body: `<p>Hi {{first_name}},</p><p>Wanted to quickly circle back on my earlier note. Any thoughts?</p>`,
variant_distribution_percentage: 100,
variants: [],
};
saveSequencesMutation.mutate([...sequences, newStep]);
};
const removeSequenceStep = (stepIndex: number) => {
const filtered = sequences.filter((_: any, idx: number) => idx !== stepIndex);
saveSequencesMutation.mutate(filtered);
};
const addVariantToStep = (stepIndex: number) => {
const updated = [...sequences];
const target = updated[stepIndex];
if (!target.variants) target.variants = [];
target.variant_distribution_percentage = 50;
target.variants.push({
id: "var_" + Math.random().toString(36).substring(2, 7),
variant_label: `Variant ${String.fromCharCode(66 + target.variants.length)}`,
subject: `Alternative: ${target.subject}`,
email_body: `<p>Hey {{first_name}}, short follow up regarding {{company_name}}.</p>`,
});
saveSequencesMutation.mutate(updated);
};
const addAccountToCampaign = async (accountId: string) => {
try {
await apiClient.post(`/campaigns/${id}/email-accounts`, { email_account_id: accountId });
toast.success("Mailbox assigned to campaign");
refetchAccounts();
} catch (err: any) {
toast.error("Failed to assign account");
}
};
const removeAccountFromCampaign = async (accountId: string) => {
try {
await apiClient.delete(`/campaigns/${id}/email-accounts/${accountId}`);
toast.success("Mailbox removed from campaign");
refetchAccounts();
} catch (err: any) {
toast.error("Failed to remove account");
}
};
if (isLoading) {
return (
<div className="p-12 flex justify-center items-center">
<div className="w-8 h-8 rounded-full border-2 border-indigo-500 border-t-transparent animate-spin" />
</div>
);
}
if (isError || !campaign) {
return (
<div className="p-12 flex flex-col items-center justify-center gap-3 text-center">
<h1 className="text-lg font-semibold text-zinc-100">Campaign unavailable</h1>
<p className="text-sm text-zinc-400">We couldn’t load this campaign. Try again or return to the campaign list.</p>
<div className="flex items-center gap-2">
<button type="button" onClick={() => refetchCampaign()} className="px-3 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-medium rounded-lg inline-flex items-center gap-2">
<RefreshCw className="w-3.5 h-3.5" /> Retry
</button>
<Link to="/campaigns" className="px-3 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 text-xs font-medium rounded-lg">
Back to campaigns
</Link>
</div>
</div>
);
}
return (
<div className="p-6 max-w-7xl mx-auto space-y-6">
{/* Back Navigation and Header */}
<div className="space-y-3">
<Link
to="/campaigns"
className="inline-flex items-center gap-1.5 text-xs text-zinc-400 hover:text-zinc-200 transition-colors"
>
<ArrowLeft className="w-3.5 h-3.5" /> Back to all campaigns
</Link>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold text-zinc-100 tracking-tight">{campaign.name}</h1>
<StatusBadge status={campaign.status} />
</div>
<p className="text-xs text-zinc-400 mt-1">
Timezone: {campaign.timezone} • Schedule: {campaign.start_schedule} - {campaign.end_schedule}
</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setIsAddLeadOpen(true)}
className="px-3.5 py-1.5 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-medium rounded-lg transition-colors flex items-center gap-1.5"
>
<Plus className="w-3.5 h-3.5" /> Add Leads
</button>
</div>
</div>
</div>
{/* Tabs Bar */}
<div className="border-b border-zinc-800 flex items-center gap-1 text-sm font-medium overflow-x-auto">
{[
{ key: "overview", label: "Overview", icon: BarChart2 },
{ key: "sequences", label: `Sequences (${sequences.length})`, icon: Layers },
{ key: "accounts", label: `Email Accounts (${assignedAccounts.length})`, icon: Mail },
{ key: "leads", label: `Leads (${campaignLeads.length})`, icon: Users },
{ key: "analytics", label: "Analytics", icon: BarChart2 },
{ key: "settings", label: "Settings", icon: SettingsIcon },
].map((tab) => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key as any)}
className={`px-4 py-2.5 border-b-2 font-medium text-xs flex items-center gap-2 transition-colors whitespace-nowrap ${
activeTab === tab.key
? "border-indigo-500 text-indigo-400 bg-indigo-500/5"
: "border-transparent text-zinc-400 hover:text-zinc-200 hover:border-zinc-700"
}`}
>
<tab.icon className="w-4 h-4" />
{tab.label}
</button>
))}
</div>
{/* TAB 1: OVERVIEW */}
{activeTab === "overview" && (
<div className="space-y-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard
title="Leads in Sequence"
value={campaign.lead_count || 0}
icon={Users}
iconColor="teal"
/>
<StatCard
title="Emails Sent"
value={campaign.sent_count || 0}
icon={Send}
iconColor="blue"
/>
<StatCard
title="Unique Opens"
value={campaign.open_count || 0}
subtitle={`${((campaign.open_count / (campaign.sent_count || 1)) * 100).toFixed(1)}% Open Rate`}
icon={Mail}
iconColor="emerald"
/>
<StatCard
title="Replies Received"
value={campaign.reply_count || 0}
subtitle={`${((campaign.reply_count / (campaign.sent_count || 1)) * 100).toFixed(1)}% Reply Rate`}
icon={CheckCircle2}
iconColor="purple"
/>
</div>
{/* 14-Day Trajectory */}
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5 shadow-sm">
<h3 className="text-sm font-semibold text-zinc-100 mb-4">
Engagement Trajectory (Last 14 Days)
</h3>
<div className="h-[260px] w-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={analyticsHistory} margin={{ top: 5, right: 10, bottom: 5, left: -15 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--nt-border-subtle)" vertical={false} />
<XAxis dataKey="date" stroke="var(--nt-text-muted)" fontSize={11} tickLine={false} axisLine={false} />
<YAxis stroke="var(--nt-text-muted)" fontSize={11} tickLine={false} axisLine={false} />
<Tooltip
contentStyle={{ backgroundColor: "var(--nt-bg-root)", borderColor: "var(--nt-border-accent)", borderRadius: "8px", fontSize: "12px" }}
/>
<Line type="monotone" dataKey="sent" stroke="var(--nt-deep-teal-600)" strokeWidth={2} name="Sent" dot={false} />
<Line type="monotone" dataKey="opens" stroke="var(--nt-deep-teal-500)" strokeWidth={2} name="Opens" dot={false} />
<Line type="monotone" dataKey="replies" stroke="var(--nt-terracotta-500)" strokeWidth={2} name="Replies" dot={false} />
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
)}
{/* TAB 2: SEQUENCES WITH A/B VARIANTS */}
{activeTab === "sequences" && (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h3 className="text-base font-semibold text-zinc-100">Multi-Step Sequence Flow</h3>
<p className="text-xs text-zinc-400 mt-0.5">
Set delay days, customize subject lines, and configure A/B variants per email step.
</p>
</div>
<button
onClick={addSequenceStep}
className="px-3.5 py-1.5 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-medium rounded-lg transition-colors flex items-center gap-1.5"
>
<Plus className="w-3.5 h-3.5" /> Add Email Step
</button>
</div>
<div className="space-y-4">
{sequences.map((step: any, stepIndex: number) => {
const currentVariantTab = selectedVariantTab[step.id] || 0;
const hasVariants = step.variants && step.variants.length > 0;
return (
<div key={step.id || stepIndex} className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden shadow-sm">
{/* Step Header */}
<div className="px-5 py-3.5 bg-zinc-950 border-b border-zinc-800 flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="w-6 h-6 rounded-full bg-indigo-500/20 text-indigo-400 font-bold text-xs flex items-center justify-center border border-indigo-500/30">
{step.seq_number}
</span>
<div>
<span className="text-xs font-semibold text-zinc-200">
{step.seq_number === 1 ? "Initial Outreach (Step 1)" : `Follow-up #${step.seq_number - 1}`}
</span>
<div className="flex items-center gap-2 text-[11px] text-zinc-400 mt-0.5">
<Clock className="w-3 h-3 text-zinc-500" />
<span>Delay:</span>
<input
type="number"
min="0"
value={step.delay_days}
onChange={(e) => {
const updated = [...sequences];
updated[stepIndex].delay_days = Number(e.target.value);
saveSequencesMutation.mutate(updated);
}}
className="w-12 bg-zinc-900 border border-zinc-700 rounded px-1.5 py-0.5 text-xs text-zinc-100"
/>
<span>days after previous email</span>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => addVariantToStep(stepIndex)}
className="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 text-xs font-medium rounded-md transition-colors flex items-center gap-1"
>
<Split className="w-3.5 h-3.5 text-indigo-400" /> + A/B Variant
</button>
<button
type="button"
onClick={() => removeSequenceStep(stepIndex)}
className="p-1.5 text-zinc-500 hover:text-rose-400 hover:bg-rose-500/10 rounded-md transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
{/* Step Body & Variant Tabs */}
<div className="p-5 space-y-4">
{hasVariants && (
<div className="flex items-center justify-between border-b border-zinc-800 pb-3">
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => setSelectedVariantTab((prev) => ({ ...prev, [step.id]: 0 }))}
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
currentVariantTab === 0 ? "bg-indigo-600 text-white" : "bg-zinc-800 text-zinc-400"
}`}
>
Variant A (Default)
</button>
{step.variants.map((v: any, vIdx: number) => (
<button
key={v.id || vIdx}
type="button"
onClick={() => setSelectedVariantTab((prev) => ({ ...prev, [step.id]: vIdx + 1 }))}
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
currentVariantTab === vIdx + 1 ? "bg-indigo-600 text-white" : "bg-zinc-800 text-zinc-400"
}`}
>
{v.variant_label || `Variant ${String.fromCharCode(66 + vIdx)}`}
</button>
))}
</div>
<div className="text-xs text-zinc-400 font-mono">
Split Traffic: 50% / 50%
</div>
</div>
)}
{/* Active Variant Editor */}
{currentVariantTab === 0 ? (
<div className="space-y-3">
<div>
<label className="block text-xs font-medium text-zinc-400 mb-1">
Subject Line (Variant A)
</label>
<input
type="text"
value={step.subject}
onChange={(e) => {
const updated = [...sequences];
updated[stepIndex].subject = e.target.value;
saveSequencesMutation.mutate(updated);
}}
className="w-full bg-zinc-950 border border-zinc-800 rounded-lg p-2.5 text-xs text-zinc-100 focus:outline-none focus:border-indigo-500"
/>
</div>
<div>
<label className="block text-xs font-medium text-zinc-400 mb-1">
Email Body (Variant A)
</label>
<RichTextEditor
content={step.email_body}
onChange={(html) => {
const updated = [...sequences];
updated[stepIndex].email_body = html;
saveSequencesMutation.mutate(updated);
}}
/>
</div>
</div>
) : (
<div className="space-y-3">
<div>
<label className="block text-xs font-medium text-zinc-400 mb-1">
Subject Line ({step.variants[currentVariantTab - 1]?.variant_label})
</label>
<input
type="text"
value={step.variants[currentVariantTab - 1]?.subject || ""}
onChange={(e) => {
const updated = [...sequences];
updated[stepIndex].variants[currentVariantTab - 1].subject = e.target.value;
saveSequencesMutation.mutate(updated);
}}
className="w-full bg-zinc-950 border border-zinc-800 rounded-lg p-2.5 text-xs text-zinc-100 focus:outline-none focus:border-indigo-500"
/>
</div>
<div>
<label className="block text-xs font-medium text-zinc-400 mb-1">
Email Body ({step.variants[currentVariantTab - 1]?.variant_label})
</label>
<RichTextEditor
content={step.variants[currentVariantTab - 1]?.email_body || ""}
onChange={(html) => {
const updated = [...sequences];
updated[stepIndex].variants[currentVariantTab - 1].email_body = html;
saveSequencesMutation.mutate(updated);
}}
/>
</div>
</div>
)}
</div>
</div>
);
})}
</div>
</div>
)}
{/* TAB 3: EMAIL ACCOUNTS ASSIGNMENT */}
{activeTab === "accounts" && (
<div className="space-y-6">
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5">
<h3 className="text-base font-semibold text-zinc-100 mb-1">Assigned Sending Inboxes</h3>
<p className="text-xs text-zinc-400 mb-4">
Emails will be rotated across these connected mailboxes to preserve deliverability.
</p>
<div className="space-y-3">
{assignedAccounts.map((acc: any) => (
<div
key={acc.id}
className="flex items-center justify-between p-3 bg-zinc-950 border border-zinc-800 rounded-lg"
>
<div className="flex items-center gap-3">
<div className="p-2 bg-indigo-500/10 text-indigo-400 rounded-md">
<Mail className="w-4 h-4" />
</div>
<div>
<span className="text-xs font-semibold text-zinc-200">{acc.from_name}</span>
<div className="text-[11px] text-zinc-400">{acc.from_email}</div>
</div>
</div>
<div className="flex items-center gap-3">
<StatusBadge status={acc.status} size="sm" />
<button
onClick={() => removeAccountFromCampaign(acc.id)}
className="text-xs text-rose-400 hover:text-rose-300 font-medium"
>
Remove
</button>
</div>
</div>
))}
{assignedAccounts.length === 0 && (
<div className="p-6 text-center text-xs text-zinc-500 bg-zinc-950/60 rounded-lg border border-dashed border-zinc-800">
No email accounts assigned yet. Select from available mailboxes below.
</div>
)}
</div>
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5">
<h4 className="text-xs font-semibold uppercase tracking-wider text-zinc-400 mb-3">
Available Mailboxes
</h4>
<div className="space-y-2">
{allEmailAccounts
.filter((a: any) => !assignedAccounts.some((aa: any) => aa.id === a.id))
.map((acc: any) => (
<div
key={acc.id}
className="flex items-center justify-between p-3 bg-zinc-950/40 border border-zinc-800/80 rounded-lg"
>
<div>
<span className="text-xs font-medium text-zinc-200">{acc.from_email}</span>
<div className="text-[10px] text-zinc-500">
Provider: {acc.provider} • Daily limit: {acc.daily_limit} emails
</div>
</div>
<button
onClick={() => addAccountToCampaign(acc.id)}
className="px-3 py-1 bg-indigo-600/20 hover:bg-indigo-600 text-indigo-300 hover:text-white rounded text-xs font-medium transition-colors"
>
+ Assign to Campaign
</button>
</div>
))}
</div>
</div>
</div>
)}
{/* TAB 4: LEADS */}
{activeTab === "leads" && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="text-xs text-zinc-400">
Showing {campaignLeads.length} leads in this campaign.
</div>
<div className="flex items-center gap-2">
<CsvExportButton data={campaignLeads} filename={`campaign-${id}-leads.csv`} />
<button
onClick={() => setIsAddLeadOpen(true)}
className="px-3 py-1.5 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-medium rounded-lg transition-colors flex items-center gap-1.5"
>
<Plus className="w-3.5 h-3.5" /> Add Lead
</button>
</div>
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden">
<table className="w-full text-sm text-left">
<thead className="bg-zinc-950 border-b border-zinc-800 text-zinc-400 text-xs uppercase">
<tr>
<th className="px-5 py-3 font-semibold">Lead</th>
<th className="px-5 py-3 font-semibold">Company</th>
<th className="px-5 py-3 font-semibold">Status</th>
<th className="px-5 py-3 font-semibold">Opens</th>
<th className="px-5 py-3 font-semibold">Clicks</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-800">
{campaignLeads.map((lead: any) => (
<tr key={lead.id} className="hover:bg-zinc-800/30">
<td className="px-5 py-3.5">
<div className="font-medium text-zinc-200">
{lead.first_name} {lead.last_name}
</div>
<div className="text-xs text-zinc-400 font-mono">{lead.email}</div>
</td>
<td className="px-5 py-3.5 text-zinc-300">{lead.company_name}</td>
<td className="px-5 py-3.5">
<StatusBadge status={lead.status} size="sm" />
</td>
<td className="px-5 py-3.5 text-zinc-300 font-mono">{lead.open_count || 0}</td>
<td className="px-5 py-3.5 text-zinc-300 font-mono">{lead.click_count || 0}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* TAB 5: ANALYTICS */}
{activeTab === "analytics" && (
<div className="space-y-6">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<span className="text-xs text-zinc-400">Open Rate</span>
<div className="text-2xl font-bold text-emerald-400 mt-1">56.2%</div>
<span className="text-[11px] text-zinc-500">Above industry avg (28%)</span>
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<span className="text-xs text-zinc-400">Reply Rate</span>
<div className="text-2xl font-bold text-purple-400 mt-1">6.4%</div>
<span className="text-[11px] text-zinc-500">Strong positive intent</span>
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<span className="text-xs text-zinc-400">Bounce Rate</span>
<div className="text-2xl font-bold text-rose-400 mt-1">0.6%</div>
<span className="text-[11px] text-zinc-500">Safe zone (&lt; 2%)</span>
</div>
</div>
</div>
)}
{/* TAB 6: SETTINGS */}
{activeTab === "settings" && (
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-6 max-w-2xl">
<h3 className="text-base font-semibold text-zinc-100 mb-4">Campaign Parameters</h3>
<div className="space-y-4 text-xs">
<div>
<label className="block text-zinc-400 font-medium mb-1">Timezone</label>
<input
disabled
value={campaign.timezone}
className="w-full bg-zinc-950 border border-zinc-800 rounded-lg p-2.5 text-zinc-300"
/>
</div>
<div>
<label className="block text-zinc-400 font-medium mb-1">Unsubscribe Footer</label>
<input
disabled
value={campaign.unsubscribe_text}
className="w-full bg-zinc-950 border border-zinc-800 rounded-lg p-2.5 text-zinc-300"
/>
</div>
</div>
</div>
)}
{/* Add Lead Slide-Over */}
<FormDrawer
isOpen={isAddLeadOpen}
onClose={() => setIsAddLeadOpen(false)}
title="Add Lead to Campaign"
subtitle="Upload prospect details to be enrolled into this sequence."
>
<form
onSubmit={async (e) => {
e.preventDefault();
const form = e.target as HTMLFormElement;
const newLead = {
first_name: (form.elements.namedItem("first_name") as HTMLInputElement).value,
last_name: (form.elements.namedItem("last_name") as HTMLInputElement).value,
email: (form.elements.namedItem("email") as HTMLInputElement).value,
company_name: (form.elements.namedItem("company_name") as HTMLInputElement).value,
website: (form.elements.namedItem("website") as HTMLInputElement).value,
status: "INTERESTED",
};
try {
await apiClient.post(`/campaigns/${id}/leads`, newLead);
toast.success("Lead enrolled in campaign");
setIsAddLeadOpen(false);
refetchLeads();
} catch (err: any) {
toast.error("Failed to add lead");
}
}}
className="space-y-4 text-xs"
>
<div>
<label className="block font-medium text-zinc-300 mb-1">First Name *</label>
<input name="first_name" required className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100" />
</div>
<div>
<label className="block font-medium text-zinc-300 mb-1">Last Name</label>
<input name="last_name" className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100" />
</div>
<div>
<label className="block font-medium text-zinc-300 mb-1">Email *</label>
<input type="email" name="email" required className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100" />
</div>
<div>
<label className="block font-medium text-zinc-300 mb-1">Company Name</label>
<input name="company_name" className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100" />
</div>
<div>
<label className="block font-medium text-zinc-300 mb-1">Website</label>
<input name="website" placeholder="https://" className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100" />
</div>
<div className="pt-4 flex justify-end gap-2">
<button
type="button"
onClick={() => setIsAddLeadOpen(false)}
className="px-3 py-2 bg-zinc-800 text-zinc-300 rounded-lg"
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 bg-indigo-600 text-white rounded-lg font-medium"
>
Enroll Lead
</button>
</div>
</form>
</FormDrawer>
</div>
);
}