feat: notification center frontend core (PR-3)
- 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
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
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}`);
|
||||
};
|
||||
Reference in New Issue
Block a user