Progress bar: forward-only crawl, never goes backward

This commit is contained in:
Timo Uttenweiler
2026-03-27 17:13:30 +01:00
parent 4c82e96f5a
commit 11197c9db1

View File

@@ -45,27 +45,35 @@ export function LoadingCard({ jobId, onDone, onError }: LoadingCardProps) {
totalLeads: 0, totalLeads: 0,
emailsFound: 0, emailsFound: 0,
}); });
const [progressWidth, setProgressWidth] = useState(40); const [progressWidth, setProgressWidth] = useState(3);
// Phase → minimum progress threshold (never go below these)
const PHASE_MIN: Record<Phase, number> = {
scraping: 3,
enriching: 35,
emails: 60,
done: 100,
};
const PHASE_MAX: Record<Phase, number> = {
scraping: 34,
enriching: 59,
emails: 88,
done: 100,
};
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
let pendulumInterval: ReturnType<typeof setInterval> | null = null; let crawlInterval: ReturnType<typeof setInterval> | null = null;
let pollTimeout: ReturnType<typeof setTimeout> | null = null; let pollTimeout: ReturnType<typeof setTimeout> | null = null;
// Pendulum animation while running // Slowly creep forward — never backwards
let goingUp = true; crawlInterval = setInterval(() => {
pendulumInterval = setInterval(() => {
if (cancelled) return; if (cancelled) return;
setProgressWidth((prev) => { setProgressWidth(prev => {
if (goingUp) { if (prev >= 88) return prev; // hard cap while loading
if (prev >= 85) { goingUp = false; return 84; } return prev + 0.4; // ~0.4% per 200ms → ~2min to reach 88%
return prev + 1;
} else {
if (prev <= 40) { goingUp = true; return 41; }
return prev - 1;
}
}); });
}, 120); }, 200);
async function poll() { async function poll() {
if (cancelled) return; if (cancelled) return;
@@ -76,16 +84,23 @@ export function LoadingCard({ jobId, onDone, onError }: LoadingCardProps) {
if (!cancelled) { if (!cancelled) {
setJobStatus(data); setJobStatus(data);
const phase = getPhase(data);
// Advance to at least the phase minimum — never go backwards
setProgressWidth(prev => Math.max(prev, PHASE_MIN[phase]));
if (data.status === "complete") { if (data.status === "complete") {
if (pendulumInterval) clearInterval(pendulumInterval); if (crawlInterval) clearInterval(crawlInterval);
setProgressWidth(100); setProgressWidth(100);
setTimeout(() => { setTimeout(() => {
if (!cancelled) onDone(data.totalLeads); if (!cancelled) onDone(data.totalLeads);
}, 800); }, 800);
} else if (data.status === "failed") { } else if (data.status === "failed") {
if (pendulumInterval) clearInterval(pendulumInterval); if (crawlInterval) clearInterval(crawlInterval);
onError(); onError();
} else { } else {
// Cap crawl at phase max while in that phase
setProgressWidth(prev => Math.min(prev, PHASE_MAX[phase]));
pollTimeout = setTimeout(poll, 2500); pollTimeout = setTimeout(poll, 2500);
} }
} }
@@ -100,7 +115,7 @@ export function LoadingCard({ jobId, onDone, onError }: LoadingCardProps) {
return () => { return () => {
cancelled = true; cancelled = true;
if (pendulumInterval) clearInterval(pendulumInterval); if (crawlInterval) clearInterval(crawlInterval);
if (pollTimeout) clearTimeout(pollTimeout); if (pollTimeout) clearTimeout(pollTimeout);
}; };
}, [jobId, onDone, onError]); }, [jobId, onDone, onError]);