"use client"; import { useEffect, useState } from "react"; import { X, Share, Plus } from "lucide-react"; const DISMISSED_KEY = "pwa-install-dismissed"; type Prompt = "android" | "ios"; function isIos() { return ( /iphone|ipad|ipod/i.test(navigator.userAgent) || // iPad on iOS 13+ reports as Mac (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1) ); } function isInStandaloneMode() { return ( "standalone" in window.navigator && (window.navigator as { standalone?: boolean }).standalone === true ); } export function InstallPrompt() { const [prompt, setPrompt] = useState(null); // eslint-disable-next-line @typescript-eslint/no-explicit-any const [deferredEvent, setDeferredEvent] = useState(null); useEffect(() => { if (localStorage.getItem(DISMISSED_KEY)) return; // Android / Chrome desktop — beforeinstallprompt fires const handler = (e: Event) => { e.preventDefault(); setDeferredEvent(e); setPrompt("android"); }; window.addEventListener("beforeinstallprompt", handler); // iOS — no beforeinstallprompt; defer setState out of the synchronous // effect body to satisfy react-hooks/set-state-in-effect. let iosTimer: ReturnType | undefined; if (isIos() && !isInStandaloneMode()) { iosTimer = setTimeout(() => setPrompt("ios"), 0); } return () => { window.removeEventListener("beforeinstallprompt", handler); clearTimeout(iosTimer); }; }, []); function dismiss() { localStorage.setItem(DISMISSED_KEY, "1"); setPrompt(null); } async function install() { if (!deferredEvent) return; deferredEvent.prompt(); const { outcome } = await deferredEvent.userChoice; if (outcome === "accepted" || outcome === "dismissed") { dismiss(); } } if (!prompt) return null; return (
{/* App icon */}
{/* eslint-disable-next-line @next/next/no-img-element */}

Add famapp to your home screen

{prompt === "android" && ( <>

Install for a faster, app-like experience.

)} {prompt === "ios" && (

Tap{" "} {" "} then{" "}  Add to Home Screen .

)}
); }