Download src/pages/Dashboard.tsx from sitcod3/modul-e: direct link, hf CLI and curl.
- Browser
- Download file 25.7 kB
-
https://huggingface.co/spaces/sitcod3/modul-e/resolve/main/src/pages/Dashboard.tsx
- Command line
-
hf download hf://spaces/sitcod3/modul-e/src/pages/Dashboard.tsx
-
curl -L -o Dashboard.tsx https://huggingface.co/spaces/sitcod3/modul-e/resolve/main/src/pages/Dashboard.tsx
25.7 kB
| 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> | |
| ); | |
| } | |