sitcod3 commited on
Commit
2b38dda
·
verified ·
1 Parent(s): cd32143

Upload src/pages/Dashboard.tsx with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/pages/Dashboard.tsx +551 -0
src/pages/Dashboard.tsx ADDED
@@ -0,0 +1,551 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { apiClient } from "../lib/apiClient";
3
+ import {
4
+ Megaphone,
5
+ Users,
6
+ Mail,
7
+ Inbox,
8
+ Flame,
9
+ TrendingUp,
10
+ Sparkles,
11
+ Activity,
12
+ Plus,
13
+ ArrowUpRight,
14
+ CheckCircle2,
15
+ ShieldCheck,
16
+ Radio,
17
+ Zap
18
+ } from "lucide-react";
19
+ import {
20
+ LineChart,
21
+ Line,
22
+ XAxis,
23
+ YAxis,
24
+ CartesianGrid,
25
+ Tooltip,
26
+ ResponsiveContainer,
27
+ PieChart,
28
+ Pie,
29
+ Cell
30
+ } from "recharts";
31
+ import { useState } from "react";
32
+ import { Link } from "react-router-dom";
33
+ import StatCard from "../components/StatCard";
34
+ import StatusBadge from "../components/StatusBadge";
35
+ import FormDrawer from "../components/FormDrawer";
36
+ import toast from "react-hot-toast";
37
+
38
+ const lineData = [
39
+ { date: "Day 1", sent: 480, open: 310, reply: 28, bounce: 6 },
40
+ { date: "Day 4", sent: 820, open: 530, reply: 46, bounce: 10 },
41
+ { date: "Day 7", sent: 1250, open: 820, reply: 74, bounce: 14 },
42
+ { date: "Day 10", sent: 1780, open: 1190, reply: 105, bounce: 19 },
43
+ { date: "Day 14", sent: 2420, open: 1650, reply: 148, bounce: 23 },
44
+ { date: "Day 18", sent: 3150, open: 2190, reply: 196, bounce: 31 },
45
+ { date: "Day 22", sent: 4020, open: 2840, reply: 254, bounce: 38 },
46
+ { date: "Day 26", sent: 4980, open: 3510, reply: 312, bounce: 46 },
47
+ { date: "Day 30", sent: 5890, open: 4120, reply: 378, bounce: 52 },
48
+ ];
49
+
50
+ const pieData = [
51
+ { name: "Interested", value: 340, color: "var(--nt-success)" },
52
+ { name: "Meeting Booked", value: 160, color: "var(--nt-accent)" },
53
+ { name: "Replied", value: 290, color: "var(--nt-info)" },
54
+ { name: "Not Interested", value: 180, color: "var(--nt-danger)" },
55
+ { name: "Bounced", value: 45, color: "var(--nt-espresso-700)" },
56
+ ];
57
+
58
+ export default function Dashboard() {
59
+ const queryClient = useQueryClient();
60
+ const [isCreateCampaignOpen, setIsCreateCampaignOpen] = useState(false);
61
+ const [activeMetric, setActiveMetric] = useState<"all" | "open" | "reply">("all");
62
+
63
+ const { data: analytics, isLoading: isAnalyticsLoading, isError: isAnalyticsError } = useQuery({
64
+ queryKey: ["analytics-summary"],
65
+ queryFn: async () => {
66
+ const res = await apiClient.get("/analytics/campaigns");
67
+ return res.data;
68
+ },
69
+ });
70
+
71
+ const { data: campaigns, isLoading: isCampaignsLoading, isError: isCampaignsError } = useQuery({
72
+ queryKey: ["campaigns"],
73
+ queryFn: async () => {
74
+ const res = await apiClient.get("/campaigns");
75
+ return res.data;
76
+ },
77
+ });
78
+
79
+ const { data: emailAccounts = [], isLoading: isEmailAccountsLoading } = useQuery({
80
+ queryKey: ["email-accounts"],
81
+ queryFn: async () => {
82
+ const res = await apiClient.get("/email-accounts");
83
+ return res.data;
84
+ },
85
+ });
86
+
87
+ const warmupDailyCount = emailAccounts.reduce((total: number, account: any) => total + (account.warmup_daily_count || 0), 0);
88
+ const connectedMailboxCount = emailAccounts.filter((account: any) => account.status === "CONNECTED").length;
89
+ const warmupScoreValues = emailAccounts.map((account: any) => account.warmup_score).filter((score: any) => typeof score === "number");
90
+ const averageWarmupScore = warmupScoreValues.length
91
+ ? (warmupScoreValues.reduce((total: number, score: number) => total + score, 0) / warmupScoreValues.length).toFixed(1)
92
+ : null;
93
+
94
+ return (
95
+ <div className="p-6 max-w-7xl mx-auto space-y-7">
96
+ {/* Header with Live Status & Quick Action Buttons */}
97
+ <div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
98
+ <div>
99
+ <div className="flex items-center gap-2">
100
+ <h1 className="text-2xl font-bold tracking-tight text-zinc-100 flex items-center gap-2.5">
101
+ Command Center
102
+ </h1>
103
+ <span className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 px-2.5 py-0.5 rounded-full">
104
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-ping" />
105
+ Live Engine
106
+ </span>
107
+ </div>
108
+ <p className="text-zinc-400 text-sm mt-1">
109
+ Real-time deliverability radar, campaign sequencing, and cold outreach throughput.
110
+ </p>
111
+ </div>
112
+
113
+ <div className="flex items-center gap-3">
114
+ <button
115
+ onClick={() => setIsCreateCampaignOpen(true)}
116
+ className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium rounded-lg transition-all shadow-md shadow-indigo-600/20 flex items-center gap-2"
117
+ >
118
+ <Plus className="w-4 h-4" />
119
+ Create Campaign
120
+ </button>
121
+ </div>
122
+ </div>
123
+
124
+ {/* High-Impact Stat Cards */}
125
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
126
+ <StatCard
127
+ title="Total Campaigns"
128
+ value={isCampaignsLoading ? "…" : isCampaignsError ? "—" : campaigns?.length ?? 0}
129
+ subtitle="Outbound Sequences"
130
+ icon={Megaphone}
131
+ iconColor="indigo"
132
+ change={{ value: "+2 new", isPositive: true, period: "this week" }}
133
+ />
134
+ <StatCard
135
+ title="Total Leads"
136
+ value={isAnalyticsLoading ? "…" : isAnalyticsError ? "—" : (analytics?.total_leads ?? 0).toLocaleString()}
137
+ subtitle="Prospects Targeted"
138
+ icon={Users}
139
+ iconColor="teal"
140
+ change={{ value: "+380", isPositive: true, period: "last 7 days" }}
141
+ />
142
+ <StatCard
143
+ title="Emails Sent"
144
+ value={isAnalyticsLoading ? "…" : isAnalyticsError ? "—" : (analytics?.total_sent ?? 0).toLocaleString()}
145
+ subtitle="Across All Mailboxes"
146
+ icon={Mail}
147
+ iconColor="blue"
148
+ change={{ value: "+14.8%", isPositive: true, period: "month-over-month" }}
149
+ />
150
+ <StatCard
151
+ title="Avg Reply Rate"
152
+ value={isAnalyticsLoading ? "…" : isAnalyticsError ? "—" : analytics?.reply_rate != null ? `${analytics.reply_rate}%` : "—"}
153
+ subtitle="Deliverability High"
154
+ icon={Inbox}
155
+ iconColor="emerald"
156
+ change={{ value: "+1.2%", isPositive: true, period: "vs benchmark" }}
157
+ />
158
+ </div>
159
+
160
+ {/* Deliverability & Mailbox Health Ribbon */}
161
+ <div className="command-signal-ribbon bg-gradient-to-r from-white/5 via-indigo-900/20 to-white/5 border border-white/10 backdrop-blur-md rounded-xl p-4 flex flex-col md:flex-row items-center justify-between gap-4 shadow-[0_8px_32px_-8px_rgba(6,10,12,0.65)]">
162
+ <div className="flex items-center gap-3">
163
+ <div className="p-2.5 bg-emerald-500/20 border border-emerald-500/30 rounded-xl text-emerald-300 shadow-[inset_0_1px_1px_rgba(246,248,242,0.1)]">
164
+ <ShieldCheck className="w-5 h-5 drop-shadow-[0_0_8px_rgba(63,156,132,0.5)]" />
165
+ </div>
166
+ <div>
167
+ <h4 className="text-sm font-semibold text-zinc-100 flex items-center gap-2">
168
+ Deliverability & Reputation Guard
169
+ <span className="text-[10px] font-bold uppercase tracking-wider bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 px-2 py-0.5 rounded-full drop-shadow-[0_0_8px_rgba(63,156,132,0.3)]">
170
+ {averageWarmupScore ? `${averageWarmupScore}% Score` : isEmailAccountsLoading ? "Checking score" : "No score"}
171
+ </span>
172
+ </h4>
173
+ <p className="text-xs text-zinc-400 mt-0.5">
174
+ {connectedMailboxCount} connected mailboxes with {warmupDailyCount} daily warmup messages.
175
+ </p>
176
+ </div>
177
+ </div>
178
+
179
+ <div className="flex items-center gap-4 text-xs">
180
+ <div className="flex items-center gap-2 bg-black/40 border border-white/5 px-3 py-1.5 rounded-lg shadow-[inset_0_1px_1px_rgba(246,248,242,0.02)]">
181
+ <Flame className="w-4 h-4 text-amber-400 drop-shadow-[0_0_8px_rgba(194,160,74,0.5)]" />
182
+ <span className="text-zinc-300 font-medium font-sans">Warmup:</span>
183
+ <span className="text-amber-400 font-bold font-mono drop-shadow-[0_0_8px_rgba(194,160,74,0.3)]">{warmupDailyCount} daily / {connectedMailboxCount} inboxes</span>
184
+ </div>
185
+ <Link
186
+ to="/warmup"
187
+ className="text-indigo-400 hover:text-indigo-300 font-medium flex items-center gap-1 transition-colors"
188
+ >
189
+ Warmup Center <ArrowUpRight className="w-3.5 h-3.5" />
190
+ </Link>
191
+ </div>
192
+ </div>
193
+
194
+ {/* Main Visualizations Grid */}
195
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
196
+ {/* 30-Day Performance Multi-Line Chart */}
197
+ <div className="lg:col-span-2 bg-white/5 border border-white/10 backdrop-blur-md rounded-xl p-5 shadow-[0_8px_32px_-8px_rgba(6,10,12,0.65)] flex flex-col">
198
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-6">
199
+ <div>
200
+ <h3 className="text-base font-semibold text-zinc-100 flex items-center gap-2">
201
+ <Activity className="w-4 h-4 text-indigo-400 drop-shadow-[0_0_8px_rgba(37,125,104,0.5)]" />
202
+ Performance Trajectory (30 Days)
203
+ </h3>
204
+ <p className="text-xs text-zinc-400 mt-0.5">
205
+ Sent volume, open rate, and reply convergence.
206
+ </p>
207
+ </div>
208
+
209
+ {/* Metric toggles */}
210
+ <div className="flex items-center bg-black/40 p-1 rounded-lg border border-white/5 text-xs shadow-[inset_0_1px_1px_rgba(246,248,242,0.02)]">
211
+ <button
212
+ onClick={() => setActiveMetric("all")}
213
+ className={`px-3 py-1 rounded-md font-medium transition-all duration-300 ${
214
+ activeMetric === "all" ? "bg-indigo-600 text-white shadow-[0_0_12px_rgba(37,125,104,0.5)]" : "text-zinc-400 hover:text-zinc-200"
215
+ }`}
216
+ >
217
+ All Metrics
218
+ </button>
219
+ <button
220
+ onClick={() => setActiveMetric("open")}
221
+ className={`px-3 py-1 rounded-md font-medium transition-all duration-300 ${
222
+ activeMetric === "open" ? "bg-emerald-600 text-white shadow-[0_0_12px_rgba(37,125,104,0.5)]" : "text-zinc-400 hover:text-zinc-200"
223
+ }`}
224
+ >
225
+ Opens
226
+ </button>
227
+ <button
228
+ onClick={() => setActiveMetric("reply")}
229
+ className={`px-3 py-1 rounded-md font-medium transition-all duration-300 ${
230
+ activeMetric === "reply" ? "bg-purple-600 text-white shadow-[0_0_12px_rgba(178,87,49,0.5)]" : "text-zinc-400 hover:text-zinc-200"
231
+ }`}
232
+ >
233
+ Replies
234
+ </button>
235
+ </div>
236
+ </div>
237
+
238
+ <div className="h-[300px] w-full">
239
+ <ResponsiveContainer width="100%" height="100%">
240
+ <LineChart data={lineData} margin={{ top: 5, right: 10, bottom: 5, left: -10 }}>
241
+ <CartesianGrid strokeDasharray="3 3" stroke="rgba(246,248,242,0.05)" vertical={false} />
242
+ <XAxis dataKey="date" stroke="var(--nt-text-muted)" fontSize={12} tickLine={false} axisLine={false} />
243
+ <YAxis stroke="var(--nt-text-muted)" fontSize={12} tickLine={false} axisLine={false} className="font-mono" />
244
+ <Tooltip
245
+ contentStyle={{
246
+ backgroundColor: "rgba(6, 10, 12, 0.88)",
247
+ backdropFilter: "blur(12px)",
248
+ borderColor: "rgba(37,125,104,0.2)",
249
+ borderRadius: "12px",
250
+ color: "var(--nt-text-primary)",
251
+ fontSize: "12px",
252
+ }}
253
+ />
254
+ {(activeMetric === "all" || activeMetric === "open") && (
255
+ <Line
256
+ type="monotone"
257
+ dataKey="sent"
258
+ name="Sent"
259
+ stroke="var(--nt-deep-teal-600)"
260
+ strokeWidth={3}
261
+ dot={{ r: 4, fill: "var(--nt-deep-teal-600)", strokeWidth: 2, stroke: "var(--nt-focus-ring-offset)" }}
262
+ activeDot={{ r: 6, fill: "var(--nt-deep-teal-700)" }}
263
+ animationDuration={1500}
264
+ />
265
+ )}
266
+ {(activeMetric === "all" || activeMetric === "open") && (
267
+ <Line
268
+ type="monotone"
269
+ dataKey="open"
270
+ name="Opens"
271
+ stroke="var(--nt-deep-teal-500)"
272
+ strokeWidth={3}
273
+ dot={{ r: 4, fill: "var(--nt-deep-teal-500)", strokeWidth: 2, stroke: "var(--nt-focus-ring-offset)" }}
274
+ activeDot={{ r: 6, fill: "var(--nt-deep-teal-700)" }}
275
+ animationDuration={1500}
276
+ />
277
+ )}
278
+ {(activeMetric === "all" || activeMetric === "reply") && (
279
+ <Line
280
+ type="monotone"
281
+ dataKey="reply"
282
+ name="Replies"
283
+ stroke="var(--nt-terracotta-500)"
284
+ strokeWidth={3}
285
+ dot={{ r: 4, fill: "var(--nt-terracotta-500)", strokeWidth: 2, stroke: "var(--nt-focus-ring-offset)" }}
286
+ activeDot={{ r: 6, fill: "var(--nt-terracotta-700)" }}
287
+ animationDuration={1500}
288
+ />
289
+ )}
290
+ {activeMetric === "all" && (
291
+ <Line
292
+ type="monotone"
293
+ dataKey="bounce"
294
+ name="Bounces"
295
+ stroke="var(--nt-danger)"
296
+ strokeWidth={2}
297
+ strokeDasharray="4 4"
298
+ dot={false}
299
+ animationDuration={1500}
300
+ />
301
+ )}
302
+ </LineChart>
303
+ </ResponsiveContainer>
304
+ </div>
305
+
306
+ <div className="mt-4 pt-3 border-t border-white/5 flex items-center justify-between text-xs text-zinc-400">
307
+ <div className="flex items-center gap-4 font-sans">
308
+ <span className="flex items-center gap-1.5">
309
+ <span className="w-2.5 h-2.5 rounded-full bg-blue-500 shadow-[0_0_8px_rgba(63,156,132,0.8)]" /> Sent
310
+ </span>
311
+ <span className="flex items-center gap-1.5">
312
+ <span className="w-2.5 h-2.5 rounded-full bg-emerald-500 shadow-[0_0_8px_rgba(37,125,104,0.8)]" /> Opens
313
+ </span>
314
+ <span className="flex items-center gap-1.5">
315
+ <span className="w-2.5 h-2.5 rounded-full bg-purple-500 shadow-[0_0_8px_rgba(178,87,49,0.8)]" /> Replies
316
+ </span>
317
+ <span className="flex items-center gap-1.5">
318
+ <span className="w-2.5 h-2.5 rounded-full bg-rose-500 shadow-[0_0_8px_rgba(203,117,80,0.8)]" /> Bounces
319
+ </span>
320
+ </div>
321
+ <Link to="/analytics" className="text-indigo-400 hover:text-indigo-300 font-medium">
322
+ View Detailed Analytics →
323
+ </Link>
324
+ </div>
325
+ </div>
326
+
327
+ {/* Lead Status Distribution Donut */}
328
+ <div className="bg-white/5 border border-white/10 backdrop-blur-md rounded-xl p-5 shadow-[0_8px_32px_-8px_rgba(6,10,12,0.65)] flex flex-col justify-between">
329
+ <div>
330
+ <h3 className="text-base font-semibold text-zinc-100 flex items-center gap-2">
331
+ <Zap className="w-4 h-4 text-amber-400 drop-shadow-[0_0_8px_rgba(194,160,74,0.5)]" />
332
+ Lead Intent Distribution
333
+ </h3>
334
+ <p className="text-xs text-zinc-400 mt-0.5">Classification of prospect responses.</p>
335
+ </div>
336
+
337
+ <div className="relative h-[220px] w-full flex items-center justify-center my-2">
338
+ <ResponsiveContainer width="100%" height="100%">
339
+ <PieChart>
340
+ <Pie
341
+ data={pieData}
342
+ innerRadius={65}
343
+ outerRadius={95}
344
+ paddingAngle={4}
345
+ dataKey="value"
346
+ stroke="none"
347
+ animationDuration={1500}
348
+ >
349
+ {pieData.map((entry, index) => (
350
+ <Cell key={`cell-${index}`} fill={entry.color} />
351
+ ))}
352
+ </Pie>
353
+ <Tooltip
354
+ contentStyle={{
355
+ backgroundColor: "rgba(6, 10, 12, 0.88)",
356
+ backdropFilter: "blur(12px)",
357
+ borderColor: "rgba(37,125,104,0.2)",
358
+ borderRadius: "12px",
359
+ fontSize: "12px",
360
+ }}
361
+ />
362
+ </PieChart>
363
+ </ResponsiveContainer>
364
+ <div className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none drop-shadow-[0_0_12px_rgba(246,248,242,0.3)]">
365
+ <span className="text-2xl font-bold text-white font-mono">1,015</span>
366
+ <span className="text-[10px] uppercase tracking-wider text-zinc-400 font-medium font-sans">
367
+ Classified
368
+ </span>
369
+ </div>
370
+ </div>
371
+
372
+ <div className="space-y-1.5 pt-3 border-t border-white/5 text-xs font-sans">
373
+ {pieData.map((item) => (
374
+ <div key={item.name} className="flex items-center justify-between text-zinc-300">
375
+ <span className="flex items-center gap-2">
376
+ <span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: item.color, boxShadow: `0 0 8px ${item.color}` }} />
377
+ {item.name}
378
+ </span>
379
+ <span className="font-semibold text-zinc-200 font-mono">{item.value}</span>
380
+ </div>
381
+ ))}
382
+ </div>
383
+ </div>
384
+ </div>
385
+
386
+ {/* Active Campaigns Overview Table */}
387
+ <div className="bg-white/5 border border-white/10 backdrop-blur-md rounded-xl overflow-hidden shadow-[0_8px_32px_-8px_rgba(6,10,12,0.65)]">
388
+ <div className="px-6 py-4 border-b border-white/5 flex items-center justify-between bg-white/[0.02]">
389
+ <div>
390
+ <h3 className="text-base font-semibold text-zinc-100 flex items-center gap-2">
391
+ <Radio className="w-4 h-4 text-indigo-400 drop-shadow-[0_0_8px_rgba(37,125,104,0.5)]" />
392
+ Active Outreach Sequences
393
+ </h3>
394
+ <p className="text-xs text-zinc-400 mt-0.5">
395
+ Live status, recipient quotas, and response metrics.
396
+ </p>
397
+ </div>
398
+ <Link
399
+ to="/campaigns"
400
+ className="text-xs font-semibold text-indigo-400 hover:text-indigo-300 transition-colors"
401
+ >
402
+ Manage Campaigns →
403
+ </Link>
404
+ </div>
405
+
406
+ <div className="overflow-x-auto">
407
+ <table className="w-full text-sm text-left">
408
+ <thead className="bg-black/20 border-b border-white/5 text-zinc-400 text-xs uppercase">
409
+ <tr>
410
+ <th className="px-6 py-3 font-medium">Campaign</th>
411
+ <th className="px-6 py-3 font-medium">Status</th>
412
+ <th className="px-6 py-3 font-medium">Leads</th>
413
+ <th className="px-6 py-3 font-medium">Sent</th>
414
+ <th className="px-6 py-3 font-medium">Opens</th>
415
+ <th className="px-6 py-3 font-medium">Replies</th>
416
+ <th className="px-6 py-3 font-medium text-right">Action</th>
417
+ </tr>
418
+ </thead>
419
+ <tbody className="divide-y divide-white/5">
420
+ {(Array.isArray(campaigns) ? campaigns : []).map((camp: any) => (
421
+ <tr key={camp.id} className="hover:bg-white/5 transition-colors">
422
+ <td className="px-6 py-4 font-semibold text-zinc-100">
423
+ <Link to={`/campaigns/${camp.id}`} className="hover:text-indigo-400 transition-colors">
424
+ {camp.name}
425
+ </Link>
426
+ <div className="text-[11px] text-zinc-400 font-normal mt-0.5 font-sans">
427
+ {camp.timezone} • {camp.sequences?.length || 1} step sequence
428
+ </div>
429
+ </td>
430
+ <td className="px-6 py-4">
431
+ <StatusBadge status={camp.status} />
432
+ </td>
433
+ <td className="px-6 py-4 text-zinc-300 font-mono">{camp.lead_count || 0}</td>
434
+ <td className="px-6 py-4 text-zinc-300 font-mono">{camp.sent_count || 0}</td>
435
+ <td className="px-6 py-4 text-emerald-400 font-mono drop-shadow-[0_0_8px_rgba(63,156,132,0.3)]">{camp.open_count || 0}</td>
436
+ <td className="px-6 py-4 text-purple-400 font-mono font-semibold drop-shadow-[0_0_8px_rgba(178,87,49,0.3)]">{camp.reply_count || 0}</td>
437
+ <td className="px-6 py-4 text-right">
438
+ <Link
439
+ to={`/campaigns/${camp.id}`}
440
+ className="px-3 py-1.5 bg-white/5 hover:bg-white/10 text-zinc-200 text-xs font-medium rounded-lg border border-white/5 transition-all inline-flex items-center gap-1 shadow-[inset_0_1px_1px_rgba(246,248,242,0.05)]"
441
+ >
442
+ Inspect <ArrowUpRight className="w-3 h-3" />
443
+ </Link>
444
+ </td>
445
+ </tr>
446
+ ))}
447
+ </tbody>
448
+ </table>
449
+ </div>
450
+ </div>
451
+
452
+ {/* Quick Campaign Creator Slide-Over Drawer */}
453
+ <FormDrawer
454
+ isOpen={isCreateCampaignOpen}
455
+ onClose={() => setIsCreateCampaignOpen(false)}
456
+ title="Create New Campaign"
457
+ subtitle="Configure schedule, tracking domains, and email sequence steps."
458
+ >
459
+ <form
460
+ onSubmit={async (e) => {
461
+ e.preventDefault();
462
+ const form = e.target as HTMLFormElement;
463
+ const name = (form.elements.namedItem("name") as HTMLInputElement).value;
464
+ const timezone = (form.elements.namedItem("timezone") as HTMLSelectElement).value;
465
+ const initial_subject = (form.elements.namedItem("subject") as HTMLInputElement).value;
466
+ const initial_body = (form.elements.namedItem("body") as HTMLTextAreaElement).value;
467
+
468
+ try {
469
+ await apiClient.post("/campaigns", {
470
+ name,
471
+ timezone,
472
+ initial_subject,
473
+ initial_body,
474
+ track_settings: "TRACK_EMAIL_OPEN_AND_CLICK",
475
+ });
476
+ await queryClient.invalidateQueries({ queryKey: ["campaigns"] });
477
+ toast.success("Campaign created successfully");
478
+ setIsCreateCampaignOpen(false);
479
+ } catch (err: any) {
480
+ toast.error(err.message || "Failed to create campaign");
481
+ }
482
+ }}
483
+ className="space-y-4 text-sm"
484
+ >
485
+ <div>
486
+ <label className="block font-medium text-zinc-300 mb-1">Campaign Name</label>
487
+ <input
488
+ name="name"
489
+ required
490
+ placeholder="e.g. Q4 Tech VP Outreach"
491
+ className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100 focus:outline-none focus:border-indigo-500"
492
+ />
493
+ </div>
494
+
495
+ <div>
496
+ <label className="block font-medium text-zinc-300 mb-1">Timezone</label>
497
+ <select
498
+ name="timezone"
499
+ defaultValue="America/New_York"
500
+ className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100 focus:outline-none focus:border-indigo-500"
501
+ >
502
+ <option value="America/New_York">Eastern Time (US & Canada)</option>
503
+ <option value="America/Chicago">Central Time (US & Canada)</option>
504
+ <option value="America/Los_Angeles">Pacific Time (US & Canada)</option>
505
+ <option value="Europe/London">London (GMT)</option>
506
+ <option value="Europe/Paris">Central European Time (CET)</option>
507
+ <option value="Asia/Singapore">Singapore (SGT)</option>
508
+ </select>
509
+ </div>
510
+
511
+ <div>
512
+ <label className="block font-medium text-zinc-300 mb-1">Step 1: Subject Line</label>
513
+ <input
514
+ name="subject"
515
+ required
516
+ defaultValue="Question about {{company_name}}'s pipeline"
517
+ className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100 focus:outline-none focus:border-indigo-500"
518
+ />
519
+ </div>
520
+
521
+ <div>
522
+ <label className="block font-medium text-zinc-300 mb-1">Step 1: Email Body</label>
523
+ <textarea
524
+ name="body"
525
+ rows={4}
526
+ required
527
+ defaultValue="Hi {{first_name}},\n\nWanted to reach out and see if you are exploring new outbound channels this quarter?\n\nBest,\nAlex"
528
+ className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100 focus:outline-none focus:border-indigo-500 font-mono text-xs"
529
+ />
530
+ </div>
531
+
532
+ <div className="pt-4 flex justify-end gap-3">
533
+ <button
534
+ type="button"
535
+ onClick={() => setIsCreateCampaignOpen(false)}
536
+ className="px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded-lg"
537
+ >
538
+ Cancel
539
+ </button>
540
+ <button
541
+ type="submit"
542
+ className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white font-medium rounded-lg shadow-md"
543
+ >
544
+ Create Campaign
545
+ </button>
546
+ </div>
547
+ </form>
548
+ </FormDrawer>
549
+ </div>
550
+ );
551
+ }