feat: add real-time notification system with Socket.IO client
Implements the frontend notification client for TEC-2217: 1. notifications-api.ts — API client for list, unread-count, markAsRead, markAllAsRead endpoints 2. notifications-store.ts — Zustand store for notification state (recent list, unread count, dropdown open state) 3. use-socket-notifications.ts — Socket.IO hook that connects with httpOnly cookie auth, listens for notification:new events, auto-reconnects, and syncs unread count on (re)connect 4. notification-bell.tsx — Bell icon with unread badge + dropdown showing 10 most recent notifications with time-ago formatting, mark-as-read on click, mark-all-as-read, and "Xem tất cả" link 5. notifications-provider.tsx — Provider wired into locale layout (inside AuthProvider) to initialize Socket.IO connection 6. Dashboard header — NotificationBell placed before LanguageSwitcher Added socket.io-client dependency. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
52
apps/web/lib/notifications-api.ts
Normal file
52
apps/web/lib/notifications-api.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { apiClient } from './api-client';
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────
|
||||
|
||||
export interface NotificationDto {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
body: string;
|
||||
link: string | null;
|
||||
isRead: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface PaginatedNotifications {
|
||||
data: NotificationDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface UnreadCountDto {
|
||||
count: number;
|
||||
}
|
||||
|
||||
// ─── API Functions ──────────────────────────────────────
|
||||
|
||||
export const notificationsApi = {
|
||||
/** Get paginated notifications for the current user */
|
||||
list: (params: { page?: number; limit?: number } = {}) => {
|
||||
const query = new URLSearchParams();
|
||||
if (params.page) query.append('page', String(params.page));
|
||||
if (params.limit) query.append('limit', String(params.limit));
|
||||
const qs = query.toString();
|
||||
return apiClient.get<PaginatedNotifications>(
|
||||
`/notifications${qs ? `?${qs}` : ''}`,
|
||||
);
|
||||
},
|
||||
|
||||
/** Get unread notification count */
|
||||
unreadCount: () =>
|
||||
apiClient.get<UnreadCountDto>('/notifications/unread-count'),
|
||||
|
||||
/** Mark a single notification as read */
|
||||
markAsRead: (id: string) =>
|
||||
apiClient.patch<{ success: boolean }>(`/notifications/${id}/read`),
|
||||
|
||||
/** Mark all notifications as read */
|
||||
markAllAsRead: () =>
|
||||
apiClient.patch<{ success: boolean }>('/notifications/read-all'),
|
||||
};
|
||||
Reference in New Issue
Block a user