'use client'; import { useEffect, useState } from 'react'; import { useAuth } from '@/contexts/AuthContext'; // Push opt-in banner. Same gating as InstallPrompt: only after 2 Reads. // We treat the result tri-state: // granted → POST the PushSubscription to /api/push/subscribe // denied → remember it; never ask again // default → user dismissed; ask again next session const READS_KEY = 'vyndr_reads_completed'; const ASKED_KEY = 'vyndr_push_asked'; const DENIED_KEY = 'vyndr_push_denied'; const REQUIRED_READS = 2; function readsCompleted(): number { if (typeof window === 'undefined') return 0; const raw = window.localStorage.getItem(READS_KEY); return raw ? parseInt(raw, 10) || 0 : 0; } function base64UrlToArrayBuffer(base64Url: string): ArrayBuffer { const padding = '='.repeat((4 - (base64Url.length % 4)) % 4); const base64 = (base64Url + padding).replace(/-/g, '+').replace(/_/g, '/'); const raw = window.atob(base64); const buffer = new ArrayBuffer(raw.length); const view = new Uint8Array(buffer); for (let i = 0; i < raw.length; i += 1) view[i] = raw.charCodeAt(i); return buffer; } export default function PushPrompt() { const { user } = useAuth(); const [visible, setVisible] = useState(false); const [busy, setBusy] = useState(false); useEffect(() => { if (!user) return; if (typeof window === 'undefined') return; if (!('serviceWorker' in navigator) || !('PushManager' in window)) return; if (Notification.permission === 'granted' || Notification.permission === 'denied') return; if (window.localStorage.getItem(DENIED_KEY)) return; if (window.sessionStorage.getItem(ASKED_KEY)) return; if (readsCompleted() < REQUIRED_READS) return; setVisible(true); }, [user]); const handleEnable = async () => { setBusy(true); window.sessionStorage.setItem(ASKED_KEY, '1'); try { const permission = await Notification.requestPermission(); if (permission === 'denied') { window.localStorage.setItem(DENIED_KEY, '1'); setVisible(false); return; } if (permission !== 'granted') { setVisible(false); return; } const registration = await navigator.serviceWorker.ready; const vapidKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY; if (!vapidKey) { console.warn('[push] NEXT_PUBLIC_VAPID_PUBLIC_KEY not set'); setVisible(false); return; } const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: base64UrlToArrayBuffer(vapidKey), }); await fetch('/api/push/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subscription }), }); setVisible(false); } catch (err) { console.warn('[push] subscribe failed:', err); setVisible(false); } finally { setBusy(false); } }; const handleDismiss = () => { window.sessionStorage.setItem(ASKED_KEY, '1'); setVisible(false); }; if (!visible) return null; return (