feat: add notification client APIs

Adds typed frontend client calls, hooks, and models for notification preferences, feed reads, and read-state mutations.
This commit is contained in:
Matthew Meszaros
2026-06-08 15:05:09 +02:00
parent 3eb96e33a1
commit 55a02b6441
3 changed files with 134 additions and 0 deletions
@@ -0,0 +1,48 @@
import type { AppNotification, NotificationPreferences } from "@/lib/api/models/app/notifications/Notification";
import Request from "../../Request";
export async function getNotificationPreferences(): Promise<NotificationPreferences> {
const res = await Request<{ preferences: NotificationPreferences }>({
method: "GET",
url: "/auth/me/notification-preferences",
authorization: true,
});
return res.preferences;
}
export async function updateNotificationPreferences(preferences: NotificationPreferences): Promise<NotificationPreferences> {
const res = await Request<{ preferences: NotificationPreferences }>({
method: "PUT",
url: "/auth/me/notification-preferences",
data: { preferences },
authorization: true,
});
return res.preferences;
}
export async function listNotifications(unreadOnly = false, limit = 50): Promise<{ notifications: AppNotification[]; unread: number }> {
const params = new URLSearchParams();
if (unreadOnly) params.set("unread", "1");
params.set("limit", String(limit));
return await Request<{ notifications: AppNotification[]; unread: number }>({
method: "GET",
url: `/auth/me/notifications?${params.toString()}`,
authorization: true,
});
}
export async function markNotificationRead(id: string): Promise<void> {
await Request<void>({
method: "POST",
url: `/auth/me/notifications/${id}/read`,
authorization: true,
});
}
export async function markAllNotificationsRead(): Promise<void> {
await Request<void>({
method: "PUT",
url: "/auth/me/notifications",
authorization: true,
});
}
@@ -0,0 +1,52 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
getNotificationPreferences,
updateNotificationPreferences,
listNotifications,
markNotificationRead,
markAllNotificationsRead,
} from "@/lib/api/client/app/notifications/notifications";
import type { NotificationPreferences } from "@/lib/api/models/app/notifications/Notification";
const PREFS_KEY = ["notifications", "preferences"];
const FEED_KEY = ["notifications", "feed"];
export function useNotificationPreferences() {
return useQuery({
queryKey: PREFS_KEY,
queryFn: getNotificationPreferences,
staleTime: 60_000,
});
}
export function useUpdateNotificationPreferences() {
const qc = useQueryClient();
return useMutation({
mutationFn: (prefs: NotificationPreferences) => updateNotificationPreferences(prefs),
onSuccess: () => qc.invalidateQueries({ queryKey: PREFS_KEY }),
});
}
export function useNotifications() {
return useQuery({
queryKey: FEED_KEY,
queryFn: () => listNotifications(false, 50),
staleTime: 15_000,
});
}
export function useMarkNotificationRead() {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => markNotificationRead(id),
onSuccess: () => qc.invalidateQueries({ queryKey: FEED_KEY }),
});
}
export function useMarkAllNotificationsRead() {
const qc = useQueryClient();
return useMutation({
mutationFn: () => markAllNotificationsRead(),
onSuccess: () => qc.invalidateQueries({ queryKey: FEED_KEY }),
});
}
@@ -0,0 +1,34 @@
// In-app notification feed + per-user preferences (mirrors the Go models).
export interface ChannelPrefs {
in_app: boolean;
email: boolean; // reserved; not delivered yet
}
export interface CategoryPref {
enabled: boolean;
channels: ChannelPrefs;
}
export interface NotificationPreferences {
inbound_reply: CategoryPref;
inbound_out_of_office: CategoryPref;
health_bounce: CategoryPref;
health_complaint: CategoryPref;
health_worker_downtime: CategoryPref;
}
export type NotificationCategoryKey = keyof NotificationPreferences;
export interface AppNotification {
id: string;
user_id: string;
organization_id?: string | null;
category: string;
title: string;
body?: string;
link?: string;
metadata?: Record<string, unknown>;
read_at?: string | null;
created_at: string;
}