import React, { useEffect, useRef } from 'react'; import { Box } from '@chakra-ui/react'; // Dynamically load Leaflet let L: any = null; interface ContactMapProps { latitude: number; longitude: number; zoom?: number; address?: string; clubName?: string; mapStyle?: string; height?: number; clubPrimaryColor?: string; clubSecondaryColor?: string; } // Available map styles export const MAP_STYLES = { // Clean & Minimal 'positron': { name: 'Positron (Light)', url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', attribution: '© OpenStreetMap © CartoDB', description: 'Clean light map, perfect for overlays' }, 'positron-no-labels': { name: 'Positron No Labels', url: 'https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png', attribution: '© OpenStreetMap © CartoDB', description: 'Minimal light map without labels' }, // Dark Themes 'dark': { name: 'Dark Matter', url: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', attribution: '© OpenStreetMap © CartoDB', description: 'Dark theme, great for night mode' }, 'dark-no-labels': { name: 'Dark No Labels', url: 'https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png', attribution: '© OpenStreetMap © CartoDB', description: 'Dark map without labels' }, // Black & White 'toner': { name: 'Toner (B&W)', url: 'https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.png', attribution: '© Stamen Design © OpenStreetMap', description: 'High contrast black and white' }, 'toner-lite': { name: 'Toner Lite (B&W)', url: 'https://tiles.stadiamaps.com/tiles/stamen_toner_lite/{z}/{x}/{y}{r}.png', attribution: '© Stamen Design © OpenStreetMap', description: 'Subtle black and white' }, // Colorful Options 'voyager': { name: 'Voyager', url: 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', attribution: '© OpenStreetMap © CartoDB', description: 'Balanced colors, good readability' }, 'terrain': { name: 'Terrain', url: 'https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}{r}.jpg', attribution: '© Stamen Design © OpenStreetMap', description: 'Natural terrain visualization' }, 'watercolor': { name: 'Watercolor', url: 'https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg', attribution: '© Stamen Design © OpenStreetMap', description: 'Artistic watercolor style' }, // Default 'default': { name: 'OpenStreetMap', url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', attribution: '© OpenStreetMap contributors', description: 'Standard OpenStreetMap' }, // Satellite 'satellite': { name: 'Satellite', url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri', description: 'Satellite imagery' }, }; const ContactMap: React.FC = ({ latitude, longitude, zoom = 15, address, clubName, mapStyle = 'default', height = 400, clubPrimaryColor, clubSecondaryColor, }) => { const mapRef = useRef(null); const mapInstanceRef = useRef(null); const [isLoaded, setIsLoaded] = React.useState(false); const [loadError, setLoadError] = React.useState(null); useEffect(() => { // Load Leaflet CSS and JS dynamically const loadLeaflet = async () => { try { // Check if already loaded if ((window as any).L) { L = (window as any).L; setIsLoaded(true); return; } // Load CSS if (!document.getElementById('leaflet-css')) { const link = document.createElement('link'); link.id = 'leaflet-css'; link.rel = 'stylesheet'; link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; link.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY='; link.crossOrigin = ''; document.head.appendChild(link); } // Load JS if (!document.getElementById('leaflet-js')) { const script = document.createElement('script'); script.id = 'leaflet-js'; script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; script.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo='; script.crossOrigin = ''; script.onload = () => { L = (window as any).L; setIsLoaded(true); }; script.onerror = () => { setLoadError('Failed to load map library'); }; document.head.appendChild(script); } } catch (error) { setLoadError('Error loading map'); } }; loadLeaflet(); }, []); useEffect(() => { if (!isLoaded || !L || !mapRef.current || mapInstanceRef.current) return; try { // Initialize map const map = L.map(mapRef.current, { center: [latitude, longitude], zoom: zoom, scrollWheelZoom: false, // Disable scroll zoom for better UX }); mapInstanceRef.current = map; // Get tile layer URL based on style let tileUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; let attribution = '© OpenStreetMap contributors'; // Use predefined styles or custom URL if (mapStyle && MAP_STYLES[mapStyle as keyof typeof MAP_STYLES]) { const style = MAP_STYLES[mapStyle as keyof typeof MAP_STYLES]; tileUrl = style.url; attribution = style.attribution; } else if (mapStyle && mapStyle.startsWith('http')) { // Custom tile URL tileUrl = mapStyle; } // Add tile layer const tileLayer = L.tileLayer(tileUrl, { attribution: attribution, maxZoom: 19, }).addTo(map); // Apply club color overlay if provided if (clubPrimaryColor && clubPrimaryColor !== '') { const colorFilter = createColorFilter(clubPrimaryColor); if (colorFilter) { const pane = map.createPane('colorOverlay'); pane.style.zIndex = '400'; pane.style.pointerEvents = 'none'; pane.style.mixBlendMode = 'multiply'; pane.style.backgroundColor = colorFilter; pane.style.opacity = '0.15'; } } // Create custom marker icon with club colors const markerColor = clubPrimaryColor || '#3388ff'; const customIcon = createCustomMarkerIcon(markerColor, L); // Add marker const marker = L.marker([latitude, longitude], { icon: customIcon }).addTo(map); // Add popup if address is provided if (clubName || address) { let popupContent = ''; if (clubName) popupContent += `${clubName}
`; if (address) popupContent += address; marker.bindPopup(popupContent); } // Enable scroll zoom on click map.on('click', () => { map.scrollWheelZoom.enable(); }); // Disable scroll zoom on mouseout map.on('mouseout', () => { map.scrollWheelZoom.disable(); }); } catch (error) { console.error('Error initializing map:', error); setLoadError('Failed to initialize map'); } // Cleanup return () => { if (mapInstanceRef.current) { mapInstanceRef.current.remove(); mapInstanceRef.current = null; } }; }, [isLoaded, latitude, longitude, zoom, address, clubName, mapStyle, clubPrimaryColor, clubSecondaryColor]); // Helper function to create color filter function createColorFilter(color: string): string | null { try { // Validate and normalize color const tempDiv = document.createElement('div'); tempDiv.style.color = color; document.body.appendChild(tempDiv); const computedColor = window.getComputedStyle(tempDiv).color; document.body.removeChild(tempDiv); return computedColor; } catch { return null; } } // Helper function to create custom marker with club colors function createCustomMarkerIcon(color: string, leaflet: any) { // Create SVG marker with custom color const svgIcon = ` `; const iconUrl = 'data:image/svg+xml;base64,' + btoa(svgIcon); return leaflet.icon({ iconUrl: iconUrl, iconSize: [36, 54], iconAnchor: [18, 54], popupAnchor: [0, -54], }); } if (loadError) { return ( {loadError} ); } return ( ); }; export default ContactMap;