import { useState, useEffect, useCallback } from "react"; interface BeforeInstallPromptEvent extends Event { prompt: () => Promise; userChoice: Promise<{ outcome: "accepted" | "dismissed" }>; } const DISMISSED_KEY = "dumpster-pwa-install-dismissed"; export function InstallBanner() { const [deferredPrompt, setDeferredPrompt] = useState(null); const [visible, setVisible] = useState(false); const [isIOS, setIsIOS] = useState(false); useEffect(() => { // Check if already dismissed if (localStorage.getItem(DISMISSED_KEY)) return; // Detect iOS (no beforeinstallprompt) const ua = window.navigator.userAgent; const isIOSDevice = /iPad|iPhone|iPod/.test(ua) || (ua.includes("Mac") && "ontouchend" in window); const isStandalone = window.matchMedia("(display-mode: standalone)").matches; if (isStandalone) return; // Already installed if (isIOSDevice && !isStandalone) { setIsIOS(true); // Show iOS instructions after a short delay const timer = setTimeout(() => setVisible(true), 3000); return () => clearTimeout(timer); } const handler = (e: Event) => { e.preventDefault(); setDeferredPrompt(e as BeforeInstallPromptEvent); // Show banner after a short delay so it doesn't clash with page load setTimeout(() => setVisible(true), 2000); }; window.addEventListener("beforeinstallprompt", handler); return () => window.removeEventListener("beforeinstallprompt", handler); }, []); const handleInstall = useCallback(async () => { if (!deferredPrompt) return; await deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice; if (outcome === "accepted") { setVisible(false); } setDeferredPrompt(null); }, [deferredPrompt]); const handleDismiss = useCallback(() => { setVisible(false); localStorage.setItem(DISMISSED_KEY, "1"); }, []); if (!visible) return null; return (
Install Dumpster
{isIOS ? (
Tap Share then{" "} Add to Home Screen
) : (
Add to home screen for the full app experience
)}
{!isIOS && deferredPrompt && ( )}
); }