"use client"; import { useEffect, useRef, useState, useCallback, useMemo } from "react"; import dynamic from "next/dynamic"; import { useTranslations } from "next-intl"; import { useHeatmapData, type HeatmapPoint } from "@/hooks/use-heatmap-data"; import { GlobeControls } from "./globe-controls"; function GlobeLoading() { const t = useTranslations("globe"); return (
{t("loading")}
); } const Globe = dynamic(() => import("react-globe.gl"), { ssr: false, loading: () => , }); interface ArcData { startLat: number; startLng: number; endLat: number; endLng: number; color: string; } export function GlobeView() { const t = useTranslations("globe"); // eslint-disable-next-line @typescript-eslint/no-explicit-any const globeRef = useRef(undefined); const containerRef = useRef(null); const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); const [hoveredPoint, setHoveredPoint] = useState(null); const { points } = useHeatmapData(30000); // Generate arcs from recent activity (connecting pairs of active points) const arcs = useMemo((): ArcData[] => { if (points.length < 2) return []; const result: ArcData[] = []; const maxArcs = Math.min(points.length - 1, 8); for (let i = 0; i < maxArcs; i++) { const from = points[i]; const to = points[(i + 1) % points.length]; result.push({ startLat: from.lat, startLng: from.lng, endLat: to.lat, endLng: to.lng, color: "rgba(0, 240, 255, 0.4)", }); } return result; }, [points]); useEffect(() => { const updateDimensions = () => { if (containerRef.current) { setDimensions({ width: containerRef.current.clientWidth, height: containerRef.current.clientHeight, }); } }; updateDimensions(); const observer = new ResizeObserver(updateDimensions); if (containerRef.current) observer.observe(containerRef.current); return () => observer.disconnect(); }, []); useEffect(() => { if (globeRef.current) { globeRef.current.pointOfView({ lat: 20, lng: 100, altitude: 2.5 }); } }, [dimensions]); const handleResetView = useCallback(() => { if (globeRef.current) { globeRef.current.pointOfView({ lat: 20, lng: 100, altitude: 2.5 }); } }, []); const handleZoomIn = useCallback(() => { if (globeRef.current) { globeRef.current.pointOfView({ lat: 20, lng: 100, altitude: 1.5 }); } }, []); const handleZoomOut = useCallback(() => { if (globeRef.current) { globeRef.current.pointOfView({ lat: 20, lng: 100, altitude: 3.5 }); } }, []); return (
{dimensions.width > 0 && ( (d as HeatmapPoint).lat} pointLng={(d: object) => (d as HeatmapPoint).lng} pointAltitude={(d: object) => Math.min((d as HeatmapPoint).weight * 0.02, 0.15)} pointRadius={(d: object) => Math.max((d as HeatmapPoint).weight * 0.3, 0.4)} pointColor={() => "#00f0ff"} pointsMerge={false} onPointHover={(point: object | null) => setHoveredPoint(point as HeatmapPoint | null)} // Arcs arcsData={arcs} arcStartLat={(d: object) => (d as ArcData).startLat} arcStartLng={(d: object) => (d as ArcData).startLng} arcEndLat={(d: object) => (d as ArcData).endLat} arcEndLng={(d: object) => (d as ArcData).endLng} arcColor={(d: object) => (d as ArcData).color} arcDashLength={0.5} arcDashGap={0.2} arcDashAnimateTime={2000} arcStroke={0.3} // Auto-rotate animateIn={true} enablePointerInteraction={true} /> )} {/* Tooltip */} {hoveredPoint && (
🦞

{hoveredPoint.city}

{hoveredPoint.country}

{t("activeClaws", { count: hoveredPoint.clawCount })}

)}
); }