ai-wpa/components/PWAInstallPrompt.tsx

138 lines
4.3 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { X, Download } from 'lucide-react'
interface BeforeInstallPromptEvent extends Event {
readonly platforms: string[]
readonly userChoice: Promise<{
outcome: 'accepted' | 'dismissed'
platform: string
}>
prompt(): Promise<void>
}
export default function PWAInstallPrompt() {
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null)
const [showPrompt, setShowPrompt] = useState(false)
const [isIOS, setIsIOS] = useState(false)
const [isStandalone, setIsStandalone] = useState(false)
useEffect(() => {
// Check if running on iOS
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
setIsIOS(iOS)
// Check if already installed (standalone mode)
const standalone =
window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone === true
setIsStandalone(standalone)
// Don't show prompt if already installed
if (standalone) return
// Handle beforeinstallprompt event (Android/Chrome)
const handleBeforeInstallPrompt = (e: Event) => {
e.preventDefault()
setDeferredPrompt(e as BeforeInstallPromptEvent)
// Show prompt after a short delay to avoid being too aggressive
setTimeout(() => {
setShowPrompt(true)
}, 3000)
}
// Handle app installed event
const handleAppInstalled = () => {
setShowPrompt(false)
setDeferredPrompt(null)
}
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
window.addEventListener('appinstalled', handleAppInstalled)
// For iOS, show prompt after delay if not already installed
if (iOS && !standalone) {
setTimeout(() => {
setShowPrompt(true)
}, 5000)
}
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
window.removeEventListener('appinstalled', handleAppInstalled)
}
}, [])
const handleInstallClick = async () => {
if (deferredPrompt) {
// Android/Chrome install
deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
if (outcome === 'accepted') {
setDeferredPrompt(null)
setShowPrompt(false)
}
}
// For iOS, the prompt just shows instructions
}
const handleDismiss = () => {
setShowPrompt(false)
// Don't show again for this session
sessionStorage.setItem('pwa-prompt-dismissed', 'true')
}
// Don't show if already dismissed in this session
useEffect(() => {
const dismissed = sessionStorage.getItem('pwa-prompt-dismissed')
if (dismissed) {
setShowPrompt(false)
}
}, [])
// Don't render if already installed or shouldn't show
if (isStandalone || !showPrompt) return null
return (
<div className="fixed bottom-4 left-4 right-4 z-50 md:left-auto md:right-4 md:max-w-sm">
<div className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center space-x-2">
<Download className="h-5 w-5 text-blue-600" />
<h3 className="font-semibold text-sm">Install SiliconPin</h3>
</div>
<Button variant="ghost" size="sm" onClick={handleDismiss} className="h-6 w-6 p-0">
<X className="h-4 w-4" />
</Button>
</div>
<p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
{isIOS
? 'Add to your home screen for a better experience'
: 'Install our app for quick access and offline use'}
</p>
{isIOS ? (
<div className="text-xs text-gray-500 dark:text-gray-400 mb-3">
Tap the share button <span className="inline-block">📤</span> and select "Add to Home
Screen"
</div>
) : null}
<div className="flex space-x-2">
<Button onClick={handleInstallClick} size="sm" className="flex-1">
{isIOS ? 'Learn How' : 'Install'}
</Button>
<Button variant="outline" size="sm" onClick={handleDismiss}>
Later
</Button>
</div>
</div>
</div>
)
}