58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { fetchDueReminders } from "@/api/diet.api";
|
|
import { isAuthenticated } from "@/store/authStore";
|
|
|
|
const POLL_MS = 60_000;
|
|
const notifiedKeys = new Set<string>();
|
|
|
|
function slotKey(id: number, date: string, hour: number, minute: number): string {
|
|
return `${date}:${hour}:${minute}:${id}`;
|
|
}
|
|
|
|
export function useReminderNotifications(): void {
|
|
const { t } = useTranslation();
|
|
const permissionRef = useRef<NotificationPermission | "unsupported">("default");
|
|
|
|
useEffect(() => {
|
|
if (typeof Notification === "undefined") {
|
|
permissionRef.current = "unsupported";
|
|
return;
|
|
}
|
|
permissionRef.current = Notification.permission;
|
|
if (Notification.permission === "default") {
|
|
void Notification.requestPermission().then((p) => {
|
|
permissionRef.current = p;
|
|
});
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!isAuthenticated()) return;
|
|
|
|
const tick = async () => {
|
|
if (permissionRef.current !== "granted") return;
|
|
try {
|
|
const due = await fetchDueReminders();
|
|
const now = new Date();
|
|
const date = now.toISOString().slice(0, 10);
|
|
for (const r of due) {
|
|
const key = slotKey(r.id, date, now.getHours(), now.getMinutes());
|
|
if (notifiedKeys.has(key)) continue;
|
|
notifiedKeys.add(key);
|
|
new Notification(r.title, {
|
|
body: r.message ?? t("diet.reminderNotificationBody"),
|
|
tag: key,
|
|
});
|
|
}
|
|
} catch {
|
|
// ignore polling errors
|
|
}
|
|
};
|
|
|
|
void tick();
|
|
const id = window.setInterval(() => void tick(), POLL_MS);
|
|
return () => window.clearInterval(id);
|
|
}, [t]);
|
|
}
|