diff --git a/src/components/ui/DynamicMapContainer.tsx b/src/components/ui/DynamicMapContainer.tsx new file mode 100644 index 0000000..9bc988a --- /dev/null +++ b/src/components/ui/DynamicMapContainer.tsx @@ -0,0 +1,84 @@ +import { useState, useEffect } from 'react'; +import WorldMap from './world-map'; +import { motion } from 'framer-motion'; + +// Interface for the simplified data passed to WorldMap +interface GeoNode { + lat: number; + lng: number; + label?: string; + color?: string; +} + +// Interface for the raw data structure expected from the gridproxy API +interface RawNode { + node_id: number; + location: { + latitude: string; // API often returns these as strings + longitude: string; // API often returns these as strings + city: string; + country: string; + }; + // ... other raw fields you don't need +} + +function DynamicMapContainer() { + const [loading, setLoading] = useState(true); + const [nodes, setNodes] = useState([]); + const API_URL = "https://gridproxy.grid.tf/nodes?healthy=true"; + + useEffect(() => { + async function fetchNodeData() { + try { + const response = await fetch(API_URL); + const data: RawNode[] = await response.json(); // Type the incoming data + + // 🚨 Map the API response to your component's expected GeoNode format + const geoNodes: GeoNode[] = data + .filter((node: RawNode) => node.location && node.location.latitude && node.location.longitude) + .map((node: RawNode) => ({ + // Convert string coordinates to numbers + lat: parseFloat(node.location.latitude), + lng: parseFloat(node.location.longitude), + label: `${node.location.city}, ${node.location.country} (${node.node_id})`, + // Optionally set color based on some node property if available + })); + + setNodes(geoNodes); + setLoading(false); + } catch (error) { + console.error("Failed to fetch node data:", error); + setLoading(false); + } + } + + fetchNodeData(); + }, []); + + // --- RENDERING --- + + if (loading) { + // Show a loading state while data is being fetched + return ( +
+ + 🌎 + +

Loading nodes...

+
+ ); + } + + // Pass the dynamically fetched nodes to your WorldMap component + return ( + + ); +} + +export default DynamicMapContainer; \ No newline at end of file diff --git a/src/components/ui/world-map.tsx b/src/components/ui/world-map.tsx index 64e30c8..bb44360 100644 --- a/src/components/ui/world-map.tsx +++ b/src/components/ui/world-map.tsx @@ -1,7 +1,7 @@ "use client"; import { useRef } from "react"; -import { motion } from "motion/react"; +import { motion } from "framer-motion"; import DottedMap from "dotted-map"; interface MapProps { @@ -9,25 +9,29 @@ interface MapProps { start: { lat: number; lng: number; label?: string }; end: { lat: number; lng: number; label?: string }; }>; + // New prop for dynamic standalone nodes + nodes?: Array<{ lat: number; lng: number; label?: string; color?: string }>; lineColor?: string; } export default function WorldMap({ dots = [], - lineColor = "#06b6d4", // cyan-500 + nodes = [], + lineColor = "#06b6d4", }: MapProps) { const svgRef = useRef(null); - // βœ… Force dark-dotted map theme + // βœ… Force dark-dotted map theme const map = new DottedMap({ height: 100, grid: "diagonal" }); const svgMap = map.getSVG({ radius: 0.22, - color: "#06b6d480", // cyan-500 at 50% opacity + color: "#06b6d480", shape: "circle", backgroundColor: "#111111", }); - // βœ… Point projection stays the same + // βœ… Point projection stays the same + // Projects lat/lng to the SVG's 800x400 viewBox coordinates const projectPoint = (lat: number, lng: number) => { const x = (lng + 180) * (800 / 360); const y = (90 - lat) * (400 / 180); @@ -36,7 +40,8 @@ export default function WorldMap({ const createCurvedPath = (start: any, end: any) => { const midX = (start.x + end.x) / 2; - const midY = Math.min(start.y, end.y) - 50; + // Creates an arc that bows upward by 50 units + const midY = Math.min(start.y, end.y) - 50; return `M ${start.x} ${start.y} Q ${midX} ${midY} ${end.x} ${end.y}`; }; @@ -49,13 +54,53 @@ export default function WorldMap({ draggable={false} /> - {/* βœ… Lines + points */} + {/* βœ… Lines + points + new standalone nodes */} - {/* βœ… animated curved travel lines */} + {/* Glowing path gradient DEFS */} + + + + + + + + + + {/* βœ… DYNAMIC STANDALONE NODE DOTS (New Section) */} + {nodes.map((node, i) => { + const p = projectPoint(node.lat, node.lng); + const dotColor = node.color || lineColor; + + return ( + + {/* Outer pulsing circle */} + + + + + {/* Inner fixed circle */} + + + ); + })} + + {/* βœ… Animated curved travel lines (Existing Logic) */} {dots.map((dot, i) => { const startPoint = projectPoint(dot.start.lat, dot.start.lng); const endPoint = projectPoint(dot.end.lat, dot.end.lng); @@ -78,17 +123,7 @@ export default function WorldMap({ ); })} - {/* βœ… glowing path gradient */} - - - - - - - - - - {/* βœ… start & end points with pulsing cyan glow */} + {/* βœ… Start & end points with pulsing cyan glow (Existing Logic) */} {dots.map((dot, i) => { const s = projectPoint(dot.start.lat, dot.start.lng); const e = projectPoint(dot.end.lat, dot.end.lng); @@ -122,4 +157,4 @@ export default function WorldMap({ ); -} +} \ No newline at end of file diff --git a/src/pages/home/HomeMap.tsx b/src/pages/home/HomeMap.tsx index 97cd2a8..95dfcf8 100644 --- a/src/pages/home/HomeMap.tsx +++ b/src/pages/home/HomeMap.tsx @@ -1,6 +1,6 @@ "use client"; import { useEffect, useState } from "react"; -import WorldMap from "@/components/ui/world-map"; +import DynamicMapContainer from "@/components/ui/DynamicMapContainer"; import { Eyebrow, H3, P } from "@/components/Texts"; type StatKey = "cores" | "nodes" | "ssd" | "countries"; @@ -112,16 +112,7 @@ Configure it once. Your node takes over from there.
{/* βœ… Match same side margins */}
- +