"use client"; import { useEffect, useState } from "react"; import { X, BellRing } from "lucide-react"; import { Button } from "@/src/components/ui/button"; import { INSTALL_PROMPT_SETTLED_EVENT, isInstallPromptPending } from "@/src/lib/pwa"; import { subscribeToPush, unsubscribeFromPush, } from "@/src/modules/notifications/actions/push-subscription-actions"; const DISMISSED_KEY = "kumasy-notification-prompt-dismissed"; function urlBase64ToUint8Array(base64String: string): Uint8Array { const padding = "=".repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/"); const rawData = window.atob(base64); const output = new Uint8Array(new ArrayBuffer(rawData.length)); for (let i = 0; i < rawData.length; i++) output[i] = rawData.charCodeAt(i); return output; } function isPushSupported(): boolean { return ( typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window && "Notification" in window ); } // Lazy-Initializer statt Effect (siehe InstallPrompt.tsx): Browser-API-Werte // werden serverseitig identisch mit dem allerersten Client-Render (false) // beantwortet, kein Hydration-Mismatch möglich. function computeEligible(): boolean { if (typeof window === "undefined") return false; if (!process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY) return false; if (!isPushSupported()) return false; if (Notification.permission !== "default") return false; // schon erlaubt oder blockiert // Wartet, bis InstallPrompt nicht mehr sichtbar ist (installiert oder // weggeklickt) - sonst überlappen sich beide "fixed bottom-4"-Banner. if (isInstallPromptPending()) return false; return localStorage.getItem(DISMISSED_KEY) !== "1"; } export function NotificationPrompt() { const [eligible, setEligible] = useState(computeEligible); const [isSubscribing, setIsSubscribing] = useState(false); // Reagiert auf InstallPrompt, das erst nach unserem Mount weggeklickt wird // (siehe computeEligible) - ansonsten bliebe eligible dauerhaft false. useEffect(() => { const handleSettled = () => setEligible(computeEligible()); window.addEventListener(INSTALL_PROMPT_SETTLED_EVENT, handleSettled); return () => window.removeEventListener(INSTALL_PROMPT_SETTLED_EVENT, handleSettled); }, []); const dismiss = () => { localStorage.setItem(DISMISSED_KEY, "1"); setEligible(false); }; const handleEnable = async () => { setIsSubscribing(true); try { const permission = await Notification.requestPermission(); if (permission !== "granted") { dismiss(); return; } const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!), }); const json = subscription.toJSON(); if (!json.endpoint || !json.keys?.p256dh || !json.keys?.auth) { throw new Error("Unvollständige Push-Subscription vom Browser erhalten."); } const result = await subscribeToPush( { endpoint: json.endpoint, keys: { p256dh: json.keys.p256dh, auth: json.keys.auth } }, navigator.userAgent ); if (!result.success) { // Serverseitig nicht gespeichert - Subscription auf dem Gerät nicht // verwaist zurücklassen, sonst denkt der Browser dauerhaft, er sei // angemeldet, ohne dass Push je zugestellt werden könnte. await subscription.unsubscribe().catch(() => {}); throw new Error(result.error); } dismiss(); } catch (error) { console.error("Push-Aktivierung fehlgeschlagen:", error); dismiss(); } finally { setIsSubscribing(false); } }; if (!eligible) return null; return (

Benachrichtigungen aktivieren

Verpasse keine Buchungsbestätigung oder freien Wartelisten-Plätze mehr.

); } // Wird von einer Einstellungen-UI importiert, um Push wieder zu deaktivieren // (z.B. künftige "Geräte verwalten"-Seite) - hier co-located, weil sie // denselben Browser-API-Zugriff (serviceWorker.ready) braucht wie handleEnable. export async function disablePushOnThisDevice(): Promise { if (!isPushSupported()) return; const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (!subscription) return; await unsubscribeFromPush(subscription.endpoint).catch(() => {}); await subscription.unsubscribe().catch(() => {}); }