From 53dd95fcacf7af298dc23a53059efdd786e7b3d9 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 19 Feb 2026 00:38:47 +0000 Subject: [PATCH] PWA auto-opens last room + visible ping notifications 1. Installed PWA (standalone mode) auto-redirects to last visited room on launch instead of showing the landing page. Uses localStorage rmaps_last_room + display-mode: standalone media query. 2. Manual pings now show visible feedback in three ways: - In-app toast: green banner "Alice pinged you for your location!" - Browser notification: fires Notification API when permission granted - Vibration: unchanged [200, 100, 200, 100, 400] pattern Co-Authored-By: Claude Opus 4.6 --- src/app/[slug]/page.tsx | 30 +++++++++++++++++++++++++++++- src/app/page.tsx | 11 ++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index b8d41ad..b44a03f 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -48,6 +48,7 @@ export default function RoomPage() { const [zoomToLocation, setZoomToLocation] = useState<{ latitude: number; longitude: number } | null>(null); const [needsJoin, setNeedsJoin] = useState(false); const [pendingManualPing, setPendingManualPing] = useState(false); + const [pingToast, setPingToast] = useState(null); // Check if opened from a manual ping notification (e.g. ?ping=manual&pinger=Alice) const [pingCallerName, setPingCallerName] = useState(undefined); @@ -154,7 +155,27 @@ export default function RoomPage() { // Handle manual ping: vibrate device + one-shot GPS regardless of sharing state const handleManualPing = useCallback((callerName?: string) => { - console.log(`Manual ping from ${callerName || 'unknown'} — vibrating and sending GPS`); + const pinger = callerName || 'Someone'; + console.log(`Manual ping from ${pinger} — vibrating and sending GPS`); + + // Show in-app toast + setPingToast(`📍 ${pinger} pinged you for your location!`); + setTimeout(() => setPingToast(null), 4000); + + // Show browser notification (works even when tab is backgrounded) + if (typeof Notification !== 'undefined' && Notification.permission === 'granted') { + try { + new Notification(`📍 ${pinger} pinged you!`, { + body: 'Sharing your location now...', + icon: '/icon-192.png', + tag: 'manual-ping', + silent: true, // vibration handles the alert + }); + } catch { + // Ignore notification errors (e.g. in standalone PWA) + } + } + if (typeof navigator !== 'undefined' && 'vibrate' in navigator) { navigator.vibrate([200, 100, 200, 100, 400]); } @@ -496,6 +517,13 @@ export default function RoomPage() { )} + {/* Ping toast notification */} + {pingToast && ( +
+ {pingToast} +
+ )} + {/* Connection status indicator */} {!isConnected && (
diff --git a/src/app/page.tsx b/src/app/page.tsx index 0a5a5d8..5b82e73 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -27,6 +27,7 @@ export default function HomePage() { const [lastRoom, setLastRoom] = useState(null); // Load saved user info from localStorage on mount + // If opened as installed PWA (standalone mode), auto-redirect to last room useEffect(() => { let loadedEmoji = ''; try { @@ -43,6 +44,14 @@ export default function HomePage() { const lastVisited = localStorage.getItem('rmaps_last_room'); if (lastVisited) { setLastRoom(lastVisited); + + // Auto-redirect if running as installed PWA and user has saved info + const isStandalone = window.matchMedia('(display-mode: standalone)').matches + || (navigator as unknown as { standalone?: boolean }).standalone === true; + if (isStandalone && stored) { + router.push(`/${lastVisited}`); + return; + } } } catch { // Ignore parse errors @@ -52,7 +61,7 @@ export default function HomePage() { setEmoji(EMOJI_OPTIONS[Math.floor(Math.random() * EMOJI_OPTIONS.length)]); } setIsLoaded(true); - }, []); + }, [router]); // Auto-fill name from EncryptID when authenticated useEffect(() => {