// /app/components/map-components.tsx import { Skeleton } from "@/components/ui/skeleton"; import { clientEnv } from "@/env/client"; import mapboxgl from 'mapbox-gl'; import 'mapbox-gl/dist/mapbox-gl.css'; import React, { useEffect, useRef } from 'react'; mapboxgl.accessToken = clientEnv.NEXT_PUBLIC_MAPBOX_TOKEN || ''; interface Location { lat: number; lng: number; } export interface Place { name: string; location: Location; vicinity?: string; } interface MapProps { center: Location; places?: Place[]; zoom?: number; onMarkerClick?: (place: Place) => void; } const MapComponent = ({ center, places = [], zoom = 14, onMarkerClick }: MapProps & { onMarkerClick?: (place: Place) => void }) => { const mapRef = useRef(null); const mapInstance = useRef(null); const markersRef = useRef([]); useEffect(() => { if (!mapRef.current || mapInstance.current) return; if (!mapboxgl.accessToken) { console.error('Mapbox access token is not set'); return; } mapInstance.current = new mapboxgl.Map({ container: mapRef.current, style: 'mapbox://styles/mapbox/dark-v11', center: [center.lng, center.lat], zoom, attributionControl: false, }); // Add zoom and rotation controls mapInstance.current.addControl(new mapboxgl.NavigationControl(), 'top-right'); // Add attribution control in bottom-left mapInstance.current.addControl( new mapboxgl.AttributionControl({ compact: true }), 'bottom-left' ); // Add fullscreen control mapInstance.current.addControl(new mapboxgl.FullscreenControl(), 'top-right'); return () => { mapInstance.current?.remove(); mapInstance.current = null; }; }, [center.lat, center.lng, zoom]); useEffect(() => { if (mapInstance.current) { mapInstance.current.flyTo({ center: [center.lng, center.lat], zoom, essential: true, duration: 1000, padding: { top: 50, bottom: 50, left: 50, right: 50 } }); } }, [center, zoom]); useEffect(() => { if (!mapInstance.current) return; markersRef.current.forEach((marker) => marker.remove()); markersRef.current = []; places.forEach((place) => { // Create custom marker element const el = document.createElement('div'); el.className = 'custom-marker'; el.innerHTML = `
`; const marker = new mapboxgl.Marker(el) .setLngLat([place.location.lng, place.location.lat]) .setPopup( new mapboxgl.Popup({ offset: 25, closeButton: false }) .setHTML(`

${place.name}

${place.vicinity ? `

${place.vicinity}

` : ''}
`) ) .addTo(mapInstance.current!); marker.getElement().addEventListener('click', () => { if (onMarkerClick) { onMarkerClick(place); } }); markersRef.current.push(marker); }); }, [places, onMarkerClick]); return (
); }; export default React.memo(MapComponent, (prevProps, nextProps) => { return ( prevProps.center.lat === nextProps.center.lat && prevProps.center.lng === nextProps.center.lng && prevProps.zoom === nextProps.zoom && JSON.stringify(prevProps.places) === JSON.stringify(nextProps.places) ); }); const MapSkeleton = () => ( ); interface PlaceDetailsProps extends Place { onDirectionsClick?: () => void; onWebsiteClick?: () => void; onCallClick?: () => void; } interface MapContainerProps { title: string; center: Location; places?: Place[]; loading?: boolean; } const MapContainer: React.FC = ({ title, center, places = [], loading = false, }) => { if (loading) { return (

Loading map...

); } return (

{title}

); }; export { MapComponent, MapContainer, MapSkeleton };