diff --git a/src/components/map/DualMapView.tsx b/src/components/map/DualMapView.tsx index 205b9a1..89be594 100644 --- a/src/components/map/DualMapView.tsx +++ b/src/components/map/DualMapView.tsx @@ -209,38 +209,11 @@ export default function DualMapView({ )} - {/* Indoor Map button - switch to indoor view */} - {activeView === 'outdoor' && ( - - )} - - {/* Auto-mode indicator */} - {mode === 'auto' && ( -
- Auto-detecting location -
- )} {/* Venue proximity indicator */} {currentLocation && isInC3NavArea(currentLocation.latitude, currentLocation.longitude) && activeView === 'outdoor' && ( -
+
You're at the venue! -
)}
diff --git a/src/components/map/MapView.tsx b/src/components/map/MapView.tsx index f16c4ce..b1b7c5a 100644 --- a/src/components/map/MapView.tsx +++ b/src/components/map/MapView.tsx @@ -57,6 +57,12 @@ function isValidCoordinate(lat: number, lng: number): boolean { ); } +// Check if a participant's location is stale (older than 5 minutes) +const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes +function isLocationStale(lastSeen: number): boolean { + return Date.now() - lastSeen > STALE_THRESHOLD_MS; +} + export default function MapView({ participants, waypoints = [], @@ -202,15 +208,36 @@ export default function MapView({ let marker = currentMarkers.get(participant.id); if (marker) { - // Update existing marker position + // Update existing marker position and stale status marker.setLngLat([longitude, latitude]); + const el = marker.getElement(); + const isStale = isLocationStale(participant.lastSeen.getTime()); + if (isStale) { + el.style.backgroundColor = '#6b7280'; + el.style.opacity = '0.6'; + el.title = `${participant.name} - last seen ${Math.round((Date.now() - participant.lastSeen.getTime()) / 60000)} min ago`; + } else { + el.style.backgroundColor = participant.color; + el.style.opacity = '1'; + el.title = participant.name; + } } else { // Create new marker const el = document.createElement('div'); el.className = 'friend-marker'; - el.style.backgroundColor = participant.color; el.innerHTML = participant.emoji; + const isStale = isLocationStale(participant.lastSeen.getTime()); + + if (isStale) { + // Grey out stale locations + el.style.backgroundColor = '#6b7280'; // gray-500 + el.style.opacity = '0.6'; + el.title = `${participant.name} - last seen ${Math.round((Date.now() - participant.lastSeen.getTime()) / 60000)} min ago`; + } else { + el.style.backgroundColor = participant.color; + } + if (participant.id === currentUserId) { el.classList.add('sharing'); } diff --git a/sync-server/server.js b/sync-server/server.js index 3112be2..cc6cd88 100644 --- a/sync-server/server.js +++ b/sync-server/server.js @@ -485,15 +485,34 @@ const server = createServer(async (req, res) => { let pushSent = 0; let pushFailed = 0; - // First, send WebSocket message to all connected clients in the room + // Get room to access participant names for deduplication + const room = rooms.get(roomSlug); + + // First, send WebSocket message to connected clients, deduplicated by name const locationRequestMsg = JSON.stringify({ type: 'request_location' }); + const pingedNames = new Set(); + for (const [ws, clientInfo] of clients.entries()) { if (clientInfo.roomSlug === roomSlug && ws.readyState === 1) { + // Get participant name for this client + const participant = room?.participants?.[clientInfo.participantId]; + const name = participant?.name; + + // Skip if we've already pinged this name + if (name && pingedNames.has(name)) { + console.log(`[${roomSlug}] Skipping duplicate ping for: ${name}`); + continue; + } + ws.send(locationRequestMsg); wsSent++; + + if (name) { + pingedNames.add(name); + } } } - console.log(`[${roomSlug}] Location request via WebSocket: ${wsSent} clients`); + console.log(`[${roomSlug}] Location request via WebSocket: ${wsSent} clients (deduped by name)`); // Then, send push notifications to offline subscribers const subs = pushSubscriptions.get(roomSlug);