mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 03:21:08 +02:00
Add notificationService (Browser Notification API wrapper), createReminderScheduler (30s poller with source pattern for checking due reminders), and ReminderPicker UI component. Todo module gets todoReminderSource (checks task dueDate - minutesBefore) and ReminderSelector now delegates to shared ReminderPicker. Scheduler supports multiple sources (todo, calendar, planta, etc.), tag-based dedup, graceful error handling, and runtime source addition. 22 new tests (8 notification + 8 scheduler + 6 ReminderPicker). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
68 lines
1.8 KiB
TypeScript
68 lines
1.8 KiB
TypeScript
/**
|
|
* Browser Notification Service
|
|
*
|
|
* Centralized wrapper for the Browser Notification API.
|
|
* Used by the reminder scheduler to fire local notifications.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* import { notificationService } from '@manacore/shared-stores';
|
|
*
|
|
* if (await notificationService.requestPermission()) {
|
|
* notificationService.send('Task fällig', { body: 'Einkaufen gehen' });
|
|
* }
|
|
* ```
|
|
*/
|
|
|
|
export interface NotificationOptions {
|
|
/** Notification body text */
|
|
body?: string;
|
|
/** Icon URL */
|
|
icon?: string;
|
|
/** Tag for deduplication (same tag replaces previous notification) */
|
|
tag?: string;
|
|
/** Called when user clicks the notification */
|
|
onClick?: () => void;
|
|
}
|
|
|
|
export const notificationService = {
|
|
/** Check if browser supports Notification API */
|
|
isSupported(): boolean {
|
|
return typeof window !== 'undefined' && 'Notification' in window;
|
|
},
|
|
|
|
/** Check if permission is already granted */
|
|
hasPermission(): boolean {
|
|
if (!this.isSupported()) return false;
|
|
return Notification.permission === 'granted';
|
|
},
|
|
|
|
/** Request notification permission. Returns true if granted. */
|
|
async requestPermission(): Promise<boolean> {
|
|
if (!this.isSupported()) return false;
|
|
if (Notification.permission === 'granted') return true;
|
|
if (Notification.permission === 'denied') return false;
|
|
|
|
const result = await Notification.requestPermission();
|
|
return result === 'granted';
|
|
},
|
|
|
|
/** Send a browser notification. No-op if permission not granted. */
|
|
send(title: string, options?: NotificationOptions): void {
|
|
if (!this.hasPermission()) return;
|
|
|
|
const notification = new Notification(title, {
|
|
body: options?.body,
|
|
icon: options?.icon ?? '/favicon.png',
|
|
tag: options?.tag,
|
|
});
|
|
|
|
if (options?.onClick) {
|
|
notification.onclick = () => {
|
|
window.focus();
|
|
options.onClick!();
|
|
notification.close();
|
|
};
|
|
}
|
|
},
|
|
};
|