fix(ux): Batch C part 1 — Near Me timing & reachability

- geolocation: cap the overall deadline at 15s (was 25s) and shorten the native
  plugin attempt to 8s, so a failed fix no longer hangs ~25-40s
- MapPage: only warm the location cache when permission is ALREADY granted, so
  opening /map no longer fires the browser's 'Allow location?' prompt
- MapPage: add a desktop Near Me control (it was mobile-only)
- MapPage: on native, a denial is now retryable instead of permanently opening
  the browser-only help sheet
This commit is contained in:
SkateSpots 2026-09-26 17:58:38 +07:00
parent 8e35859188
commit dca0c0ede1
2 changed files with 57 additions and 10 deletions

View file

@ -228,7 +228,7 @@ async function getNativePosition(native: CapGeolocationPlugin): Promise<GeoCoord
// it defensively and fall back to navigator.geolocation only if it fails. // it defensively and fall back to navigator.geolocation only if it fails.
try { try {
const pos = await safeCall(() => const pos = await safeCall(() =>
native.getCurrentPosition({ enableHighAccuracy: false, timeout: 15000, maximumAge: 60000 }), native.getCurrentPosition({ enableHighAccuracy: false, timeout: 8000, maximumAge: 60000 }),
); );
const lat = pos?.coords?.latitude; const lat = pos?.coords?.latitude;
const lng = pos?.coords?.longitude; const lng = pos?.coords?.longitude;
@ -340,7 +340,7 @@ function getWebPosition(): Promise<GeoCoords> {
settled = true; settled = true;
cleanup(); cleanup();
reject(new GeolocationError('timeout', 'Location request timed out.')); reject(new GeolocationError('timeout', 'Location request timed out.'));
}, 25000); }, 15000);
// 1) watchPosition — first update wins (fast network fix). // 1) watchPosition — first update wins (fast network fix).
try { try {

View file

@ -804,13 +804,29 @@ export default function MapPage() {
useEffect(() => { useEffect(() => {
if (isNativeGeolocation()) return; if (isNativeGeolocation()) return;
let cancelled = false; let cancelled = false;
getCurrentPosition()
.then(({ lat, lng }) => { const warm = () =>
if (!cancelled) setUserLocation({ lat, lon: lng }); getCurrentPosition()
}) .then(({ lat, lng }) => {
.catch(() => { if (!cancelled) setUserLocation({ lat, lon: lng });
// Silent — user hasn't asked to be located yet. })
}); .catch(() => {
// Silent — user hasn't asked to be located yet.
});
// Only warm up when permission is ALREADY granted — otherwise this fires the
// browser's "Allow location?" prompt the moment someone opens /map.
if (typeof navigator !== 'undefined' && navigator.permissions?.query) {
navigator.permissions
.query({ name: 'geolocation' as PermissionName })
.then((status) => {
if (!cancelled && status.state === 'granted') warm();
})
.catch(() => {
/* permissions API unavailable — don't prompt */
});
}
return () => { return () => {
cancelled = true; cancelled = true;
}; };
@ -1432,6 +1448,29 @@ export default function MapPage() {
compact compact
/> />
{/* Near Me — desktop parity (mobile has the floating control) */}
<Button
variant="outline"
size="sm"
onClick={() => {
if (nearMeError === 'denied' && !isNativeGeolocation()) {
setShowLocationHelp(true);
return;
}
handleNearMe();
}}
disabled={nearMeLoading}
title={nearMeError === 'denied' ? 'Enable location access' : 'Find my location'}
className="hidden md:inline-flex h-8 gap-1.5 border-white/20 flex-shrink-0 touch-manipulation"
>
{nearMeError === 'denied' ? (
<LocateOff className="h-4 w-4 text-red-400" />
) : (
<LocateFixed className={cn('h-4 w-4', userLocation ? 'text-[#ccff00]' : 'text-gray-300')} />
)}
<span className="hidden lg:inline">Near Me</span>
</Button>
{/* Clear filters */} {/* Clear filters */}
{hasActiveFilters && ( {hasActiveFilters && (
<Button <Button
@ -1723,7 +1762,15 @@ export default function MapPage() {
> >
{/* Near Me */} {/* Near Me */}
<button <button
onClick={nearMeError === 'denied' ? () => setShowLocationHelp(true) : handleNearMe} onClick={() => {
// On native a denial is an OS permission the user can grant
// and retry; only the web needs the browser re-enable sheet.
if (nearMeError === 'denied' && !isNativeGeolocation()) {
setShowLocationHelp(true);
return;
}
handleNearMe();
}}
disabled={nearMeLoading} disabled={nearMeLoading}
className={cn( className={cn(
'h-11 w-11 rounded-full flex items-center justify-center shadow-lg border backdrop-blur transition-all touch-manipulation active:scale-90', 'h-11 w-11 rounded-full flex items-center justify-center shadow-lg border backdrop-blur transition-all touch-manipulation active:scale-90',