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 <noreply@anthropic.com>
This commit is contained in:
parent
a1132371f4
commit
53dd95fcac
|
|
@ -48,6 +48,7 @@ export default function RoomPage() {
|
||||||
const [zoomToLocation, setZoomToLocation] = useState<{ latitude: number; longitude: number } | null>(null);
|
const [zoomToLocation, setZoomToLocation] = useState<{ latitude: number; longitude: number } | null>(null);
|
||||||
const [needsJoin, setNeedsJoin] = useState(false);
|
const [needsJoin, setNeedsJoin] = useState(false);
|
||||||
const [pendingManualPing, setPendingManualPing] = useState(false);
|
const [pendingManualPing, setPendingManualPing] = useState(false);
|
||||||
|
const [pingToast, setPingToast] = useState<string | null>(null);
|
||||||
|
|
||||||
// Check if opened from a manual ping notification (e.g. ?ping=manual&pinger=Alice)
|
// Check if opened from a manual ping notification (e.g. ?ping=manual&pinger=Alice)
|
||||||
const [pingCallerName, setPingCallerName] = useState<string | undefined>(undefined);
|
const [pingCallerName, setPingCallerName] = useState<string | undefined>(undefined);
|
||||||
|
|
@ -154,7 +155,27 @@ export default function RoomPage() {
|
||||||
|
|
||||||
// Handle manual ping: vibrate device + one-shot GPS regardless of sharing state
|
// Handle manual ping: vibrate device + one-shot GPS regardless of sharing state
|
||||||
const handleManualPing = useCallback((callerName?: string) => {
|
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) {
|
if (typeof navigator !== 'undefined' && 'vibrate' in navigator) {
|
||||||
navigator.vibrate([200, 100, 200, 100, 400]);
|
navigator.vibrate([200, 100, 200, 100, 400]);
|
||||||
}
|
}
|
||||||
|
|
@ -496,6 +517,13 @@ export default function RoomPage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Ping toast notification */}
|
||||||
|
{pingToast && (
|
||||||
|
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-emerald-500/90 text-white text-sm px-4 py-2 rounded-full z-30 shadow-lg animate-pulse">
|
||||||
|
{pingToast}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Connection status indicator */}
|
{/* Connection status indicator */}
|
||||||
{!isConnected && (
|
{!isConnected && (
|
||||||
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-yellow-500/90 text-black text-sm px-3 py-1.5 rounded-full z-30">
|
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-yellow-500/90 text-black text-sm px-3 py-1.5 rounded-full z-30">
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ export default function HomePage() {
|
||||||
const [lastRoom, setLastRoom] = useState<string | null>(null);
|
const [lastRoom, setLastRoom] = useState<string | null>(null);
|
||||||
|
|
||||||
// Load saved user info from localStorage on mount
|
// Load saved user info from localStorage on mount
|
||||||
|
// If opened as installed PWA (standalone mode), auto-redirect to last room
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let loadedEmoji = '';
|
let loadedEmoji = '';
|
||||||
try {
|
try {
|
||||||
|
|
@ -43,6 +44,14 @@ export default function HomePage() {
|
||||||
const lastVisited = localStorage.getItem('rmaps_last_room');
|
const lastVisited = localStorage.getItem('rmaps_last_room');
|
||||||
if (lastVisited) {
|
if (lastVisited) {
|
||||||
setLastRoom(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 {
|
} catch {
|
||||||
// Ignore parse errors
|
// Ignore parse errors
|
||||||
|
|
@ -52,7 +61,7 @@ export default function HomePage() {
|
||||||
setEmoji(EMOJI_OPTIONS[Math.floor(Math.random() * EMOJI_OPTIONS.length)]);
|
setEmoji(EMOJI_OPTIONS[Math.floor(Math.random() * EMOJI_OPTIONS.length)]);
|
||||||
}
|
}
|
||||||
setIsLoaded(true);
|
setIsLoaded(true);
|
||||||
}, []);
|
}, [router]);
|
||||||
|
|
||||||
// Auto-fill name from EncryptID when authenticated
|
// Auto-fill name from EncryptID when authenticated
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue