initial commit
This commit is contained in:
137
components/PWAInstallPrompt.tsx
Normal file
137
components/PWAInstallPrompt.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user