From cfbb1625e7873264e2ef435cc76fec2b59cf58d8 Mon Sep 17 00:00:00 2001
From: Ariel Costas Guerrero <ariel@costas.dev>
Date: Wed, 24 Dec 2025 19:33:49 +0100
Subject: Refactor map components and improve modal functionality

---
 src/frontend/app/routes/map.tsx | 96 ++++-------------------------------------
 1 file changed, 8 insertions(+), 88 deletions(-)

(limited to 'src/frontend/app/routes/map.tsx')

diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx
index 517549b..45dd935 100644
--- a/src/frontend/app/routes/map.tsx
+++ b/src/frontend/app/routes/map.tsx
@@ -1,20 +1,17 @@
 import StopDataProvider from "../data/StopDataProvider";
 import "./map.css";
 
-import { DEFAULT_STYLE, loadStyle } from "app/maps/styleloader";
 import { useEffect, useRef, useState } from "react";
 import { useTranslation } from "react-i18next";
-import Map, {
-  GeolocateControl,
+import {
   Layer,
-  NavigationControl,
   Source,
   type MapLayerMouseEvent,
   type MapRef,
-  type StyleSpecification,
 } from "react-map-gl/maplibre";
 import { useNavigate } from "react-router";
 import { PlannerOverlay } from "~/components/PlannerOverlay";
+import { AppMap } from "~/components/shared/AppMap";
 import {
   StopSummarySheet,
   type StopSheetProps,
@@ -34,14 +31,10 @@ export default function StopMap() {
     StopSheetProps["stop"] | null
   >(null);
   const [isSheetOpen, setIsSheetOpen] = useState(false);
-  const { mapState, updateMapState, theme } = useApp();
   const mapRef = useRef<MapRef>(null);
 
   const { searchRoute } = usePlanner();
 
-  // Style state for Map component
-  const [mapStyle, setMapStyle] = useState<StyleSpecification>(DEFAULT_STYLE);
-
   // Handle click events on clusters and individual stops
   const onMapClick = (e: MapLayerMouseEvent) => {
     const features = e.features;
@@ -59,63 +52,6 @@ export default function StopMap() {
     handlePointClick(feature);
   };
 
-  useEffect(() => {
-    //const styleName = "carto";
-    const styleName = "openfreemap";
-    loadStyle(styleName, theme)
-      .then((style) => setMapStyle(style))
-      .catch((error) => console.error("Failed to load map style:", error));
-  }, [theme]);
-
-  useEffect(() => {
-    const handleMapChange = () => {
-      if (!mapRef.current) return;
-      const map = mapRef.current.getMap();
-      if (!map) return;
-      const center = map.getCenter();
-      const zoom = map.getZoom();
-      updateMapState([center.lat, center.lng], zoom);
-    };
-
-    const handleStyleImageMissing = (e: any) => {
-      // Suppress warnings for missing sprite images from base style
-      // This prevents console noise from OpenFreeMap's missing icons
-      if (!mapRef.current) return;
-      const map = mapRef.current.getMap();
-      if (!map || map.hasImage(e.id)) return;
-
-      // Log warning for our own icons if they are missing
-      if (e.id.startsWith("stop-")) {
-        console.warn(`Missing icon image: ${e.id}`);
-      }
-
-      // Add a transparent 1x1 placeholder to prevent repeated warnings
-      map.addImage(e.id, {
-        width: 1,
-        height: 1,
-        data: new Uint8Array(4),
-      });
-    };
-
-    if (mapRef.current) {
-      const map = mapRef.current.getMap();
-      if (map) {
-        map.on("moveend", handleMapChange);
-        map.on("styleimagemissing", handleStyleImageMissing);
-      }
-    }
-
-    return () => {
-      if (mapRef.current) {
-        const map = mapRef.current.getMap();
-        if (map) {
-          map.off("moveend", handleMapChange);
-          map.off("styleimagemissing", handleStyleImageMissing);
-        }
-      }
-    };
-  }, [mapRef.current]);
-
   const getLatitude = (center: any) =>
     Array.isArray(center) ? center[0] : center.lat;
   const getLongitude = (center: any) =>
@@ -166,31 +102,15 @@ export default function StopMap() {
         cardBackground="bg-white/95 dark:bg-slate-900/90"
       />
 
-      <Map
-        mapStyle={mapStyle}
-        style={{ width: "100%", height: "100%" }}
+      <AppMap
+        ref={mapRef}
+        syncState={true}
+        showNavigation={true}
+        showGeolocate={true}
         interactiveLayerIds={["stops", "stops-label"]}
         onClick={onMapClick}
-        minZoom={5}
-        scrollZoom
-        pitch={0}
-        roll={0}
-        ref={mapRef}
-        initialViewState={{
-          latitude: getLatitude(mapState.center),
-          longitude: getLongitude(mapState.center),
-          zoom: mapState.zoom,
-        }}
         attributionControl={{ compact: false }}
-        maxBounds={[APP_CONSTANTS.bounds.sw, APP_CONSTANTS.bounds.ne]}
       >
-        <NavigationControl position="bottom-right" />
-        <GeolocateControl
-          position="bottom-right"
-          trackUserLocation={true}
-          positionOptions={{ enableHighAccuracy: false }}
-        />
-
         <Source
           id="stops-source"
           type="vector"
@@ -284,7 +204,7 @@ export default function StopMap() {
             stop={selectedStop}
           />
         )}
-      </Map>
+      </AppMap>
     </div>
   );
 }
-- 
cgit v1.3

