diff --git a/src/components/PWASettings.tsx b/src/components/PWASettings.tsx index aa656b1..5af5f32 100644 --- a/src/components/PWASettings.tsx +++ b/src/components/PWASettings.tsx @@ -24,19 +24,80 @@ export function PWASettings() { registration } = usePWA() - const [notificationPermission, setNotificationPermission] = useState('default') + const [notificationPermission, setNotificationPermission] = useState( + 'default' + ) const [cacheSize, setCacheSize] = useState(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) } } diff --git a/src/components/pwa-settings/NotificationsSection.tsx b/src/components/pwa-settings/NotificationsSection.tsx index cf8c54b..4f720e5 100644 --- a/src/components/pwa-settings/NotificationsSection.tsx +++ b/src/components/pwa-settings/NotificationsSection.tsx @@ -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 case 'denied': return + case 'unsupported': + return default: return } @@ -51,7 +54,7 @@ export function NotificationsSection({ permission, onToggle, copy }: Notificatio @@ -60,6 +63,12 @@ export function NotificationsSection({ permission, onToggle, copy }: Notificatio {copy.blocked} )} + + {permission === 'unsupported' && ( +
+ {copy.unsupported} +
+ )} ) diff --git a/src/data/pwa-settings.json b/src/data/pwa-settings.json index 680d2fa..e670f23 100644 --- a/src/data/pwa-settings.json +++ b/src/data/pwa-settings.json @@ -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" } } diff --git a/src/hooks/use-pwa.ts b/src/hooks/use-pwa.ts index ae12489..48019eb 100644 --- a/src/hooks/use-pwa.ts +++ b/src/hooks/use-pwa.ts @@ -136,7 +136,7 @@ export function usePWA() { } } - const requestNotificationPermission = async () => { + const requestNotificationPermission = async (): Promise => { if (!('Notification' in window)) { return 'unsupported' }