+ {images.map((url) => (
+
-
- {/* Address (required) — the human-readable place name. */}
-
-
-
{ setLocation(e.target.value); clearError('address'); }}
- className={cn(errors.address && 'border-red-500/60')}
- />
- {errors.address &&
{errors.address}
}
-
-
- {/* Duplicate warning — appears as soon as the pin lands, right
- below the map, instead of only on submit. */}
- {dupWarning && (
-
-
- {t('spotUi.spotMayAlreadyExist')}
-
-
- {dupWarning.length === 1
- ? t('spotUi.duplicateOne')
- : t('spotUi.duplicateOther')}
-
-
- {dupWarning.map(({ spot, km }) => (
- -
-
- {spot.title || t('spotUi.untitledSpot')} · {t('spotUi.metresAway', { distance: Math.round(km * 1000) })}
-
-
- ))}
-
-
-
-
-
- )}
-
- {/* ── Title ─────────────────────────────────────────────── */}
-
-
-
{ setTitle(e.target.value); clearError('title'); }}
- className={cn(errors.title && 'border-red-500/60')}
+ ))}
+
- {errors.title &&
{errors.title}
}
-
- {/* ── Description ───────────────────────────────────────── */}
-
-
-
);
}
diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx
index b8ca544..6938efe 100644
--- a/src/pages/MapPage.tsx
+++ b/src/pages/MapPage.tsx
@@ -1,4 +1,4 @@
-import { useState, useMemo, useEffect, lazy, Suspense, useCallback } from 'react';
+import { useState, useMemo, useEffect, useRef, lazy, Suspense, useCallback } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
@@ -94,7 +94,7 @@ import { SpotChat } from '@/components/SpotChat';
import { useToast } from '@/hooks/useToast';
import { cn } from '@/lib/utils';
import { getCurrentPosition, GeolocationError, isNativeGeolocation } from '@/lib/geolocation';
-import { getLastLocation } from '@/lib/lastLocation';
+import { getLastLocation, getLastMapView, saveLastMapView } from '@/lib/lastLocation';
type ViewMode = 'map' | 'list';
@@ -644,7 +644,7 @@ export default function MapPage() {
const cached = getLastLocation();
return cached ? { lat: cached.lat, lon: cached.lng } : null;
});
-
+
// Map entity type filters (state lives above the queries so hidden layers
// don't open relay requests).
const [showBtcMap, setShowBtcMap] = useState(false);
@@ -680,24 +680,14 @@ export default function MapPage() {
const [initialView] = useState(() => {
// Restore the last map view first; a brand-new device falls back to the
// cached location, then to the world view.
- try {
- const raw = localStorage.getItem('skatespots:map-view');
- const saved = raw ? JSON.parse(raw) as { lat?: unknown; lon?: unknown; zoom?: unknown } : null;
- if (
- saved &&
- typeof saved.lat === 'number' && Number.isFinite(saved.lat) &&
- typeof saved.lon === 'number' && Number.isFinite(saved.lon) &&
- typeof saved.zoom === 'number' && Number.isFinite(saved.zoom)
- ) {
- return {
- center: [saved.lat, saved.lon] as [number, number],
- zoom: Math.min(18, Math.max(1, saved.zoom)),
- lat: saved.lat,
- lon: saved.lon,
- };
- }
- } catch {
- // fall through to the location/world default
+ const saved = getLastMapView();
+ if (saved) {
+ return {
+ center: [saved.lat, saved.lon] as [number, number],
+ zoom: Math.min(18, Math.max(1, saved.zoom)),
+ lat: saved.lat,
+ lon: saved.lon,
+ };
}
const cached = getLastLocation();
return cached
@@ -715,19 +705,32 @@ export default function MapPage() {
});
const handleMapMove = (center: { lat: number; lon: number; zoom?: number }) => {
setMapCenter({ lat: center.lat, lon: center.lon });
- try {
- localStorage.setItem('skatespots:map-view', JSON.stringify(center));
- } catch {
- // storage unavailable — map still works
- }
+ saveLastMapView(center.lat, center.lon, center.zoom ?? 12);
};
+ // Every Add Spot entry point should carry the place the user is already
+ // looking at; otherwise the form has to guess with a stale/default location.
+ const createSpotHref = `/create-spot?lat=${mapCenter.lat.toFixed(6)}&lng=${mapCenter.lon.toFixed(6)}`;
+
// Near Me fly-to state
const [flyTo, setFlyTo] = useState<{ lat: number; lng: number; zoom?: number } | null>(null);
const [nearMeLoading, setNearMeLoading] = useState(false);
const [nearMeError, setNearMeError] = useState<'denied' | 'unavailable' | 'timeout' | null>(null);
const [showLocationHelp, setShowLocationHelp] = useState(false);
+ // Do not let a second account inherit the previous account's live-location
+ // dot/sort state when accounts are switched without a reload.
+ const lastUserPubkeyRef = useRef(user?.pubkey ?? null);
+ useEffect(() => {
+ const next = user?.pubkey ?? null;
+ if (lastUserPubkeyRef.current && lastUserPubkeyRef.current !== next) {
+ setUserLocation(null);
+ setNearMeError(null);
+ setSortBy('recent');
+ }
+ lastUserPubkeyRef.current = next;
+ }, [user?.pubkey]);
+
const handleNearMe = async () => {
@@ -957,6 +960,7 @@ export default function MapPage() {
// Drop a pin and ask first — do NOT navigate immediately. The user confirms
// with the card below, which is the only thing that opens the create form.
setSelectedSpot(null);
+ setAddDialOpen(false);
setPendingAddPin({ lat, lng });
};
@@ -1695,7 +1699,7 @@ export default function MapPage() {
{user ? (
<>
{/* Primary action: add a spot (this is a map, after all) */}
-
+