feat: Grey out stale locations, remove indoor map, dedupe pings
1. Stale locations (>5 min old) are now greyed out on the map with reduced opacity and hover tooltip showing time since last seen 2. Removed indoor map button and related UI elements 3. Location request pings now deduplicate by participant name to avoid pinging the same user multiple times 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
007a7e877f
commit
ff45193ba2
|
|
@ -209,38 +209,11 @@ export default function DualMapView({
|
|||
</button>
|
||||
)}
|
||||
|
||||
{/* Indoor Map button - switch to indoor view */}
|
||||
{activeView === 'outdoor' && (
|
||||
<button
|
||||
onClick={goIndoor}
|
||||
className="absolute bottom-4 left-4 bg-rmaps-dark/90 text-white px-3 py-2 rounded-lg text-sm hover:bg-rmaps-dark transition-colors flex items-center gap-2 z-30"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
|
||||
/>
|
||||
</svg>
|
||||
Indoor Map
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Auto-mode indicator */}
|
||||
{mode === 'auto' && (
|
||||
<div className="absolute top-4 left-4 bg-rmaps-primary/20 text-rmaps-primary text-xs px-2 py-1 rounded-full">
|
||||
Auto-detecting location
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Venue proximity indicator */}
|
||||
{currentLocation && isInC3NavArea(currentLocation.latitude, currentLocation.longitude) && activeView === 'outdoor' && (
|
||||
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-rmaps-secondary/90 text-white text-sm px-3 py-1.5 rounded-full flex items-center gap-2 z-30">
|
||||
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-rmaps-secondary/90 text-white text-sm px-3 py-1.5 rounded-full z-30">
|
||||
<span>You're at the venue!</span>
|
||||
<button onClick={goIndoor} className="underline">
|
||||
Switch to indoor
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue