import { useState, useEffect, useCallback } from 'react'; import { apiClient } from '../api/client'; interface PushPreferences { reminder_enabled: boolean; reminder_time: string; // "HH:MM" in UTC } function urlBase64ToUint8Array(base64String: string): Uint8Array { const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const raw = atob(base64); const arr = new Uint8Array(raw.length); for (let i = 0; i < raw.length; i++) { arr[i] = raw.charCodeAt(i); } return arr; } export function localTimeToUtc(localTime: string): string { const [hours, minutes] = localTime.split(':').map(Number); const now = new Date(); now.setHours(hours, minutes, 0, 0); const utcH = now.getUTCHours().toString().padStart(2, '0'); const utcM = now.getUTCMinutes().toString().padStart(2, '0'); return `${utcH}:${utcM}`; } export function utcTimeToLocal(utcTime: string): string { const [hours, minutes] = utcTime.split(':').map(Number); const now = new Date(); now.setUTCHours(hours, minutes, 0, 0); const localH = now.getHours().toString().padStart(2, '0'); const localM = now.getMinutes().toString().padStart(2, '0'); return `${localH}:${localM}`; } export function usePushNotifications() { const [permission, setPermission] = useState( typeof window !== 'undefined' && 'Notification' in window ? Notification.permission : 'denied', ); const [preferences, setPreferences] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { apiClient .get('/api/push/preferences') .then(setPreferences) .catch(() => {}) .finally(() => setLoading(false)); }, []); const subscribe = useCallback(async () => { if (!('Notification' in window) || !('serviceWorker' in navigator)) return; const perm = await Notification.requestPermission(); setPermission(perm); if (perm !== 'granted') return; const { vapid_public_key } = await apiClient.get<{ vapid_public_key: string }>( '/api/push/vapid-key', ); const applicationServerKey = urlBase64ToUint8Array(vapid_public_key); const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: applicationServerKey.buffer as ArrayBuffer, }); const json = subscription.toJSON(); await apiClient.post('/api/push/subscribe', { endpoint: json.endpoint, p256dh: json.keys?.p256dh, auth: json.keys?.auth, }); }, []); const unsubscribe = useCallback(async () => { if (!('serviceWorker' in navigator)) return; try { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { await subscription.unsubscribe(); } } catch { // ignore errors } await apiClient.delete('/api/push/subscribe'); }, []); const updatePreferences = useCallback(async (prefs: PushPreferences) => { await apiClient.put('/api/push/preferences', prefs); setPreferences(prefs); }, []); return { permission, preferences, loading, subscribe, unsubscribe, updatePreferences }; }