/** * UI Integration Examples for Daily Notification Plugin * * This file provides comprehensive examples of how to integrate * the Daily Notification Plugin UI components into your application. * * @author Matthew Raymer * @version 1.0.0 * @created 2025-01-27 12:00:00 UTC */ import { DailyNotification } from '@timesafari/daily-notification-plugin'; import type { NotificationSettings, ConfigureOptions, DualScheduleStatus, PermissionStatus, BatteryStatus, ExactAlarmStatus, RollingWindowStats, PerformanceMetrics } from '@timesafari/daily-notification-plugin'; // ============================================================================ // 1. PERMISSION MANAGEMENT UI // ============================================================================ /** * Permission Request Dialog Component * Handles initial permission requests and status display */ export class PermissionRequestDialog { private container: HTMLElement; private onAllow: () => Promise; private onDeny: () => void; private onNever: () => void; constructor(container: HTMLElement, callbacks: { onAllow: () => Promise; onDeny: () => void; onNever: () => void; }) { this.container = container; this.onAllow = callbacks.onAllow; this.onDeny = callbacks.onDeny; this.onNever = callbacks.onNever; } /** * Show permission request dialog */ async show(): Promise { const dialog = this.createDialog(); this.container.appendChild(dialog); // Add event listeners dialog.querySelector('#allow-btn')?.addEventListener('click', async () => { await this.onAllow(); this.hide(); }); dialog.querySelector('#deny-btn')?.addEventListener('click', () => { this.onDeny(); this.hide(); }); dialog.querySelector('#never-btn')?.addEventListener('click', () => { this.onNever(); this.hide(); }); } /** * Hide permission request dialog */ hide(): void { const dialog = this.container.querySelector('.permission-dialog'); if (dialog) { dialog.remove(); } } /** * Create permission request dialog HTML */ private createDialog(): HTMLElement { const dialog = document.createElement('div'); dialog.className = 'permission-dialog'; dialog.innerHTML = `

Enable Daily Notifications

Get notified about new offers, projects, people, and items in your TimeSafari community.

  • New offers directed to you
  • Changes to your projects
  • Updates from favorited people
  • New items of interest
`; return dialog; } } /** * Permission Status Display Component * Shows current permission status and provides actions */ export class PermissionStatusDisplay { private container: HTMLElement; constructor(container: HTMLElement) { this.container = container; } /** * Update permission status display */ async updateStatus(): Promise { try { const status = await DailyNotification.checkPermissions(); this.renderStatus(status); } catch (error) { console.error('Failed to check permissions:', error); this.renderError('Failed to check permission status'); } } /** * Render permission status */ private renderStatus(status: PermissionStatus): void { const statusClass = status.granted ? 'status-granted' : 'status-denied'; const statusText = status.granted ? 'Granted' : 'Denied'; this.container.innerHTML = `
${status.granted ? '✓' : '✗'} ${statusText}
Notifications: ${status.notifications}
${status.backgroundRefresh ? `
Background Refresh: ${status.backgroundRefresh}
` : ''}
`; // Add event listeners this.container.querySelector('#request-permissions')?.addEventListener('click', async () => { try { await DailyNotification.requestPermissions(); await this.updateStatus(); } catch (error) { console.error('Failed to request permissions:', error); } }); this.container.querySelector('#open-settings')?.addEventListener('click', () => { // Platform-specific settings opening this.openSettings(); }); } /** * Render error state */ private renderError(message: string): void { this.container.innerHTML = `
${message}
`; this.container.querySelector('#retry-permissions')?.addEventListener('click', () => { this.updateStatus(); }); } /** * Open platform-specific settings */ private openSettings(): void { // This would be platform-specific implementation console.log('Opening settings...'); } } // ============================================================================ // 2. CONFIGURATION UI // ============================================================================ /** * Notification Settings Panel Component * Handles notification configuration and preferences */ export class NotificationSettingsPanel { private container: HTMLElement; private settings: NotificationSettings; constructor(container: HTMLElement) { this.container = container; this.settings = this.getDefaultSettings(); } /** * Initialize settings panel */ async initialize(): Promise { try { // Load current settings const status = await DailyNotification.getNotificationStatus(); this.settings = { ...this.settings, ...status }; this.render(); this.attachEventListeners(); } catch (error) { console.error('Failed to initialize settings:', error); this.renderError('Failed to load settings'); } } /** * Render settings panel */ private render(): void { this.container.innerHTML = `

Notification Settings

`; } /** * Attach event listeners */ private attachEventListeners(): void { // Save settings this.container.querySelector('#save-settings')?.addEventListener('click', async () => { await this.saveSettings(); }); // Test notification this.container.querySelector('#test-notification')?.addEventListener('click', async () => { await this.sendTestNotification(); }); // Real-time updates this.container.querySelectorAll('input, select').forEach(element => { element.addEventListener('change', () => { this.updateSettingsFromUI(); }); }); } /** * Update settings from UI */ private updateSettingsFromUI(): void { const enableNotifications = (this.container.querySelector('#enable-notifications') as HTMLInputElement)?.checked; const notificationTime = (this.container.querySelector('#notification-time') as HTMLInputElement)?.value; const soundEnabled = (this.container.querySelector('#sound-enabled') as HTMLInputElement)?.checked; const priorityLevel = (this.container.querySelector('#priority-level') as HTMLSelectElement)?.value; this.settings = { ...this.settings, isEnabled: enableNotifications, time: notificationTime, sound: soundEnabled, priority: priorityLevel }; } /** * Save settings */ private async saveSettings(): Promise { try { await DailyNotification.updateSettings(this.settings); this.showSuccess('Settings saved successfully'); } catch (error) { console.error('Failed to save settings:', error); this.showError('Failed to save settings'); } } /** * Send test notification */ private async sendTestNotification(): Promise { try { // This would trigger a test notification this.showSuccess('Test notification sent'); } catch (error) { console.error('Failed to send test notification:', error); this.showError('Failed to send test notification'); } } /** * Get default settings */ private getDefaultSettings(): NotificationSettings { return { isEnabled: true, time: '09:00', sound: true, priority: 'normal', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }; } /** * Show success message */ private showSuccess(message: string): void { // Implementation for success message console.log('Success:', message); } /** * Show error message */ private showError(message: string): void { // Implementation for error message console.error('Error:', message); } /** * Render error state */ private renderError(message: string): void { this.container.innerHTML = `

${message}

`; this.container.querySelector('#retry-settings')?.addEventListener('click', () => { this.initialize(); }); } } // ============================================================================ // 3. STATUS MONITORING UI // ============================================================================ /** * Status Dashboard Component * Displays real-time notification system status */ export class StatusDashboard { private container: HTMLElement; private refreshInterval: number | null = null; constructor(container: HTMLElement) { this.container = container; } /** * Initialize status dashboard */ async initialize(): Promise { await this.updateStatus(); this.startAutoRefresh(); } /** * Update status display */ async updateStatus(): Promise { try { const status = await DailyNotification.getDualScheduleStatus(); this.renderStatus(status); } catch (error) { console.error('Failed to get status:', error); this.renderError('Failed to load status'); } } /** * Render status display */ private renderStatus(status: DualScheduleStatus): void { const nextRun = status.nextRuns[0] ? new Date(status.nextRuns[0]) : null; const nextRunText = nextRun ? this.formatTimeUntil(nextRun) : 'Not scheduled'; const lastOutcome = status.lastOutcomes[0] || 'Unknown'; const lastOutcomeClass = lastOutcome === 'success' ? 'success' : 'error'; this.container.innerHTML = `

Notification Status

Overall Status
${status.staleArmed ? 'Stale' : 'Active'}
Next Notification
${nextRunText}
Last Outcome
${lastOutcome}
Cache Age
${status.cacheAgeMs ? this.formatDuration(status.cacheAgeMs) : 'No cache'}

Performance

Success Rate: ${status.performance.successRate}%
Error Count: ${status.performance.errorCount}
`; // Add event listeners this.container.querySelector('#refresh-status')?.addEventListener('click', () => { this.updateStatus(); }); this.container.querySelector('#view-details')?.addEventListener('click', () => { this.showDetailedStatus(); }); } /** * Start auto-refresh */ private startAutoRefresh(): void { this.refreshInterval = window.setInterval(() => { this.updateStatus(); }, 30000); // Refresh every 30 seconds } /** * Stop auto-refresh */ stopAutoRefresh(): void { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } } /** * Format time until date */ private formatTimeUntil(date: Date): string { const now = new Date(); const diff = date.getTime() - now.getTime(); if (diff <= 0) return 'Now'; const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); if (hours > 0) { return `${hours}h ${minutes}m`; } else { return `${minutes}m`; } } /** * Format duration in milliseconds */ private formatDuration(ms: number): string { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}h ${minutes % 60}m`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } } /** * Show detailed status */ private showDetailedStatus(): void { // Implementation for detailed status view console.log('Showing detailed status...'); } /** * Render error state */ private renderError(message: string): void { this.container.innerHTML = `

${message}

`; this.container.querySelector('#retry-status')?.addEventListener('click', () => { this.updateStatus(); }); } } // ============================================================================ // 4. PLATFORM-SPECIFIC UI // ============================================================================ /** * Android Battery Optimization Dialog * Handles Android-specific battery optimization prompts */ export class AndroidBatteryOptimizationDialog { private container: HTMLElement; constructor(container: HTMLElement) { this.container = container; } /** * Check and show battery optimization dialog if needed */ async checkAndShow(): Promise { try { const batteryStatus = await DailyNotification.getBatteryStatus(); if (batteryStatus.optimizationEnabled) { this.showDialog(); } } catch (error) { console.error('Failed to check battery status:', error); } } /** * Show battery optimization dialog */ private showDialog(): void { const dialog = document.createElement('div'); dialog.className = 'battery-optimization-dialog'; dialog.innerHTML = `

Battery Optimization

Battery optimization may prevent notifications from being delivered reliably.

For the best experience, please disable battery optimization for this app.

`; this.container.appendChild(dialog); // Add event listeners dialog.querySelector('#open-battery-settings')?.addEventListener('click', async () => { try { await DailyNotification.requestBatteryOptimizationExemption(); this.hideDialog(); } catch (error) { console.error('Failed to open battery settings:', error); } }); dialog.querySelector('#skip-battery-optimization')?.addEventListener('click', () => { this.hideDialog(); }); } /** * Hide battery optimization dialog */ private hideDialog(): void { const dialog = this.container.querySelector('.battery-optimization-dialog'); if (dialog) { dialog.remove(); } } } /** * iOS Background App Refresh Dialog * Handles iOS-specific background app refresh prompts */ export class iOSBackgroundRefreshDialog { private container: HTMLElement; constructor(container: HTMLElement) { this.container = container; } /** * Check and show background refresh dialog if needed */ async checkAndShow(): Promise { try { const status = await DailyNotification.checkPermissions(); if (status.backgroundRefresh === 'denied') { this.showDialog(); } } catch (error) { console.error('Failed to check background refresh status:', error); } } /** * Show background refresh dialog */ private showDialog(): void { const dialog = document.createElement('div'); dialog.className = 'background-refresh-dialog'; dialog.innerHTML = `

Background App Refresh

Background App Refresh enables the app to fetch new content even when it's not actively being used.

Without this, notifications will only show cached content.

`; this.container.appendChild(dialog); // Add event listeners dialog.querySelector('#open-settings')?.addEventListener('click', () => { // Open iOS settings window.open('app-settings:', '_blank'); this.hideDialog(); }); dialog.querySelector('#continue-without')?.addEventListener('click', () => { this.hideDialog(); }); } /** * Hide background refresh dialog */ private hideDialog(): void { const dialog = this.container.querySelector('.background-refresh-dialog'); if (dialog) { dialog.remove(); } } } // ============================================================================ // 5. ERROR HANDLING UI // ============================================================================ /** * Error Display Component * Shows errors and provides recovery options */ export class ErrorDisplay { private container: HTMLElement; constructor(container: HTMLElement) { this.container = container; } /** * Show error */ showError(error: Error, recoveryActions?: { onRetry?: () => Promise; onReset?: () => Promise; onContactSupport?: () => void; }): void { this.container.innerHTML = `
⚠️

Something went wrong

${error.message}

Error Code: ${error.name}

${recoveryActions?.onRetry ? '' : ''} ${recoveryActions?.onReset ? '' : ''} ${recoveryActions?.onContactSupport ? '' : ''}
`; // Add event listeners if (recoveryActions?.onRetry) { this.container.querySelector('#retry-action')?.addEventListener('click', async () => { try { await recoveryActions.onRetry!(); this.hide(); } catch (retryError) { console.error('Retry failed:', retryError); } }); } if (recoveryActions?.onReset) { this.container.querySelector('#reset-action')?.addEventListener('click', async () => { try { await recoveryActions.onReset!(); this.hide(); } catch (resetError) { console.error('Reset failed:', resetError); } }); } if (recoveryActions?.onContactSupport) { this.container.querySelector('#support-action')?.addEventListener('click', () => { recoveryActions.onContactSupport!(); }); } } /** * Hide error display */ hide(): void { this.container.innerHTML = ''; } } // ============================================================================ // 6. USAGE EXAMPLES // ============================================================================ /** * Example: Complete UI Integration * Shows how to integrate all UI components */ export class NotificationUIManager { private permissionDialog: PermissionRequestDialog; private permissionStatus: PermissionStatusDisplay; private settingsPanel: NotificationSettingsPanel; private statusDashboard: StatusDashboard; private errorDisplay: ErrorDisplay; private batteryDialog: AndroidBatteryOptimizationDialog; private backgroundRefreshDialog: iOSBackgroundRefreshDialog; constructor(container: HTMLElement) { // Initialize all UI components this.permissionDialog = new PermissionRequestDialog( container.querySelector('#permission-dialog-container')!, { onAllow: this.handlePermissionAllow.bind(this), onDeny: this.handlePermissionDeny.bind(this), onNever: this.handlePermissionNever.bind(this) } ); this.permissionStatus = new PermissionStatusDisplay( container.querySelector('#permission-status-container')! ); this.settingsPanel = new NotificationSettingsPanel( container.querySelector('#settings-container')! ); this.statusDashboard = new StatusDashboard( container.querySelector('#status-container')! ); this.errorDisplay = new ErrorDisplay( container.querySelector('#error-container')! ); this.batteryDialog = new AndroidBatteryOptimizationDialog( container.querySelector('#battery-dialog-container')! ); this.backgroundRefreshDialog = new iOSBackgroundRefreshDialog( container.querySelector('#background-refresh-dialog-container')! ); } /** * Initialize the complete UI */ async initialize(): Promise { try { // Check permissions first const permissionStatus = await DailyNotification.checkPermissions(); if (!permissionStatus.granted) { await this.permissionDialog.show(); } else { await this.permissionStatus.updateStatus(); } // Initialize other components await this.settingsPanel.initialize(); await this.statusDashboard.initialize(); // Check platform-specific requirements await this.batteryDialog.checkAndShow(); await this.backgroundRefreshDialog.checkAndShow(); } catch (error) { console.error('Failed to initialize UI:', error); this.errorDisplay.showError(error as Error, { onRetry: () => this.initialize(), onReset: () => this.resetConfiguration(), onContactSupport: () => this.contactSupport() }); } } /** * Handle permission allow */ private async handlePermissionAllow(): Promise { try { await DailyNotification.requestPermissions(); await this.permissionStatus.updateStatus(); } catch (error) { console.error('Failed to request permissions:', error); this.errorDisplay.showError(error as Error); } } /** * Handle permission deny */ private handlePermissionDeny(): void { console.log('User denied permissions'); // Show limited functionality message } /** * Handle permission never */ private handlePermissionNever(): void { console.log('User chose never ask again'); // Store preference and show limited functionality message } /** * Reset configuration */ private async resetConfiguration(): Promise { try { await DailyNotification.cancelAllNotifications(); // Reset to default settings await this.settingsPanel.initialize(); } catch (error) { console.error('Failed to reset configuration:', error); } } /** * Contact support */ private contactSupport(): void { // Implementation for contacting support console.log('Contacting support...'); } /** * Cleanup */ destroy(): void { this.statusDashboard.stopAutoRefresh(); } } // ============================================================================ // 7. CSS STYLES (for reference) // ============================================================================ export const notificationUIStyles = ` /* Permission Dialog Styles */ .permission-dialog .dialog-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; } .permission-dialog .dialog-content { background: white; border-radius: 12px; padding: 24px; max-width: 400px; margin: 20px; } .permission-dialog h2 { margin: 0 0 16px 0; color: #333; } .permission-dialog p { margin: 0 0 16px 0; color: #666; line-height: 1.5; } .permission-dialog ul { margin: 0 0 24px 0; padding-left: 20px; } .permission-dialog li { margin: 8px 0; color: #666; } .dialog-actions { display: flex; flex-direction: column; gap: 12px; } .btn-primary, .btn-secondary, .btn-tertiary { padding: 12px 24px; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; transition: background-color 0.2s; } .btn-primary { background: #1976d2; color: white; } .btn-primary:hover { background: #1565c0; } .btn-secondary { background: #f5f5f5; color: #333; border: 1px solid #ddd; } .btn-secondary:hover { background: #e0e0e0; } .btn-tertiary { background: transparent; color: #666; } .btn-tertiary:hover { background: #f5f5f5; } /* Status Display Styles */ .permission-status { padding: 16px; border-radius: 8px; margin: 16px 0; } .status-granted { background: #e8f5e8; border: 1px solid #4caf50; } .status-denied { background: #ffebee; border: 1px solid #f44336; } .status-indicator { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; } .status-icon { font-size: 20px; font-weight: bold; } .permission-details { margin: 12px 0; } .permission-item { display: flex; justify-content: space-between; margin: 8px 0; } .granted { color: #4caf50; font-weight: bold; } .denied { color: #f44336; font-weight: bold; } /* Settings Panel Styles */ .settings-panel { background: white; border-radius: 12px; padding: 24px; margin: 16px 0; } .settings-panel h3 { margin: 0 0 24px 0; color: #333; } .setting-group { margin: 20px 0; } .setting-label { display: flex; align-items: center; gap: 8px; font-weight: 500; color: #333; } .checkbox-group { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 8px; } .preference-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin-top: 8px; } .setting-actions { display: flex; gap: 12px; margin-top: 24px; } /* Status Dashboard Styles */ .status-dashboard { background: white; border-radius: 12px; padding: 24px; margin: 16px 0; } .status-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 20px 0; } .status-item { padding: 16px; background: #f8f9fa; border-radius: 8px; } .status-label { font-size: 14px; color: #666; margin-bottom: 8px; } .status-value { font-size: 18px; font-weight: bold; color: #333; } .status-value.active { color: #4caf50; } .status-value.warning { color: #ff9800; } .status-value.error { color: #f44336; } .performance-metrics { margin: 24px 0; padding: 16px; background: #f8f9fa; border-radius: 8px; } .metrics-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 12px; } .metric-item { display: flex; justify-content: space-between; } .metric-label { color: #666; } .metric-value { font-weight: bold; color: #333; } /* Error Display Styles */ .error-display { background: #ffebee; border: 1px solid #f44336; border-radius: 8px; padding: 16px; margin: 16px 0; } .error-icon { font-size: 24px; margin-bottom: 12px; } .error-content h3 { margin: 0 0 8px 0; color: #d32f2f; } .error-message { color: #666; margin: 8px 0; } .error-code { font-size: 12px; color: #999; font-family: monospace; } .error-actions { display: flex; gap: 12px; margin-top: 16px; } /* Responsive Design */ @media (max-width: 768px) { .status-grid { grid-template-columns: 1fr; } .preference-grid { grid-template-columns: 1fr; } .checkbox-group { grid-template-columns: 1fr; } .metrics-grid { grid-template-columns: 1fr; } } `; // ============================================================================ // 8. EXPORT ALL COMPONENTS // ============================================================================ export { PermissionRequestDialog, PermissionStatusDisplay, NotificationSettingsPanel, StatusDashboard, AndroidBatteryOptimizationDialog, iOSBackgroundRefreshDialog, ErrorDisplay, NotificationUIManager };