added in browser notifications for notifications in settings
This commit is contained in:
@@ -123,6 +123,9 @@ function parseProfilePreferences(
|
||||
dailyReminderTime:
|
||||
parsed.dailyReminderTime ??
|
||||
defaultProfilePreferences.dailyReminderTime,
|
||||
dailyReminderDelivery:
|
||||
parsed.dailyReminderDelivery ??
|
||||
defaultProfilePreferences.dailyReminderDelivery,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -45,6 +45,7 @@ function parseStoredPreferences(
|
||||
avatarVibe: "Calm",
|
||||
dailyReminderEnabled: false,
|
||||
dailyReminderTime: "20:00",
|
||||
dailyReminderDelivery: "in_app",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,6 +61,7 @@ function parseStoredPreferences(
|
||||
avatarVibe: "Calm",
|
||||
dailyReminderEnabled: false,
|
||||
dailyReminderTime: "20:00",
|
||||
dailyReminderDelivery: "in_app",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,5 +70,6 @@ function parseStoredPreferences(
|
||||
avatarVibe: "Calm",
|
||||
dailyReminderEnabled: false,
|
||||
dailyReminderTime: "20:00",
|
||||
dailyReminderDelivery: "in_app",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import type { DailyReminderStatus } from "@/lib/daily-reminder-status";
|
||||
import { showInAppDailyReminder } from "@/lib/in-app-reminders";
|
||||
import { routes } from "@/lib/routes";
|
||||
import {
|
||||
type ReminderDeliveryMethod,
|
||||
usesInAppReminders,
|
||||
usesSystemReminders,
|
||||
} from "@/lib/reminder-delivery";
|
||||
|
||||
const REMINDER_SENT_STORAGE_PREFIX = "habit-pet-daily-reminder-sent";
|
||||
const REMINDER_CHECK_INTERVAL_MS = 15_000;
|
||||
@@ -135,7 +141,7 @@ export function buildDailyReminderNotification(status: DailyReminderStatus) {
|
||||
};
|
||||
}
|
||||
|
||||
export function showDailyReminderNotification(
|
||||
export function showSystemDailyReminderNotification(
|
||||
status: DailyReminderStatus,
|
||||
) {
|
||||
if (!isNotificationSupported() || Notification.permission !== "granted") {
|
||||
@@ -160,7 +166,7 @@ export function showDailyReminderNotification(
|
||||
return true;
|
||||
}
|
||||
|
||||
export function showTestDailyReminderNotification(
|
||||
export function showTestSystemDailyReminderNotification(
|
||||
status: DailyReminderStatus,
|
||||
) {
|
||||
if (!isNotificationSupported() || Notification.permission !== "granted") {
|
||||
@@ -169,7 +175,7 @@ export function showTestDailyReminderNotification(
|
||||
|
||||
const { body, targetUrl } = buildDailyReminderNotification(status);
|
||||
const notification = new Notification("Test: Habit Pet reminder", {
|
||||
body: `This is a test notification. ${body}`,
|
||||
body: `This is a browser test notification. ${body}`,
|
||||
tag: `habit-pet-daily-reminder-test-${Date.now()}`,
|
||||
});
|
||||
|
||||
@@ -185,6 +191,61 @@ export function showTestDailyReminderNotification(
|
||||
return true;
|
||||
}
|
||||
|
||||
export function deliverDailyReminder(
|
||||
status: DailyReminderStatus,
|
||||
deliveryMethod: ReminderDeliveryMethod,
|
||||
) {
|
||||
let delivered = false;
|
||||
|
||||
if (usesInAppReminders(deliveryMethod)) {
|
||||
const { title, body, targetUrl } = buildDailyReminderNotification(status);
|
||||
delivered =
|
||||
showInAppDailyReminder({ title, body, targetUrl }) || delivered;
|
||||
}
|
||||
|
||||
if (usesSystemReminders(deliveryMethod)) {
|
||||
delivered =
|
||||
showSystemDailyReminderNotification(status) || delivered;
|
||||
}
|
||||
|
||||
return delivered;
|
||||
}
|
||||
|
||||
export function deliverTestDailyReminder(
|
||||
status: DailyReminderStatus,
|
||||
deliveryMethod: ReminderDeliveryMethod,
|
||||
) {
|
||||
let delivered = false;
|
||||
|
||||
if (usesInAppReminders(deliveryMethod)) {
|
||||
const { title, body, targetUrl } = buildDailyReminderNotification(status);
|
||||
delivered =
|
||||
showInAppDailyReminder({
|
||||
title: `Test: ${title}`,
|
||||
body: `This is an in-app test reminder. ${body}`,
|
||||
targetUrl,
|
||||
isTest: true,
|
||||
}) || delivered;
|
||||
}
|
||||
|
||||
if (usesSystemReminders(deliveryMethod)) {
|
||||
delivered =
|
||||
showTestSystemDailyReminderNotification(status) || delivered;
|
||||
}
|
||||
|
||||
return delivered;
|
||||
}
|
||||
|
||||
/** @deprecated Use showSystemDailyReminderNotification */
|
||||
export function showDailyReminderNotification(status: DailyReminderStatus) {
|
||||
return showSystemDailyReminderNotification(status);
|
||||
}
|
||||
|
||||
/** @deprecated Use showTestSystemDailyReminderNotification */
|
||||
export function showTestDailyReminderNotification(status: DailyReminderStatus) {
|
||||
return showTestSystemDailyReminderNotification(status);
|
||||
}
|
||||
|
||||
export function getReminderCheckIntervalMs() {
|
||||
return REMINDER_CHECK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
38
lib/in-app-reminders.ts
Normal file
38
lib/in-app-reminders.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { routes } from "@/lib/routes";
|
||||
|
||||
export const IN_APP_REMINDER_EVENT = "habit-pet-in-app-reminder";
|
||||
|
||||
export type InAppReminderPayload = {
|
||||
title: string;
|
||||
body: string;
|
||||
targetUrl: string;
|
||||
isTest?: boolean;
|
||||
};
|
||||
|
||||
export function dispatchInAppReminder(payload: InAppReminderPayload) {
|
||||
if (typeof window === "undefined") {
|
||||
return false;
|
||||
}
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent<InAppReminderPayload>(IN_APP_REMINDER_EVENT, {
|
||||
detail: payload,
|
||||
}),
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function showInAppDailyReminder(input: {
|
||||
title: string;
|
||||
body: string;
|
||||
targetUrl?: string;
|
||||
isTest?: boolean;
|
||||
}) {
|
||||
return dispatchInAppReminder({
|
||||
title: input.title,
|
||||
body: input.body,
|
||||
targetUrl: input.targetUrl ?? routes.avatar,
|
||||
isTest: input.isTest,
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import { notifyHabitPetDataUpdated } from "@/lib/app-events";
|
||||
import {
|
||||
normalizeReminderDeliveryMethod,
|
||||
type ReminderDeliveryMethod,
|
||||
} from "@/lib/reminder-delivery";
|
||||
import { createClient } from "@/lib/supabase/client";
|
||||
|
||||
export type ProfilePreferences = {
|
||||
@@ -6,6 +10,7 @@ export type ProfilePreferences = {
|
||||
avatarVibe: string;
|
||||
dailyReminderEnabled: boolean;
|
||||
dailyReminderTime: string;
|
||||
dailyReminderDelivery: ReminderDeliveryMethod;
|
||||
};
|
||||
|
||||
type StoredProfilePreferences = {
|
||||
@@ -13,6 +18,7 @@ type StoredProfilePreferences = {
|
||||
avatarVibe?: string;
|
||||
dailyReminderEnabled?: boolean;
|
||||
dailyReminderTime?: string;
|
||||
dailyReminderDelivery?: string;
|
||||
};
|
||||
|
||||
type ProfileRow = {
|
||||
@@ -34,6 +40,7 @@ export const defaultProfilePreferences: ProfilePreferences = {
|
||||
avatarVibe: "Calm",
|
||||
dailyReminderEnabled: false,
|
||||
dailyReminderTime: "20:00",
|
||||
dailyReminderDelivery: "in_app",
|
||||
};
|
||||
|
||||
function isFocusTopic(value: string) {
|
||||
@@ -71,6 +78,9 @@ function normalizePreferences(
|
||||
defaultProfilePreferences.dailyReminderEnabled,
|
||||
dailyReminderTime:
|
||||
partial.dailyReminderTime ?? defaultProfilePreferences.dailyReminderTime,
|
||||
dailyReminderDelivery: normalizeReminderDeliveryMethod(
|
||||
partial.dailyReminderDelivery,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,7 +89,7 @@ function parseStoredPreferencesJson(raw: string): Partial<ProfilePreferences> {
|
||||
const parsed = JSON.parse(raw) as StoredProfilePreferences;
|
||||
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed;
|
||||
return parsed as Partial<ProfilePreferences>;
|
||||
}
|
||||
} catch {
|
||||
return { focusTopic: raw };
|
||||
@@ -112,6 +122,7 @@ function serializeFocusTopicsField(preferences: ProfilePreferences) {
|
||||
avatarVibe: preferences.avatarVibe,
|
||||
dailyReminderEnabled: preferences.dailyReminderEnabled,
|
||||
dailyReminderTime: preferences.dailyReminderTime,
|
||||
dailyReminderDelivery: preferences.dailyReminderDelivery,
|
||||
};
|
||||
|
||||
return JSON.stringify(payload);
|
||||
|
||||
53
lib/reminder-delivery.ts
Normal file
53
lib/reminder-delivery.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
export const reminderDeliveryOptions = [
|
||||
{
|
||||
value: "in_app",
|
||||
label: "In-app",
|
||||
description:
|
||||
"Show reminders inside Habit Pet when you have the site open in a tab.",
|
||||
},
|
||||
{
|
||||
value: "system",
|
||||
label: "Browser (OS)",
|
||||
description:
|
||||
"Use the Web Notifications API. Your browser may show these in the Windows or macOS notification center.",
|
||||
},
|
||||
{
|
||||
value: "both",
|
||||
label: "Both",
|
||||
description: "In-app banners plus browser notifications when permitted.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type ReminderDeliveryMethod =
|
||||
(typeof reminderDeliveryOptions)[number]["value"];
|
||||
|
||||
export function isReminderDeliveryMethod(
|
||||
value: string | undefined,
|
||||
): value is ReminderDeliveryMethod {
|
||||
return reminderDeliveryOptions.some((option) => option.value === value);
|
||||
}
|
||||
|
||||
export function normalizeReminderDeliveryMethod(
|
||||
value: string | undefined,
|
||||
): ReminderDeliveryMethod {
|
||||
if (value && isReminderDeliveryMethod(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return "in_app";
|
||||
}
|
||||
|
||||
export function usesInAppReminders(method: ReminderDeliveryMethod) {
|
||||
return method === "in_app" || method === "both";
|
||||
}
|
||||
|
||||
export function usesSystemReminders(method: ReminderDeliveryMethod) {
|
||||
return method === "system" || method === "both";
|
||||
}
|
||||
|
||||
export function getReminderDeliveryLabel(method: ReminderDeliveryMethod) {
|
||||
return (
|
||||
reminderDeliveryOptions.find((option) => option.value === method)?.label ??
|
||||
"In-app"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user