/** * GOOGLE MAPS FRONTEND INTEGRATION - ESSENTIAL GUIDE * * USAGE FROM PARENT COMPONENT: * ====== * * const mapRef = useRef(null); * * { * mapRef.current = map; // Store to control map from parent anytime, google map itself is in charge of the re-rendering, not react state. * * * ====== * Available Libraries and Core Features: * ------------------------------- * 📍 MARKER (from `marker` library) * - Attaches to map using { map, position } * new google.maps.marker.AdvancedMarkerElement({ * map, * position: { lat: 37.7749, lng: -122.4194 }, * title: "San Francisco", * }); * * ------------------------------- * 🏢 PLACES (from `places` library) * - Does not attach directly to map; use data with your map manually. * const place = new google.maps.places.Place({ id: PLACE_ID }); * await place.fetchFields({ fields: ["displayName", "location"] }); * map.setCenter(place.location); * new google.maps.marker.AdvancedMarkerElement({ map, position: place.location }); * * ------------------------------- * 🧭 GEOCODER (from `geocoding` library) * - Standalone service; manually apply results to map. * const geocoder = new google.maps.Geocoder(); * geocoder.geocode({ address: "New York" }, (results, status) => { * if (status === "OK" && results[0]) { * map.setCenter(results[0].geometry.location); * new google.maps.marker.AdvancedMarkerElement({ * map, * position: results[0].geometry.location, * }); * } * }); * * ------------------------------- * 📐 GEOMETRY (from `geometry` library) * - Pure utility functions; not attached to map. * const dist = google.maps.geometry.spherical.computeDistanceBetween(p1, p2); * * ------------------------------- * 🛣️ ROUTES (from `routes` library) * - Combines DirectionsService (standalone) + DirectionsRenderer (map-attached) * const directionsService = new google.maps.DirectionsService(); * const directionsRenderer = new google.maps.DirectionsRenderer({ map }); * directionsService.route( * { origin, destination, travelMode: "DRIVING" }, * (res, status) => status === "OK" && directionsRenderer.setDirections(res) * ); * * ------------------------------- * 🌦️ MAP LAYERS (attach directly to map) * - new google.maps.TrafficLayer().setMap(map); * - new google.maps.TransitLayer().setMap(map); * - new google.maps.BicyclingLayer().setMap(map); * * ------------------------------- * ✅ SUMMARY * - “map-attached” → AdvancedMarkerElement, DirectionsRenderer, Layers. * - “standalone” → Geocoder, DirectionsService, DistanceMatrixService, ElevationService. * - “data-only” → Place, Geometry utilities. */ /// import { useEffect, useRef } from "react"; import { usePersistFn } from "@/hooks/usePersistFn"; import { cn } from "@/lib/utils"; declare global { interface Window { google?: typeof google; } } const API_KEY = import.meta.env.VITE_FRONTEND_FORGE_API_KEY; const FORGE_BASE_URL = import.meta.env.VITE_FRONTEND_FORGE_API_URL || "https://forge.butterfly-effect.dev"; const MAPS_PROXY_URL = `${FORGE_BASE_URL}/v1/maps/proxy`; function loadMapScript() { return new Promise(resolve => { const script = document.createElement("script"); script.src = `${MAPS_PROXY_URL}/maps/api/js?key=${API_KEY}&v=weekly&libraries=marker,places,geocoding,geometry`; script.async = true; script.crossOrigin = "anonymous"; script.onload = () => { resolve(null); script.remove(); // Clean up immediately }; script.onerror = () => { console.error("Failed to load Google Maps script"); }; document.head.appendChild(script); }); } interface MapViewProps { className?: string; initialCenter?: google.maps.LatLngLiteral; initialZoom?: number; onMapReady?: (map: google.maps.Map) => void; } export function MapView({ className, initialCenter = { lat: 37.7749, lng: -122.4194 }, initialZoom = 12, onMapReady, }: MapViewProps) { const mapContainer = useRef(null); const map = useRef(null); const init = usePersistFn(async () => { await loadMapScript(); if (!mapContainer.current) { console.error("Map container not found"); return; } map.current = new window.google.maps.Map(mapContainer.current, { zoom: initialZoom, center: initialCenter, mapTypeControl: true, fullscreenControl: true, zoomControl: true, streetViewControl: true, mapId: "DEMO_MAP_ID", }); if (onMapReady) { onMapReady(map.current); } }); useEffect(() => { init(); }, [init]); return (
); }