feat(pwa): add one-time install prompt for mobile devices

This commit is contained in:
Hördle Bot
2025-11-23 00:22:23 +01:00
parent 4b9e7ac9ec
commit e24588f3ee
2 changed files with 135 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
'use client';
import { useState, useEffect } from 'react';
export default function InstallPrompt() {
const [isIOS, setIsIOS] = useState(false);
const [isStandalone, setIsStandalone] = useState(false);
const [showPrompt, setShowPrompt] = useState(false);
const [deferredPrompt, setDeferredPrompt] = useState<any>(null);
useEffect(() => {
// Check if already in standalone mode
const isStandaloneMode = window.matchMedia('(display-mode: standalone)').matches || (window.navigator as any).standalone;
setIsStandalone(isStandaloneMode);
// Check if iOS
const userAgent = window.navigator.userAgent.toLowerCase();
const isIosDevice = /iphone|ipad|ipod/.test(userAgent);
setIsIOS(isIosDevice);
// Check if already dismissed
const isDismissed = localStorage.getItem('installPromptDismissed');
if (!isStandaloneMode && !isDismissed) {
if (isIosDevice) {
// Show prompt for iOS immediately if not dismissed
setShowPrompt(true);
} else {
// For Android/Desktop, wait for beforeinstallprompt
const handleBeforeInstallPrompt = (e: Event) => {
e.preventDefault();
setDeferredPrompt(e);
setShowPrompt(true);
};
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
};
}
}
}, []);
const handleInstallClick = async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
setDeferredPrompt(null);
setShowPrompt(false);
}
}
};
const handleDismiss = () => {
setShowPrompt(false);
localStorage.setItem('installPromptDismissed', 'true');
};
if (!showPrompt) return null;
return (
<div style={{
position: 'fixed',
bottom: '20px',
left: '20px',
right: '20px',
background: 'rgba(255, 255, 255, 0.95)',
backdropFilter: 'blur(10px)',
padding: '1rem',
borderRadius: '1rem',
boxShadow: '0 10px 25px rgba(0,0,0,0.2)',
zIndex: 1000,
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
border: '1px solid rgba(0,0,0,0.1)',
animation: 'slideUp 0.5s ease-out'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start' }}>
<div>
<h3 style={{ fontWeight: 'bold', fontSize: '1rem', marginBottom: '0.25rem' }}>Install Hördle App</h3>
<p style={{ fontSize: '0.875rem', color: '#666' }}>
Install the app for a better experience and quick access!
</p>
</div>
<button
onClick={handleDismiss}
style={{
background: 'none',
border: 'none',
fontSize: '1.25rem',
cursor: 'pointer',
padding: '0.25rem',
color: '#999'
}}
>
×
</button>
</div>
{isIOS ? (
<div style={{ fontSize: '0.875rem', background: '#f3f4f6', padding: '0.75rem', borderRadius: '0.5rem', marginTop: '0.5rem' }}>
Tap <span style={{ fontSize: '1.2rem' }}>share</span> then "Add to Home Screen" <span style={{ fontSize: '1.2rem' }}>+</span>
</div>
) : (
<button
onClick={handleInstallClick}
style={{
background: '#4f46e5',
color: 'white',
border: 'none',
padding: '0.75rem',
borderRadius: '0.5rem',
fontWeight: 'bold',
cursor: 'pointer',
marginTop: '0.5rem'
}}
>
Install App
</button>
)}
<style jsx>{`
@keyframes slideUp {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
`}</style>
</div>
);
}