Repository navigation
Added mapbox support to all pages - #74
raptor1820 wants to merge 1 commit into
Conversation
✅ Deploy Preview for able-alliance ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Greptile SummaryThis PR extracts inline Mapbox logic from
Confidence Score: 4/5Safe to merge after addressing the marker-rebuild P1; P2 style issues can follow in a cleanup PR. One P1 issue remains: static pickup/dropoff markers are rebuilt on every live GPS update due to unstable prop references, causing visible flickering during active rides. The P2 findings (hardcoded colors, px box-shadow, console.error) are style violations that don't block functionality. src/components/RideMap/RideMap.tsx — split the marker effect or use stable references to prevent the per-GPS-update full rebuild. Important Files Changed
|
| useEffect(() => { | ||
| const map = mapRef.current; | ||
| if (!map || !mapReady) return; | ||
|
|
||
| markerRefs.current.forEach((marker) => marker.remove()); | ||
| markerRefs.current = []; | ||
|
|
||
| const framePoints: [number, number][] = []; | ||
| const selectedIds = new Set( | ||
| [pickupLocationId, dropoffLocationId].filter( | ||
| (value): value is string => | ||
| typeof value === "string" && value.length > 0, | ||
| ), | ||
| ); | ||
|
|
||
| if (showOtherLocations) { | ||
| for (const location of locations) { | ||
| if (selectedIds.has(location._id)) continue; | ||
| if (!isValidLngLat(location.longitude, location.latitude)) continue; | ||
|
|
||
| const marker = new mapboxgl.Marker({ | ||
| element: createLocationDot(location.name), | ||
| anchor: "center", | ||
| }) | ||
| .setLngLat([location.longitude, location.latitude]) | ||
| .addTo(map); | ||
|
|
||
| markerRefs.current.push(marker); | ||
| } | ||
| } | ||
|
|
||
| const pickupLngLat = | ||
| selectedLocations.pickup && | ||
| isValidLngLat( | ||
| selectedLocations.pickup.longitude, | ||
| selectedLocations.pickup.latitude, | ||
| ) | ||
| ? ([ | ||
| selectedLocations.pickup.longitude, | ||
| selectedLocations.pickup.latitude, | ||
| ] as [number, number]) | ||
| : null; | ||
| const dropoffLngLat = | ||
| selectedLocations.dropoff && | ||
| isValidLngLat( | ||
| selectedLocations.dropoff.longitude, | ||
| selectedLocations.dropoff.latitude, | ||
| ) | ||
| ? ([ | ||
| selectedLocations.dropoff.longitude, | ||
| selectedLocations.dropoff.latitude, | ||
| ] as [number, number]) | ||
| : null; | ||
|
|
||
| const overlap = | ||
| pickupLngLat !== null && | ||
| dropoffLngLat !== null && | ||
| coordsOverlap(pickupLngLat, dropoffLngLat); | ||
|
|
||
| if (selectedLocations.pickup && pickupLngLat) { | ||
| const marker = new mapboxgl.Marker({ | ||
| element: createCustomPin( | ||
| `Pickup: ${selectedLocations.pickup.name}`, | ||
| "#183777", | ||
| ), | ||
| anchor: "bottom", | ||
| }) | ||
| .setLngLat(pickupLngLat) | ||
| .addTo(map); | ||
|
|
||
| markerRefs.current.push(marker); | ||
| framePoints.push(pickupLngLat); | ||
| } | ||
|
|
||
| if (selectedLocations.dropoff && dropoffLngLat) { | ||
| const marker = new mapboxgl.Marker({ | ||
| element: createCustomPin( | ||
| `Dropoff: ${selectedLocations.dropoff.name}`, | ||
| "#183777", | ||
| overlap ? 50 : 0, | ||
| ), | ||
| anchor: "bottom", | ||
| }) | ||
| .setLngLat(dropoffLngLat) | ||
| .addTo(map); | ||
|
|
||
| markerRefs.current.push(marker); | ||
| framePoints.push(dropoffLngLat); | ||
| } | ||
|
|
||
| for (const liveMarker of liveMarkers) { | ||
| if (!isValidLngLat(liveMarker.longitude, liveMarker.latitude)) continue; | ||
|
|
||
| const lngLat: [number, number] = [ | ||
| liveMarker.longitude, | ||
| liveMarker.latitude, | ||
| ]; | ||
| const color = liveMarker.color ?? "#2563eb"; | ||
| const variant = liveMarker.variant ?? "pin"; | ||
| const marker = new mapboxgl.Marker({ | ||
| element: | ||
| variant === "dot" | ||
| ? createLiveDot(color) | ||
| : createCustomPin(liveMarker.label ?? "Live", color), | ||
| anchor: variant === "dot" ? "center" : "bottom", | ||
| }) | ||
| .setLngLat(lngLat) | ||
| .addTo(map); | ||
|
|
||
| markerRefs.current.push(marker); | ||
| framePoints.push(lngLat); | ||
| } | ||
|
|
||
| map.resize(); | ||
|
|
||
| if (framePoints.length === 0) { | ||
| map.easeTo({ center: defaultCenter, zoom: defaultZoom, duration: 0 }); | ||
| return; | ||
| } | ||
|
|
||
| if (framePoints.length === 1) { | ||
| map.easeTo({ center: framePoints[0], zoom: defaultZoom, duration: 0 }); | ||
| return; | ||
| } | ||
|
|
||
| const bounds = new mapboxgl.LngLatBounds(framePoints[0], framePoints[0]); | ||
| for (const point of framePoints.slice(1)) { | ||
| bounds.extend(point); | ||
| } | ||
|
|
||
| map.fitBounds(bounds, { | ||
| padding: FRAME_PADDING, | ||
| maxZoom: MAX_FRAME_ZOOM, | ||
| duration: 0, | ||
| }); | ||
| }, [ | ||
| defaultCenter, | ||
| defaultZoom, | ||
| dropoffLocationId, | ||
| liveMarkers, | ||
| locations, | ||
| mapReady, | ||
| pickupLocationId, | ||
| selectedLocations, | ||
| showOtherLocations, | ||
| ]); |
There was a problem hiding this comment.
All markers rebuilt on every live GPS update
The single useEffect handles both static (pickup/dropoff) and live markers together. Both liveMarkers and locations are in the dependency array, and the caller passes Object.values(locations) (new array each render) and an inline-computed rideMapLiveMarkers array (also new each render). Every GPS position update re-renders the parent, which produces new array references for both props, triggering this effect to tear down and recreate all markers — including the static pickup/dropoff pins — every second. This causes visible flickering and wasted DOM work during live tracking.
Consider splitting static markers and live markers into separate useEffects, and using marker.setLngLat() for live updates instead of recreating markers. The caller should also useMemo the rideMapLiveMarkers array.
| background: #2563eb; | ||
| border: 0.2rem solid white; | ||
| box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); | ||
| } | ||
|
|
||
| .mapDotTooltip { | ||
| position: absolute; | ||
| bottom: calc(100% + 0.5rem); | ||
| left: 50%; | ||
| transform: translateX(-50%); | ||
| background: #183777; |
There was a problem hiding this comment.
Hardcoded color values — use CSS variables
#2563eb (.mapLocationDot) and #183777 (.mapDotTooltip) are hardcoded hex values. Per project rules, color variables from the global stylesheet should be used instead of hardcoded color values in component CSS files. The same colors are also hardcoded in RideMap.tsx (lines 256, 271, 290) and should be replaced with CSS variables or Tailwind config colors.
Rule Used: Use color variables from the global stylesheet (`s... (source)
Learnt From
GTBitsOfGood/design-system#61
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| font-family: var(--font-paragraph); | ||
| line-height: 1.4; | ||
| white-space: nowrap; | ||
| box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25); |
There was a problem hiding this comment.
px units in box-shadow — use rem
box-shadow: 0 2px 6px ... uses px for offsets. Per the project convention (1rem = 10px), these should be 0 0.2rem 0.6rem. The same applies to .mapPinDot (line 38: 0 2px 4px) and .mapLiveDot (line 91: 0 2px 8px).
Rule Used: Use 'rem' units instead of 'px' for most CSS measu... (source)
Learnt From
GTBitsOfGood/design-system#61
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| map.on("load", handleLoad); | ||
| } | ||
| } catch (error) { | ||
| console.error("Failed to initialize ride map:", error); |
There was a problem hiding this comment.
console.error in production component
The checklist rule is "no console.logs or commented-out code." console.error falls in the same category for production client components. The onError callback is already called on the next line to surface the error to the UI — the console.error can be removed.
Context Used: Fill out this checklist in addition to code commen... (source)
Made a mapbox component and re-used it across pages.