From 673d9053808eacd471bc1754cbb28110af4147f2 Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Thu, 7 May 2026 21:16:44 +0200 Subject: [PATCH] ST-cmovs8jvq: PushToggle component met 3 states + iOS-banner Client component met states loading/unsupported/ios-needs-install/ denied/subscribed/unsubscribed. useEffect detecteert initial status, permission-prompt alleen via user-click. iOS-banner NL, denied-uitleg, subscribe/unsubscribe knoppen met sonner-toasts. Co-Authored-By: Claude Sonnet 4.6 --- components/notifications/push-toggle.tsx | 116 +++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 components/notifications/push-toggle.tsx diff --git a/components/notifications/push-toggle.tsx b/components/notifications/push-toggle.tsx new file mode 100644 index 0000000..0351335 --- /dev/null +++ b/components/notifications/push-toggle.tsx @@ -0,0 +1,116 @@ +'use client' + +import { useEffect, useState } from 'react' +import { toast } from 'sonner' +import { Button } from '@/components/ui/button' +import { + isPushSupported, + isIOSSafari, + isStandalonePWA, + subscribeToPush, + unsubscribeFromPush, +} from '@/lib/push-client' + +type PushStatus = + | 'loading' + | 'unsupported' + | 'ios-needs-install' + | 'denied' + | 'subscribed' + | 'unsubscribed' + +interface PushToggleProps { + vapidPublicKey?: string +} + +export function PushToggle({ vapidPublicKey }: PushToggleProps) { + const [status, setStatus] = useState('loading') + + useEffect(() => { + async function detectStatus() { + if (!isPushSupported()) { + if (isIOSSafari() && !isStandalonePWA()) { + setStatus('ios-needs-install') + } else { + setStatus('unsupported') + } + return + } + + if (Notification.permission === 'denied') { + setStatus('denied') + return + } + + try { + const reg = await navigator.serviceWorker.getRegistration() + const sub = await reg?.pushManager.getSubscription() + setStatus(sub ? 'subscribed' : 'unsubscribed') + } catch { + setStatus('unsubscribed') + } + } + + detectStatus() + }, []) + + async function handleSubscribe() { + if (!vapidPublicKey) { + toast.error('Push niet beschikbaar — VAPID-sleutel ontbreekt') + return + } + try { + await subscribeToPush(vapidPublicKey) + setStatus('subscribed') + toast.success('Push-notificaties geactiveerd') + } catch { + if (Notification.permission === 'denied') { + setStatus('denied') + } + toast.error('Kon push niet activeren. Controleer je browserinstellingen.') + } + } + + async function handleUnsubscribe() { + try { + await unsubscribeFromPush() + setStatus('unsubscribed') + toast.success('Push-notificaties uitgeschakeld') + } catch { + toast.error('Kon push niet uitschakelen') + } + } + + if (status === 'loading' || status === 'unsupported') return null + + if (status === 'ios-needs-install') { + return ( +
+ Op iPhone/iPad: tik op het delen-icoon en kies{' '} + Zet op beginscherm. Daarna kun je notificaties activeren. +
+ ) + } + + if (status === 'denied') { + return ( +

+ Notificaties zijn geblokkeerd. Schakel ze in via je browser-instellingen. +

+ ) + } + + if (status === 'unsubscribed') { + return ( + + ) + } + + return ( + + ) +}