// InstallPrompt.jsx // Floating "Install app" banner. Two flavors: // - Android/Chrome/Edge: uses the captured `beforeinstallprompt` event. // - iOS Safari: shows manual "Share → Add to Home Screen" instructions // (iOS doesn't expose a programmatic install API). function InstallPrompt() { const [installable, setInstallable] = React.useState(!!window.__deferredInstallPrompt); const [showIosHint, setShowIosHint] = React.useState(false); const [dismissed, setDismissed] = React.useState(() => { return localStorage.getItem('powlee-install-dismissed') === '1'; }); const [isStandalone, setIsStandalone] = React.useState(() => { return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true; }); React.useEffect(() => { const onAvail = () => setInstallable(true); const onInstalled = () => { setInstallable(false); setIsStandalone(true); }; window.addEventListener('pwa-installable', onAvail); window.addEventListener('pwa-installed', onInstalled); return () => { window.removeEventListener('pwa-installable', onAvail); window.removeEventListener('pwa-installed', onInstalled); }; }, []); const isIos = /iphone|ipad|ipod/i.test(navigator.userAgent) && !window.MSStream; // Already running as installed app — nothing to do. if (isStandalone) return null; // User dismissed the banner this session/profile. if (dismissed) return null; // Nothing to show: not installable AND not iOS. if (!installable && !isIos) return null; const handleInstall = async () => { if (window.__deferredInstallPrompt) { window.__deferredInstallPrompt.prompt(); const choice = await window.__deferredInstallPrompt.userChoice; if (choice && choice.outcome === 'accepted') { setInstallable(false); } window.__deferredInstallPrompt = null; } else if (isIos) { setShowIosHint(true); } }; const handleDismiss = () => { setDismissed(true); localStorage.setItem('powlee-install-dismissed', '1'); }; return ( <>