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:
@@ -71,3 +71,56 @@ export function badgeLabel(count: number): string {
|
||||
if (count <= 0) return '';
|
||||
return count > 9 ? '9+' : String(count);
|
||||
}
|
||||
|
||||
// Per-user settings: which channels each kind reaches. Kinds the user cannot
|
||||
// receive (admin kinds, for a non-admin) are not listed at all.
|
||||
export type NotificationKindSetting = {
|
||||
kind: string;
|
||||
admin_only: boolean;
|
||||
inbox: boolean;
|
||||
phone: boolean;
|
||||
email: boolean;
|
||||
};
|
||||
|
||||
export type NotificationChannel = 'inbox' | 'phone' | 'email';
|
||||
|
||||
export type NotificationSettings = {
|
||||
kinds: NotificationKindSetting[];
|
||||
email_available: boolean;
|
||||
email_unavailable_reason?: 'no_address' | 'smtp_not_configured';
|
||||
};
|
||||
|
||||
export type NotificationSettingChange = { kind: string } & Partial<Record<NotificationChannel, boolean>>;
|
||||
|
||||
export async function getNotificationSettings(): Promise<NotificationSettings> {
|
||||
return api.get<NotificationSettings>('/api/me/notification-settings');
|
||||
}
|
||||
|
||||
// Partial: only what the user touched, so two quick toggles on different rows
|
||||
// cannot overwrite each other on the server.
|
||||
export async function putNotificationSettings(
|
||||
changes: NotificationSettingChange[]
|
||||
): Promise<NotificationSettings> {
|
||||
return api.put<NotificationSettings>('/api/me/notification-settings', { kinds: changes });
|
||||
}
|
||||
|
||||
export function createNotificationSettingsQuery() {
|
||||
return createQuery({
|
||||
queryKey: qk.notificationSettings(),
|
||||
queryFn: getNotificationSettings,
|
||||
staleTime: 60_000
|
||||
});
|
||||
}
|
||||
|
||||
/** Short row labels, menu-style (rule 188). Unknown kinds show their key. */
|
||||
export const NOTIFICATION_KIND_LABELS: Record<string, string> = {
|
||||
request_approved: 'Request approved',
|
||||
request_rejected: 'Request declined',
|
||||
request_completed: 'New music arrived',
|
||||
request_pending: 'Requests to review',
|
||||
quarantine_flagged: 'Tracks flagged',
|
||||
scan_failed: 'Library scan failed',
|
||||
tracks_missing: 'Tracks gone missing',
|
||||
duplicates_found: 'Duplicates to review',
|
||||
playback_errors: 'Playback errors'
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user