Progress bar: forward-only crawl, never goes backward
This commit is contained in:
@@ -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]);
|
||||||
|
|||||||
Reference in New Issue
Block a user