feat(web): notification settings per kind and channel (#5345, web half)
- A Notifications section on Settings has a row per kind and a toggle each for Inbox, Phone and Email. Labels are short, menu-style. - Admin kinds sit under "Library health", for admins only. - Toggles are optimistic and send only the kind and channel touched. A failed save reverts unless something newer has happened (snippet #5106's generation counter). - With the inbox off, phone and email are disabled: they ride on it. - When email isn't usable, one line says why. With no address it links to the profile. With no SMTP an admin gets a link to Integrations and a listener is simply told. Saving the profile refreshes the settings so the line clears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
import ServerVersion from '$lib/components/ServerVersion.svelte';
|
||||
import ActiveSessions from '$lib/components/ActiveSessions.svelte';
|
||||
import NormalizationSetting from '$lib/components/NormalizationSetting.svelte';
|
||||
import NotificationSettings from '$lib/components/NotificationSettings.svelte';
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -131,6 +132,8 @@
|
||||
email: profileForm.email,
|
||||
});
|
||||
pushToast('Profile saved.');
|
||||
// Whether email notifications are possible depends on the address.
|
||||
queryClient.invalidateQueries({ queryKey: ['notificationSettings'] });
|
||||
} catch (e: unknown) {
|
||||
const code = errCode(e);
|
||||
if (code === 'email_taken') pushToast('That email is already in use.', 'error');
|
||||
@@ -346,6 +349,8 @@
|
||||
<NormalizationSetting />
|
||||
</section>
|
||||
|
||||
<NotificationSettings />
|
||||
|
||||
<section class="space-y-4 rounded border border-border bg-surface p-4">
|
||||
<h2 class="text-lg font-semibold">ListenBrainz</h2>
|
||||
|
||||
@@ -550,7 +555,7 @@
|
||||
</section>
|
||||
|
||||
<!-- Profile card -->
|
||||
<section class="space-y-3 rounded border border-border bg-surface p-4">
|
||||
<section id="profile" class="space-y-3 rounded border border-border bg-surface p-4">
|
||||
<h2 class="text-lg font-semibold">Profile</h2>
|
||||
<p class="text-sm text-text-secondary">
|
||||
Your display name and recovery email.
|
||||
|
||||
@@ -25,6 +25,19 @@ vi.mock('$lib/api/listenbrainz', () => {
|
||||
};
|
||||
});
|
||||
|
||||
// The Notifications section has its own tests (NotificationSettings.test.ts);
|
||||
// here it only needs a settled query so the page renders.
|
||||
vi.mock('$lib/api/notifications', async (importOriginal) => {
|
||||
const mod = await importOriginal<typeof import('$lib/api/notifications')>();
|
||||
const { mockQuery } = await import('../../test-utils/query');
|
||||
return {
|
||||
...mod,
|
||||
createNotificationSettingsQuery: () =>
|
||||
mockQuery({ data: { kinds: [], email_available: true } }),
|
||||
putNotificationSettings: vi.fn()
|
||||
};
|
||||
});
|
||||
|
||||
// The Volume leveling setting loads on mount; keep it off the network.
|
||||
vi.mock('$lib/api/normalization', async (importOriginal) => {
|
||||
const mod = await importOriginal<typeof import('$lib/api/normalization')>();
|
||||
|
||||
@@ -11,6 +11,19 @@ vi.mock('$lib/api/listenbrainz', () => ({
|
||||
setListenBrainzEnabled: vi.fn()
|
||||
}));
|
||||
|
||||
// The Notifications section has its own tests (NotificationSettings.test.ts);
|
||||
// here it only needs a settled query so the page renders.
|
||||
vi.mock('$lib/api/notifications', async (importOriginal) => {
|
||||
const mod = await importOriginal<typeof import('$lib/api/notifications')>();
|
||||
const { mockQuery } = await import('../../test-utils/query');
|
||||
return {
|
||||
...mod,
|
||||
createNotificationSettingsQuery: () =>
|
||||
mockQuery({ data: { kinds: [], email_available: true } }),
|
||||
putNotificationSettings: vi.fn()
|
||||
};
|
||||
});
|
||||
|
||||
// The Volume leveling setting loads on mount; keep it off the network.
|
||||
vi.mock('$lib/api/normalization', async (importOriginal) => {
|
||||
const mod = await importOriginal<typeof import('$lib/api/normalization')>();
|
||||
|
||||
Reference in New Issue
Block a user