Files
travelplanning/UNLOCK_CENTER.md

6.9 KiB

To AI Agent: Audit Location Lock Bug and Refactor GPS Tracking into a Toggle Stateful Button

1. Context & Problem Statement

Currently, in our travel planner application map engine (on pages like TourNavigationPage.tsx, LocationNavigationModal.tsx, or map utilities), the viewport is continuously forced to lock onto the user's live GPS position. This architecture severely damages mobile UX because:

  1. It prevents users from manually dragging, panning, or scouting other areas of the map terrain.
  2. There is no control interface to temporarily mute or disable live GPS tracking.

Objective: - Run a global audit across the entire codebase to locate functions driving this forced-center trap (e.g., custom hooks, requestGpsPosition, native geolocation callbacks, or reactive map state updates).

  • Refactor the logic so that live tracking is bound strictly to an independent toggle state button. The map must ONLY lock/re-center on the user's coordinates when this tracking toggle button is actively switched ON.

Scan the entire project repository (specifically /frontend/src) using code search patterns to intercept the lock mechanism. Target the following files and keywords:

Key Target Files to Inspect:

  • frontend/src/pages/TourNavigationPage.tsx
  • frontend/src/components/LocationNavigationModal.tsx
  • Any custom hooks or contexts handling geography, such as useGeolocation.ts, useMap.ts, or generic map setup wrappers.

Regex & Keyword Global Search Queries:

  • Search for native background watchers: navigator.geolocation.watchPosition or navigator.geolocation.getCurrentPosition
  • Search for custom map-centering loops: requestGpsPosition, followUser, centerToUser
  • Search for viewport mutation commands specific to our active map engine stack:
    • Leaflet: .setView(, .panTo(, center={
    • Mapbox GL JS: .flyTo(, .easeTo(, .jumpTo(

3. Phase 2: Technical Refactoring Blueprint

Once the tracking logic code blocks are isolated from Phase 1, implement the structural state safety rails below:

Step 1: Initialize the Tracking State Guard

Introduce a state hook controller (isTrackingLocation) to manage whether the view should actively mirror device coordinates:

// Add inside the map controller/page container component
const [isTrackingLocation, setIsTrackingLocation] = useState(false);
const watchIdRef = useRef<number | null>(null);

### Step 2: Encapsulate the Geolocation Watcher Handler
Wrap your positioning tracking engine loop inside a conditional check governed directly by the state guard. Ensure that if the tracking state is disabled, the background watcher cleanly unmounts:

useEffect(() => {
  if (isTrackingLocation) {
    if (navigator.geolocation) {
      console.log("GPS Location Tracking engaged. Syncing viewport to center...");
      
      watchIdRef.current = navigator.geolocation.watchPosition(
        (position) => {
          const { latitude, longitude, heading } = position.coords;
          
          if (mapRef.current) {
            // ✅ CORRECTION: Viewport ONLY repositions center when tracking button is active
            mapRef.current.easeTo({
              center: [longitude, latitude],
              zoom: 16, // Lock to comfortable navigation zoom level
              duration: 600
            });
          }
        },
        (error) => console.error("GPS stream tracking lost:", error),
        { enableHighAccuracy: true }
      );
    }
  } else {
    // Clean up tracking process instantly when toggled OFF
    if (watchIdRef.current !== null) {
      navigator.geolocation.clearWatch(watchIdRef.current);
      watchIdRef.current = null;
      console.log("GPS Location Tracking disabled. Map control released to user.");
    }
  }

  return () => {
    if (watchIdRef.current !== null) navigator.geolocation.clearWatch(watchIdRef.current);
  };
}, [isTrackingLocation]);

### Step 3: Implement Gesture Detection (UX Safety Rail)
If the user manually drags the screen while tracking is active, the tracking state must automatically toggle OFF so the viewport doesn't fight against the user's finger movements:

useEffect(() => {
  if (!mapRef.current) return;
  const map = mapRef.current;

  const breakTrackingOnGesture = () => {
    if (isTrackingLocation) {
      console.log("User touch map interaction detected. Disengaging auto-center lock.");
      setIsTrackingLocation(false); // Automatically drop tracking flag on map pan/zoom
    }
  };

  map.on('dragstart', breakTrackingOnGesture);
  map.on('zoomstart', breakTrackingOnGesture);
  map.on('movestart', breakTrackingOnGesture);

  return () => {
    map.off('dragstart', breakTrackingOnGesture);
    map.off('zoomstart', breakTrackingOnGesture);
    map.off('movestart', breakTrackingOnGesture);
  };
}, [isTrackingLocation]);

### Step 4: Render the UI Toggle Button UI Component
Deploy a new independent floating button on top of the map canvas workspace (placed at bottom-24 right-6, just right above your custom Compass button layout):

{/* FLOATING LIVE GPS TRACKING CENTER LOCK BUTTON */}
<button
  onClick={() => setIsTrackingLocation(!isTrackingLocation)}
  className={`absolute bottom-24 right-6 z-40 w-14 h-14 rounded-full border-2 flex items-center justify-center shadow-2xl transition-all active:scale-95 ${
    isTrackingLocation 
      ? 'bg-green-600 border-green-400 text-white animate-pulse' 
      : 'bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 text-slate-600 dark:text-green-500'
  }`}
  title={isTrackingLocation ? "Tắt tự động định tâm vị trí" : "Bật tự động định tâm theo vị trí của bạn"}
>
  {/* Replace Crosshair icon element with your active layout icon package asset */}
  <svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" className="w-7 h-7" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
    <path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
  </svg>
</button>

## 4. Verification & Quality Acceptance Criteria

[ ] Code Erasure Verification: Confirm that old continuous loops or uncontrolled recursive .setView/.easeTo methods triggered instantly on map load are fully removed or properly contained inside the state block.

[ ] Default State Freedom: Upon opening the map page path, tracking must default to OFF. Users must be able to drag the map anywhere in the world without the screen snapped or yanked back to their physical house position.

[ ] Toggle Activation Centering: Pressing the new GPS tracking button must instantly engage the animation, center the map view directly on top of the user blue dot icon, and follow them smoothly if they move.

[ ] Manual Override Interception: Turn tracking ON. Drag the map manually with a finger gesture. Verify that the tracking button instantly changes style states back to deactivated and tracking shuts down cleanly.