diff --git a/apps/findbobastore/package.json b/apps/findbobastore/package.json index 8ee59ed..02fb433 100644 --- a/apps/findbobastore/package.json +++ b/apps/findbobastore/package.json @@ -15,6 +15,7 @@ "next": "15.1.2", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-map-gl": "^7.1.8", "tailwind-merge": "^2.5.5" }, "devDependencies": { diff --git a/apps/findbobastore/src/components/map-view.tsx b/apps/findbobastore/src/components/map-view.tsx index b8e8d30..360182f 100644 --- a/apps/findbobastore/src/components/map-view.tsx +++ b/apps/findbobastore/src/components/map-view.tsx @@ -1,216 +1,10 @@ -import { useCallback, useEffect, useRef, memo, useState } from 'react' -import mapboxgl from 'mapbox-gl' +import { useCallback, memo, useState } from 'react' +import ReactMapGL, { Marker, NavigationControl, GeolocateControl } from 'react-map-gl' import { motion, AnimatePresence } from 'framer-motion' -import { cn } from '../lib/utils' +import { cn } from '@/lib/utils' +import { stores, type BobaStore } from '@/data/stores' import 'mapbox-gl/dist/mapbox-gl.css' -interface BobaStore { - id: string - name: string - coordinates: [number, number] - rating: number - address: string -} - -const mockBobaStores: BobaStore[] = [ - { - id: '1', - name: 'Urban Ritual', - coordinates: [-122.426, 37.553], - rating: 4.8, - address: '140 South B St, San Mateo, CA 94401', - }, - { - id: '2', - name: 'Tea Hut', - coordinates: [-122.4862, 37.7324], - rating: 4.5, - address: '1541 Sloat Blvd, San Francisco, CA 94132', - }, - { - id: '3', - name: 'District Tea', - coordinates: [-122.4194, 37.7749], - rating: 4.3, - address: '2154 Mission St, San Francisco, CA 94110', - }, - { - id: '4', - name: 'Plentea', - coordinates: [-122.4044, 37.7946], - rating: 4.7, - address: '341 Kearny St, San Francisco, CA 94108', - }, - { - id: '5', - name: 'Boba Guys', - coordinates: [-122.4194, 37.7649], - rating: 4.0, - address: '3491 19th St, San Francisco, CA 94110', - }, - { - id: '6', - name: 'Purple Kow', - coordinates: [-122.4862, 37.7857], - rating: 4.5, - address: '3620 Balboa St, San Francisco, CA 94121', - }, - { - id: '7', - name: 'Little Sweet', - coordinates: [-122.475, 37.779], - rating: 4.2, - address: '3836 Geary Blvd, San Francisco, CA', - }, - { - id: '8', - name: 'Honeybear Boba', - coordinates: [-122.4194, 37.7749], - rating: 4.2, - address: '801 22nd St, San Francisco, CA 94107', - }, - { - id: '9', - name: 'Yi Fang Taiwan Fruit Tea', - coordinates: [-122.4194, 37.7849], - rating: 4.5, - address: '950 E 3rd St Unit 2A, Los Angeles, CA 90013', - }, - { - id: '10', - name: 'Lady Luck Cafe', - coordinates: [-122.4064, 37.7946], - rating: 4.5, - address: '956 Grant Ave, San Francisco, CA 94108', - }, - { - id: '11', - name: 'Boba Bliss', - coordinates: [-122.1089, 37.4006], - rating: 4.6, - address: '685 San Antonio Rd Suite 15, Mountain View, CA 94040', - }, - { - id: '12', - name: 'Ume Tea', - coordinates: [-122.075, 37.3894], - rating: 4.4, - address: '220 Castro St, Mountain View, CA 94041', - }, - { - id: '13', - name: 'Tea Era', - coordinates: [-122.075, 37.3894], - rating: 4.3, - address: '271 Castro St, Mountain View, CA 94041', - }, - { - id: '14', - name: 'Teaspoon', - coordinates: [-122.075, 37.3894], - rating: 4.2, - address: '134A Castro St, Mountain View, CA 94041', - }, - { - id: '15', - name: 'FENG CHA', - coordinates: [-122.0891, 37.3894], - rating: 4.8, - address: '1040 Grant Rd Suite #350, Mountain View, CA 94040', - }, - { - id: '16', - name: 'Happy Lemon', - coordinates: [-122.075, 37.3894], - rating: 4.0, - address: '742 Villa St, Mountain View, CA 94041', - }, - { - id: '17', - name: 'Alma Dessert', - coordinates: [-122.0307, 37.3774], - rating: 4.8, - address: '165 S Murphy Ave Suite D, Sunnyvale, CA 94086', - }, - { - id: '18', - name: 'Molly Tea', - coordinates: [-122.0317, 37.3784], - rating: 4.6, - address: '605 E El Camino Real Suite 1, Sunnyvale, CA 94087', - }, - { - id: '19', - name: 'TP TEA Sunnyvale', - coordinates: [-122.0297, 37.3764], - rating: 4.7, - address: '567b E El Camino Real, Sunnyvale, CA 94087', - }, - { - id: '20', - name: 'R&B Tea Sunnyvale', - coordinates: [-122.0327, 37.3794], - rating: 4.1, - address: '568 E El Camino Real ste a, Sunnyvale, CA 94087', - }, - { - id: '21', - name: 'Teazzi Tea Shop', - coordinates: [-122.0287, 37.3754], - rating: 4.2, - address: '200 W McKinley Ave #105, Sunnyvale, CA 94086', - }, - { - id: '22', - name: 'MOOMO TEA', - coordinates: [-122.0337, 37.3804], - rating: 4.6, - address: '715 Sunnyvale Saratoga Rd, Sunnyvale, CA 94087', - }, - { - id: '23', - name: 'Chun Yang Tea', - coordinates: [-122.0277, 37.3744], - rating: 4.0, - address: '1120 Kifer Rd Suite C, Sunnyvale, CA 94086', - }, - { - id: '24', - name: 'Sunright Tea Studio', - coordinates: [-122.0347, 37.3814], - rating: 4.4, - address: '795 E El Camino Real, Sunnyvale, CA 94087', - }, - { - id: '25', - name: 'Boba Drive', - coordinates: [-122.0267, 37.3734], - rating: 4.5, - address: '677 Tasman Dr, Sunnyvale, CA 94089', - }, - { - id: '26', - name: 'Pekoe', - coordinates: [-122.0317, 37.3784], - rating: 3.9, - address: '939 W El Camino Real Suite 117, Sunnyvale, CA 94087', - }, - { - id: '27', - name: 'Tastea Sunnyvale', - coordinates: [-122.0327, 37.3794], - rating: 4.2, - address: '114 E El Camino Real, Sunnyvale, CA 94087', - }, - { - id: '28', - name: 'Mr. Sun Tea Mountain View', - coordinates: [-122.0891, 37.3894], - rating: 4.1, - address: '801 W El Camino Real A, Mountain View, CA 94040', - }, -] - function MapOverlay({ store }: { store: BobaStore | null }) { if (!store) return null @@ -238,156 +32,97 @@ function MapOverlay({ store }: { store: BobaStore | null }) { } export function MapView({ token }: { token: string | undefined }) { - const mapContainer = useRef(null) - const map = useRef(null) + console.log({ token }) const [selectedStore, setSelectedStore] = useState(null) - const markersRef = useRef([]) + const [viewState, setViewState] = useState({ + longitude: -122.4194, + latitude: 37.7749, + zoom: 12, + }) const [locationError, setLocationError] = useState(null) - const clearMarkers = useCallback(() => { - for (const marker of markersRef.current) { - marker.remove() - } - markersRef.current = [] - }, []) - - const addUserLocationMarker = useCallback((location: [number, number]) => { - if (!map.current) return - - const el = document.createElement('div') - el.className = 'user-location-marker' - el.innerHTML = '📍' - el.style.fontSize = '2rem' - el.setAttribute('aria-label', 'Your location') - - const marker = new mapboxgl.Marker({ element: el }).setLngLat(location).addTo(map.current) - markersRef.current.push(marker) - }, []) - - const addBobaMarkers = useCallback(() => { - if (!map.current) return - - for (const store of mockBobaStores) { - const el = document.createElement('div') - el.className = 'boba-marker' - el.innerHTML = '🧋' - el.style.fontSize = '2rem' - el.style.cursor = 'pointer' - el.setAttribute('aria-label', `${store.name} location marker`) - - const marker = new mapboxgl.Marker({ element: el }).setLngLat(store.coordinates).addTo(map.current) - - el.addEventListener('click', () => { - setSelectedStore(store) - map.current?.flyTo({ - center: store.coordinates, - zoom: 15, - duration: 1500, - essential: true, - }) - }) - - markersRef.current.push(marker) - } - }, []) - const getUserLocation = useCallback(() => { - return new Promise<[number, number]>((resolve, reject) => { - if (!navigator.geolocation) { - reject(new Error('Geolocation is not supported by your browser')) - return - } - - navigator.geolocation.getCurrentPosition( - (position) => { - resolve([position.coords.longitude, position.coords.latitude]) - }, - (error) => { - reject(error) - }, - { - enableHighAccuracy: true, - timeout: 5000, - maximumAge: 0, - }, - ) - }) - }, []) - - const initializeMap = useCallback(async () => { - if (!mapContainer.current) return - if (!token) return - console.log('token', token) - - try { - // Try to get user location first - const location = await getUserLocation() - - map.current = new mapboxgl.Map({ - container: mapContainer.current, - style: 'mapbox://styles/mapbox/dark-v11', - center: location, - zoom: 12, - maxZoom: 15, - accessToken: token, - }) - - map.current.on('load', () => { - clearMarkers() - addUserLocationMarker(location) - addBobaMarkers() - }) - } catch (error) { - setLocationError(error instanceof Error ? error.message : 'Failed to get location') - - // Fall back to showing all stores if location access fails - const bounds = new mapboxgl.LngLatBounds() - for (const store of mockBobaStores) { - bounds.extend(store.coordinates) - } - - map.current = new mapboxgl.Map({ - container: mapContainer.current, - style: 'mapbox://styles/mapbox/dark-v11', - bounds: bounds, - fitBoundsOptions: { padding: 50 }, - maxZoom: 15, - }) - - map.current.on('load', () => { - clearMarkers() - addBobaMarkers() - }) + if (!navigator.geolocation) { + setLocationError('Geolocation is not supported by your browser') + return } - map.current.addControl(new mapboxgl.NavigationControl(), 'top-right') - map.current.addControl( - new mapboxgl.GeolocateControl({ - positionOptions: { - enableHighAccuracy: true, - }, - trackUserLocation: true, - }), + navigator.geolocation.getCurrentPosition( + (position) => { + setViewState((prev) => ({ + ...prev, + longitude: position.coords.longitude, + latitude: position.coords.latitude, + })) + }, + (error) => { + setLocationError(error.message) + }, + { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0, + }, ) - }, [getUserLocation, clearMarkers, addUserLocationMarker, addBobaMarkers, token]) - - useEffect(() => { - if (!map.current) { - initializeMap() - } + }, []) - return () => { - if (map.current) { - clearMarkers() - map.current.remove() - map.current = null - } - } - }, [initializeMap, clearMarkers]) + if (!token) { + return ( +
+
+

Mapbox token is missing

+
+
+ ) + } return (
-
+ setViewState(evt.viewState)} + mapboxAccessToken={token} + style={{ width: '100%', height: '100%' }} + mapStyle="mapbox://styles/mapbox/dark-v11" + maxZoom={15} + onLoad={() => { + getUserLocation() + }} + > + + + + {stores.map((store) => ( + { + e.originalEvent.stopPropagation() + setSelectedStore(store) + setViewState((prev) => ({ + ...prev, + longitude: store.coordinates[0], + latitude: store.coordinates[1], + zoom: 15, + })) + }} + > +
+ 🧋 +
+
+ ))} + + {/* User location marker */} + {viewState.longitude !== -122.4194 && ( + +
+ 📍 +
+
+ )} +
{locationError &&
{locationError}
}
diff --git a/apps/findbobastore/src/data/stores.ts b/apps/findbobastore/src/data/stores.ts new file mode 100644 index 0000000..9e0a528 --- /dev/null +++ b/apps/findbobastore/src/data/stores.ts @@ -0,0 +1,150 @@ +export interface BobaStore { + id: string + name: string + coordinates: [number, number] + rating: number + address: string +} + +export const stores: BobaStore[] = [ + { + id: '1', + name: 'Boba Bliss', + coordinates: [-122.113011, 37.401621], + rating: 10, + address: '685 San Antonio Rd Suite 15, Mountain View, CA 94040', + }, + { + id: '2', + name: 'Ume Tea', + coordinates: [-122.079391, 37.393631], + rating: 9, + address: '220 Castro St, Mountain View, CA 94041', + }, + { + id: '3', + name: 'Teaspoon', + coordinates: [-122.078385, 37.394757], + rating: 7, + address: '134A Castro St, Mountain View, CA 94041', + }, + { + id: '4', + name: 'Happy Lemon', + coordinates: [-122.0786353, 37.3935868], + rating: 5, + address: '742 Villa St, Mountain View, CA 94041', + }, + { + id: '5', + name: 'Yi Fang Taiwan Fruit Tea', + coordinates: [-122.0783587, 37.3944675], + rating: 5, + address: '143 Castro St, Mountain View, CA 94041', + }, + { + id: '6', + name: 'Molly Tea 茉莉奶白', + coordinates: [-122.0398861, 37.3696433], + rating: 9, + address: '605 E El Camino Real Suite 1, Sunnyvale, CA 94087', + }, + { + id: '7', + name: 'TP TEA Sunnyvale', + coordinates: [-122.0384433, 37.3692649], + rating: 9, + address: '567b E El Camino Real, Sunnyvale, CA 94087', + }, + { + id: '8', + name: 'MoonTea', + coordinates: [-122.0407241, 37.3709747], + rating: 8, + address: '513 S Pastoria Ave, Sunnyvale, CA 94086', + }, + { + id: '9', + name: 'Sunright Tea Studio - Sunnyvale', + coordinates: [-122.0179472, 37.3562008], + rating: 8, + address: '795 E El Camino Real, Sunnyvale, CA 94087', + }, + { + id: '10', + name: 'Tong Sui Desserts & Drinks (Sunnyvale)', + coordinates: [-122.0076786, 37.3812265], + rating: 9, + address: '927 E Arques Ave #151, Sunnyvale, CA 94085', + }, + { + id: '11', + name: 'MOOMO TEA', + coordinates: [-122.033623, 37.3674971], + rating: 7, + address: '715 Sunnyvale Saratoga Rd, Sunnyvale, CA 94087', + }, + { + id: '12', + name: 'Teazzi Tea Shop', + coordinates: [-122.032146, 37.3742401], + rating: 8, + address: '200 W McKinley Ave #105, Sunnyvale, CA 94086', + }, + { + id: '13', + name: 'Chun Yang Tea', + coordinates: [-121.9998913, 37.3732914], + rating: 7, + address: '1120 Kifer Rd Suite C, Sunnyvale, CA 94086', + }, + { + id: '14', + name: 'T% Coffee + Tea Santa Clara', + coordinates: [-121.9812209, 37.3522062], + rating: 8, + address: '3030 El Camino Real, Santa Clara, CA 95051', + }, + { + id: '15', + name: 'Meet Fresh Cupertino', + coordinates: [-122.011207, 37.3244107], + rating: 6, + address: '19449 Stevens Creek Blvd STE 120, Cupertino, CA 95014', + }, + { + id: '16', + name: 'ZERO& | Cupertino Village', + coordinates: [-122.014669, 37.3357109], + rating: 7, + address: '10815 N Wolfe Rd Suite 102, Cupertino, CA 95014', + }, + { + id: '17', + name: 'Wanpo Tea Shop', + coordinates: [-122.054475, 37.3228851], + rating: 9, + address: '19319 Stevens Creek Blvd, Cupertino, CA 95014', + }, + { + id: '18', + name: 'Chicha San Chen 吃茶三千', + coordinates: [-122.0347858, 37.3225755], + rating: 9, + address: '20688 Stevens Creek Blvd, Cupertino, CA 95014', + }, + { + id: '19', + name: 'Shang Yu Lin-Cupertino上宇林', + coordinates: [-122.040335, 37.3367908], + rating: 7, + address: '20956 Homestead Rd, Cupertino, CA 95014', + }, + { + id: '20', + name: 'Tea Top 台灣第一味', + coordinates: [-122.012591, 37.308738], + rating: 7, + address: '6158 Bollinger Rd, San Jose, CA 95129', + }, +] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c80a8fb..897a293 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.0.0(react@19.0.0) + react-map-gl: + specifier: ^7.1.8 + version: 7.1.8(mapbox-gl@3.9.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) tailwind-merge: specifier: ^2.5.5 version: 2.5.5 @@ -347,6 +350,10 @@ packages: resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} engines: {node: '>=6.0.0'} + '@maplibre/maplibre-gl-style-spec@19.3.3': + resolution: {integrity: sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==} + hasBin: true + '@next/env@15.1.2': resolution: {integrity: sha512-Hm3jIGsoUl6RLB1vzY+dZeqb+/kWPZ+h34yiWxW0dV87l8Im/eMOwpOA+a0L78U0HM04syEjXuRlCozqpwuojQ==} @@ -471,6 +478,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/mapbox-gl@3.4.1': + resolution: {integrity: sha512-NsGKKtgW93B+UaLPti6B7NwlxYlES5DpV5Gzj9F75rK5ALKsqSk15CiEHbOnTr09RGbr6ZYiCdI+59NNNcAImg==} + '@types/mapbox__point-geometry@0.1.4': resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==} @@ -587,6 +597,10 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + arr-union@3.1.0: + resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} + engines: {node: '>=0.10.0'} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -619,6 +633,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assign-symbols@1.0.0: + resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} + engines: {node: '>=0.10.0'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -655,6 +673,12 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + bytewise-core@1.2.3: + resolution: {integrity: sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==} + + bytewise@1.1.0: + resolution: {integrity: sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==} + call-bind-apply-helpers@1.0.1: resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} @@ -963,6 +987,14 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend-shallow@3.0.2: + resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} + engines: {node: '>=0.10.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1052,6 +1084,10 @@ packages: get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + get-value@2.0.6: + resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} + engines: {node: '>=0.10.0'} + gl-matrix@3.4.3: resolution: {integrity: sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==} @@ -1181,6 +1217,14 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extendable@1.0.1: + resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} + engines: {node: '>=0.10.0'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1213,6 +1257,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1255,6 +1303,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + iterator.prototype@1.1.4: resolution: {integrity: sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==} engines: {node: '>= 0.4'} @@ -1282,6 +1334,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-pretty-compact@3.0.0: + resolution: {integrity: sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==} + json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -1594,6 +1649,19 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-map-gl@7.1.8: + resolution: {integrity: sha512-zwF16XMOdOKH4py5ehS1bgQIChqW8UN3b1bXps+JnADbYLSbOoUPQ3tNw0EZ2OTBWArR5aaQlhlEqg4lE47T8A==} + peerDependencies: + mapbox-gl: '>=1.13.0' + maplibre-gl: '>=1.13.0' + react: '>=16.3.0' + react-dom: '>=16.3.0' + peerDependenciesMeta: + mapbox-gl: + optional: true + maplibre-gl: + optional: true + react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} @@ -1639,6 +1707,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -1671,6 +1742,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + set-value@2.0.1: + resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} + engines: {node: '>=0.10.0'} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1706,10 +1781,26 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sort-asc@0.2.0: + resolution: {integrity: sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==} + engines: {node: '>=0.10.0'} + + sort-desc@0.2.0: + resolution: {integrity: sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==} + engines: {node: '>=0.10.0'} + + sort-object@3.0.3: + resolution: {integrity: sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==} + engines: {node: '>=0.10.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split-string@3.1.0: + resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} + engines: {node: '>=0.10.0'} + stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} @@ -1864,6 +1955,12 @@ packages: engines: {node: '>=14.17'} hasBin: true + typewise-core@1.2.0: + resolution: {integrity: sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==} + + typewise@1.0.3: + resolution: {integrity: sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -1871,6 +1968,10 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + union-value@1.0.1: + resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} + engines: {node: '>=0.10.0'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2102,6 +2203,15 @@ snapshots: '@mapbox/whoots-js@3.1.0': {} + '@maplibre/maplibre-gl-style-spec@19.3.3': + dependencies: + '@mapbox/jsonlint-lines-primitives': 2.0.2 + '@mapbox/unitbezier': 0.0.1 + json-stringify-pretty-compact: 3.0.0 + minimist: 1.2.8 + rw: 1.3.3 + sort-object: 3.0.3 + '@next/env@15.1.2': {} '@next/eslint-plugin-next@15.1.2': @@ -2192,6 +2302,10 @@ snapshots: '@types/json5@0.0.29': {} + '@types/mapbox-gl@3.4.1': + dependencies: + '@types/geojson': 7946.0.15 + '@types/mapbox__point-geometry@0.1.4': {} '@types/mapbox__vector-tile@1.3.4': @@ -2331,6 +2445,8 @@ snapshots: aria-query@5.3.2: {} + arr-union@3.1.0: {} + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.3 @@ -2395,6 +2511,8 @@ snapshots: get-intrinsic: 1.2.6 is-array-buffer: 3.0.5 + assign-symbols@1.0.0: {} + ast-types-flow@0.0.8: {} available-typed-arrays@1.0.7: @@ -2426,6 +2544,15 @@ snapshots: dependencies: streamsearch: 1.1.0 + bytewise-core@1.2.3: + dependencies: + typewise-core: 1.2.0 + + bytewise@1.1.0: + dependencies: + bytewise-core: 1.2.3 + typewise: 1.0.3 + call-bind-apply-helpers@1.0.1: dependencies: es-errors: 1.3.0 @@ -2874,6 +3001,15 @@ snapshots: esutils@2.0.3: {} + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend-shallow@3.0.2: + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -2979,6 +3115,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-value@2.0.6: {} + gl-matrix@3.4.3: {} glob-parent@5.1.2: @@ -3101,6 +3239,12 @@ snapshots: call-bound: 1.0.3 has-tostringtag: 1.0.2 + is-extendable@0.1.1: {} + + is-extendable@1.0.1: + dependencies: + is-plain-object: 2.0.4 + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -3126,6 +3270,10 @@ snapshots: is-number@7.0.0: {} + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + is-regex@1.2.1: dependencies: call-bound: 1.0.3 @@ -3169,6 +3317,8 @@ snapshots: isexe@2.0.0: {} + isobject@3.0.1: {} + iterator.prototype@1.1.4: dependencies: define-data-property: 1.1.4 @@ -3198,6 +3348,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-pretty-compact@3.0.0: {} + json5@1.0.2: dependencies: minimist: 1.2.8 @@ -3514,6 +3666,15 @@ snapshots: react-is@16.13.1: {} + react-map-gl@7.1.8(mapbox-gl@3.9.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@maplibre/maplibre-gl-style-spec': 19.3.3 + '@types/mapbox-gl': 3.4.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + mapbox-gl: 3.9.1 + react@19.0.0: {} read-cache@1.0.0: @@ -3568,6 +3729,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -3606,6 +3769,13 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + set-value@2.0.1: + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + sharp@0.33.5: dependencies: color: 4.2.3 @@ -3674,8 +3844,25 @@ snapshots: is-arrayish: 0.3.2 optional: true + sort-asc@0.2.0: {} + + sort-desc@0.2.0: {} + + sort-object@3.0.3: + dependencies: + bytewise: 1.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + sort-asc: 0.2.0 + sort-desc: 0.2.0 + union-value: 1.0.1 + source-map-js@1.2.1: {} + split-string@3.1.0: + dependencies: + extend-shallow: 3.0.2 + stable-hash@0.0.4: {} streamsearch@1.1.0: {} @@ -3882,6 +4069,12 @@ snapshots: typescript@5.7.2: {} + typewise-core@1.2.0: {} + + typewise@1.0.3: + dependencies: + typewise-core: 1.2.0 + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.3 @@ -3891,6 +4084,13 @@ snapshots: undici-types@6.19.8: {} + union-value@1.0.1: + dependencies: + arr-union: 3.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + set-value: 2.0.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1