import React, { useRef, useEffect, useState } from 'react'; const NetworkMap = ({ users }) => { const canvasRef = useRef(null); const [hoveredUser, setHoveredUser] = useState(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); let animationFrameId; // Simple auto-scaling projection const lats = users.map(u => u.location.lat); const lngs = users.map(u => u.location.lng); const minLat = Math.min(...lats) - 5; const maxLat = Math.max(...lats) + 5; const minLng = Math.min(...lngs) - 5; const maxLng = Math.max(...lngs) + 5; const project = (lat, lng) => { const x = ((lng - minLng) / (maxLng - minLng)) * canvas.width; const y = canvas.height - ((lat - minLat) / (maxLat - minLat)) * canvas.height; return { x, y }; }; const render = () => { // Resize canvas to parent if (canvas.width !== canvas.parentElement.clientWidth || canvas.height !== canvas.parentElement.clientHeight) { canvas.width = canvas.parentElement.clientWidth; canvas.height = canvas.parentElement.clientHeight; } ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw Grid ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)'; ctx.lineWidth = 1; const gridSize = 40; for (let x = 0; x < canvas.width; x += gridSize) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); } for (let y = 0; y < canvas.height; y += gridSize) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } // Draw Connections (just for visual flair) ctx.strokeStyle = 'rgba(0, 240, 255, 0.05)'; ctx.lineWidth = 0.5; users.forEach((u, i) => { const p1 = project(u.location.lat, u.location.lng); // Connect to nearest neighbor (simplified, just connect to next few) for (let j = i + 1; j < Math.min(i + 3, users.length); j++) { const p2 = project(users[j].location.lat, users[j].location.lng); ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke(); } }); // Draw Users users.forEach(user => { const { x, y } = project(user.location.lat, user.location.lng); const isActive = user.status === 'Online'; const isRoaming = user.isRoaming; const color = isActive ? '#00ff9d' : (isRoaming ? '#ffbf00' : '#ff0055'); // Pulse if (isActive) { const time = Date.now() / 1000; const radius = 4 + Math.sin(time * 2 + user.id) * 2; ctx.beginPath(); ctx.arc(x, y, radius * 2, 0, Math.PI * 2); ctx.fillStyle = `color-mix(in srgb, ${color} 20%, transparent)`; ctx.fill(); } ctx.beginPath(); ctx.arc(x, y, 4, 0, Math.PI * 2); ctx.fillStyle = color; ctx.fill(); // Label if active or Roaming if (isRoaming) { ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.font = '10px Inter'; ctx.fillText(user.location.city, x + 8, y + 3); } }); animationFrameId = requestAnimationFrame(render); }; render(); return () => cancelAnimationFrame(animationFrameId); }, [users]); return (
Online
Roaming
Offline
); }; export default NetworkMap;