import { useEffect, useState } from 'react'; import { useI18n } from '../i18n/I18nProvider'; import { OFFER_EVENT, canInstall, dismissInstall, dismissed, isInstalled, isIosSafari, promptInstall, subscribe, } from '../pwa/install'; // The install offer, and the only dialog the app opens without being asked. It waits // for the browser to say the app is installable, and then for the visitor to have // stayed long enough to want it — a prompt on the way in is a prompt nobody reads. // Markup and classes are the auth dialog's (see AuthModal.tsx); there is nothing new // to draw here. // // Long enough to have opened a photo and worked on it, short enough that the offer // still lands in the visit that earned it. const DELAY_MS = 120_000; export function InstallPrompt() { const { t } = useI18n(); // 'manual' is Safari: no install event exists there, so the only thing to show is // where the instruction lives. const [face, setFace] = useState<'native' | 'manual' | null>(null); useEffect(() => { // An installed app has nothing to offer, and a refusal is final — the visitor // said no to this dialog, not to this session. if (isInstalled() || dismissed()) return; const manual = isIosSafari(); let waited = false; let asked = false; let shown = false; // Both ways in have to agree: installability (or Safari, which never fires the // event) and either the delay running out or something in the app asking for the // offer — a Library button, say. const maybeShow = () => { if (shown || (!waited && !asked)) return; if (manual) { shown = true; setFace('manual'); } else if (canInstall()) { shown = true; setFace('native'); } }; const onOffer = () => { asked = true; maybeShow(); }; window.addEventListener(OFFER_EVENT, onOffer); const unsubscribe = subscribe(maybeShow); const timer = window.setTimeout(() => { waited = true; maybeShow(); }, DELAY_MS); return () => { window.clearTimeout(timer); window.removeEventListener(OFFER_EVENT, onOffer); unsubscribe(); }; }, []); if (!face) return null; const close = () => { dismissInstall(); setFace(null); }; return (
{t('install.body')}
{face === 'manual' ?{t('install.ios')}
: null} {face === 'native' ? ( ) : null}