From f14d1395a19f8bb22bcd7589b70b6cc0bdf968cf Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Thu, 27 Feb 2025 10:21:03 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=EC=95=8C=EB=A6=BC=20=EC=BB=A8?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EC=83=81=ED=98=B8=EC=9E=91=EC=9A=A9,=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=8C=85=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/notification.ts | 14 +++ .../components/NotificationItem.tsx | 8 +- .../Notifications/components/WarningModal.tsx | 7 +- src/pages/Notifications/constants/index.ts | 7 +- src/pages/Notifications/index.tsx | 99 +++++++++++++++---- src/types/notifications.d.ts | 7 ++ 6 files changed, 116 insertions(+), 26 deletions(-) create mode 100644 src/apis/notification.ts create mode 100644 src/types/notifications.d.ts diff --git a/src/apis/notification.ts b/src/apis/notification.ts new file mode 100644 index 0000000..7db9516 --- /dev/null +++ b/src/apis/notification.ts @@ -0,0 +1,14 @@ +import client from './client'; + +const getTimeLines = async (getNoti: React.Dispatch>) => { + try { + const res = await client.get('/api/timelines'); + if (!res) throw new Error('타임라인을 받아오는 도중 오류가 발생했습니다.'); + getNoti(res.data.data); + console.log(res); + } catch (error) { + console.error(error); + } +}; + +export { getTimeLines }; diff --git a/src/pages/Notifications/components/NotificationItem.tsx b/src/pages/Notifications/components/NotificationItem.tsx index ff9428e..304945d 100644 --- a/src/pages/Notifications/components/NotificationItem.tsx +++ b/src/pages/Notifications/components/NotificationItem.tsx @@ -5,11 +5,11 @@ import { NOTIFICATION_ICON } from '../constants'; interface NotificationItemProps { type: string; message: string; - isRead: boolean; + read: boolean; onClick: () => void; } -const NotificationItem = ({ type, message, isRead, onClick }: NotificationItemProps) => { +const NotificationItem = ({ type, message, read, onClick }: NotificationItemProps) => { const Icon = NOTIFICATION_ICON[type]; const handleClick = (e: React.MouseEvent) => { @@ -18,9 +18,9 @@ const NotificationItem = ({ type, message, isRead, onClick }: NotificationItemPr }; return ( - +
- {isRead &&
} + {read &&
}

{message}

diff --git a/src/pages/Notifications/components/WarningModal.tsx b/src/pages/Notifications/components/WarningModal.tsx index 8e7e922..e76b17d 100644 --- a/src/pages/Notifications/components/WarningModal.tsx +++ b/src/pages/Notifications/components/WarningModal.tsx @@ -4,10 +4,11 @@ import ModalOverlay from '@/components/ModalOverlay'; interface WarningModalProps { isOpen: boolean; + adminText: string; onClose: () => void; } -const WarningModal = ({ isOpen, onClose }: WarningModalProps) => { +const WarningModal = ({ isOpen, adminText, onClose }: WarningModalProps) => { if (!isOpen) return null; return ( @@ -20,11 +21,15 @@ const WarningModal = ({ isOpen, onClose }: WarningModalProps) => { >
+

관리자 코멘트

+

{adminText}

+

경고 안내

따사로운 서비스 이용을 위해, 부적절하다고 판단되는 편지는 반려하고 있어요. 서로를 존중하는 따뜻한 공간을 만들기 위해 협조 부탁드립니다.

+

경고 규칙

1회 경고: 주의 안내 diff --git a/src/pages/Notifications/constants/index.ts b/src/pages/Notifications/constants/index.ts index 1134831..0b09832 100644 --- a/src/pages/Notifications/constants/index.ts +++ b/src/pages/Notifications/constants/index.ts @@ -4,7 +4,8 @@ export const NOTIFICATION_ICON: Record< string, React.ComponentType> > = { - letter: EnvelopeIcon, - warning: SirenFilledIcon, - board: BoardIcon, + LETTER: EnvelopeIcon, + REPORT: SirenFilledIcon, + SHARE: BoardIcon, + POSTED: BoardIcon, }; diff --git a/src/pages/Notifications/index.tsx b/src/pages/Notifications/index.tsx index bef3d9c..8c7c355 100644 --- a/src/pages/Notifications/index.tsx +++ b/src/pages/Notifications/index.tsx @@ -1,37 +1,100 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; +import { getTimeLines } from '@/apis/notification'; import PageTitle from '@/components/PageTitle'; import NotificationItem from './components/NotificationItem'; import WarningModal from './components/WarningModal'; -const DUMMY_NOTI = [ - { id: 1, type: 'letter', message: '12E31님이 편지를 보냈습니다.', isRead: false }, - { id: 2, type: 'warning', message: '따숨님, 욕설로 인해 경고를 받으셨어요.', isRead: false }, - { id: 3, type: 'letter', message: '12E31님이 편지를 보냈습니다.', isRead: false }, - { id: 4, type: 'letter', message: '12E31님이 편지를 보냈습니다.', isRead: true }, - { id: 5, type: 'letter', message: '12E31님이 편지를 보냈습니다.', isRead: false }, - { id: 6, type: 'board', message: '12E31님과의 대화가 게시판에 공유되었어요.', isRead: false }, +const DUMMY_NOTI: Noti[] = [ { - id: 7, - type: 'board', + timelineId: 1, + alarmType: 'LETTER', + content: 1, + message: '12E31님이 편지를 보냈습니다.', + read: false, + }, + { + timelineId: 2, + alarmType: 'REPORT', + content: '욕설 확인되어 경고 조치함.', + message: '따숨님, 욕설로 인해 경고를 받으셨어요.', + read: false, + }, + { + timelineId: 3, + alarmType: 'LETTER', + content: 1, + message: '12E31님이 편지를 보냈습니다.', + read: false, + }, + { + timelineId: 4, + alarmType: 'LETTER', + content: 1, + message: '12E31님이 편지를 보냈습니다.', + read: true, + }, + { + timelineId: 5, + alarmType: 'LETTER', + content: 1, + message: '12E31님이 편지를 보냈습니다.', + read: false, + }, + { + timelineId: 6, + alarmType: 'POSTED', + content: 1, + message: '12E31님과의 대화가 게시판에 공유되었어요.', + read: false, + }, + { + timelineId: 7, + alarmType: 'SHARE', + content: 1, message: '12E31님과의 게시글에 대한 공유요청을 보냈어요.', - isRead: false, + read: false, }, ]; const NotificationsPage = () => { + const navigate = useNavigate(); + + const [noti, getNoti] = useState([]); + const [isOpenWarningModal, setIsOpenWarningModal] = useState(false); - const handleClickItem = (type: string) => { - if (type === 'warning') { + const [adminText, setAdmintext] = useState(''); + + const handleClickItem = (alarmType: string, content?: string | number) => { + if (alarmType === 'LETTER') { + navigate(`/letter/${content}`); + } + if (alarmType === 'REPORT') { setIsOpenWarningModal(true); + if (typeof content === 'string') setAdmintext(content); + } + if (alarmType === 'SHARE') { + navigate(`/board/letter/${content}`, { state: { isShareLetterPreview: true } }); + } + if (alarmType === 'POSTED') { + navigate(`/board/letter/${content}`); } }; + useEffect(() => { + getTimeLines(getNoti); + }, []); + return ( <> - setIsOpenWarningModal(false)} /> + setIsOpenWarningModal(false)} + />

알림
    {DUMMY_NOTI.map((notification) => ( -
  • +
  • handleClickItem(notification.type)} + read={notification.read} + onClick={() => handleClickItem(notification.alarmType, notification.content)} />
  • ))} diff --git a/src/types/notifications.d.ts b/src/types/notifications.d.ts new file mode 100644 index 0000000..f5d28d2 --- /dev/null +++ b/src/types/notifications.d.ts @@ -0,0 +1,7 @@ +interface Noti { + timelineId: number; + alarmType: string; + content: string | number; + message: string; + read: boolean; +} From 9dc3f837aa6ba66dfbde3c5a852d0ff09742aeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=A7=80=EC=9B=90?= Date: Wed, 5 Mar 2025 23:00:03 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat=20:=20SSE=EA=B5=AC=ED=98=84=EC=8B=9C?= =?UTF-8?q?=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + pnpm-lock.yaml | 16 +++ src/apis/notification.ts | 26 ++++- src/hooks/useServerSentEvents.tsx | 51 +++++++++ .../components/NotificationItem.tsx | 6 +- src/pages/Notifications/index.tsx | 100 +++++++----------- src/types/notifications.d.ts | 2 +- 7 files changed, 137 insertions(+), 66 deletions(-) create mode 100644 src/hooks/useServerSentEvents.tsx diff --git a/package.json b/package.json index 9514795..b25cece 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@tailwindcss/vite": "^4.0.6", "@tanstack/react-query": "^5.66.0", "axios": "^1.7.9", + "event-source-polyfill": "^1.0.31", "gsap": "^3.12.7", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -31,6 +32,7 @@ "devDependencies": { "@eslint/js": "^9.19.0", "@tanstack/eslint-plugin-query": "^5.66.1", + "@types/event-source-polyfill": "^1.0.5", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react-swc": "^3.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dac4254..b22f4db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: axios: specifier: ^1.7.9 version: 1.7.9 + event-source-polyfill: + specifier: ^1.0.31 + version: 1.0.31 gsap: specifier: ^3.12.7 version: 3.12.7 @@ -66,6 +69,9 @@ importers: '@tanstack/eslint-plugin-query': specifier: ^5.66.1 version: 5.66.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@types/event-source-polyfill': + specifier: ^1.0.5 + version: 1.0.5 '@types/react': specifier: ^19.0.8 version: 19.0.8 @@ -948,6 +954,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/event-source-polyfill@1.0.5': + resolution: {integrity: sha512-iaiDuDI2aIFft7XkcwMzDWLqo7LVDixd2sR6B4wxJut9xcp/Ev9bO4EFg4rm6S9QxATLBj5OPxdeocgmhjwKaw==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1394,6 +1403,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-source-polyfill@1.0.31: + resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3137,6 +3149,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/event-source-polyfill@1.0.5': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -3741,6 +3755,8 @@ snapshots: esutils@2.0.3: {} + event-source-polyfill@1.0.31: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: diff --git a/src/apis/notification.ts b/src/apis/notification.ts index 7db9516..869df0a 100644 --- a/src/apis/notification.ts +++ b/src/apis/notification.ts @@ -1,14 +1,34 @@ import client from './client'; -const getTimeLines = async (getNoti: React.Dispatch>) => { +const getTimeLines = async () => { try { const res = await client.get('/api/timelines'); if (!res) throw new Error('타임라인을 받아오는 도중 오류가 발생했습니다.'); - getNoti(res.data.data); console.log(res); + return res; } catch (error) { console.error(error); } }; -export { getTimeLines }; +const patchReadNotification = async (timelineId: number) => { + try { + const res = await client.patch(`/api/notifications/${timelineId}/read`); + if (!res) throw new Error('편지 개별 읽음 처리를 하는 도중 오류가 발생했습니다.'); + return res; + } catch (error) { + console.error(error); + } +}; + +const patchReadNotificationAll = async () => { + try { + const res = await client.patch(`/api/notifications/read`); + if (!res) throw new Error('편지 개별 읽음 처리를 하는 도중 오류가 발생했습니다.'); + return res; + } catch (error) { + console.error(error); + } +}; + +export { getTimeLines, patchReadNotification, patchReadNotificationAll }; diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx new file mode 100644 index 0000000..475c4aa --- /dev/null +++ b/src/hooks/useServerSentEvents.tsx @@ -0,0 +1,51 @@ +import { EventSourcePolyfill } from 'event-source-polyfill'; +import { useEffect } from 'react'; + +import useAuthStore from '@/stores/authStore'; + +export const useServerSentEvents = () => { + const accessToken = useAuthStore.getState().accessToken; + + useEffect(() => { + if (!accessToken) return console.log('구독 연결 실패'); + + let source: EventSourcePolyfill | null = null; + + const connectSSE = () => { + try { + console.log('구독 시작'); + source = new EventSourcePolyfill(`${import.meta.env.VITE_API_URL}/api/notifications/sub`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + source.onmessage = (payload) => { + console.log(payload); + console.log('알림 전송'); + }; + + source.addEventListener('notification', (event) => { + console.log(event); + console.log('알림 전송 dd'); + }); + + source.onerror = (error) => { + console.log(error); + console.log('에러 발생함'); + source?.close(); + // 재연결 로직 추가 가능 + setTimeout(connectSSE, 5000); // 5초 후 재연결 시도 + }; + } catch (error) { + console.error(error); + } + }; + + connectSSE(); + + return () => { + source?.close(); + }; + }, [accessToken]); +}; diff --git a/src/pages/Notifications/components/NotificationItem.tsx b/src/pages/Notifications/components/NotificationItem.tsx index 304945d..52f0db5 100644 --- a/src/pages/Notifications/components/NotificationItem.tsx +++ b/src/pages/Notifications/components/NotificationItem.tsx @@ -4,12 +4,12 @@ import { NOTIFICATION_ICON } from '../constants'; interface NotificationItemProps { type: string; - message: string; + title: string; read: boolean; onClick: () => void; } -const NotificationItem = ({ type, message, read, onClick }: NotificationItemProps) => { +const NotificationItem = ({ type, title, read, onClick }: NotificationItemProps) => { const Icon = NOTIFICATION_ICON[type]; const handleClick = (e: React.MouseEvent) => { @@ -22,7 +22,7 @@ const NotificationItem = ({ type, message, read, onClick }: NotificationItemProp
    {read &&
    } -

    {message}

    +

    {title}

    ); diff --git a/src/pages/Notifications/index.tsx b/src/pages/Notifications/index.tsx index 8c7c355..52cd7c6 100644 --- a/src/pages/Notifications/index.tsx +++ b/src/pages/Notifications/index.tsx @@ -1,68 +1,19 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router'; -import { getTimeLines } from '@/apis/notification'; +import { getTimeLines, patchReadNotification, patchReadNotificationAll } from '@/apis/notification'; import PageTitle from '@/components/PageTitle'; +import { useServerSentEvents } from '@/hooks/useServerSentEvents'; import NotificationItem from './components/NotificationItem'; import WarningModal from './components/WarningModal'; -const DUMMY_NOTI: Noti[] = [ - { - timelineId: 1, - alarmType: 'LETTER', - content: 1, - message: '12E31님이 편지를 보냈습니다.', - read: false, - }, - { - timelineId: 2, - alarmType: 'REPORT', - content: '욕설 확인되어 경고 조치함.', - message: '따숨님, 욕설로 인해 경고를 받으셨어요.', - read: false, - }, - { - timelineId: 3, - alarmType: 'LETTER', - content: 1, - message: '12E31님이 편지를 보냈습니다.', - read: false, - }, - { - timelineId: 4, - alarmType: 'LETTER', - content: 1, - message: '12E31님이 편지를 보냈습니다.', - read: true, - }, - { - timelineId: 5, - alarmType: 'LETTER', - content: 1, - message: '12E31님이 편지를 보냈습니다.', - read: false, - }, - { - timelineId: 6, - alarmType: 'POSTED', - content: 1, - message: '12E31님과의 대화가 게시판에 공유되었어요.', - read: false, - }, - { - timelineId: 7, - alarmType: 'SHARE', - content: 1, - message: '12E31님과의 게시글에 대한 공유요청을 보냈어요.', - read: false, - }, -]; - const NotificationsPage = () => { + useServerSentEvents(); + const navigate = useNavigate(); - const [noti, getNoti] = useState([]); + const [noti, setNoti] = useState([]); const [isOpenWarningModal, setIsOpenWarningModal] = useState(false); @@ -84,8 +35,30 @@ const NotificationsPage = () => { } }; + const handleGetTimeLines = async () => { + const res = await getTimeLines(); + if (res?.status === 200) { + console.log(res); + setNoti(res.data.data.content); + } + }; + + const handlePatchReadNotification = async (timelineId: number) => { + const res = await patchReadNotification(timelineId); + if (res?.status !== 200) { + console.log('읽음처리 에러 발생'); + } + }; + + const handlePatchReadNotificationAll = async () => { + const res = await patchReadNotificationAll(); + if (res?.status !== 200) { + console.log('모두 읽음처리 에러 발생'); + } + }; + useEffect(() => { - getTimeLines(getNoti); + handleGetTimeLines(); }, []); return ( @@ -97,17 +70,26 @@ const NotificationsPage = () => { />
    알림 -
      - {DUMMY_NOTI.map((notification) => ( + {noti.map((notification) => (
    • handleClickItem(notification.alarmType, notification.content)} + onClick={() => { + handleClickItem(notification.alarmType, notification.content); + handlePatchReadNotification(notification.timelineId); + }} />
    • ))} diff --git a/src/types/notifications.d.ts b/src/types/notifications.d.ts index f5d28d2..672864a 100644 --- a/src/types/notifications.d.ts +++ b/src/types/notifications.d.ts @@ -2,6 +2,6 @@ interface Noti { timelineId: number; alarmType: string; content: string | number; - message: string; + title: string; read: boolean; } From cdc0183dc33aa6563301a043af01f96eb4b6f5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=A7=80=EC=9B=90?= Date: Thu, 6 Mar 2025 11:22:07 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat=20:=20SSE=EC=A0=84=EC=97=AD=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EA=B4=80=EB=A6=AC=ED=95=A0=EC=A7=80=20sto?= =?UTF-8?q?re=20=ED=95=98=EB=82=98=20=EB=A7=8C=EB=93=A4=EC=96=B4=EC=84=9C?= =?UTF-8?q?=20=EB=A7=8C=EB=93=A4=EB=A9=B4=EC=84=9C=20=EA=B3=A0=EB=AF=BC?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useServerSentEvents.tsx | 48 +++++++++++++++++---------- src/stores/sseStore.ts | 55 +++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 src/stores/sseStore.ts diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index 475c4aa..9a80953 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -1,39 +1,44 @@ import { EventSourcePolyfill } from 'event-source-polyfill'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import useAuthStore from '@/stores/authStore'; export const useServerSentEvents = () => { const accessToken = useAuthStore.getState().accessToken; + const sourceRef = useRef(null); useEffect(() => { - if (!accessToken) return console.log('구독 연결 실패'); - - let source: EventSourcePolyfill | null = null; + if (!accessToken) { + console.log('로그인 정보 확인불가'); + return; + } const connectSSE = () => { try { console.log('구독 시작'); - source = new EventSourcePolyfill(`${import.meta.env.VITE_API_URL}/api/notifications/sub`, { - headers: { - Authorization: `Bearer ${accessToken}`, + sourceRef.current = new EventSourcePolyfill( + `${import.meta.env.VITE_API_URL}/api/notifications/sub`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, }, - }); + ); - source.onmessage = (payload) => { - console.log(payload); + sourceRef.current.onmessage = (event) => { + console.log(event); console.log('알림 전송'); }; - source.addEventListener('notification', (event) => { - console.log(event); - console.log('알림 전송 dd'); - }); + // sourceRef.current.addEventListener('notification', (event) => { + // console.log(event); + // console.log('알림 전송 dd'); + // }); - source.onerror = (error) => { + sourceRef.current.onerror = (error) => { console.log(error); console.log('에러 발생함'); - source?.close(); + sourceRef.current?.close(); // 재연결 로직 추가 가능 setTimeout(connectSSE, 5000); // 5초 후 재연결 시도 }; @@ -45,7 +50,16 @@ export const useServerSentEvents = () => { connectSSE(); return () => { - source?.close(); + console.log('컴포넌트 언마운트로 인한 구독해제'); + closeSSE(); }; }, [accessToken]); + + // 바깥으로 보낼 closeSSE 함수 + const closeSSE = () => { + sourceRef.current?.close(); + sourceRef.current = null; + }; + + return { closeSSE }; }; diff --git a/src/stores/sseStore.ts b/src/stores/sseStore.ts new file mode 100644 index 0000000..3d52593 --- /dev/null +++ b/src/stores/sseStore.ts @@ -0,0 +1,55 @@ +import { EventSourcePolyfill } from 'event-source-polyfill'; +import { create } from 'zustand'; + +import useAuthStore from '@/stores/authStore'; // 액세스 토큰을 가져올 Zustand 스토어 + +interface SSEState { + messages: string[]; + connectSSE: () => void; + closeSSE: () => void; +} + +export const useSSEStore = create((set, get) => { + let source: EventSourcePolyfill | null = null; // SSE 인스턴스 저장 + + return { + messages: [], + + connectSSE: () => { + const accessToken = useAuthStore.getState().accessToken; // authStore에서 변수 가져오기 + if (!accessToken) { + console.log('엑세스 토큰이 존재하지 않습니다. 구독 불가'); + return; + } + + console.log('🟢 SSE 구독 시작'); + + // 기존 SSE 연결 종료 + get().closeSSE(); + + // 새로운 SSE 연결 생성 + source = new EventSourcePolyfill(`${import.meta.env.VITE_API_URL}/api/notifications/sub`, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + source.onmessage = (event) => { + console.log('SSE 메시지 수신:', event.data); + set((state) => ({ messages: [...state.messages, event.data] })); // 메시지 전역 저장 + }; + + source.onerror = (error) => { + console.log('SSE 오류 발생:', error); + get().closeSSE(); + setTimeout(() => get().connectSSE(), 5000); // 5초 후 재연결 + }; + }, + + // 🔥 SSE 종료 함수 (로그아웃 시 실행) + closeSSE: () => { + console.log('SSE 수동 종료'); + source?.close(); + source = null; + set({ messages: [] }); // 상태 초기화 + }, + }; +}); From 8b1447f6898a480886cbe29c27ac233323c86666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=A7=80=EC=9B=90?= Date: Thu, 6 Mar 2025 16:49:30 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat=20:=20=EC=95=8C=EB=A6=BC=EA=B5=AC?= =?UTF-8?q?=EB=8F=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20App.tsx?= =?UTF-8?q?=EC=97=90=20=ED=9B=85=20=ED=98=B8=EC=B6=9C=ED=95=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 ++ src/hooks/useServerSentEvents.tsx | 8 ++++---- src/pages/Notifications/index.tsx | 4 +--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 57a96b0..4a796a5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import { Route, Routes } from 'react-router'; +import { useServerSentEvents } from './hooks/useServerSentEvents'; import useViewport from './hooks/useViewport'; import Layout from './layouts/Layout'; import MobileLayout from './layouts/MobileLayout'; @@ -27,6 +28,7 @@ import WritePage from './pages/Write'; const App = () => { useViewport(); + useServerSentEvents(); return ( diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index 9a80953..2db424f 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -30,10 +30,10 @@ export const useServerSentEvents = () => { console.log('알림 전송'); }; - // sourceRef.current.addEventListener('notification', (event) => { - // console.log(event); - // console.log('알림 전송 dd'); - // }); + sourceRef.current.addEventListener('notification', (event) => { + console.log(event); + console.log('알림 전송 dd'); + }); sourceRef.current.onerror = (error) => { console.log(error); diff --git a/src/pages/Notifications/index.tsx b/src/pages/Notifications/index.tsx index 52cd7c6..d2bc5de 100644 --- a/src/pages/Notifications/index.tsx +++ b/src/pages/Notifications/index.tsx @@ -3,14 +3,11 @@ import { useNavigate } from 'react-router'; import { getTimeLines, patchReadNotification, patchReadNotificationAll } from '@/apis/notification'; import PageTitle from '@/components/PageTitle'; -import { useServerSentEvents } from '@/hooks/useServerSentEvents'; import NotificationItem from './components/NotificationItem'; import WarningModal from './components/WarningModal'; const NotificationsPage = () => { - useServerSentEvents(); - const navigate = useNavigate(); const [noti, setNoti] = useState([]); @@ -19,6 +16,7 @@ const NotificationsPage = () => { const [adminText, setAdmintext] = useState(''); + // MEMO : 편지 데이터 전송중 데이터도 추가될건데 나중에 데이터 추가되면 코드 업데이트 하긔 const handleClickItem = (alarmType: string, content?: string | number) => { if (alarmType === 'LETTER') { navigate(`/letter/${content}`);