"use client"; import React, { useEffect, useRef, useState } from "react"; import mapboxgl from "mapbox-gl"; interface Props { mapRef: React.MutableRefObject; placeholder?: string; value?: string; onChange?: (v: string) => void; onSelect: (feature: any) => void; onMapPick?: () => void; // New prop for map picking mode isMapPickingMode?: boolean; // Whether currently in map picking mode } export default function GeocodeInput({ mapRef, placeholder = 'Search location or enter coordinates...', value = '', onChange, onSelect, onMapPick, isMapPickingMode = false }: Props) { const [query, setQuery] = useState(value); const [suggestions, setSuggestions] = useState([]); const [showDropdown, setShowDropdown] = useState(false); const timer = useRef(null); const mounted = useRef(true); const containerRef = useRef(null); useEffect(() => { return () => { mounted.current = false; }; }, []); // Handle click outside to close dropdown useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setShowDropdown(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); useEffect(() => { if (value !== query) setQuery(value); }, [value]); const fetchSuggestions = async (q: string) => { const token = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || mapboxgl.accessToken || undefined; if (!token) return []; if (!q || q.trim().length === 0) return []; // Check if the query looks like coordinates (lat,lng or lng,lat) const coordinatePattern = /^(-?\d+\.?\d*),?\s*(-?\d+\.?\d*)$/; const coordMatch = q.trim().match(coordinatePattern); if (coordMatch) { const [, first, second] = coordMatch; const num1 = parseFloat(first); const num2 = parseFloat(second); // Determine which is lat and which is lng based on typical ranges // Latitude: -90 to 90, Longitude: -180 to 180 // For DC area: lat around 38-39, lng around -77 let lat, lng; if (Math.abs(num1) <= 90 && Math.abs(num2) <= 180) { // Check if first number looks like latitude for DC area if (num1 >= 38 && num1 <= 39 && num2 >= -78 && num2 <= -76) { lat = num1; lng = num2; } else if (num2 >= 38 && num2 <= 39 && num1 >= -78 && num1 <= -76) { lat = num2; lng = num1; } else { // Default assumption: first is lat, second is lng lat = num1; lng = num2; } // Validate coordinates are in reasonable ranges if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) { // Create a synthetic feature for coordinates return [{ center: [lng, lat], place_name: `${lat}, ${lng}`, text: `${lat}, ${lng}`, properties: { isCoordinate: true }, geometry: { type: 'Point', coordinates: [lng, lat] } }]; } } } // Washington DC area bounding box: SW corner (-77.25, 38.80), NE corner (-76.90, 39.05) const dcBounds = '-77.25,38.80,-76.90,39.05'; // Add proximity to center of DC for better ranking const dcCenter = '-77.0369,38.9072'; // Washington DC coordinates const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(q)}.json?` + `autocomplete=true&limit=6&types=place,locality,address,region,poi&` + `bbox=${dcBounds}&proximity=${dcCenter}&` + `country=US&access_token=${token}`; try { const res = await fetch(url); if (!res.ok) return []; const data = await res.json(); // Additional client-side filtering to ensure results are in DC area const dcAreaFeatures = (data.features || []).filter((feature: any) => { const coords = feature.center; if (!coords || coords.length !== 2) return false; const [lng, lat] = coords; // Check if coordinates are within DC metropolitan area bounds return lng >= -77.25 && lng <= -76.90 && lat >= 38.80 && lat <= 39.05; }); return dcAreaFeatures; } catch (e) { return []; } }; useEffect(() => { if (timer.current) window.clearTimeout(timer.current); if (!query) { setSuggestions([]); setShowDropdown(false); return; } timer.current = window.setTimeout(async () => { const feats = await fetchSuggestions(query); if (mounted.current) { setSuggestions(feats); setShowDropdown(feats.length > 0); } }, 250) as unknown as number; return () => { if (timer.current) window.clearTimeout(timer.current); }; }, [query]); return (
{/* Search bar container matching the design */}
{/* Input field */} { setQuery(e.target.value); onChange && onChange(e.target.value); }} onFocus={() => { if (!isMapPickingMode && suggestions.length > 0) { setShowDropdown(true); } }} disabled={isMapPickingMode} /> {/* Pin button */}
{/* Suggestions dropdown */} {!isMapPickingMode && showDropdown && suggestions.length > 0 && (
{suggestions.map((f: any, i: number) => ( ))}
)} {/* Map picking mode indicator */} {isMapPickingMode && (
Click anywhere on the map to select a location
)}
); }