Spaces:
Sleeping
Sleeping
File size: 5,415 Bytes
e9d1895 | 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 | import {
CheckCircle2, XCircle, Clock, Loader2, SkipForward, Send, Zap, Square, Eye,
} from "lucide-react";
const STATUS_CONFIG = {
queued: { icon: Clock, color: "text-[var(--c-text-muted)]", bg: "bg-[var(--c-bg-subtle)]", border: "border-[var(--c-border)]", label: "Queued" },
generating: { icon: Loader2, color: "text-indigo-500 dark:text-indigo-400", bg: "bg-indigo-500/5", border: "border-indigo-500/10", label: "Generating", spin: true },
ready: { icon: Eye, color: "text-amber-600 dark:text-amber-300", bg: "bg-amber-500/10", border: "border-amber-500/20", label: "Ready" },
sending: { icon: Loader2, color: "text-indigo-600 dark:text-indigo-300", bg: "bg-indigo-500/10", border: "border-indigo-500/20", label: "Sending", spin: true },
sent: { icon: CheckCircle2, color: "text-emerald-700 dark:text-emerald-300", bg: "bg-emerald-500/10", border: "border-emerald-500/20", label: "Sent" },
skipped: { icon: SkipForward, color: "text-[var(--c-text-muted)]", bg: "bg-[var(--c-bg-subtle)]", border: "border-[var(--c-border)]", label: "Skipped" },
failed: { icon: XCircle, color: "text-red-500 dark:text-red-400", bg: "bg-red-500/5", border: "border-red-500/10", label: "Failed" },
};
export default function EmailQueue({ queue, currentIndex, stats, batchMode, autoRunning, onSelect, onApprove, onSkip, onAutoRun, onStopAuto }) {
if (!queue || queue.length === 0) return null;
const total = queue.length;
const sent = stats?.sent || 0;
const failed = stats?.failed || 0;
const skipped = stats?.skipped || 0;
const done = sent + failed + skipped;
const progress = total > 0 ? (done / total) * 100 : 0;
return (
<div className="sticky bottom-0 bg-[var(--c-bg-primary)]/80 backdrop-blur-xl border-t border-[var(--c-border)] px-6 py-3 transition-colors duration-300 z-30">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3 text-[11px]">
<span className="font-bold text-[var(--c-text-primary)]">{sent}/{total}</span>
<span className="text-[var(--c-text-muted)]">sent</span>
{skipped > 0 && <span className="text-[var(--c-text-muted)]">{skipped} skipped</span>}
{failed > 0 && <span className="text-red-500 dark:text-red-400 font-medium">{failed} failed</span>}
{autoRunning && <span className="flex items-center gap-1 text-indigo-500 dark:text-indigo-400 font-medium"><Loader2 size={10} className="animate-spin" /> Running...</span>}
</div>
<div className="flex items-center gap-2">
{batchMode === "auto" && !autoRunning && sent < total && (
<button onClick={onAutoRun} className="flex items-center gap-1.5 px-4 py-1.5 bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 text-white text-[11px] font-bold rounded-lg transition-all shadow-lg shadow-emerald-500/15">
<Zap size={11} /> Auto-Send All
</button>
)}
{autoRunning && (
<button onClick={onStopAuto} className="flex items-center gap-1.5 px-4 py-1.5 bg-red-500/20 hover:bg-red-500/30 text-red-500 dark:text-red-400 border border-red-500/20 text-[11px] font-semibold rounded-lg transition-all">
<Square size={11} /> Stop
</button>
)}
</div>
</div>
<div className="w-full bg-gray-200 dark:bg-white/[0.04] rounded-full h-1 mb-3 overflow-hidden">
<div className="bg-gradient-to-r from-emerald-400 to-teal-400 h-1 rounded-full transition-all duration-700 ease-out" style={{ width: `${progress}%` }} />
</div>
<div className="flex gap-1.5 overflow-x-auto pb-1">
{queue.map((item, i) => {
const config = STATUS_CONFIG[item.status] || STATUS_CONFIG.queued;
const Icon = config.icon;
const isActive = i === currentIndex;
return (
<div key={i} className="shrink-0 flex flex-col items-center gap-0.5">
<button onClick={() => onSelect?.(i)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[11px] font-medium transition-all border ${isActive ? "bg-indigo-500/20 text-indigo-600 dark:text-indigo-300 border-indigo-500/30 shadow-lg shadow-indigo-500/10" : `${config.bg} ${config.color} ${config.border} hover:brightness-110`
}`}
title={`${item.email} — ${config.label}${item.error ? ": " + item.error : ""}`}>
<Icon size={11} className={config.spin ? "animate-spin" : ""} />
<span className="max-w-[90px] truncate">{item.email?.split("@")[0] || item.input}</span>
</button>
{isActive && item.status === "ready" && batchMode === "manual" && (
<div className="flex gap-1 mt-0.5">
<button onClick={() => onApprove?.(i)} className="flex items-center gap-0.5 px-2 py-0.5 bg-emerald-500 hover:bg-emerald-400 text-white rounded text-[9px] font-semibold transition-colors" title="Send"><Send size={8} /> Send</button>
<button onClick={() => onSkip?.(i)} className="px-2 py-0.5 bg-gray-100 dark:bg-white/5 text-gray-500 dark:text-[#5a5a72] hover:bg-gray-200 dark:hover:bg-white/10 rounded text-[9px] font-medium transition-colors" title="Skip">Skip</button>
</div>
)}
</div>
);
})}
</div>
</div>
);
}
|