import { useCallback, useEffect, useState } from 'react' import { IconDownload } from '../icons/download.tsx' type DeferredPrompt = { prompt: () => Promise userChoice?: Promise } /** * Module-level capture so remounts cannot drop a preventDefault'd BIP * event (Chrome warns if preventDefault runs and prompt() never does). */ let capturedPrompt: DeferredPrompt | null = null let bipListenerBound = false const listeners = new Set<() => void>() function bindBeforeInstallPrompt() { if (typeof window === 'undefined' || bipListenerBound) return bipListenerBound = true window.addEventListener('beforeinstallprompt', (event) => { const promptFn = (event as unknown as DeferredPrompt).prompt if (typeof promptFn !== 'function' || !document.querySelector('[data-install]')) { return } event.preventDefault() capturedPrompt = event as unknown as DeferredPrompt for (const listener of listeners) listener() }) window.addEventListener('appinstalled', () => { capturedPrompt = null for (const listener of listeners) listener() }) } async function promptCapturedInstall() { const event = capturedPrompt if (typeof event?.prompt !== 'function') return false capturedPrompt = null try { await event.prompt() if (event.userChoice) await event.userChoice return true } catch { return false } } /** * Header install control (reserved slot) + optional iOS A2HS overlay. */ export function InstallCta({ appName }: { appName: string }) { const [standalone, setStandalone] = useState(false) const [ios, setIos] = useState(false) const [hintDismissed, setHintDismissed] = useState(false) const [iosHintOpen, setIosHintOpen] = useState(false) const [canNative, setCanNative] = useState(false) const [clientReady, setClientReady] = useState(false) const hintKey = '__PACKAGE_ID__-install-hint-dismissed' const detect = useCallback(() => { try { setStandalone( (navigator as { standalone?: boolean }).standalone === true || window.matchMedia( '(display-mode: standalone), (display-mode: fullscreen), (display-mode: minimal-ui)', ).matches, ) } catch { setStandalone(false) } setIos( /iphone|ipad|ipod/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && (navigator.maxTouchPoints || 0) > 1), ) try { setHintDismissed(localStorage.getItem(hintKey) !== null) } catch { /* ignore */ } setCanNative(Boolean(capturedPrompt?.prompt)) }, []) useEffect(() => { setClientReady(true) detect() bindBeforeInstallPrompt() const onChange = () => detect() listeners.add(onChange) return () => { listeners.delete(onChange) } }, [detect]) const showIos = clientReady && !standalone && ios && !hintDismissed && !canNative const installAvailable = canNative || showIos const tip = `Install ${appName}` return (
{showIos && iosHintOpen ? (

Add {appName} to your Home Screen: tap Share, then{' '} Add to Home Screen.

) : null}
) }