first commit
This commit is contained in:
224
components/PWAInstallPrompt.tsx
Normal file
224
components/PWAInstallPrompt.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { X, Download, Smartphone } from 'lucide-react'
|
||||
|
||||
interface BeforeInstallPromptEvent extends Event {
|
||||
prompt(): Promise<void>
|
||||
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
|
||||
}
|
||||
|
||||
export default function PWAInstallPrompt() {
|
||||
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null)
|
||||
const [showPrompt, setShowPrompt] = useState(false)
|
||||
const [isInstalled, setIsInstalled] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// Check if app is already installed
|
||||
const checkInstalled = () => {
|
||||
const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|
||||
const isInWebAppiOS = (window.navigator as any).standalone === true
|
||||
const isAndroidInstalled = document.referrer.includes('android-app://')
|
||||
|
||||
setIsInstalled(isStandalone || isInWebAppiOS || isAndroidInstalled)
|
||||
}
|
||||
|
||||
checkInstalled()
|
||||
|
||||
const handleBeforeInstallPrompt = (e: Event) => {
|
||||
e.preventDefault()
|
||||
setDeferredPrompt(e as BeforeInstallPromptEvent)
|
||||
|
||||
// Don't show prompt immediately, wait a bit for user engagement
|
||||
setTimeout(() => {
|
||||
if (!isInstalled && !localStorage.getItem('pwa-install-dismissed')) {
|
||||
setShowPrompt(true)
|
||||
}
|
||||
}, 10000) // Show after 10 seconds
|
||||
}
|
||||
|
||||
const handleAppInstalled = () => {
|
||||
setShowPrompt(false)
|
||||
setIsInstalled(true)
|
||||
setDeferredPrompt(null)
|
||||
}
|
||||
|
||||
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
||||
window.addEventListener('appinstalled', handleAppInstalled)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
||||
window.removeEventListener('appinstalled', handleAppInstalled)
|
||||
}
|
||||
}, [isInstalled])
|
||||
|
||||
const handleInstallClick = async () => {
|
||||
if (!deferredPrompt) return
|
||||
|
||||
try {
|
||||
await deferredPrompt.prompt()
|
||||
const choiceResult = await deferredPrompt.userChoice
|
||||
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
console.log('User accepted the install prompt')
|
||||
} else {
|
||||
console.log('User dismissed the install prompt')
|
||||
}
|
||||
|
||||
setDeferredPrompt(null)
|
||||
setShowPrompt(false)
|
||||
} catch (error) {
|
||||
console.error('Error showing install prompt:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDismiss = () => {
|
||||
setShowPrompt(false)
|
||||
localStorage.setItem('pwa-install-dismissed', 'true')
|
||||
|
||||
// Show again after 7 days
|
||||
setTimeout(() => {
|
||||
localStorage.removeItem('pwa-install-dismissed')
|
||||
}, 7 * 24 * 60 * 60 * 1000)
|
||||
}
|
||||
|
||||
if (isInstalled || !showPrompt || !deferredPrompt) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 100 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 100 }}
|
||||
className="fixed bottom-4 left-4 right-4 z-50 max-w-sm mx-auto"
|
||||
>
|
||||
<Card className="bg-gradient-to-r from-green-500 to-emerald-600 text-white border-0 shadow-2xl">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-12 h-12 bg-white/20 rounded-full flex items-center justify-center">
|
||||
<Smartphone className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-white mb-1">
|
||||
Install Padmaaja Rasooi App
|
||||
</h3>
|
||||
<p className="text-xs text-green-100 mb-3">
|
||||
Get the full experience with offline access, faster loading, and push notifications.
|
||||
</p>
|
||||
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
onClick={handleInstallClick}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="flex-1 text-green-700 hover:text-green-800"
|
||||
>
|
||||
<Download className="h-3 w-3 mr-1" />
|
||||
Install
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDismiss}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="text-white hover:bg-white/20"
|
||||
>
|
||||
Later
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleDismiss}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="flex-shrink-0 text-white hover:bg-white/20 p-1"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
// Alternative iOS Safari install instructions
|
||||
export function IOSInstallPrompt() {
|
||||
const [showIOS, setShowIOS] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
const isInStandaloneMode = (window.navigator as any).standalone === true
|
||||
const hasPromptBeenShown = localStorage.getItem('ios-install-prompt-shown')
|
||||
|
||||
if (isIOS && !isInStandaloneMode && !hasPromptBeenShown) {
|
||||
setTimeout(() => setShowIOS(true), 15000) // Show after 15 seconds on iOS
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleDismiss = () => {
|
||||
setShowIOS(false)
|
||||
localStorage.setItem('ios-install-prompt-shown', 'true')
|
||||
}
|
||||
|
||||
if (!showIOS) return null
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 100 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 100 }}
|
||||
className="fixed bottom-4 left-4 right-4 z-50 max-w-sm mx-auto"
|
||||
>
|
||||
<Card className="bg-blue-600 text-white border-0 shadow-2xl">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-12 h-12 bg-white/20 rounded-full flex items-center justify-center">
|
||||
<Smartphone className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-white mb-1">
|
||||
Add to Home Screen
|
||||
</h3>
|
||||
<p className="text-xs text-blue-100 mb-2">
|
||||
Tap the share button <span className="inline-block w-4 h-4 bg-white/20 rounded text-center text-xs">⬆️</span> below and select "Add to Home Screen"
|
||||
</p>
|
||||
|
||||
<Button
|
||||
onClick={handleDismiss}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="text-blue-700 hover:text-blue-800"
|
||||
>
|
||||
Got it
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleDismiss}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="flex-shrink-0 text-white hover:bg-white/20 p-1"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user