fix(pwa): guarantee mobile install banner visibility, capture early beforeinstallprompt in head, and provide install guide modal

This commit is contained in:
chrisf committed 2026-10-22 20:03:05 -06:00
1 parent 4d4b8c860d
commit 704d3b9eca
2 files changed
+62 -18

No files matched your search

+17
View File
@@ -42,6 +42,23 @@ export default function RootLayout({
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<script
dangerouslySetInnerHTML={{
__html: `
window.__everclone_install_prompt = null;
window.addEventListener('beforeinstallprompt', function(e) {
e.preventDefault();
window.__everclone_install_prompt = e;
window.dispatchEvent(new CustomEvent('everclone:installable', { detail: true }));
console.log('[Everclone PWA] Early beforeinstallprompt captured!');
});
window.addEventListener('appinstalled', function() {
window.__everclone_install_prompt = null;
window.dispatchEvent(new CustomEvent('everclone:installable', { detail: false }));
});
`,
}}
/>
</head> </head>
<body> <body>
<PwaRegister /> <PwaRegister />
+45 -18
View File
@@ -102,6 +102,7 @@ export default function EvercloneWorkspace() {
const [newUserEmail, setNewUserEmail] = useState(''); const [newUserEmail, setNewUserEmail] = useState('');
const [triageModalOpen, setTriageModalOpen] = useState(false); const [triageModalOpen, setTriageModalOpen] = useState(false);
const [canInstallPwa, setCanInstallPwa] = useState(false); const [canInstallPwa, setCanInstallPwa] = useState(false);
const [installHelpModalOpen, setInstallHelpModalOpen] = useState(false);
const [shareToast, setShareToast] = useState<string | null>(null); const [shareToast, setShareToast] = useState<string | null>(null);
const [historyStack, setHistoryStack] = useState<string[]>([]); const [historyStack, setHistoryStack] = useState<string[]>([]);
const [historyIndex, setHistoryIndex] = useState<number>(-1); const [historyIndex, setHistoryIndex] = useState<number>(-1);
@@ -122,12 +123,37 @@ export default function EvercloneWorkspace() {
// PWA In-App Installation Listener // PWA In-App Installation Listener
useEffect(() => { useEffect(() => {
if (typeof window === 'undefined') return; if (typeof window === 'undefined') return;
if (window.__everclone_install_prompt) {
setCanInstallPwa(true); const checkInstallState = () => {
} const isStandalone =
window.matchMedia('(display-mode: standalone)').matches ||
(navigator as unknown as { standalone?: boolean }).standalone === true;
if (isStandalone) {
setCanInstallPwa(false);
return;
}
const dismissed = localStorage.getItem('evc_dismiss_install');
if (dismissed === '1') {
if (window.__everclone_install_prompt) {
setCanInstallPwa(true);
}
return;
}
if (window.innerWidth < 768 || window.__everclone_install_prompt) {
setCanInstallPwa(true);
}
};
checkInstallState();
const onInstallable = (e: Event) => { const onInstallable = (e: Event) => {
const detail = (e as CustomEvent).detail; const detail = (e as CustomEvent).detail;
setCanInstallPwa(Boolean(detail)); if (detail) {
setCanInstallPwa(true);
}
}; };
window.addEventListener('everclone:installable', onInstallable); window.addEventListener('everclone:installable', onInstallable);
return () => window.removeEventListener('everclone:installable', onInstallable); return () => window.removeEventListener('everclone:installable', onInstallable);
@@ -135,21 +161,22 @@ export default function EvercloneWorkspace() {
const handleInstallPwa = async () => { const handleInstallPwa = async () => {
const promptEvent = window.__everclone_install_prompt; const promptEvent = window.__everclone_install_prompt;
if (!promptEvent) { if (promptEvent) {
alert("To install Everclone on Android, tap your browser's menu (⋮) and select 'Install app' or 'Add to Home screen'."); try {
return; await promptEvent.prompt();
} const choice = await promptEvent.userChoice;
try { if (choice.outcome === 'accepted') {
await promptEvent.prompt(); setCanInstallPwa(false);
const choice = await promptEvent.userChoice; window.__everclone_install_prompt = null;
if (choice.outcome === 'accepted') { setShareToast('Everclone installed! Native Share Target active.');
setCanInstallPwa(false); setTimeout(() => setShareToast(null), 5000);
window.__everclone_install_prompt = null; }
setShareToast('Everclone installed! You can now share directly to Everclone.'); } catch (err) {
setTimeout(() => setShareToast(null), 5000); console.error('Install prompt failed:', err);
setInstallHelpModalOpen(true);
} }
} catch (err) { } else {
console.error('Install prompt failed:', err); setInstallHelpModalOpen(true);
} }
}; };