Improve PWA permission and cache handling

This commit is contained in:
2026-01-18 00:57:03 +00:00
parent 7130919576
commit a97d32e1ec
4 changed files with 94 additions and 20 deletions

View File

@@ -24,19 +24,80 @@ export function PWASettings() {
registration
} = usePWA()
const [notificationPermission, setNotificationPermission] = useState<NotificationPermission>('default')
const [notificationPermission, setNotificationPermission] = useState<NotificationPermission | 'unsupported'>(
'default'
)
const [cacheSize, setCacheSize] = useState<string>(pwaSettingsCopy.defaults.cacheCalculating)
useEffect(() => {
if ('Notification' in window) {
setNotificationPermission(Notification.permission)
let isMounted = true
let permissionStatus: PermissionStatus | null = null
let handlePermissionChange: (() => void) | null = null
const setPermissionState = (state: PermissionState | NotificationPermission | 'unsupported') => {
if (!isMounted) return
if (state === 'prompt') {
setNotificationPermission('default')
return
}
if (state === 'granted' || state === 'denied' || state === 'default' || state === 'unsupported') {
setNotificationPermission(state)
}
}
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then((estimate) => {
const usageInMB = ((estimate.usage || 0) / (1024 * 1024)).toFixed(2)
const updatePermission = async () => {
if (!('Notification' in window)) {
setPermissionState('unsupported')
return
}
setPermissionState(Notification.permission)
if ('permissions' in navigator && 'query' in navigator.permissions) {
try {
permissionStatus = await navigator.permissions.query({ name: 'notifications' })
handlePermissionChange = () => setPermissionState(permissionStatus?.state ?? 'default')
permissionStatus.addEventListener('change', handlePermissionChange)
handlePermissionChange()
} catch (error) {
console.error('[PWA] Notification permission query failed:', error)
}
}
}
const updateCacheSize = async () => {
if (!('storage' in navigator && 'estimate' in navigator.storage)) {
if (isMounted) {
setCacheSize(pwaSettingsCopy.defaults.cacheUnavailable)
}
return
}
try {
const estimate = await navigator.storage.estimate()
const usage = estimate.usage
if (typeof usage !== 'number') {
setCacheSize(pwaSettingsCopy.defaults.cacheUnavailable)
return
}
const usageInMB = (usage / (1024 * 1024)).toFixed(2)
setCacheSize(`${usageInMB} ${pwaSettingsCopy.cache.storageUnit}`)
})
} catch (error) {
console.error('[PWA] Storage estimate failed:', error)
if (isMounted) {
setCacheSize(pwaSettingsCopy.defaults.cacheUnavailable)
}
}
}
updatePermission()
updateCacheSize()
return () => {
isMounted = false
if (permissionStatus && handlePermissionChange) {
permissionStatus.removeEventListener('change', handlePermissionChange)
}
}
}, [])
@@ -60,15 +121,17 @@ export function PWASettings() {
}
const handleNotificationToggle = async (enabled: boolean) => {
if (enabled) {
const permission = await requestNotificationPermission()
setNotificationPermission(permission as NotificationPermission)
if (!enabled || notificationPermission === 'unsupported') {
return
}
if (permission === 'granted') {
toast.success(pwaSettingsCopy.toasts.notificationsEnabled)
} else {
toast.error(pwaSettingsCopy.toasts.notificationsDenied)
}
const permission = await requestNotificationPermission()
setNotificationPermission(permission)
if (permission === 'granted') {
toast.success(pwaSettingsCopy.toasts.notificationsEnabled)
} else {
toast.error(pwaSettingsCopy.toasts.notificationsDenied)
}
}

View File

@@ -4,7 +4,7 @@ import { Switch } from '@/components/ui/switch'
import { Bell, CheckCircle, Question, XCircle } from '@phosphor-icons/react'
interface NotificationsSectionProps {
permission: NotificationPermission
permission: NotificationPermission | 'unsupported'
onToggle: (enabled: boolean) => void
copy: {
title: string
@@ -12,6 +12,7 @@ interface NotificationsSectionProps {
label: string
permissionLabel: string
blocked: string
unsupported: string
}
}
@@ -22,6 +23,8 @@ export function NotificationsSection({ permission, onToggle, copy }: Notificatio
return <CheckCircle size={16} className="text-accent" weight="fill" />
case 'denied':
return <XCircle size={16} className="text-destructive" weight="fill" />
case 'unsupported':
return <XCircle size={16} className="text-muted-foreground" weight="fill" />
default:
return <Question size={16} className="text-muted-foreground" weight="fill" />
}
@@ -51,7 +54,7 @@ export function NotificationsSection({ permission, onToggle, copy }: Notificatio
<Switch
checked={permission === 'granted'}
onCheckedChange={onToggle}
disabled={permission === 'denied'}
disabled={permission === 'denied' || permission === 'unsupported'}
/>
</div>
@@ -60,6 +63,12 @@ export function NotificationsSection({ permission, onToggle, copy }: Notificatio
{copy.blocked}
</div>
)}
{permission === 'unsupported' && (
<div className="text-xs text-muted-foreground bg-muted/50 p-3 rounded-md">
{copy.unsupported}
</div>
)}
</div>
</Card>
)

View File

@@ -45,7 +45,8 @@
"description": "Receive updates about your projects and builds",
"label": "Push Notifications",
"permissionLabel": "Permission:",
"blocked": "Notifications are blocked. Please enable them in your browser settings."
"blocked": "Notifications are blocked. Please enable them in your browser settings.",
"unsupported": "Notifications aren't supported in this browser."
},
"cache": {
"title": "Cache Management",
@@ -84,6 +85,7 @@
"notificationsDenied": "Notification permission denied"
},
"defaults": {
"cacheCalculating": "Calculating..."
"cacheCalculating": "Calculating...",
"cacheUnavailable": "Unavailable"
}
}

View File

@@ -136,7 +136,7 @@ export function usePWA() {
}
}
const requestNotificationPermission = async () => {
const requestNotificationPermission = async (): Promise<NotificationPermission | 'unsupported'> => {
if (!('Notification' in window)) {
return 'unsupported'
}