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,
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(() => {
let cancelled = false;
let pendulumInterval: ReturnType<typeof setInterval> | null = null;
let crawlInterval: ReturnType<typeof setInterval> | null = null;
let pollTimeout: ReturnType<typeof setTimeout> | null = null;
// Pendulum animation while running
let goingUp = true;
pendulumInterval = setInterval(() => {
// Slowly creep forward — never backwards
crawlInterval = setInterval(() => {
if (cancelled) return;
setProgressWidth((prev) => {
if (goingUp) {
if (prev >= 85) { goingUp = false; return 84; }
return prev + 1;
} else {
if (prev <= 40) { goingUp = true; return 41; }
return prev - 1;
}
setProgressWidth(prev => {
if (prev >= 88) return prev; // hard cap while loading
return prev + 0.4; // ~0.4% per 200ms → ~2min to reach 88%
});
}, 120);
}, 200);
async function poll() {
if (cancelled) return;
@@ -76,16 +84,23 @@ export function LoadingCard({ jobId, onDone, onError }: LoadingCardProps) {
if (!cancelled) {
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 (pendulumInterval) clearInterval(pendulumInterval);
if (crawlInterval) clearInterval(crawlInterval);
setProgressWidth(100);
setTimeout(() => {
if (!cancelled) onDone(data.totalLeads);
}, 800);
} else if (data.status === "failed") {
if (pendulumInterval) clearInterval(pendulumInterval);
if (crawlInterval) clearInterval(crawlInterval);
onError();
} else {
// Cap crawl at phase max while in that phase
setProgressWidth(prev => Math.min(prev, PHASE_MAX[phase]));
pollTimeout = setTimeout(poll, 2500);
}
}
@@ -100,7 +115,7 @@ export function LoadingCard({ jobId, onDone, onError }: LoadingCardProps) {
return () => {
cancelled = true;
if (pendulumInterval) clearInterval(pendulumInterval);
if (crawlInterval) clearInterval(crawlInterval);
if (pollTimeout) clearTimeout(pollTimeout);
};
}, [jobId, onDone, onError]);