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:
@@ -43,6 +43,10 @@ export interface LocationState {
|
|||||||
interface LocationContextType {
|
interface LocationContextType {
|
||||||
location: LocationState | null;
|
location: LocationState | null;
|
||||||
savedLocations: SavedLocation[];
|
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.
|
// Throws BannedCountryError if the LocationState has a banned country/countryCode.
|
||||||
setLocation: (location: LocationState | null) => void;
|
setLocation: (location: LocationState | null) => void;
|
||||||
// Throws BannedCountryError if the country is on the Feeld-restricted list.
|
// 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
|
// The server is the single source of truth for the active location.
|
||||||
const cleanCurrent = sanitizeCurrent(serverData.currentLocation);
|
// Adopt whatever it currently holds (prefer currentLocation, fall back to
|
||||||
if (cleanCurrent && !location) {
|
// customLocation), OVERRIDING this device's local cache — so two devices on
|
||||||
setLocationState(cleanCurrent);
|
// the same account always converge to the same place instead of each
|
||||||
localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(cleanCurrent));
|
// re-asserting its own stale copy. Only keep local if the server has none.
|
||||||
}
|
const serverActive =
|
||||||
|
sanitizeCurrent(serverData.currentLocation) ||
|
||||||
// Restore custom location if we don't have one
|
sanitizeCurrent(serverData.customLocation);
|
||||||
const cleanCustom = sanitizeCurrent(serverData.customLocation);
|
if (serverActive) {
|
||||||
if (cleanCustom && !location) {
|
setLocationState(serverActive);
|
||||||
setLocationState(cleanCustom);
|
localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(serverActive));
|
||||||
localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(cleanCustom));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -199,6 +202,7 @@ export function LocationProvider({ children }: { children: ReactNode }) {
|
|||||||
value={{
|
value={{
|
||||||
location,
|
location,
|
||||||
savedLocations,
|
savedLocations,
|
||||||
|
locationReady: hasSyncedFromServer,
|
||||||
setLocation,
|
setLocation,
|
||||||
saveLocation,
|
saveLocation,
|
||||||
deleteLocation,
|
deleteLocation,
|
||||||
|
|||||||
@@ -291,24 +291,29 @@ export function DiscoverPage() {
|
|||||||
});
|
});
|
||||||
const initialLoadDone = useRef(false);
|
const initialLoadDone = useRef(false);
|
||||||
const savedLikedMeProfiles = useRef<Set<string>>(new Set());
|
const savedLikedMeProfiles = useRef<Set<string>>(new Set());
|
||||||
const { location } = useLocation();
|
const { location, locationReady } = useLocation();
|
||||||
const { isFavorite, toggleFavorite } = useFavorites();
|
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 [updateDeviceLocation] = useMutation(DEVICE_LOCATION_UPDATE_MUTATION);
|
||||||
const locationSetRef = useRef(false);
|
const lastPushedRef = useRef<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location && !locationSetRef.current) {
|
if (!locationReady || !location) return;
|
||||||
locationSetRef.current = true;
|
const coordKey = `${location.latitude},${location.longitude}`;
|
||||||
|
if (lastPushedRef.current === coordKey) return;
|
||||||
|
lastPushedRef.current = coordKey;
|
||||||
updateDeviceLocation({
|
updateDeviceLocation({
|
||||||
variables: { input: { latitude: location.latitude, longitude: location.longitude } },
|
variables: { input: { latitude: location.latitude, longitude: location.longitude } },
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
console.log(`[Discover] Location set to ${location.name || `${location.latitude},${location.longitude}`}`);
|
console.log(`[Discover] Location set to ${location.name || coordKey}`);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error('[Discover] Failed to set location:', err);
|
console.error('[Discover] Failed to set location:', err);
|
||||||
});
|
});
|
||||||
}
|
}, [locationReady, location, updateDeviceLocation]);
|
||||||
}, [location, updateDeviceLocation]);
|
|
||||||
|
|
||||||
// Fetch rotation location (what the cron actually set on Feeld)
|
// Fetch rotation location (what the cron actually set on Feeld)
|
||||||
const [rotationLocation, setRotationLocation] = useState<string | null>(null);
|
const [rotationLocation, setRotationLocation] = useState<string | null>(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user