useNotification.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. 'use client';
  2. import { useState, useEffect, useCallback } from 'react';
  3. import { toast } from 'sonner';
  4. import { useSignalRContext } from '@/contexts/signalrProvider';
  5. import { fetchApi } from '@/lib/utils/client';
  6. import { NotificationItem, NotePreview } from '@/types/notification';
  7. import { NotificationReadRequest } from '@/types/request/notification/read';
  8. export type { NotificationItem };
  9. export function useNotification()
  10. {
  11. const { appConnection } = useSignalRContext();
  12. const [unreadNotifCount, setUnreadNotifCount] = useState(0);
  13. const [unreadNoteCount, setUnreadNoteCount] = useState(0);
  14. const [latestNotification, setLatestNotification] = useState<NotificationItem|null>(null);
  15. const [latestNote, setLatestNote] = useState<NotePreview|null>(null);
  16. useEffect(() => {
  17. if (!appConnection) {
  18. return;
  19. }
  20. // AppHub(/hubs/app)에서 알림/쪽지 이벤트 수신
  21. appConnection.on('ReceiveNotification', (data: NotificationItem) => {
  22. setLatestNotification(data);
  23. setUnreadNotifCount(prev => prev + 1);
  24. // 하단 우측 toast (sonner) — 알림 ID 로 중복 방지
  25. toast.info(data.title, {
  26. id: `notif-${data.id}`,
  27. description: data.message,
  28. ...(data.actionUrl ? {
  29. action: {
  30. label: '이동',
  31. onClick: () => { window.location.href = data.actionUrl!; }
  32. }
  33. } : {})
  34. });
  35. });
  36. appConnection.on('ReceiveNote', (data: NotePreview) => {
  37. setLatestNote(data);
  38. setUnreadNoteCount(prev => prev + 1);
  39. toast.info('새 쪽지가 도착했어요', {
  40. id: `note-${data.id}`,
  41. description: `${data.senderName ?? '알 수 없음'}: ${data.title}`,
  42. action: {
  43. label: '쪽지함',
  44. onClick: () => { window.location.href = '/note/inbox'; }
  45. }
  46. });
  47. });
  48. appConnection.on('ReceiveUnreadCounts', (notifCount: number, noteCount: number) => {
  49. setUnreadNotifCount(notifCount);
  50. setUnreadNoteCount(noteCount);
  51. });
  52. return () => {
  53. appConnection.off('ReceiveNotification');
  54. appConnection.off('ReceiveNote');
  55. appConnection.off('ReceiveUnreadCounts');
  56. };
  57. }, [appConnection]);
  58. // 마운트 시 미읽음 카운트 초기 fetch — SignalR ReceiveUnreadCounts 는 새 알림 발생 시에만 오므로
  59. // 페이지 진입 시점의 기존 미읽음 수는 이 endpoint 로 보정.
  60. useEffect(() => {
  61. fetchApi<{ unreadNotif: number; unreadNote: number }>('/api/notification/unread-counts', { silent: true }).then(res => {
  62. if (res.success && res.data) {
  63. setUnreadNotifCount(res.data.unreadNotif);
  64. setUnreadNoteCount(res.data.unreadNote);
  65. }
  66. }).catch(() => {});
  67. }, []);
  68. const markNotifRead = useCallback(async (notificationID?: number) => {
  69. await fetchApi('/api/notification/read', {
  70. method: 'POST',
  71. body: { notificationID } as NotificationReadRequest,
  72. silent: true
  73. });
  74. if (notificationID) {
  75. setUnreadNotifCount(prev => Math.max(0, prev - 1));
  76. } else {
  77. setUnreadNotifCount(0);
  78. }
  79. }, []);
  80. const clearLatestNotification = useCallback(() => setLatestNotification(null), []);
  81. const clearLatestNote = useCallback(() => setLatestNote(null), []);
  82. return {
  83. unreadNotifCount,
  84. unreadNoteCount,
  85. latestNotification,
  86. latestNote,
  87. markNotifRead,
  88. clearLatestNotification,
  89. clearLatestNote
  90. };
  91. }