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="apple-mobile-web-app-capable" content="yes" />
<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>
<body>
<PwaRegister />
+45 -18
View File
@@ -102,6 +102,7 @@ export default function EvercloneWorkspace() {
const [newUserEmail, setNewUserEmail] = useState('');
const [triageModalOpen, setTriageModalOpen] = useState(false);
const [canInstallPwa, setCanInstallPwa] = useState(false);
const [installHelpModalOpen, setInstallHelpModalOpen] = useState(false);
const [shareToast, setShareToast] = useState<string | null>(null);
const [historyStack, setHistoryStack] = useState<string[]>([]);
const [historyIndex, setHistoryIndex] = useState<number>(-1);
@@ -122,12 +123,37 @@ export default function EvercloneWorkspace() {
// PWA In-App Installation Listener
useEffect(() => {
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 detail = (e as CustomEvent).detail;
setCanInstallPwa(Boolean(detail));
if (detail) {
setCanInstallPwa(true);
}
};
window.addEventListener('everclone:installable', onInstallable);
return () => window.removeEventListener('everclone:installable', onInstallable);
@@ -135,21 +161,22 @@ export default function EvercloneWorkspace() {
const handleInstallPwa = async () => {
const promptEvent = window.__everclone_install_prompt;
if (!promptEvent) {
alert("To install Everclone on Android, tap your browser's menu (⋮) and select 'Install app' or 'Add to Home screen'.");
return;
}
try {
await promptEvent.prompt();
const choice = await promptEvent.userChoice;
if (choice.outcome === 'accepted') {
setCanInstallPwa(false);
window.__everclone_install_prompt = null;
setShareToast('Everclone installed! You can now share directly to Everclone.');
setTimeout(() => setShareToast(null), 5000);
if (promptEvent) {
try {
await promptEvent.prompt();
const choice = await promptEvent.userChoice;
if (choice.outcome === 'accepted') {
setCanInstallPwa(false);
window.__everclone_install_prompt = null;
setShareToast('Everclone installed! Native Share Target active.');
setTimeout(() => setShareToast(null), 5000);
}
} catch (err) {
console.error('Install prompt failed:', err);
setInstallHelpModalOpen(true);
}
} catch (err) {
console.error('Install prompt failed:', err);
} else {
setInstallHelpModalOpen(true);
}
};