Files
grateful-journal/src/hooks/usePWAInstall.ts

62 lines
1.8 KiB
TypeScript

import { useState, useEffect } from 'react'
interface BeforeInstallPromptEvent extends Event {
prompt: () => Promise<void>
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
}
interface PWAInstall {
canInstall: boolean // Android/Chrome: native prompt available
isIOS: boolean // iOS Safari: must show manual instructions
isInstalled: boolean // Already running as installed PWA
triggerInstall: () => Promise<void>
}
export function usePWAInstall(): PWAInstall {
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null)
const [isInstalled, setIsInstalled] = useState(false)
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as unknown as { MSStream?: unknown }).MSStream
useEffect(() => {
// Detect if already installed (standalone mode)
const mq = window.matchMedia('(display-mode: standalone)')
const iosStandalone = (navigator as unknown as { standalone?: boolean }).standalone === true
if (mq.matches || iosStandalone) {
setIsInstalled(true)
return
}
const handler = (e: Event) => {
e.preventDefault()
setDeferredPrompt(e as BeforeInstallPromptEvent)
}
window.addEventListener('beforeinstallprompt', handler)
window.addEventListener('appinstalled', () => {
setIsInstalled(true)
setDeferredPrompt(null)
})
return () => window.removeEventListener('beforeinstallprompt', handler)
}, [])
const triggerInstall = async () => {
if (!deferredPrompt) return
await deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
if (outcome === 'accepted') {
setIsInstalled(true)
setDeferredPrompt(null)
}
}
return {
canInstall: !!deferredPrompt,
isIOS,
isInstalled,
triggerInstall,
}
}