import { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react'; import { toast } from '@/hooks/use-toast'; export type NotificationSeverity = 'critical' | 'warning' | 'info'; export interface Notification { id: string; title: string; message: string; severity: NotificationSeverity; timestamp: number; read: boolean; source?: string; } interface NotificationSettings { soundEnabled: boolean; desktopEnabled: boolean; criticalOnly: boolean; } interface NotificationContextType { notifications: Notification[]; unreadCount: number; settings: NotificationSettings; addNotification: (notification: Omit) => void; markAsRead: (id: string) => void; markAllAsRead: () => void; clearAll: () => void; updateSettings: (settings: Partial) => void; } const NotificationContext = createContext(null); const SETTINGS_KEY = 'cyber-notification-settings'; const NOTIFICATIONS_KEY = 'cyber-notifications'; // Audio for notifications const playNotificationSound = (severity: NotificationSeverity) => { try { const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); // Different frequencies for different severities const freq = severity === 'critical' ? 880 : severity === 'warning' ? 660 : 440; oscillator.frequency.value = freq; oscillator.type = 'sine'; gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.3); } catch (e) { console.warn('Could not play notification sound:', e); } }; // Request desktop notification permission export const requestNotificationPermission = async (): Promise => { if (!('Notification' in window)) { return 'denied'; } if (Notification.permission === 'default') { return await Notification.requestPermission(); } return Notification.permission; }; // Get current permission status export const getNotificationPermission = (): NotificationPermission | 'unsupported' => { if (!('Notification' in window)) { return 'unsupported'; } return Notification.permission; }; const showDesktopNotification = (notification: Notification) => { if ('Notification' in window && Notification.permission === 'granted') { const severityEmoji = notification.severity === 'critical' ? '🚨' : notification.severity === 'warning' ? '⚠️' : 'ℹ️'; new Notification(`${severityEmoji} ${notification.title}`, { body: notification.message, icon: '/icon-192.png', badge: '/icon-192.png', tag: notification.id, requireInteraction: notification.severity === 'critical', silent: false, }); } }; export const NotificationProvider = ({ children }: { children: ReactNode }) => { const [notifications, setNotifications] = useState([]); const [settings, setSettings] = useState({ soundEnabled: true, desktopEnabled: false, criticalOnly: false, }); // Load settings and notifications from localStorage useEffect(() => { const savedSettings = localStorage.getItem(SETTINGS_KEY); if (savedSettings) { try { setSettings(JSON.parse(savedSettings)); } catch (e) {} } const savedNotifications = localStorage.getItem(NOTIFICATIONS_KEY); if (savedNotifications) { try { setNotifications(JSON.parse(savedNotifications)); } catch (e) {} } }, []); // Save notifications to localStorage useEffect(() => { localStorage.setItem(NOTIFICATIONS_KEY, JSON.stringify(notifications.slice(0, 100))); }, [notifications]); // Save settings to localStorage useEffect(() => { localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); }, [settings]); const addNotification = useCallback((notification: Omit) => { const newNotification: Notification = { ...notification, id: `notif-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: Date.now(), read: false, }; // Check if we should show this notification based on settings if (settings.criticalOnly && notification.severity !== 'critical') { return; } setNotifications(prev => [newNotification, ...prev.slice(0, 99)]); // Play sound if (settings.soundEnabled) { playNotificationSound(notification.severity); } // Show desktop notification if (settings.desktopEnabled) { showDesktopNotification(newNotification); } // Show toast toast({ title: newNotification.title, description: newNotification.message, variant: notification.severity === 'critical' ? 'destructive' : 'default', }); }, [settings]); const markAsRead = useCallback((id: string) => { setNotifications(prev => prev.map(n => n.id === id ? { ...n, read: true } : n) ); }, []); const markAllAsRead = useCallback(() => { setNotifications(prev => prev.map(n => ({ ...n, read: true }))); }, []); const clearAll = useCallback(() => { setNotifications([]); }, []); const updateSettings = useCallback((newSettings: Partial) => { setSettings(prev => { const updated = { ...prev, ...newSettings }; // Request permission if enabling desktop notifications if (newSettings.desktopEnabled && !prev.desktopEnabled) { requestNotificationPermission(); } return updated; }); }, []); const unreadCount = notifications.filter(n => !n.read).length; return ( {children} ); }; export const useNotifications = () => { const context = useContext(NotificationContext); if (!context) { throw new Error('useNotifications must be used within a NotificationProvider'); } return context; };