// Simple notification storage using localStorage export interface NotificationRequest { id: string; libraryName: string; passTypes: ('Pass Only' | 'Backpack Only' | 'Both')[]; email?: string; // Optional for future email notifications createdAt: string; lastChecked?: string; } export class NotificationManager { private static STORAGE_KEY = 'park-pass-notifications'; static getNotifications(): NotificationRequest[] { if (typeof window === 'undefined') return []; try { const stored = localStorage.getItem(this.STORAGE_KEY); return stored ? JSON.parse(stored) : []; } catch { return []; } } static addNotification(request: Omit): NotificationRequest { const newRequest: NotificationRequest = { ...request, id: Date.now().toString() + Math.random().toString(36).substr(2, 9), createdAt: new Date().toISOString(), }; const notifications = this.getNotifications(); notifications.push(newRequest); if (typeof window !== 'undefined') { localStorage.setItem(this.STORAGE_KEY, JSON.stringify(notifications)); } return newRequest; } static removeNotification(id: string): void { const notifications = this.getNotifications().filter(n => n.id !== id); if (typeof window !== 'undefined') { localStorage.setItem(this.STORAGE_KEY, JSON.stringify(notifications)); } } static updateLastChecked(id: string): void { const notifications = this.getNotifications(); const notification = notifications.find(n => n.id === id); if (notification) { notification.lastChecked = new Date().toISOString(); if (typeof window !== 'undefined') { localStorage.setItem(this.STORAGE_KEY, JSON.stringify(notifications)); } } } } // Geolocation utilities export function calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number { const R = 3959; // Earth's radius in miles const dLat = (lat2 - lat1) * Math.PI / 180; const dLng = (lng2 - lng1) * Math.PI / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng / 2) * Math.sin(dLng / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } export async function getCurrentLocation(): Promise<{ lat: number; lng: number } | null> { return new Promise((resolve) => { if (!navigator.geolocation) { resolve(null); return; } navigator.geolocation.getCurrentPosition( (position) => { resolve({ lat: position.coords.latitude, lng: position.coords.longitude, }); }, () => { resolve(null); }, { timeout: 10000 } ); }); } // Web Push Notification utilities export class WebPushManager { static async requestPermission(): Promise { if (!('Notification' in window)) { console.log('This browser does not support notifications'); return false; } if (Notification.permission === 'granted') { return true; } if (Notification.permission === 'denied') { return false; } const permission = await Notification.requestPermission(); return permission === 'granted'; } static async showNotification(title: string, body: string, options?: NotificationOptions): Promise { if (!await this.requestPermission()) { console.log('Notification permission denied'); return; } new Notification(title, { body, icon: '/favicon.ico', badge: '/favicon.ico', tag: 'park-pass-notification', requireInteraction: true, ...options }); } static isSupported(): boolean { return 'Notification' in window; } static getPermissionStatus(): NotificationPermission { return Notification.permission; } } // Zip code to coordinates lookup (San Diego area zip codes) const ZIP_COORDINATES: Record = { // Central San Diego '92101': { lat: 32.7157, lng: -117.1611, area: 'Downtown' }, '92102': { lat: 32.7081, lng: -117.1367, area: 'Golden Hill' }, '92103': { lat: 32.7441, lng: -117.1739, area: 'Hillcrest' }, '92104': { lat: 32.7489, lng: -117.1364, area: 'Normal Heights' }, '92105': { lat: 32.7089, lng: -117.1242, area: 'City Heights' }, '92106': { lat: 32.7330, lng: -117.1430, area: 'Point Loma' }, '92107': { lat: 32.7572, lng: -117.2528, area: 'Ocean Beach' }, '92108': { lat: 32.7678, lng: -117.1189, area: 'Mission Valley' }, '92109': { lat: 32.7889, lng: -117.2306, area: 'Pacific Beach' }, '92110': { lat: 32.7742, lng: -117.1936, area: 'Mission Bay' }, '92111': { lat: 32.7714, lng: -117.1789, area: 'Linda Vista' }, '92113': { lat: 32.6781, lng: -117.0200, area: 'Skyline' }, '92114': { lat: 32.7030, lng: -117.1289, area: 'Logan Heights' }, '92115': { lat: 32.7328, lng: -117.1461, area: 'Oak Park' }, '92116': { lat: 32.7644, lng: -117.1164, area: 'Kensington' }, '92117': { lat: 32.8328, lng: -117.2050, area: 'Clairemont' }, '92118': { lat: 32.6100, lng: -117.0842, area: 'Coronado' }, '92119': { lat: 32.7482, lng: -117.0704, area: 'College Area' }, '92120': { lat: 32.7678, lng: -117.0731, area: 'Del Cerro' }, '92121': { lat: 32.8797, lng: -117.2072, area: 'Sorrento Valley' }, '92122': { lat: 32.8544, lng: -117.2144, area: 'University City' }, '92123': { lat: 32.8100, lng: -117.1350, area: 'Serra Mesa' }, '92124': { lat: 32.8086, lng: -117.0547, area: 'Tierrasanta' }, '92126': { lat: 32.8831, lng: -117.1558, area: 'Mira Mesa' }, '92127': { lat: 32.9581, lng: -117.1089, area: 'Rancho Penasquitos' }, '92128': { lat: 33.0200, lng: -117.1156, area: 'Rancho Bernardo' }, '92129': { lat: 32.9584, lng: -117.1253, area: 'Rancho Penasquitos' }, '92130': { lat: 32.9340, lng: -117.2340, area: 'Carmel Valley' }, '92131': { lat: 32.9286, lng: -117.1311, area: 'Carmel Mountain' }, '92132': { lat: 32.6747, lng: -117.0742, area: 'Paradise Hills' }, '92154': { lat: 32.5592, lng: -117.0431, area: 'San Ysidro' }, '92173': { lat: 32.6400, lng: -117.0200, area: 'San Diego East' }, }; export function getCoordinatesFromZip(zipCode: string): { lat: number; lng: number; area: string } | null { return ZIP_COORDINATES[zipCode] || null; } export function isValidSanDiegoZip(zipCode: string): boolean { return zipCode in ZIP_COORDINATES; }