import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Fragment, useState } from 'react'; import { getScan, getScanDiff, listScans, triggerScan } from '../api/scanner'; import { trackFeatureUsage } from '../services/analytics'; import type { CookieDiffItem, ScanDiff, ScanJob, ScanJobDetail, ScanResult } from '../types/api'; import { Alert } from './ui/alert'; import { Badge } from './ui/badge'; import { Button } from './ui/button'; import { LoadingState } from './ui/loading-state'; interface Props { siteId: string; } function statusVariant(status: string): 'warning' | 'info' | 'success' | 'error' | 'neutral' { const map: Record = { pending: 'warning', running: 'info', completed: 'success', failed: 'error', }; return map[status] ?? 'neutral'; } function diffVariant(status: string): 'success' | 'error' | 'warning' | 'neutral' { const map: Record = { new: 'success', removed: 'error', changed: 'warning', }; return map[status] ?? 'neutral'; } function DiffSection({ title, items }: { title: string; items: CookieDiffItem[] }) { if (items.length === 0) return null; return (

{title} ({items.length})

{items.map((item, idx) => ( ))}
Name Domain Type Status Details
{item.name} {item.domain} {item.storage_type} {item.diff_status} {item.details ?? '—'}
); } function ScanDiffView({ scanId }: { scanId: string }) { const { data: diff, isLoading } = useQuery({ queryKey: ['scans', scanId, 'diff'], queryFn: () => getScanDiff(scanId), }); if (isLoading) return ; if (!diff) return null; const hasChanges = diff.total_new + diff.total_removed + diff.total_changed > 0; return (

Scan Diff {diff.previous_scan_id ? '' : ' (first scan — no comparison available)'}

{hasChanges ? ( <> ) : (

No changes detected.

)}
); } function InitiatorChain({ chain }: { chain: string[] }) { if (chain.length === 0) return ; return (
{chain.map((url, idx) => { // Show just the pathname for brevity let label: string; try { const parsed = new URL(url); label = parsed.pathname.length > 40 ? '…' + parsed.pathname.slice(-38) : parsed.pathname; } catch { label = url.length > 40 ? '…' + url.slice(-38) : url; } return ( {idx > 0 && } {label} ); })}
); } function ScanResultsView({ scanId }: { scanId: string }) { const { data: detail, isLoading } = useQuery({ queryKey: ['scans', scanId, 'detail'], queryFn: () => getScan(scanId), }); if (isLoading) return ; if (!detail || detail.results.length === 0) { return

No results recorded.

; } // Only show results that have an initiator chain const withChain = detail.results.filter( (r: ScanResult) => r.initiator_chain && r.initiator_chain.length > 1, ); if (withChain.length === 0) { return

No initiator chains detected in this scan.

; } return (

Initiator Chains ({withChain.length} cookies)

{withChain.map((r: ScanResult) => ( ))}
Cookie Domain Chain
{r.cookie_name} {r.cookie_domain}
); } export default function SiteScannerTab({ siteId }: Props) { const queryClient = useQueryClient(); const [expandedScanId, setExpandedScanId] = useState(null); const { data: scans, isLoading } = useQuery({ queryKey: ['scans', siteId], queryFn: () => listScans(siteId), }); const triggerMutation = useMutation({ mutationFn: () => triggerScan(siteId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['scans', siteId] }); trackFeatureUsage('scan', 'trigger', { site_id: siteId }); }, }); if (isLoading) { return ; } return (
{/* Header with trigger button */}

Cookie Scans

{triggerMutation.isError && ( Failed to trigger scan. A scan may already be in progress. )} {/* Scan history */} {!scans || scans.length === 0 ? (
No scans yet. Trigger a scan to discover cookies on your site.
) : (
{scans.map((scan) => ( {expandedScanId === scan.id && ( )} ))}
Status Trigger Pages Cookies Found Started Completed Actions
{scan.status} {scan.trigger} {scan.pages_scanned}{scan.pages_total ? ` / ${scan.pages_total}` : ''} {scan.cookies_found} {scan.started_at ? new Date(scan.started_at).toLocaleString() : '—'} {scan.completed_at ? new Date(scan.completed_at).toLocaleString() : '—'} {scan.status === 'completed' && ( )} {scan.status === 'failed' && scan.error_message && ( Error )}
)}
); }