import React, { useEffect, useRef, useState } from 'react'; import mapboxgl from 'mapbox-gl'; import 'mapbox-gl/dist/mapbox-gl.css'; import { Badge } from "@/components/ui/badge"; import { Star, MapPin, Globe, Phone } from 'lucide-react'; import { Skeleton } from "@/components/ui/skeleton"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || ''; interface Location { lat: number; lng: number; } export interface Place { name: string; location: Location; vicinity?: string; rating?: number; user_ratings_total?: number; place_id?: string; // TripAdvisor location_id distance?: string; // Distance from search center bearing?: string; // Direction from search center (e.g., "north", "southeast") type?: string; // Type of place (e.g., "restaurant", "hotel") phone?: string; // Phone number if available website?: string; // Website URL if available photos?: string[]; // Array of photo URLs } interface MapProps { center: Location; places?: Place[]; zoom?: number; } const MapComponent = ({ center, places = [], zoom = 14, onMarkerClick }: MapProps & { onMarkerClick?: (place: Place) => void }) => { const mapRef = useRef(null); const mapInstance = useRef(null); const markersRef = useRef([]); // Initialize the map only once 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/standard', center: [center.lng, center.lat], zoom, }); return () => { mapInstance.current?.remove(); mapInstance.current = null; }; }, [center.lat, center.lng, zoom]); // Update map center when 'center' prop changes useEffect(() => { if (mapInstance.current) { mapInstance.current.flyTo({ center: [center.lng, center.lat], zoom, essential: true, }); } }, [center, zoom]); // Update markers when 'places' prop changes useEffect(() => { if (!mapInstance.current) return; // Remove existing markers markersRef.current.forEach((marker) => marker.remove()); markersRef.current = []; // Add new markers places.forEach((place) => { const marker = new mapboxgl.Marker() .setLngLat([place.location.lng, place.location.lat]) .setPopup( new mapboxgl.Popup({ offset: 25 }).setText( `${place.name}${place.vicinity ? `\n${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; } const PlaceDetails = ({ name, vicinity, rating, user_ratings_total, onDirectionsClick, onWebsiteClick, onCallClick }: PlaceDetailsProps) => (

{name}

{vicinity &&

{vicinity}

}
{rating && ( {rating} {user_ratings_total && ({user_ratings_total})} )}
); 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}

{places.map((place, index) => ( ))}
); }; interface MapViewProps extends MapProps { view: 'map' | 'list'; onViewChange: (view: 'map' | 'list') => void; } const MapView: React.FC = ({ center, places = [], zoom = 14, view, onViewChange }) => { const [selectedPlace, setSelectedPlace] = useState(null); return (

Nearby Places

onViewChange(v as 'map' | 'list')}> Map List
{view === 'map' ? (
{selectedPlace && (
)}
) : (
{places.map((place, index) => ( ))}
)}
); }; export { MapComponent, MapSkeleton, MapContainer, PlaceDetails, MapView };