Make server the single source of truth for active location

Two devices on the same account each kept their own localStorage location and
re-asserted it onto Feeld's single shared account-location slot on every Discover
mount — so loading the site on device B would overwrite the location device A
had just set (San Antonio → Dallas).

- useLocation: on server sync, always adopt the server's current/custom location,
  overriding this device's local cache (was local-first via a `!location` guard).
  Devices now converge to the server value on load. Expose `locationReady`.
- Discover: wait for `locationReady` before pushing DeviceLocationUpdate, and push
  whenever the authoritative location changes (deduped by coords) instead of a
  one-shot stale-cache assert. Device B now stamps the server's value onto Feeld,
  not its own old location.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-06-07 10:46:43 -05:00
parent bb5c6d3bd5
commit 8b3e3eb839
2 changed files with 35 additions and 26 deletions
+16 -12
View File
@@ -43,6 +43,10 @@ export interface LocationState {
interface LocationContextType {
location: LocationState | null;
savedLocations: SavedLocation[];
// True once the initial sync from the server (the source of truth) has completed.
// Consumers that push the location onward (e.g. Discover → Feeld) should wait for this
// so they assert the server's authoritative value, not this device's stale cache.
locationReady: boolean;
// Throws BannedCountryError if the LocationState has a banned country/countryCode.
setLocation: (location: LocationState | null) => void;
// Throws BannedCountryError if the country is on the Feeld-restricted list.
@@ -106,18 +110,17 @@ export function LocationProvider({ children }: { children: ReactNode }) {
});
}
// Restore current location if we don't have one
const cleanCurrent = sanitizeCurrent(serverData.currentLocation);
if (cleanCurrent && !location) {
setLocationState(cleanCurrent);
localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(cleanCurrent));
}
// Restore custom location if we don't have one
const cleanCustom = sanitizeCurrent(serverData.customLocation);
if (cleanCustom && !location) {
setLocationState(cleanCustom);
localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(cleanCustom));
// The server is the single source of truth for the active location.
// Adopt whatever it currently holds (prefer currentLocation, fall back to
// customLocation), OVERRIDING this device's local cache — so two devices on
// the same account always converge to the same place instead of each
// re-asserting its own stale copy. Only keep local if the server has none.
const serverActive =
sanitizeCurrent(serverData.currentLocation) ||
sanitizeCurrent(serverData.customLocation);
if (serverActive) {
setLocationState(serverActive);
localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(serverActive));
}
}
} catch (e) {
@@ -199,6 +202,7 @@ export function LocationProvider({ children }: { children: ReactNode }) {
value={{
location,
savedLocations,
locationReady: hasSyncedFromServer,
setLocation,
saveLocation,
deleteLocation,
+19 -14
View File
@@ -291,24 +291,29 @@ export function DiscoverPage() {
});
const initialLoadDone = useRef(false);
const savedLikedMeProfiles = useRef<Set<string>>(new Set());
const { location } = useLocation();
const { location, locationReady } = useLocation();
const { isFavorite, toggleFavorite } = useFavorites();
// Set device location on Feeld when Discover page loads (emulates app open GPS)
// Push the location to Feeld whenever the authoritative location changes.
// Gate on `locationReady` so we wait for the server (the source of truth) to load
// first — otherwise this device would stamp its own stale localStorage location onto
// Feeld's single shared account-location slot, overriding what another device just set.
// Dedupe by coordinates so we don't re-push the same place on every re-render.
const [updateDeviceLocation] = useMutation(DEVICE_LOCATION_UPDATE_MUTATION);
const locationSetRef = useRef(false);
const lastPushedRef = useRef<string | null>(null);
useEffect(() => {
if (location && !locationSetRef.current) {
locationSetRef.current = true;
updateDeviceLocation({
variables: { input: { latitude: location.latitude, longitude: location.longitude } },
}).then(() => {
console.log(`[Discover] Location set to ${location.name || `${location.latitude},${location.longitude}`}`);
}).catch((err) => {
console.error('[Discover] Failed to set location:', err);
});
}
}, [location, updateDeviceLocation]);
if (!locationReady || !location) return;
const coordKey = `${location.latitude},${location.longitude}`;
if (lastPushedRef.current === coordKey) return;
lastPushedRef.current = coordKey;
updateDeviceLocation({
variables: { input: { latitude: location.latitude, longitude: location.longitude } },
}).then(() => {
console.log(`[Discover] Location set to ${location.name || coordKey}`);
}).catch((err) => {
console.error('[Discover] Failed to set location:', err);
});
}, [locationReady, location, updateDeviceLocation]);
// Fetch rotation location (what the cron actually set on Feeld)
const [rotationLocation, setRotationLocation] = useState<string | null>(null);