Android PWA-Installationshinweis mit direktem Install-Button hinzugefügt
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface BeforeInstallPromptEvent extends Event {
|
||||
prompt: () => Promise<void>;
|
||||
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Navigator { standalone?: boolean }
|
||||
interface WindowEventMap {
|
||||
beforeinstallprompt: BeforeInstallPromptEvent;
|
||||
}
|
||||
}
|
||||
|
||||
const LAST_SHOWN_KEY = 'pwaInstallPrompt_lastShown';
|
||||
const ANDROID_DISMISSED_KEY = 'pwaInstallPrompt_androidDismissed';
|
||||
|
||||
function isIOS(): boolean {
|
||||
if (typeof navigator === 'undefined') return false;
|
||||
@@ -31,59 +40,144 @@ function isStandalone(): boolean {
|
||||
export function PWAInstallPrompt({ hidden = false }: { hidden?: boolean }) {
|
||||
if (hidden) return null;
|
||||
const [show, setShow] = useState(false);
|
||||
const [promptType, setPromptType] = useState<'ios' | 'android' | null>(null);
|
||||
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Only run on client
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const eligible = isIOS() && isSafari() && !isStandalone();
|
||||
if (!eligible) {
|
||||
// Check if already in standalone mode
|
||||
if (isStandalone()) {
|
||||
setInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
let lastShown = 0;
|
||||
try {
|
||||
lastShown = Number(localStorage.getItem(LAST_SHOWN_KEY) || 0);
|
||||
} catch {}
|
||||
const oneWeek = 7 * 24 * 60 * 60 * 1000;
|
||||
const shouldShow = !lastShown || Date.now() - lastShown > oneWeek;
|
||||
// Handle iOS
|
||||
const isIOSDevice = isIOS() && isSafari();
|
||||
if (isIOSDevice) {
|
||||
let lastShown = 0;
|
||||
try {
|
||||
lastShown = Number(localStorage.getItem(LAST_SHOWN_KEY) || 0);
|
||||
} catch {}
|
||||
const oneWeek = 7 * 24 * 60 * 60 * 1000;
|
||||
const shouldShow = !lastShown || Date.now() - lastShown > oneWeek;
|
||||
|
||||
setShow(shouldShow);
|
||||
if (shouldShow) {
|
||||
setPromptType('ios');
|
||||
setShow(true);
|
||||
}
|
||||
setInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Android (beforeinstallprompt)
|
||||
const handleBeforeInstall = (e: BeforeInstallPromptEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Check if user has dismissed Android prompt before
|
||||
let dismissed = false;
|
||||
try {
|
||||
dismissed = localStorage.getItem(ANDROID_DISMISSED_KEY) === 'true';
|
||||
} catch {}
|
||||
|
||||
if (!dismissed) {
|
||||
setDeferredPrompt(e);
|
||||
setPromptType('android');
|
||||
setShow(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('beforeinstallprompt', handleBeforeInstall);
|
||||
setInitialized(true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeinstallprompt', handleBeforeInstall);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const dismiss = () => {
|
||||
try { localStorage.setItem(LAST_SHOWN_KEY, String(Date.now())); } catch {}
|
||||
if (promptType === 'ios') {
|
||||
try { localStorage.setItem(LAST_SHOWN_KEY, String(Date.now())); } catch {}
|
||||
} else if (promptType === 'android') {
|
||||
try { localStorage.setItem(ANDROID_DISMISSED_KEY, 'true'); } catch {}
|
||||
}
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
if (!initialized || !show) return null;
|
||||
const handleAndroidInstall = async () => {
|
||||
if (!deferredPrompt) return;
|
||||
|
||||
try {
|
||||
await deferredPrompt.prompt();
|
||||
const choiceResult = await deferredPrompt.userChoice;
|
||||
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
console.log('PWA installation accepted');
|
||||
}
|
||||
|
||||
setDeferredPrompt(null);
|
||||
setShow(false);
|
||||
} catch (error) {
|
||||
console.error('Error during PWA installation:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!initialized || !show || !promptType) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-label="PWA Installation Anleitung"
|
||||
aria-label={promptType === 'android' ? 'PWA Installation' : 'PWA Installation Anleitung'}
|
||||
className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-4"
|
||||
style={{ paddingBottom: `max(env(safe-area-inset-bottom), 1rem)` }}
|
||||
>
|
||||
<div className="relative max-w-3xl mx-auto rounded-xl shadow-lg bg-gradient-to-r from-pink-500 to-purple-600 text-white p-4 sm:p-6">
|
||||
<button aria-label="Hinweis schließen" onClick={dismiss} className="absolute top-2 right-2 text-white/90 hover:text-white text-2xl leading-none">×</button>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-2xl">📱</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg sm:text-xl font-bold mb-2">App installieren</h3>
|
||||
<p className="text-white/90 mb-2">So installierst du Stargirlnails als App auf deinem iPhone/iPad:</p>
|
||||
<ol className="list-decimal pl-5 space-y-1 text-white/95">
|
||||
<li>Öffne diese Seite in Safari (nicht Chrome oder andere Browser).</li>
|
||||
<li>Tippe auf das Teilen-Symbol (□↑) unten in der Mitte.</li>
|
||||
<li>Scrolle nach unten und wähle „Zum Home-Bildschirm".</li>
|
||||
<li>Tippe auf „Hinzufügen".</li>
|
||||
</ol>
|
||||
<p className="mt-3 text-sm text-white/90">✨ Schneller Zugriff, keine App-Store-Installation nötig, automatische Updates.</p>
|
||||
|
||||
{promptType === 'android' ? (
|
||||
// Android: Direct install button
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-3xl">📱</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg sm:text-xl font-bold mb-2">App installieren</h3>
|
||||
<p className="text-white/90 mb-3">
|
||||
Installiere Stargirlnails als App für schnellen Zugriff direkt vom Startbildschirm!
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleAndroidInstall}
|
||||
className="bg-white text-pink-600 px-4 py-2 rounded-lg font-semibold hover:bg-pink-50 transition-colors"
|
||||
>
|
||||
Jetzt installieren
|
||||
</button>
|
||||
<button
|
||||
onClick={dismiss}
|
||||
className="bg-white/20 text-white px-4 py-2 rounded-lg font-semibold hover:bg-white/30 transition-colors"
|
||||
>
|
||||
Später
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// iOS: Manual instructions
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-2xl">📱</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg sm:text-xl font-bold mb-2">App installieren</h3>
|
||||
<p className="text-white/90 mb-2">So installierst du Stargirlnails als App auf deinem iPhone/iPad:</p>
|
||||
<ol className="list-decimal pl-5 space-y-1 text-white/95">
|
||||
<li>Öffne diese Seite in Safari (nicht Chrome oder andere Browser).</li>
|
||||
<li>Tippe auf das Teilen-Symbol (□↑) unten in der Mitte.</li>
|
||||
<li>Scrolle nach unten und wähle „Zum Home-Bildschirm".</li>
|
||||
<li>Tippe auf „Hinzufügen".</li>
|
||||
</ol>
|
||||
<p className="mt-3 text-sm text-white/90">✨ Schneller Zugriff, keine App-Store-Installation nötig, automatische Updates.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
Reference in New Issue
Block a user