diff --git a/frontend/src/pages/Properties/PropertyDetail.js b/frontend/src/pages/Properties/PropertyDetail.js index cfaa121..b6f8fe6 100644 --- a/frontend/src/pages/Properties/PropertyDetail.js +++ b/frontend/src/pages/Properties/PropertyDetail.js @@ -356,6 +356,7 @@ const PropertyDetail = () => { const [gpsFixAgeMs, setGpsFixAgeMs] = useState(null); const [gpsReady, setGpsReady] = useState(false); const [warmWatchId, setWarmWatchId] = useState(null); + const [warmPrimed, setWarmPrimed] = useState(false); const [isSnapPreview, setIsSnapPreview] = useState(false); const [showAddMenu, setShowAddMenu] = useState(false); // Modal picker to ensure options show reliably on mobile @@ -381,10 +382,10 @@ const PropertyDetail = () => { fetchPropertyDetails(); }, [id]); // eslint-disable-line react-hooks/exhaustive-deps - // Start a passive GPS watch when the page mounts to warm up the fix - useEffect(() => { + // Helper: ensure a passive watch is running + const ensureWarmWatch = () => { if (!navigator.geolocation) return; - if (warmWatchId) return; // already running + if (warmWatchId) return; const id = navigator.geolocation.watchPosition( (pos) => { const { latitude, longitude, accuracy } = pos.coords; @@ -396,13 +397,49 @@ const PropertyDetail = () => { { enableHighAccuracy: true, maximumAge: 1000, timeout: 20000 } ); setWarmWatchId(id); + }; + + // Start warm watch on mount + useEffect(() => { + ensureWarmWatch(); return () => { - try { if (id) navigator.geolocation.clearWatch(id); } catch {} + try { if (warmWatchId) navigator.geolocation.clearWatch(warmWatchId); } catch {} setWarmWatchId(null); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // On first user interaction, trigger a one-time getCurrentPosition to prompt permission on iOS + useEffect(() => { + if (!navigator.geolocation) return; + if (warmPrimed) return; + const handler = () => { + navigator.geolocation.getCurrentPosition( + (pos) => { + addSampleAndSmooth(pos.coords.latitude, pos.coords.longitude, pos.coords.accuracy); + ensureWarmWatch(); + setWarmPrimed(true); + document.removeEventListener('click', handler); + document.removeEventListener('touchstart', handler); + }, + () => { + // even on error, try to start warm watch; user may grant later + ensureWarmWatch(); + setWarmPrimed(true); + document.removeEventListener('click', handler); + document.removeEventListener('touchstart', handler); + }, + { enableHighAccuracy: true, maximumAge: 1000, timeout: 8000 } + ); + }; + document.addEventListener('click', handler, { once: true }); + document.addEventListener('touchstart', handler, { once: true }); + return () => { + document.removeEventListener('click', handler); + document.removeEventListener('touchstart', handler); + }; + }, [warmPrimed]); + // Load recent history when property is available useEffect(() => { if (!property?.id) return; @@ -1085,6 +1122,15 @@ const PropertyDetail = () => { }}>Pause )} + {isSnapPreview && Snap to start available}