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}