138 lines
4.3 KiB
TypeScript
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>
|
|
)
|
|
}
|