2bec205a30
- Bell icon in icon registry (Phosphor Bell) - NotificationProvider context with polling (15s unread / 30s list) - useNotifications() hook with optimistic updates - NotificationCenter component: bell + badge + dropdown panel - NotificationItem component: severity icon, title, relative time, actions - AppShell integration: mount in header-actions, hidden on mobile - CSS styles: dropdown, items, unread/read states, empty state - formatRelativeTime utility (custom, no new deps) - 25 frontend tests (9 hook + 6 item + 10 center) Quality gates: vitest 25 passed, tsc clean, eslint clean
65 lines
1.5 KiB
TypeScript
65 lines
1.5 KiB
TypeScript
import { apiClient } from "./client";
|
|
|
|
export interface NotificationItem {
|
|
id: string;
|
|
user_id: string;
|
|
category: string;
|
|
severity: "info" | "warning" | "error" | "success";
|
|
title: string;
|
|
message: string | null;
|
|
source_type: string | null;
|
|
source_id: string | null;
|
|
metadata: Record<string, unknown>;
|
|
read_at: string | null;
|
|
dismissed_at: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface NotificationListResponse {
|
|
items: NotificationItem[];
|
|
total: number;
|
|
limit: number;
|
|
offset: number;
|
|
}
|
|
|
|
export interface UnreadCountResponse {
|
|
count: number;
|
|
}
|
|
|
|
export interface MarkAllReadResponse {
|
|
marked_count: number;
|
|
}
|
|
|
|
export const getNotifications = async (): Promise<NotificationListResponse> => {
|
|
const response =
|
|
await apiClient.get<NotificationListResponse>("/notifications");
|
|
return response.data;
|
|
};
|
|
|
|
export const getUnreadCount = async (): Promise<number> => {
|
|
const response = await apiClient.get<UnreadCountResponse>(
|
|
"/notifications/unread",
|
|
);
|
|
return response.data.count;
|
|
};
|
|
|
|
export const markNotificationRead = async (
|
|
id: string,
|
|
): Promise<NotificationItem> => {
|
|
const response = await apiClient.patch<NotificationItem>(
|
|
`/notifications/${id}/read`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
export const markAllNotificationsRead = async (): Promise<number> => {
|
|
const response = await apiClient.post<MarkAllReadResponse>(
|
|
"/notifications/mark-all-read",
|
|
);
|
|
return response.data.marked_count;
|
|
};
|
|
|
|
export const dismissNotification = async (id: string): Promise<void> => {
|
|
await apiClient.delete(`/notifications/${id}`);
|
|
};
|