modul-e / src /pages /Warmup.tsx
sitcod3's picture
Upload src/pages/Warmup.tsx with huggingface_hub
f3eb729 verified
Raw History Blame Contribute Delete
12.1 kB
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { apiClient } from "../lib/apiClient";
import {
Flame,
ShieldCheck,
TrendingUp,
Sliders,
CheckCircle2,
AlertTriangle,
Zap,
Mail,
Play,
Pause,
ArrowUpRight
} from "lucide-react";
import { useState } from "react";
import StatusBadge from "../components/StatusBadge";
import FormDrawer from "../components/FormDrawer";
import toast from "react-hot-toast";
export default function Warmup() {
const queryClient = useQueryClient();
const [selectedWarmupAccount, setSelectedWarmupAccount] = useState<any | null>(null);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const { data: accounts = [], isLoading, refetch } = useQuery({
queryKey: ["email-accounts"],
queryFn: async () => {
const res = await apiClient.get("/email-accounts");
return res.data;
},
});
const toggleWarmupMutation = useMutation({
mutationFn: async ({ id, status }: { id: string; status: "ACTIVE" | "PAUSED" }) => {
const action = status === "ACTIVE" ? "enable" : "disable";
const res = await apiClient.post(`/email-accounts/${id}/warmup/${action}`);
return res.data;
},
onSuccess: (_, variables) => {
toast.success(`Warmup ${variables.status === "ACTIVE" ? "activated" : "paused"}`);
refetch();
},
onError: () => {
toast.error("Failed to update warmup status");
}
});
const activeWarmups = accounts.filter((a: any) => a.warmup_enabled).length;
const dailyWarmupVolume = accounts.reduce((total: number, account: any) => total + (account.warmup_daily_count || 0), 0);
const replyRates = accounts.map((account: any) => account.reply_rate_percentage).filter((rate: any) => typeof rate === "number");
const averageReplyRate = replyRates.length ? (replyRates.reduce((total: number, rate: number) => total + rate, 0) / replyRates.length).toFixed(1) : "—";
const scores = accounts.map((account: any) => account.warmup_score).filter((score: any) => typeof score === "number");
const averageScore = scores.length ? (scores.reduce((total: number, score: number) => total + score, 0) / scores.length).toFixed(1) : "—";
return (
<div className="p-6 max-w-7xl mx-auto space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold tracking-tight text-zinc-100 flex items-center gap-2.5">
<Flame className="w-6 h-6 text-amber-400" />
Autonomous Warmup Engine
</h1>
<p className="text-zinc-400 text-sm mt-1">
Gradually ramps mailbox sending volume, interacts with peer networks, and rescues emails from spam folders.
</p>
</div>
<div className="flex items-center gap-3">
<button
onClick={() => {
accounts.forEach((a: any) => {
toggleWarmupMutation.mutate({ id: a.id, status: "ACTIVE" });
});
toast.success("Warmup activated on all connected mailboxes");
}}
className="px-3.5 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 text-xs font-medium rounded-lg transition-colors flex items-center gap-1.5"
>
<Play className="w-3.5 h-3.5 text-emerald-400" /> Bulk Enable All
</button>
<button
onClick={() => setIsSettingsOpen(true)}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-medium rounded-lg transition-all shadow-md shadow-indigo-600/20 flex items-center gap-1.5"
>
<Sliders className="w-4 h-4" /> Global Warmup Rules
</button>
</div>
</div>
{/* KPI Overview */}
<div className="grid grid-cols-1 sm:grid-cols-4 gap-4">
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<span className="text-xs text-zinc-400">Warmup Coverage</span>
<div className="text-2xl font-bold text-zinc-100 mt-1">
{activeWarmups} / {accounts.length}
</div>
<span className="text-[11px] text-emerald-400 font-medium">Inboxes in active pool</span>
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<span className="text-xs text-zinc-400">Daily Warmup Volume</span>
<div className="text-2xl font-bold text-amber-400 mt-1">{dailyWarmupVolume} / day</div>
<span className="text-[11px] text-zinc-500">Auto-incrementing daily</span>
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<span className="text-xs text-zinc-400">Warmup Reply Rate</span>
<div className="text-2xl font-bold text-indigo-400 mt-1">{averageReplyRate}%</div>
<span className="text-[11px] text-zinc-500">Autonomous peer response</span>
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<span className="text-xs text-zinc-400">Inbox Health Score</span>
<div className="text-2xl font-bold text-emerald-400 mt-1">{averageScore} / 100</div>
<span className="text-[11px] text-emerald-400 font-medium">Excellent deliverability</span>
</div>
</div>
{/* Warmup Mailboxes Table */}
<div className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden shadow-sm">
<div className="overflow-x-auto">
<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-6 py-3.5 font-semibold">Mailbox</th>
<th className="px-6 py-3.5 font-semibold">Warmup Status</th>
<th className="px-6 py-3.5 font-semibold">Ramp Day</th>
<th className="px-6 py-3.5 font-semibold">Reputation Score</th>
<th className="px-6 py-3.5 font-semibold">Warmup Volume</th>
<th className="px-6 py-3.5 font-semibold text-right">Warmup Toggle</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-800/80">
{accounts.map((acc: any) => {
const isWarmupActive = Boolean(acc.warmup_enabled);
const rampDay = acc.ramp_day || "—";
const score = acc.warmup_score || 0;
return (
<tr key={acc.id} className="hover:bg-zinc-800/30 transition-colors">
<td className="px-6 py-4">
<div className="font-semibold text-zinc-100 flex items-center gap-2">
{acc.from_name}
</div>
<div className="text-xs text-zinc-400 font-mono mt-0.5">{acc.from_email}</div>
</td>
<td className="px-6 py-4">
<StatusBadge status={isWarmupActive ? "ACTIVE" : "PAUSED"} size="sm" />
</td>
<td className="px-6 py-4 text-zinc-300 font-mono text-xs">
Day {rampDay}
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2">
<span className="text-xs font-bold text-emerald-400">{score}%</span>
<div className="w-20 h-1.5 bg-zinc-950 rounded-full overflow-hidden border border-zinc-800">
<div className="h-full bg-emerald-400 rounded-full" style={{ width: `${score}%` }} />
</div>
</div>
</td>
<td className="px-6 py-4 text-xs font-mono text-zinc-300">
<span className="text-amber-400 font-semibold">{acc.warmup_daily_count || 0}</span> warm emails sent today
</td>
<td className="px-6 py-4 text-right">
<button
onClick={() =>
toggleWarmupMutation.mutate({
id: acc.id,
status: isWarmupActive ? "PAUSED" : "ACTIVE",
})
}
className={`px-3 py-1 text-xs font-medium rounded-lg transition-colors ${
isWarmupActive
? "bg-rose-500/10 text-rose-400 hover:bg-rose-500/20 border border-rose-500/30"
: "bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20 border border-emerald-500/30"
}`}
>
{isWarmupActive ? "Pause Warmup" : "Start Warmup"}
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
{/* Global Warmup Rules Drawer */}
<FormDrawer
isOpen={isSettingsOpen}
onClose={() => setIsSettingsOpen(false)}
title="Autonomous Warmup Settings"
subtitle="Fine-tune daily ramping schedules and simulated reply rates."
>
<div className="space-y-4 text-xs">
<div>
<label className="block font-medium text-zinc-300 mb-1">Target Daily Warmup Max</label>
<input
type="number"
defaultValue="40"
className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100"
/>
<span className="text-[11px] text-zinc-500 mt-1 block">Maximum simulated emails per inbox daily</span>
</div>
<div>
<label className="block font-medium text-zinc-300 mb-1">Daily Ramp Increment</label>
<input
type="number"
defaultValue="4"
className="w-full bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 text-zinc-100"
/>
<span className="text-[11px] text-zinc-500 mt-1 block">Additional emails added each consecutive day</span>
</div>
<div>
<label className="block font-medium text-zinc-300 mb-1">Simulated Reply Rate</label>
<input
type="range"
min="10"
max="60"
defaultValue="35"
className="w-full accent-indigo-500"
/>
<div className="flex justify-between text-[11px] text-zinc-400">
<span>Conservative (15%)</span>
<span className="text-indigo-400 font-semibold">Target: 35%</span>
<span>Aggressive (60%)</span>
</div>
</div>
<div className="flex items-center justify-between p-3 bg-zinc-900 border border-zinc-800 rounded-lg">
<div>
<span className="font-medium text-zinc-200 block text-xs">Spam Rescue Protocol</span>
<span className="text-[11px] text-zinc-500">Automatically moves warm emails out of spam folders</span>
</div>
<input
type="checkbox"
defaultChecked
className="w-4 h-4 rounded text-indigo-600 focus:ring-indigo-500 bg-zinc-950 border-zinc-700"
/>
</div>
<div className="pt-4 flex justify-end gap-2 border-t border-zinc-800">
<button
type="button"
onClick={() => setIsSettingsOpen(false)}
className="px-3 py-2 bg-zinc-800 text-zinc-300 rounded-lg"
>
Cancel
</button>
<button
type="button"
onClick={() => {
toast.success("Warmup parameters saved");
setIsSettingsOpen(false);
}}
className="px-4 py-2 bg-indigo-600 text-white font-medium rounded-lg"
>
Save Configuration
</button>
</div>
</div>
</FormDrawer>
</div>
);
}