"use client"; import { useEffect, useState, useCallback } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { motion } from "framer-motion"; import { Download, Loader2, Search } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge, Skeleton } from "@/components/ui/badge"; import type { ScanResult } from "@/lib/types"; import Link from "next/link"; import { NetworkExport } from "@/components/scan/network-export"; export function ScanPanel() { const searchParams = useSearchParams(); const router = useRouter(); const [domain, setDomain] = useState(searchParams.get("domain") ?? ""); const [scanId, setScanId] = useState(null); const [progress, setProgress] = useState(0); const [stage, setStage] = useState(null); const [status, setStatus] = useState("idle"); const [result, setResult] = useState(null); const [error, setError] = useState(null); const startScan = useCallback(async (d: string) => { setError(null); setResult(null); setProgress(0); setStage(null); setStatus("starting"); const res = await fetch("/api/scan", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ domain: d }), }); const data = await res.json(); if (!res.ok) { setError(data.error ?? "Scan failed"); setStatus("failed"); return; } setScanId(data.id); setStatus("running"); router.replace(`/dashboard?domain=${encodeURIComponent(d)}`); if (data.cached) { const full = await fetch(`/api/scan/${data.id}`); const scan = await full.json(); setResult(scan.result as ScanResult); setProgress(100); setStatus("completed"); return; } const es = new EventSource(`/api/scan/${data.id}/stream`); es.onmessage = (ev) => { const msg = JSON.parse(ev.data) as { status: string; progress: number; stage?: string; result?: ScanResult; error?: string; }; setProgress(msg.progress ?? 0); if (msg.stage) setStage(msg.stage); if (msg.status === "COMPLETED" && msg.result) { setResult(msg.result); setStatus("completed"); es.close(); } if (msg.status === "FAILED") { setError(msg.error ?? "Scan failed"); setStatus("failed"); es.close(); } }; es.onerror = () => es.close(); }, [router]); useEffect(() => { const d = searchParams.get("domain"); if (d && status === "idle") { setDomain(d); startScan(d); } }, [searchParams, status, startScan]); const onSubmit = (e: React.FormEvent) => { e.preventDefault(); if (domain.trim()) startScan(domain.trim()); }; return (
setDomain(e.target.value)} disabled={status === "running"} />
{error && (
{error}
)} {status === "running" && !result && (
{stage && (

{stage}

)}
{[1, 2, 3, 4, 5, 6].map((i) => ( ))}
)} {result && ( )}
); } function ScanResults({ result, scanId }: { result: ScanResult; scanId: string | null }) { return (

{result.domain}

Scanned {new Date(result.scannedAt).toLocaleString()}

{scanId && ( <> )}
= 60} />
DNS records {result.dns.records.length === 0 ? (

No records found

) : (
    {result.dns.records.map((r, i) => (
  • {r.type} {r.value}
  • ))}
)}
Security headers

{result.security.score}

{result.security.present.map((h) => ( {h} ))} {result.security.missing.slice(0, 4).map((h) => ( missing: {h} ))}
Geo / IP {result.geo ? (
IP
{result.geo.ip}
Location
{[result.geo.city, result.geo.region, result.geo.country].filter(Boolean).join(", ")}
{result.geo.isp && (
ISP
{result.geo.isp}
)}
) : (

Geo lookup unavailable

)}
Tech & CDN
{result.tech.map((t) => ( {t.name} ))} {result.cdnWaf.detected.map((c) => ( {c} ))} {result.tech.length === 0 && result.cdnWaf.detected.length === 0 && (

No strong signals detected

)}
); } function MetricCard({ title, value, ok }: { title: string; value: string; ok: boolean }) { return (

{title}

{value}

); }