"use client"; import { useEffect, useRef, useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSpinner } from "@fortawesome/free-solid-svg-icons"; import type { AnalysisProgress } from "../types/analysis"; import type { AnalysisStatus } from "../hooks/useAnalysis"; type ProgressIndicatorProps = { status: AnalysisStatus; progress: AnalysisProgress | null; timeoutSeconds: number; incompleteTests?: string[]; }; type ProgressVariant = "normal" | "warning" | "timeout"; const formatSeconds = (seconds: number): string => `${seconds}s`; const getVariant = ( status: AnalysisStatus, remainingSeconds: number, timeoutSeconds: number, ): ProgressVariant => { if (status === "timeout") { return "timeout"; } if (status !== "analysing") { return "normal"; } const warningThreshold = Math.max(5, Math.round(timeoutSeconds * 0.15)); return remainingSeconds <= warningThreshold ? "warning" : "normal"; }; const variantStyles: Record< ProgressVariant, { bar: string; badge: string; track: string; } > = { normal: { bar: "bg-clean", badge: "text-clean border-clean/40", track: "bg-clean/10", }, warning: { bar: "bg-suspicious", badge: "text-suspicious border-suspicious/40", track: "bg-suspicious/10", }, timeout: { bar: "bg-spam", badge: "text-spam border-spam/40", track: "bg-spam/10", }, }; export default function ProgressIndicator({ status, progress, timeoutSeconds, incompleteTests = [], }: ProgressIndicatorProps) { const [elapsedMs, setElapsedMs] = useState(() => progress?.elapsedMs ?? 0); const progressRef = useRef(progress); const statusRef = useRef(status); const anchorRef = useRef<{ elapsedMs: number; timestamp: number } | null>(null); useEffect(() => { progressRef.current = progress; statusRef.current = status; if (!progress || status !== "analysing") { anchorRef.current = null; return; } anchorRef.current = { elapsedMs: progress.elapsedMs, timestamp: Date.now(), }; }, [progress, status]); useEffect(() => { const interval = window.setInterval(() => { const currentStatus = statusRef.current; const currentProgress = progressRef.current; const anchor = anchorRef.current; const baseElapsedMs = currentProgress?.elapsedMs ?? 0; const nextElapsedMs = currentStatus === "analysing" && currentProgress && anchor ? anchor.elapsedMs + Math.max(0, Date.now() - anchor.timestamp) : baseElapsedMs; setElapsedMs((previous) => (previous === nextElapsedMs ? previous : nextElapsedMs)); }, 1000); return () => { window.clearInterval(interval); }; }, []); const elapsedSeconds = Math.floor(elapsedMs / 1000); const remainingSeconds = Math.max(0, timeoutSeconds - elapsedSeconds); const percentage = progress ? Math.round(progress.percentage) : 0; const variant = getVariant(status, remainingSeconds, timeoutSeconds); const styles = variantStyles[variant]; return (
{status === "analysing" ? "Analysing" : status} {status === "analysing" ? ( ) : null}
{formatSeconds(elapsedSeconds)} / {formatSeconds(remainingSeconds)}
{progress?.currentTest ?? "Preparing analysis"} {percentage}%
{status === "timeout" ? (

Timeout reached at {timeoutSeconds} seconds.

Incomplete tests:

{incompleteTests.length > 0 ? incompleteTests.join(", ") : "None"}

) : null}
); }