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