feat(frontend): Implement SignalR client integration for real-time notifications

Add comprehensive SignalR client implementation with connection management,
React hooks, and UI components for real-time notifications and project updates.

Changes:
- Install @microsoft/signalr package (v9.0.6)
- Create SignalR connection manager with auto-reconnect
- Implement useNotificationHub hook for notification hub
- Implement useProjectHub hook for project hub with room-based subscriptions
- Add NotificationPopover UI component with badge and dropdown
- Create Badge UI component
- Add SignalRProvider for global connection initialization
- Update Header to display real-time notifications
- Update app layout to include SignalRProvider
- Add comprehensive documentation in SIGNALR_INTEGRATION.md

Features:
- JWT authentication with automatic token management
- Auto-reconnect with exponential backoff (0s, 2s, 5s, 10s, 30s)
- Connection state management and indicators
- Real-time notification push
- Project event subscriptions (create, update, delete, status change)
- Room-based project subscriptions
- Typing indicators support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2025-11-04 09:41:13 +01:00
parent 9f05836226
commit bdbb187ee4
12 changed files with 1034 additions and 7 deletions

View File

@@ -0,0 +1,79 @@
'use client';
import { useEffect, useState, useCallback, useRef } from 'react';
import { SignalRConnectionManager } from '@/lib/signalr/ConnectionManager';
import { SIGNALR_CONFIG } from '@/lib/signalr/config';
import { useAuthStore } from '@/stores/authStore';
export interface Notification {
message: string;
type: 'info' | 'success' | 'warning' | 'error' | 'test';
timestamp: string;
}
export function useNotificationHub() {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const [connectionState, setConnectionState] = useState<
'disconnected' | 'connecting' | 'connected' | 'reconnecting'
>('disconnected');
const [notifications, setNotifications] = useState<Notification[]>([]);
const managerRef = useRef<SignalRConnectionManager | null>(null);
useEffect(() => {
if (!isAuthenticated) return;
const manager = new SignalRConnectionManager(
SIGNALR_CONFIG.HUB_URLS.NOTIFICATION
);
managerRef.current = manager;
// 监听连接状态
const unsubscribe = manager.onStateChange(setConnectionState);
// 监听通知事件
manager.on('Notification', (notification: Notification) => {
console.log('[NotificationHub] Received notification:', notification);
setNotifications((prev) => [notification, ...prev].slice(0, 50)); // 保留最近 50 条
});
manager.on(
'NotificationRead',
(data: { NotificationId: string; ReadAt: string }) => {
console.log('[NotificationHub] Notification read:', data);
}
);
// 启动连接
manager.start();
return () => {
unsubscribe();
manager.stop();
};
}, [isAuthenticated]);
const markAsRead = useCallback(async (notificationId: string) => {
if (!managerRef.current) return;
try {
await managerRef.current.invoke('MarkAsRead', notificationId);
} catch (error) {
console.error(
'[NotificationHub] Error marking notification as read:',
error
);
}
}, []);
const clearNotifications = useCallback(() => {
setNotifications([]);
}, []);
return {
connectionState,
notifications,
markAsRead,
clearNotifications,
isConnected: connectionState === 'connected',
};
}