mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
Add complete PWA implementation including service worker registration, offline support, and installation prompts: - Add vite-plugin-pwa and workbox-window dependencies for PWA tooling - Configure VitePWA plugin with web app manifest and app metadata - Set up Workbox caching strategies for API endpoints and CDN assets - Implement service worker auto-update with user-triggered refresh - Create usePWAInstall hook to handle beforeinstallprompt events - Create useOnlineStatus hook for monitoring network connectivity - Add InstallPrompt component with haptic feedback integration - Add OfflineBanner component to notify users of offline status - Configure PWA icons and assets (64x64, 192x192, 512x512 variants) - Add TypeScript type declarations for virtual PWA register module - Integrate PWA components and service worker into App.tsx and main.tsx - Add PWA meta tags and viewport configuration to index.html
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
|
|
|
interface BeforeInstallPromptEvent extends Event {
|
|
prompt: () => Promise<void>
|
|
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
|
|
}
|
|
|
|
type InstallState = 'idle' | 'available' | 'installing' | 'installed'
|
|
|
|
export function usePWAInstall(): {
|
|
installState: InstallState
|
|
canInstall: boolean
|
|
isStandalone: boolean
|
|
promptInstall: () => Promise<boolean>
|
|
} {
|
|
const [installState, setInstallState] = useState<InstallState>('idle')
|
|
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null)
|
|
|
|
const isStandalone =
|
|
typeof window !== 'undefined' &&
|
|
(window.matchMedia('(display-mode: standalone)').matches ||
|
|
(window.navigator as Navigator & { standalone?: boolean }).standalone === true)
|
|
|
|
useEffect(() => {
|
|
if (isStandalone) {
|
|
setInstallState('installed')
|
|
return
|
|
}
|
|
|
|
const handleBeforeInstallPrompt = (e: Event) => {
|
|
e.preventDefault()
|
|
setDeferredPrompt(e as BeforeInstallPromptEvent)
|
|
setInstallState('available')
|
|
}
|
|
|
|
const handleAppInstalled = () => {
|
|
setInstallState('installed')
|
|
setDeferredPrompt(null)
|
|
}
|
|
|
|
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
|
window.addEventListener('appinstalled', handleAppInstalled)
|
|
|
|
return () => {
|
|
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
|
window.removeEventListener('appinstalled', handleAppInstalled)
|
|
}
|
|
}, [isStandalone])
|
|
|
|
const promptInstall = useCallback(async (): Promise<boolean> => {
|
|
if (!deferredPrompt) {
|
|
return false
|
|
}
|
|
|
|
// Clear immediately to prevent re-entrancy while userChoice is pending
|
|
const prompt = deferredPrompt
|
|
setDeferredPrompt(null)
|
|
setInstallState('installing')
|
|
|
|
try {
|
|
await prompt.prompt()
|
|
const { outcome } = await prompt.userChoice
|
|
|
|
if (outcome === 'accepted') {
|
|
setInstallState('installed')
|
|
return true
|
|
} else {
|
|
// User dismissed, wait for a new beforeinstallprompt event
|
|
setInstallState('idle')
|
|
return false
|
|
}
|
|
} catch {
|
|
setInstallState('idle')
|
|
return false
|
|
}
|
|
}, [deferredPrompt])
|
|
|
|
return {
|
|
installState,
|
|
canInstall: installState === 'available',
|
|
isStandalone,
|
|
promptInstall
|
|
}
|
|
}
|