import { useState, useEffect, useCallback } from "react"; export type NotificationMode = "my_turn" | "all_picks"; function getEnabledKey(userId: string, seasonId: string) { return `draftNotifications-${userId}-${seasonId}`; } function getModeKey(userId: string, seasonId: string) { return `draftNotificationMode-${userId}-${seasonId}`; } export function useDraftNotifications(seasonId: string, userId: string) { const [permissionState, setPermissionState] = useState< NotificationPermission | "unsupported" >("unsupported"); const [enabled, setEnabledState] = useState(false); const [mode, setModeState] = useState("my_turn"); // Check browser support, restore persisted preferences, and watch for permission changes useEffect(() => { if (typeof window === "undefined" || !("Notification" in window)) { return; } setPermissionState(Notification.permission); // Restore preferences from localStorage only if permission is granted if (Notification.permission === "granted") { const storedEnabled = localStorage.getItem(getEnabledKey(userId, seasonId)); if (storedEnabled === "true") { setEnabledState(true); } } const storedMode = localStorage.getItem(getModeKey(userId, seasonId)); if (storedMode === "my_turn" || storedMode === "all_picks") { setModeState(storedMode); } // Watch for the user revoking/granting permission in browser settings. // Use an abort flag so that if the component unmounts before the promise // resolves, the cleanup doesn't fail to clear onchange (permissionStatus // would still be null at that point without the flag). let aborted = false; let permissionStatus: PermissionStatus | null = null; let changeHandler: (() => void) | null = null; navigator.permissions .query({ name: "notifications" }) .then((status) => { if (aborted) return; permissionStatus = status; changeHandler = () => { setPermissionState(status.state as NotificationPermission); // If permission was revoked, disable notifications if (status.state !== "granted") { setEnabledState(false); } }; status.addEventListener("change", changeHandler); }) .catch(() => { // Permissions API not available in all environments; silently ignore }); return () => { aborted = true; if (permissionStatus && changeHandler) { permissionStatus.removeEventListener("change", changeHandler); } }; }, [seasonId, userId]); const setEnabled = useCallback( async (value: boolean) => { if (typeof window === "undefined" || !("Notification" in window)) return; if (value) { // Request permission if not yet granted if (Notification.permission === "default") { const result = await Notification.requestPermission(); setPermissionState(result); if (result !== "granted") { return; } } else if (Notification.permission === "denied") { return; } setEnabledState(true); localStorage.setItem(getEnabledKey(userId, seasonId), "true"); } else { setEnabledState(false); localStorage.setItem(getEnabledKey(userId, seasonId), "false"); } }, [userId, seasonId] ); const setMode = useCallback( (value: NotificationMode) => { setModeState(value); localStorage.setItem(getModeKey(userId, seasonId), value); }, [userId, seasonId] ); const sendNotification = useCallback( (title: string, body: string) => { if ( !enabled || typeof window === "undefined" || !("Notification" in window) || Notification.permission !== "granted" || typeof document === "undefined" || !document.hidden ) { return; } const n = new Notification(title, { body, tag: `draft-${seasonId}`, }); n.addEventListener("click", () => window.focus()); }, [enabled, seasonId] ); return { permissionState, enabled, setEnabled, mode, setMode, sendNotification, }; }