"use client"; import { geoPath } from "d3"; import type { Feature, FeatureCollection, GeoJsonProperties, Geometry, } from "geojson"; import { AlertTriangle, ChevronDown, Info, MapPin } from "lucide-react"; import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { Card } from "@/components/shadcn/card/card"; import { mapProviderFiltersForFindings } from "@/lib/provider-helpers"; import { HorizontalBarChart } from "./horizontal-bar-chart"; import { DEFAULT_MAP_COLORS, LocationPoint, MAP_CONFIG, MapColorsConfig, STATUS_FILTER_MAP, ThreatMapProps, } from "./threat-map.types"; import { createProjection, createSVGElement, fetchWorldData, getMapColors, } from "./threat-map.utils"; import { BarDataPoint } from "./types"; // Sub-components function MapTooltip({ location, position, }: { location: LocationPoint; position: { x: number; y: number }; }) { return (
{location.name}
{location.failFindings.toLocaleString()} Fail Findings
{location.change !== undefined && (

0 ? "text-pass-primary" : "text-fail-primary"}`} > {location.change > 0 ? "+" : ""} {location.change}%{" "} since last scan

)}
); } function LocationDetails({ location, onBarClick, }: { location: Pick; onBarClick: (dataPoint: BarDataPoint) => void; }) { return (

{location.totalFindings.toLocaleString()} Total Findings

); } export function ThreatMap({ data, height = MAP_CONFIG.defaultHeight, }: ThreatMapProps) { const router = useRouter(); const searchParams = useSearchParams(); const svgRef = useRef(null); const containerRef = useRef(null); const [selectedLocation, setSelectedLocation] = useState(null); const [hoveredLocation, setHoveredLocation] = useState( null, ); const [tooltipPosition, setTooltipPosition] = useState<{ x: number; y: number; } | null>(null); const [selectedRegion, setSelectedRegion] = useState("All Regions"); const [worldData, setWorldData] = useState(null); const [isLoadingMap, setIsLoadingMap] = useState(true); const [dimensions, setDimensions] = useState<{ width: number; height: number; }>({ width: MAP_CONFIG.defaultWidth, height, }); const [mapColors, setMapColors] = useState(DEFAULT_MAP_COLORS); const isGlobalSelected = selectedRegion.toLowerCase() === "global"; const isAllRegions = selectedRegion === "All Regions"; // For display count only (not used in useEffect to avoid infinite loop) const locationCount = data.locations.filter((loc) => { if (loc.region.toLowerCase() === "global") return false; if (isAllRegions) return true; if (isGlobalSelected) return false; return loc.region === selectedRegion; }).length; const sortedRegions = [...data.regions].sort((a, b) => { if (a.toLowerCase() === "global") return -1; if (b.toLowerCase() === "global") return 1; return a.localeCompare(b); }); // Compute global aggregated data const globalLocations = data.locations.filter( (loc) => loc.region.toLowerCase() === "global", ); const globalAggregatedData = globalLocations.length > 0 ? (() => { const aggregate = (name: string) => globalLocations.reduce( (sum, loc) => sum + (loc.severityData.find((d) => d.name === name)?.value || 0), 0, ); const failValue = aggregate("Fail"); const passValue = aggregate("Pass"); const total = failValue + passValue; return { name: "Global Regions", regionCode: "global", providerType: "global", totalFindings: total, failFindings: failValue, severityData: [ { name: "Fail", value: failValue, percentage: total > 0 ? Math.round((failValue / total) * 100) : 0, color: "var(--color-bg-fail)", }, { name: "Pass", value: passValue, percentage: total > 0 ? Math.round((passValue / total) * 100) : 0, color: "var(--color-bg-pass)", }, ], }; })() : null; // Reset selected location when region changes useEffect(() => { setSelectedLocation(null); }, [selectedRegion]); // Theme colors useEffect(() => { setMapColors(getMapColors()); const observer = new MutationObserver(() => setMapColors(getMapColors())); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"], }); return () => observer.disconnect(); }, []); // Fetch world data useEffect(() => { let mounted = true; fetchWorldData() .then((d) => mounted && d && setWorldData(d)) .finally(() => mounted && setIsLoadingMap(false)); return () => { mounted = false; }; }, []); // Resize handler useEffect(() => { const update = () => containerRef.current && setDimensions({ width: containerRef.current.clientWidth, height }); update(); window.addEventListener("resize", update); return () => window.removeEventListener("resize", update); }, [height]); // Render map useEffect(() => { if (!svgRef.current || !worldData || isLoadingMap) return; const svg = svgRef.current; svg.innerHTML = ""; // Compute filtered locations inside useEffect to avoid infinite loop const isGlobal = selectedRegion.toLowerCase() === "global"; const isAll = selectedRegion === "All Regions"; const locationsToRender = data.locations.filter((loc) => { if (loc.region.toLowerCase() === "global") return false; if (isAll) return true; if (isGlobal) return false; return loc.region === selectedRegion; }); const projection = createProjection(dimensions.width, dimensions.height); const path = geoPath().projection(projection); // Countries const mapGroup = createSVGElement("g", { class: "map-countries", }); const fillColor = isGlobal ? mapColors.pointDefault : mapColors.landFill; worldData.features?.forEach( (feat: Feature) => { const pathData = path(feat); if (pathData) { const el = createSVGElement("path", { d: pathData, fill: fillColor, stroke: mapColors.landStroke, "stroke-width": "0.5", }); mapGroup.appendChild(el); } }, ); svg.appendChild(mapGroup); // Helper to create glow rings const createGlowRing = ( cx: string, cy: string, r: number, color: string, opacity: string, ) => createSVGElement("circle", { cx, cy, r: r.toString(), fill: "none", stroke: color, "stroke-width": "1", opacity, }); // Points const pointsGroup = createSVGElement("g", { class: "threat-points", }); const createPoint = (loc: LocationPoint) => { const proj = projection(loc.coordinates); if ( !proj || proj[0] < 0 || proj[0] > dimensions.width || proj[1] < 0 || proj[1] > dimensions.height ) { return null; } const [x, y] = proj; const isSelected = selectedLocation?.id === loc.id; const radius = isSelected ? MAP_CONFIG.selectedPointRadius : MAP_CONFIG.pointRadius; const color = isSelected ? mapColors.pointSelected : mapColors.pointDefault; const group = createSVGElement("g", { class: "cursor-pointer", }); group.appendChild( createGlowRing(x.toString(), y.toString(), radius + 4, color, "0.4"), ); group.appendChild( createGlowRing(x.toString(), y.toString(), radius + 8, color, "0.2"), ); const circle = createSVGElement("circle", { cx: x.toString(), cy: y.toString(), r: radius.toString(), fill: color, }); group.appendChild(circle); group.addEventListener("click", () => setSelectedLocation(isSelected ? null : loc), ); group.addEventListener("mouseenter", (e) => { setHoveredLocation(loc); const rect = svg.getBoundingClientRect(); setTooltipPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top, }); }); group.addEventListener("mousemove", (e) => { const rect = svg.getBoundingClientRect(); setTooltipPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top, }); }); group.addEventListener("mouseleave", () => { setHoveredLocation(null); setTooltipPosition(null); }); return group; }; locationsToRender.forEach((loc) => { if (selectedLocation?.id !== loc.id) { const point = createPoint(loc); if (point) pointsGroup.appendChild(point); } }); if (selectedLocation) { const loc = locationsToRender.find((l) => l.id === selectedLocation.id); if (loc) { const point = createPoint(loc); if (point) pointsGroup.appendChild(point); } } svg.appendChild(pointsGroup); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ dimensions, data.locations, selectedRegion, selectedLocation, worldData, isLoadingMap, mapColors, ]); const navigateToFindings = ( status: string, regionCode: string, providerType?: string, ) => { const params = new URLSearchParams(searchParams.toString()); mapProviderFiltersForFindings(params); if (providerType) params.set("filter[provider_type__in]", providerType); params.set("filter[region__in]", regionCode); params.set("filter[status__in]", status); params.set("filter[muted]", "false"); router.push(`/findings?${params.toString()}`); }; return (

Threat Map

{isLoadingMap ? (
Loading map...
) : (
{hoveredLocation && tooltipPosition && ( )}
)}
{selectedLocation ? ( { const status = STATUS_FILTER_MAP[dp.name]; if (status && selectedLocation.providerType) { navigateToFindings( status, selectedLocation.regionCode, selectedLocation.providerType, ); } }} /> ) : isGlobalSelected && globalAggregatedData ? ( { const status = STATUS_FILTER_MAP[dp.name]; if (status) { navigateToFindings(status, "global"); } }} /> ) : (

Select a location on the map to view details

)}
); }