diff --git a/.env.example b/.env.example index 7105031..d15364f 100644 --- a/.env.example +++ b/.env.example @@ -21,3 +21,6 @@ VITE_BUILD_TARGET=development # Support Configuration VITE_SUPPORT_PHONE=+989223740993 + +# Map.ir key for the address map (MapLibre vector tiles + reverse geocoding) +VITE_MAPIR_KEY=your_mapir_api_key diff --git a/app/components/InteractiveMap.tsx b/app/components/InteractiveMap.tsx index a6a3736..5959fc6 100644 --- a/app/components/InteractiveMap.tsx +++ b/app/components/InteractiveMap.tsx @@ -1,15 +1,7 @@ -import React, { useState, useEffect, useRef } from "react"; -import { - MapContainer, - TileLayer, - Marker, - Popup, - useMapEvents, - useMap, -} from "react-leaflet"; -import "leaflet/dist/leaflet.css"; -import L from "leaflet"; -import { Search, X, ChevronLeft } from "lucide-react"; +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 AppInput from "./AppInput"; import AppTextArea from "./AppTextArea"; import { @@ -32,6 +24,25 @@ import { type IranianCity, } from "../data/iranian-cities"; +// Map.ir config (vector tiles + reverse geocoding). The key is a client-side +// map key (sent with every tile request), provided via env. +const MAPIR_KEY = import.meta.env.VITE_MAPIR_KEY || ""; +const MAPIR_STYLE = "https://map.ir/vector/styles/main/mapir-style.json"; + +// Reverse geocode (lat/lng -> Persian address) via Map.ir. +async function reverseGeocode(lat: number, lon: number): Promise { + try { + const res = await fetch(`https://map.ir/reverse?lat=${lat}&lon=${lon}`, { + headers: { "x-api-key": MAPIR_KEY }, + }); + const data = await res.json(); + return data?.address || data?.address_compact || "آدرس نامشخص"; + } catch (error) { + console.error("Error fetching address:", error); + return ""; + } +} + // Form data interface interface FormData { title: string; @@ -71,30 +82,6 @@ function convertPersianToEnglish(str: string): string { return result; } -// Component to change map view when city is selected -function CityChangeView({ center }: { center: [number, number] }) { - const map = useMap(); - useEffect(() => { - map.setView(center, 13); - }, [center, map]); - return null; -} - -// Component to handle map clicks -function MapClickHandler({ - onMapClick, -}: { - onMapClick: (latlng: [number, number]) => void; -}) { - useMapEvents({ - click: (e) => { - const { lat, lng } = e.latlng; - onMapClick([lat, lng]); - }, - }); - return null; -} - export default function InteractiveMap({ onLocationSelected, initialAddress, @@ -112,7 +99,7 @@ export default function InteractiveMap({ isEditing ? "" : "تهران" ); const [currentCenter, setCurrentCenter] = useState<[number, number]>( - iranianCities[0].position + (iranianCities.find((c) => c.name === "تهران") || iranianCities[0]).position ); const [mounted, setMounted] = useState(false); @@ -166,34 +153,10 @@ export default function InteractiveMap({ isDefault: formData.is_default, }); - // Fix Leaflet's default icon path issues when the component mounts - useEffect(() => { - // Fix Leaflet icon issues - delete (L.Icon.Default.prototype as { _getIconUrl?: string })._getIconUrl; - L.Icon.Default.mergeOptions({ - iconUrl: "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png", - iconRetinaUrl: - "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png", - shadowUrl: - "https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png", - }); - - // Cleanup function to remove map instance when component unmounts - return () => { - // This code ensures previous maps are cleaned up - // eslint-disable-next-line react-hooks/exhaustive-deps - const mapContainer = document.getElementById(mapIdRef.current); - if (mapContainer) { - // Clean up previous map on this element - // In Leaflet version 1+, using map._leaflet no longer works - // and instead removeAttribute should be used - mapContainer.innerHTML = ""; - mapContainer.removeAttribute("data-leaflet-internal-id"); - mapContainer.removeAttribute("_leaflet_id"); - console.log("Map container cleaned up successfully"); - } - }; - }, []); + // MapLibre refs + const mapContainerRef = useRef(null); + const mapRef = useRef(null); + const moveDebounceRef = useRef | null>(null); // Initialize with the existing address data when in editing mode useEffect(() => { @@ -245,76 +208,94 @@ export default function InteractiveMap({ setFilteredCities(cities); }, [citySearchTerm, selectedProvince]); - // Function to get address from coordinates - const getAddressFromCoordinates = async (latlng: [number, number]) => { - try { - const response = await fetch( - `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latlng[0]}&lon=${latlng[1]}&zoom=18&addressdetails=1&accept-language=fa&countrycodes=ir`, - { - headers: { - "Accept-Language": "fa", - }, - } - ); - const data = await response.json(); - return data.display_name || "آدرس نامشخص"; - } catch (error) { - console.error("Error fetching address:", error); - return ""; - } - }; - - // Extract postal code from address data - const getPostalCodeFromAddress = async (latlng: [number, number]) => { - try { - const response = await fetch( - `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latlng[0]}&lon=${latlng[1]}&zoom=18&addressdetails=1&accept-language=fa&countrycodes=ir`, - { - headers: { - "Accept-Language": "fa", - }, - } - ); - const data = await response.json(); - - if (data && data.address && data.address.postcode) { - return data.address.postcode; - } - return ""; - } catch (error) { - console.error("Error fetching postal code:", error); - return ""; - } - }; - const [isSubmitting, setIsSubmitting] = useState(false); const [errors, setErrors] = useState>({}); - const handleMapClick = async (latlng: [number, number]) => { - const address = await getAddressFromCoordinates(latlng); - const postalCode = await getPostalCodeFromAddress(latlng); + // Reverse-geocode a point and push it into the form. Used by the map's + // center pin (on move) — replaces the old click handler. + const applyLocation = useCallback( + async (latlng: [number, number]) => { + const address = await reverseGeocode(latlng[0], latlng[1]); + setSelectedPosition(latlng); + setSelectedAddress(address); + setFormData((prev) => ({ + ...prev, + main_address: address, + latitude: latlng[0], + longitude: latlng[1], + })); + if (onLocationSelected) { + onLocationSelected({ position: latlng, address }); + } + }, + [onLocationSelected] + ); - setSelectedPosition(latlng); - setSelectedAddress(address); + // Keep a stable ref so the map's event listeners always call the latest. + const applyLocationRef = useRef(applyLocation); + useEffect(() => { + applyLocationRef.current = applyLocation; + }, [applyLocation]); - // Don't update postal_code in edit mode, preserve the original value - setFormData({ - ...formData, - main_address: address, - // Don't set postal_code here in either mode, preserve the existing value in edit mode - latitude: latlng[0], - longitude: latlng[1], - }); + // Initialize the MapLibre map (client-only, once) with the Map.ir style. + useEffect(() => { + if (!mounted || formOnly || !mapContainerRef.current || mapRef.current) { + return; + } + let cancelled = false; + (async () => { + const maplibregl = (await import("maplibre-gl")).default; + if (cancelled || !mapContainerRef.current) return; + const start = selectedPosition || currentCenter; // [lat, lng] + const map = new maplibregl.Map({ + container: mapContainerRef.current, + style: MAPIR_STYLE, + center: [start[1], start[0]], // MapLibre uses [lng, lat] + zoom: 14, + attributionControl: false, + transformRequest: (url: string) => + url.startsWith("https://map.ir") + ? { url, headers: { "x-api-key": MAPIR_KEY } } + : { url }, + }); + mapRef.current = map; + map.addControl( + 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]); + }); + })(); + return () => { + cancelled = true; + if (moveDebounceRef.current) clearTimeout(moveDebounceRef.current); + if (mapRef.current) { + mapRef.current.remove(); + mapRef.current = null; + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mounted, formOnly]); - // If using as a location picker in another component, call the callback - if (onLocationSelected) { - onLocationSelected({ - position: latlng, - address, - postalCode: isEditing ? formData.postal_code : postalCode, + // Recenter the map when the chosen city changes. + useEffect(() => { + if (mapRef.current) { + mapRef.current.flyTo({ + center: [currentCenter[1], currentCenter[0]], + zoom: 14, }); } - }; + }, [currentCenter]); const handleCitySelect = (cityName: string) => { setSelectedCity(cityName); @@ -499,7 +480,11 @@ export default function InteractiveMap({ const handleLocationFound = (event: Event) => { const customEvent = event as CustomEvent<{ lat: number; lng: number }>; const { lat, lng } = customEvent.detail; - handleMapClick([lat, lng]); + if (mapRef.current) { + mapRef.current.flyTo({ center: [lng, lat], zoom: 15 }); + } else { + applyLocationRef.current([lat, lng]); + } }; window.addEventListener( @@ -832,44 +817,30 @@ export default function InteractiveMap({ - {/* Map Container */} -
-
- {mounted && ( - - - + {/* Map Container — MapLibre + Map.ir vector tiles. dir=ltr keeps the + map's internal positioning correct inside the RTL page. */} +
+
- - {/* */} - - {selectedPosition && ( - - -
-

آدرس:

-

{selectedAddress}

-

- مختصات: - {selectedPosition[0].toFixed(6)},{" "} - {selectedPosition[1].toFixed(6)} -

-
-
-
- )} - - )} + {/* Fixed center pin — the map moves under it; its tip marks the point */} +
+
+ + {/* Selected-address preview */} + {selectedAddress && ( +
+

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

+

{selectedAddress}

+
+ )}
{/* Select Location Button */} diff --git a/package-lock.json b/package-lock.json index af63b19..199c425 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,13 +35,12 @@ "input-otp": "^1.4.2", "isbot": "^4.1.0", "jalali-moment": "^3.3.11", - "leaflet": "^1.9.4", "lucide-react": "^0.477.0", + "maplibre-gl": "^5.24.0", "moment-jalaali": "^0.10.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^15.4.1", - "react-leaflet": "^4.2.1", "remix-auth": "^3.6.0", "remix-auth-form": "^1.4.0", "tailwind-merge": "^3.0.2", @@ -52,7 +51,6 @@ }, "devDependencies": { "@remix-run/dev": "^2.16.0", - "@types/leaflet": "^1.9.17", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.7.4", @@ -2002,6 +2000,119 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.3.tgz", + "integrity": "sha512-0SElaV0uMxEnxzBhhX9WTuPyUeMsAN/SS0i16tjuba4/mio63MG9khjC1a0JAiPGXAwvwm4UfHJURCN7nyudQg==", + "license": "MIT", + "engines": { + "node": ">= 22" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.2.0.tgz", + "integrity": "sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.5.tgz", + "integrity": "sha512-pXj8m7KTsqZt+1jsE0xIpGvqTSbblfkuEJL/NJmNePMtEwxO8V3XMDo9WMSfDeqHvCtBI9Lmt4mGcGR10zecmw==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.2" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/geojson-vt": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-6.1.0.tgz", + "integrity": "sha512-2eIY4gZxeKIVOZVNkAMb+5NgXhgsMQpOveTQAvnp53LYqHGJZDidk7Ew0Tged9PThidpbS+NFTh0g4zivhPDzQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.10.0.tgz", + "integrity": "sha512-lichxSiagMEBBrqHF0trtMQH9RKh+9jUlIJl0qW0QHvt2H/tbvUWdE+ZzI2Jd0/pT7j/iavLonlPu7EQ/ixTOw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^1.0.0", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/@mapbox/unitbezier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-1.0.0.tgz", + "integrity": "sha512-fqd515fjBmANKGGsQ286E2Wvj/XvDFpGzwJxq4CI6jMQue6Oy04uCKp+JWKF00xRTmk6cEu1jPJ9p3xqH8YWqQ==", + "license": "BSD-2-Clause" + }, + "node_modules/@maplibre/mlt": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@maplibre/mlt/-/mlt-1.1.12.tgz", + "integrity": "sha512-ZeK5w2TTeHOajcLaEQs1KZXw2V9wIKo1PmThlxlsHoXsQsYlBqLJzPOd6tJHRtGTChUY3DPPmjXRArYVvAbmZw==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0" + } + }, + "node_modules/@maplibre/vt-pbf": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.3.2.tgz", + "integrity": "sha512-j6p0AdjvAR19Z3XaCysle7A4ZSo08tYOzxD0Y9NQylwPAkwJJeYub5b2eVucdeDh7erhv69DahoLOevDRERRUw==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^5.1.0" + } + }, + "node_modules/@maplibre/vt-pbf/node_modules/pbf": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-5.1.0.tgz", + "integrity": "sha512-Wv0yo0+uZepnoNEKsquhar1F18LogB8oeEikIhUXG16udbiXG7JecHGySwoo6kuMgjmbQYzdrTZlO+/K9t8eZg==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/@mdx-js/mdx": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz", @@ -3211,17 +3322,6 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, - "node_modules/@react-leaflet/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", - "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", - "license": "Hippocratic-2.1", - "peerDependencies": { - "leaflet": "^1.9.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, "node_modules/@remix-run/dev": { "version": "2.17.2", "resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.17.2.tgz", @@ -3948,7 +4048,6 @@ "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "dev": true, "license": "MIT" }, "node_modules/@types/hast": { @@ -3975,16 +4074,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/leaflet": { - "version": "1.9.21", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", - "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -6859,6 +6948,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -8555,6 +8650,12 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, "node_modules/glob": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", @@ -9919,6 +10020,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9961,6 +10068,12 @@ "node": ">=4.0" } }, + "node_modules/kdbush": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.1.0.tgz", + "integrity": "sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==", + "license": "ISC" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10001,12 +10114,6 @@ "node": ">=0.10" } }, - "node_modules/leaflet": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", - "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause" - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -10168,6 +10275,40 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/maplibre-gl": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.24.0.tgz", + "integrity": "sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.1.0", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/geojson-vt": "^6.1.0", + "@maplibre/maplibre-gl-style-spec": "^24.8.1", + "@maplibre/mlt": "^1.1.8", + "@maplibre/vt-pbf": "^4.3.0", + "@types/geojson": "^7946.0.16", + "earcut": "^3.0.2", + "gl-matrix": "^3.4.4", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.1.0", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, "node_modules/markdown-extensions": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", @@ -11171,7 +11312,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11500,6 +11640,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "license": "MIT" + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -12159,6 +12305,18 @@ "dev": true, "license": "MIT" }, + "node_modules/pbf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.2.tgz", + "integrity": "sha512-J0ajxARhZfpUEebxYs1vhMGMuLSXtBe1e+fFPDrf2uA2hgo+UshKfNUWOz92HJNz6/NFEXseQPddnHkTreWRqg==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/peek-stream": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", @@ -12519,6 +12677,12 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", + "license": "ISC" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12622,6 +12786,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -12720,6 +12890,12 @@ ], "license": "MIT" }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -12801,20 +12977,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/react-leaflet": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", - "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", - "license": "Hippocratic-2.1", - "dependencies": { - "@react-leaflet/core": "^2.1.0" - }, - "peerDependencies": { - "leaflet": "^1.9.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -13168,6 +13330,15 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/resolve.exports": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", @@ -14631,6 +14802,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index 9c1b1ec..613ebe9 100644 --- a/package.json +++ b/package.json @@ -44,13 +44,12 @@ "input-otp": "^1.4.2", "isbot": "^4.1.0", "jalali-moment": "^3.3.11", - "leaflet": "^1.9.4", "lucide-react": "^0.477.0", + "maplibre-gl": "^5.24.0", "moment-jalaali": "^0.10.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^15.4.1", - "react-leaflet": "^4.2.1", "remix-auth": "^3.6.0", "remix-auth-form": "^1.4.0", "tailwind-merge": "^3.0.2", @@ -61,7 +60,6 @@ }, "devDependencies": { "@remix-run/dev": "^2.16.0", - "@types/leaflet": "^1.9.17", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.7.4",