diff --git a/app/components/InteractiveMap.tsx b/app/components/InteractiveMap.tsx index 136a37f..9950315 100644 --- a/app/components/InteractiveMap.tsx +++ b/app/components/InteractiveMap.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef, useCallback } from "react"; import "maplibre-gl/dist/maplibre-gl.css"; -import type { Map as MaplibreMap } from "maplibre-gl"; -import { Search, X, ChevronLeft, MapPin } from "lucide-react"; +import type { Map as MaplibreMap, Marker as MaplibreMarker } from "maplibre-gl"; +import { Search, X, ChevronLeft, MapPin, Crosshair } from "lucide-react"; import AppInput from "./AppInput"; import AppTextArea from "./AppTextArea"; import { @@ -156,7 +156,10 @@ export default function InteractiveMap({ // MapLibre refs const mapContainerRef = useRef(null); const mapRef = useRef(null); - const moveDebounceRef = useRef | null>(null); + const markerRef = useRef(null); + // select(lat,lng): drop/move the pin and reverse-geocode that point once. + const selectRef = useRef<((lat: number, lng: number) => void) | null>(null); + const [isLocating, setIsLocating] = useState(false); // Initialize with the existing address data when in editing mode useEffect(() => { @@ -277,22 +280,37 @@ export default function InteractiveMap({ new maplibregl.NavigationControl({ showCompass: false }), "top-left" ); - // Reverse-geocode the centre whenever the map settles. - map.on("moveend", () => { - if (moveDebounceRef.current) clearTimeout(moveDebounceRef.current); - moveDebounceRef.current = setTimeout(() => { - const c = map.getCenter(); - applyLocationRef.current([c.lat, c.lng]); - }, 350); - }); - map.on("load", () => { - const c = map.getCenter(); - applyLocationRef.current([c.lat, c.lng]); - }); + + // Drop/move the pin at a point and reverse-geocode it once (saves + // tokens — we only geocode on an explicit tap, not on every move). + const select = (lat: number, lng: number) => { + if (markerRef.current) { + markerRef.current.setLngLat([lng, lat]); + } else { + markerRef.current = new maplibregl.Marker({ color: "#ff3b30" }) + .setLngLat([lng, lat]) + .addTo(map); + } + applyLocationRef.current([lat, lng]); + }; + selectRef.current = select; + + // Pan with drag; a tap selects that point. + map.on("click", (e) => select(e.lngLat.lat, e.lngLat.lng)); + + // Edit mode: show the existing pin without re-geocoding. + if (selectedPosition) { + markerRef.current = new maplibregl.Marker({ color: "#ff3b30" }) + .setLngLat([selectedPosition[1], selectedPosition[0]]) + .addTo(map); + } })(); return () => { cancelled = true; - if (moveDebounceRef.current) clearTimeout(moveDebounceRef.current); + if (markerRef.current) { + markerRef.current.remove(); + markerRef.current = null; + } if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; @@ -322,6 +340,35 @@ export default function InteractiveMap({ setIsCityDrawerOpen(false); }; + // Use the device's current location: fly there and select it. + const handleUseMyLocation = () => { + if (typeof navigator === "undefined" || !navigator.geolocation) { + toast({ + title: "موقعیت مکانی پشتیبانی نمی‌شود", + variant: "destructive", + }); + return; + } + setIsLocating(true); + navigator.geolocation.getCurrentPosition( + (pos) => { + const { latitude, longitude } = pos.coords; + mapRef.current?.flyTo({ center: [longitude, latitude], zoom: 16 }); + selectRef.current?.(latitude, longitude); + setIsLocating(false); + }, + () => { + setIsLocating(false); + toast({ + title: "دسترسی به موقعیت مکانی امکان‌پذیر نشد", + description: "لطفاً دسترسی موقعیت مکانی را در مرورگر فعال کنید.", + variant: "destructive", + }); + }, + { enableHighAccuracy: true, timeout: 10000 } + ); + }; + const handleOpenDrawer = () => { setIsDrawerOpen(true); }; @@ -473,49 +520,6 @@ export default function InteractiveMap({ } }, [isEditing, initialAddress]); - // Add keyframes for the pulse animation - useEffect(() => { - const styleTag = document.createElement("style"); - styleTag.innerHTML = ` - @keyframes pulse { - 0% { - transform: translate(-50%, -50%) scale(0.5); - opacity: 1; - } - 100% { - transform: translate(-50%, -50%) scale(1.5); - opacity: 0; - } - } - `; - document.head.appendChild(styleTag); - - // Listen for the custom location event - const handleLocationFound = (event: Event) => { - const customEvent = event as CustomEvent<{ lat: number; lng: number }>; - const { lat, lng } = customEvent.detail; - if (mapRef.current) { - mapRef.current.flyTo({ center: [lng, lat], zoom: 15 }); - } else { - applyLocationRef.current([lat, lng]); - } - }; - - window.addEventListener( - "user-location-found", - handleLocationFound as EventListener - ); - - return () => { - document.head.removeChild(styleTag); - window.removeEventListener( - "user-location-found", - handleLocationFound as EventListener - ); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - // If formOnly mode, render only the form if (formOnly) { return ( @@ -840,21 +844,37 @@ export default function InteractiveMap({ className="h-full w-full" /> - {/* Fixed center pin — the map moves under it; its tip marks the point */} -
- -
+ {/* Use my current location */} + - {/* Selected-address preview */} - {selectedAddress && ( -
-

موقعیت انتخاب‌شده

-

{selectedAddress}

-
- )} + {/* Hint / selected-address preview */} +
+ {selectedAddress ? ( + <> +

موقعیت انتخاب‌شده

+

+ {selectedAddress} +

+ + ) : ( +

+ + برای انتخاب موقعیت، روی نقشه بزنید +

+ )} +
{/* Select Location Button */}