diff --git a/ui/src/lib/server/pocketbase.ts b/ui/src/lib/server/pocketbase.ts index 53b9f13..6e4ebe3 100644 --- a/ui/src/lib/server/pocketbase.ts +++ b/ui/src/lib/server/pocketbase.ts @@ -96,6 +96,7 @@ export interface User { polar_customer_id?: string; polar_subscription_id?: string; notify_new_chapters?: boolean; + notify_new_chapters_push?: boolean; } // ─── Auth token cache ───────────────────────────────────────────────────────── @@ -1582,7 +1583,10 @@ export async function updateUserAvatarUrl(userId: string, avatarUrl: string): Pr */ export async function updateUserNotificationPrefs( userId: string, - prefs: { notify_new_chapters?: boolean } + prefs: { + notify_new_chapters?: boolean; + notify_new_chapters_push?: boolean; + } ): Promise { const token = await getToken(); const res = await fetch(`${PB_URL}/api/collections/app_users/records/${userId}`, { diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte index c88d0c9..0f3a43b 100644 --- a/ui/src/routes/+layout.svelte +++ b/ui/src/routes/+layout.svelte @@ -64,6 +64,7 @@ } catch (e) { console.error('clear notifications:', e); } } $effect(() => { if (data.user) loadNotifications(); }); + $effect(() => { if (notificationsOpen && data.user) loadNotifications(); }); const unreadCount = $derived(notifications.filter(n => !n.read).length); // Close search on navigation diff --git a/ui/src/routes/api/profile/+server.ts b/ui/src/routes/api/profile/+server.ts index f7f186f..667d254 100644 --- a/ui/src/routes/api/profile/+server.ts +++ b/ui/src/routes/api/profile/+server.ts @@ -7,7 +7,7 @@ import { log } from '$lib/server/logger'; * PATCH /api/profile * * Update mutable profile preferences (currently: notification preferences). - * Body: { notify_new_chapters?: boolean } + * Body: { notify_new_chapters?: boolean, notify_new_chapters_push?: boolean } */ export const PATCH: RequestHandler = async ({ locals, request }) => { if (!locals.user) error(401, 'Not authenticated'); @@ -19,10 +19,13 @@ export const PATCH: RequestHandler = async ({ locals, request }) => { error(400, 'Invalid JSON'); } - const prefs: { notify_new_chapters?: boolean } = {}; + const prefs: { notify_new_chapters?: boolean; notify_new_chapters_push?: boolean } = {}; if (typeof body.notify_new_chapters === 'boolean') { prefs.notify_new_chapters = body.notify_new_chapters; } + if (typeof body.notify_new_chapters_push === 'boolean') { + prefs.notify_new_chapters_push = body.notify_new_chapters_push; + } if (Object.keys(prefs).length === 0) { error(400, 'No valid preferences provided'); diff --git a/ui/src/routes/profile/+page.server.ts b/ui/src/routes/profile/+page.server.ts index dfef050..ce241c7 100644 --- a/ui/src/routes/profile/+page.server.ts +++ b/ui/src/routes/profile/+page.server.ts @@ -81,6 +81,7 @@ export const load: PageServerLoad = async ({ locals }) => { email, polarCustomerId, notifyNewChapters: freshUser?.notify_new_chapters ?? true, + notifyNewChaptersPush: freshUser?.notify_new_chapters_push ?? true, stats: stats ?? { totalChaptersRead: 0, booksReading: 0, booksCompleted: 0, booksPlanToRead: 0, booksDropped: 0, topGenres: [], diff --git a/ui/src/routes/profile/+page.svelte b/ui/src/routes/profile/+page.svelte index 3cebe8d..b00a320 100644 --- a/ui/src/routes/profile/+page.svelte +++ b/ui/src/routes/profile/+page.svelte @@ -236,8 +236,10 @@ let pushError = $state(''); // ── In-app notifications ────────────────────────────────────────────────────── - let notifyNewChapters = $state(data.notifyNewChapters ?? true); - let notifyNewChaptersSaving = $state(false); + let notifyNewChapters = $state(data.notifyNewChapters ?? true); + let notifyNewChaptersPush = $state(data.notifyNewChaptersPush ?? true); + let notifyNewChaptersSaving = $state(false); + let notifyNewChaptersPushSaving = $state(false); async function toggleNotifyNewChapters() { notifyNewChaptersSaving = true; @@ -248,14 +250,27 @@ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ notify_new_chapters: next }) }); - if (res.ok) { - notifyNewChapters = next; - } + if (res.ok) notifyNewChapters = next; } catch { /* ignore */ } finally { notifyNewChaptersSaving = false; } } + async function toggleNotifyNewChaptersPush() { + notifyNewChaptersPushSaving = true; + const next = !notifyNewChaptersPush; + try { + const res = await fetch('/api/profile', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ notify_new_chapters_push: next }) + }); + if (res.ok) notifyNewChaptersPush = next; + } catch { /* ignore */ } finally { + notifyNewChaptersPushSaving = false; + } + } + $effect(() => { if (!browser) return; if (!('serviceWorker' in navigator) || !('PushManager' in window)) { @@ -802,8 +817,16 @@ Notifications -