Spaces:
Paused
Paused
| 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<Notification, 'id' | 'timestamp' | 'read'>) => void; | |
| markAsRead: (id: string) => void; | |
| markAllAsRead: () => void; | |
| clearAll: () => void; | |
| updateSettings: (settings: Partial<NotificationSettings>) => void; | |
| } | |
| const NotificationContext = createContext<NotificationContextType | null>(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<NotificationPermission> => { | |
| 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<Notification[]>([]); | |
| const [settings, setSettings] = useState<NotificationSettings>({ | |
| 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<Notification, 'id' | 'timestamp' | 'read'>) => { | |
| 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<NotificationSettings>) => { | |
| 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 ( | |
| <NotificationContext.Provider value={{ | |
| notifications, | |
| unreadCount, | |
| settings, | |
| addNotification, | |
| markAsRead, | |
| markAllAsRead, | |
| clearAll, | |
| updateSettings, | |
| }}> | |
| {children} | |
| </NotificationContext.Provider> | |
| ); | |
| }; | |
| export const useNotifications = () => { | |
| const context = useContext(NotificationContext); | |
| if (!context) { | |
| throw new Error('useNotifications must be used within a NotificationProvider'); | |
| } | |
| return context; | |
| }; | |