diff --git a/src/components/forms/fields/GeolocationInput.tsx b/src/components/forms/fields/GeolocationInput.tsx index 71eafbe..14e9316 100644 --- a/src/components/forms/fields/GeolocationInput.tsx +++ b/src/components/forms/fields/GeolocationInput.tsx @@ -1,5 +1,14 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useCallback } from 'react'; +import { Modal } from '../../reusable/Modal'; import { Button } from '../../buttons/Button'; +import { useJsApiLoader, GoogleMap, Marker } from '@react-google-maps/api'; +import { MapPin, Crosshair, Map as MapIcon, Loader2, X } from 'lucide-react'; + +const mapContainerStyle = { + width: '100%', + height: '400px', + borderRadius: '8px' +}; export function GeolocationInput({ label, @@ -9,65 +18,190 @@ export function GeolocationInput({ }: { label: string; required?: boolean; - value: { latitude: number; longitude: number } | null; - onChange: (val: { latitude: number; longitude: number } | null) => void; + value: { latitude?: number; longitude?: number; lat?: number; lng?: number; accuracy?: number } | null; + onChange: (val: { latitude: number; longitude: number; accuracy?: number } | null) => void; }) { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + + const [isMapModalOpen, setIsMapModalOpen] = useState(false); + const [mapMarkerPos, setMapMarkerPos] = useState<{ lat: number; lng: number } | null>(null); - const autoFetchDone = useRef(false); + const isLocalhost = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); - const fetchLocation = () => { - if (!navigator.geolocation) { - setError('Geolocation is not supported by your browser.'); + const { isLoaded } = useJsApiLoader({ + id: 'google-map-script', + googleMapsApiKey: isLocalhost ? '' : (import.meta.env.VITE_GOOGLE_MAPS_API_KEY || '') + }); + + // Extract lat, lng & accuracy from value (supporting both latitude/longitude and lat/lng) + const lat = value ? (value.latitude ?? value.lat ?? null) : null; + const lng = value ? (value.longitude ?? value.lng ?? null) : null; + const accuracy = value?.accuracy; + + const fetchLocation = useCallback(() => { + setError(null); + if (!("geolocation" in navigator)) { + setError("Geolocation not supported on this device"); return; } setLoading(true); - setError(null); navigator.geolocation.getCurrentPosition( - (position) => { + (pos) => { onChange({ - latitude: position.coords.latitude, - longitude: position.coords.longitude, + latitude: pos.coords.latitude, + longitude: pos.coords.longitude, + accuracy: pos.coords.accuracy, }); setLoading(false); }, (err) => { - setError(err.message); + setError(err.message || "Unable to get location"); setLoading(false); }, - { enableHighAccuracy: true } + { enableHighAccuracy: true, timeout: 10000 } ); - }; + }, [onChange]); + // Auto-fetch location on first mount if no value is present useEffect(() => { - if (!value && !autoFetchDone.current) { - autoFetchDone.current = true; + if (!value || (lat == null && lng == null)) { fetchLocation(); } - }, [value]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleOpenMap = () => { + if (lat != null && lng != null) { + setMapMarkerPos({ lat, lng }); + } else { + setMapMarkerPos(null); + } + setIsMapModalOpen(true); + }; + + const handleMapClick = useCallback((e: google.maps.MapMouseEvent) => { + if (e.latLng) { + setMapMarkerPos({ + lat: e.latLng.lat(), + lng: e.latLng.lng() + }); + } + }, []); + + const confirmMapSelection = () => { + if (mapMarkerPos) { + onChange({ + latitude: mapMarkerPos.lat, + longitude: mapMarkerPos.lng, + accuracy: 10, + }); + setIsMapModalOpen(false); + } + }; + + const clear = () => onChange(null); + const hasValidCoords = lat != null && lng != null; return ( -
- +
+