import { LatLng } from "leaflet"; import { MapPinIcon, XIcon } from "lucide-react"; import { useEffect, useState, useRef } from "react"; import toast from "react-hot-toast"; import LeafletMap from "@/components/LeafletMap"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Location } from "@/types/proto/api/v1/memo_service"; import { useTranslate } from "@/utils/i18n"; interface Props { location?: Location; onChange: (location?: Location) => void; } interface State { initilized: boolean; placeholder: string; position?: LatLng; } interface NominatimRateLimit { lastNominatimFetch: Date; nominatimTimeoutId: number | undefined; timeBetweenFetch: number; } const LocationSelector = (props: Props) => { const t = useTranslate(); const [state, setState] = useState({ initilized: false, placeholder: props.location?.placeholder || "", position: props.location ? new LatLng(props.location.latitude, props.location.longitude) : undefined, }); const rateLimit = useRef({ lastNominatimFetch: new Date(0), nominatimTimeoutId: undefined, timeBetweenFetch: 1300, }); const [popoverOpen, setPopoverOpen] = useState(false); useEffect(() => { setState((state) => ({ ...state, placeholder: props.location?.placeholder || "", position: new LatLng(props.location?.latitude || 0, props.location?.longitude || 0), })); }, [props.location]); useEffect(() => { if (popoverOpen && !props.location) { const handleError = (error: any, errorMessage: string) => { setState({ ...state, initilized: true }); toast.error(errorMessage); console.error(error); }; if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { const lat = position.coords.latitude; const lng = position.coords.longitude; setState({ ...state, position: new LatLng(lat, lng), initilized: true }); }, (error) => { handleError(error, "Failed to get current position"); }, ); } else { handleError("Geolocation is not supported by this browser.", "Geolocation is not supported by this browser."); } } }, [popoverOpen]); const updateReverseGeocoding = () => { if (!state.position) { setState({ ...state, placeholder: "" }); return; } fetch(`https://nominatim.openstreetmap.org/reverse?lat=${state.position.lat}&lon=${state.position.lng}&format=json`, { cache: "default", headers: new Headers({ "Cache-Control": "max-age=86400" }), }) .then((response) => response.json()) .then((data) => { if (data && data.display_name) { setState({ ...state, placeholder: data.display_name }); } }) .catch((error) => { toast.error("Failed to fetch reverse geocoding data"); console.error("Failed to fetch reverse geocoding data:", error); }); }; useEffect(() => { // Fetch reverse geocoding with rate limits clearTimeout(rateLimit.current.nominatimTimeoutId); const timeLeft = rateLimit.current.timeBetweenFetch - (new Date().getTime() - rateLimit.current.lastNominatimFetch.getTime()); rateLimit.current.nominatimTimeoutId = setTimeout(() => { updateReverseGeocoding(); rateLimit.current.lastNominatimFetch = new Date(); }, Math.max(0, timeLeft)); }, [state.position]); const onPositionChanged = (position: LatLng) => { setState({ ...state, position }); }; const removeLocation = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); props.onChange(undefined); }; return ( {!props.location && (

{t("tooltip.select-location")}

)}
{state.position && (
[{state.position.lat.toFixed(2)}, {state.position.lng.toFixed(2)}]
)} setState((state) => ({ ...state, placeholder: e.target.value }))} />
); }; export default LocationSelector;