From d3db2f09b3ca8bafed2f8c61fc938297056c6180 Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Mon, 10 Mar 2025 12:19:03 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat=20:=20=EC=95=88=EC=9D=BD=EC=9D=80=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20count=EA=B0=AF=EC=88=98=20ui=20=EB=B0=91?= =?UTF-8?q?=EC=9E=91=EC=97=85(=EC=A0=84=EC=97=AD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=A7=8C=EB=93=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AlarmButton.tsx | 16 ++++++++++++++++ src/hooks/useServerSentEvents.tsx | 7 +++++++ src/layouts/Header.tsx | 8 ++++---- src/pages/Home/components/HomeHeader.tsx | 5 +++-- src/stores/notification.ts | 15 +++++++++++++++ 5 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 src/components/AlarmButton.tsx create mode 100644 src/stores/notification.ts diff --git a/src/components/AlarmButton.tsx b/src/components/AlarmButton.tsx new file mode 100644 index 0000000..b2f60bd --- /dev/null +++ b/src/components/AlarmButton.tsx @@ -0,0 +1,16 @@ +import { AlarmIcon } from '@/assets/icons'; +import useNotificationStore from '@/stores/notification'; +import { Link } from 'react-router'; + +export default function AlarmButton() { + const notReadCount = useNotificationStore((state) => state.notReadCount); + + return ( + + {notReadCount > 0 && ( +
{notReadCount}
+ )} + + + ); +} diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index 1ad9850..8eee494 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef } from 'react'; import useAuthStore from '@/stores/authStore'; import useToastStore from '@/stores/toastStore'; import { useNavigate } from 'react-router'; +import useNotificationStore from '@/stores/notification'; export const useServerSentEvents = () => { const navigate = useNavigate(); @@ -13,6 +14,8 @@ export const useServerSentEvents = () => { const setToastActive = useToastStore((state) => state.setToastActive); + const addNotReadCount = useNotificationStore((state) => state.addNotReadCount); + useEffect(() => { if (!accessToken) { console.log('로그인 정보 확인불가'); @@ -34,6 +37,8 @@ export const useServerSentEvents = () => { sourceRef.current.onmessage = (event) => { console.log(event); console.log('알림 수신'); + addNotReadCount(); + setToastActive({ toastType: 'Info', title: '새 알림이 도착했어요!', @@ -46,6 +51,8 @@ export const useServerSentEvents = () => { sourceRef.current.onerror = (error) => { console.log(error); console.log('에러 발생함'); + addNotReadCount(); + closeSSE(); // 재연결 로직 추가 가능 setTimeout(connectSSE, 5000); // 5초 후 재연결 시도 diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx index 8efa549..14e0cce 100644 --- a/src/layouts/Header.tsx +++ b/src/layouts/Header.tsx @@ -1,20 +1,20 @@ import { Link, useNavigate } from 'react-router'; -import { AlarmIcon, ArrowLeftIcon, PersonIcon } from '@/assets/icons'; +import { ArrowLeftIcon, PersonIcon } from '@/assets/icons'; +import AlarmButton from '@/components/AlarmButton'; const Header = () => { // TODO: 뒤로 가기 버튼이 보이는 조건 추가 // TODO: 스크롤 발생 시, 어떻게 보여져야 하는지 const navigate = useNavigate(); + return (
- - - + diff --git a/src/pages/Home/components/HomeHeader.tsx b/src/pages/Home/components/HomeHeader.tsx index 3c1db95..e2cbf28 100644 --- a/src/pages/Home/components/HomeHeader.tsx +++ b/src/pages/Home/components/HomeHeader.tsx @@ -1,13 +1,14 @@ import { Link } from 'react-router'; -import { AlarmIcon, PersonIcon } from '@/assets/icons'; +import { PersonIcon } from '@/assets/icons'; +import AlarmButton from '@/components/AlarmButton'; const HomeHeader = () => { return (
- + diff --git a/src/stores/notification.ts b/src/stores/notification.ts new file mode 100644 index 0000000..2a701b9 --- /dev/null +++ b/src/stores/notification.ts @@ -0,0 +1,15 @@ +import { create } from 'zustand'; + +interface NotificationStore { + notReadCount: number; + addNotReadCount: () => void; +} +const useNotificationStore = create((set) => ({ + notReadCount: 0, + addNotReadCount: () => + set((state) => ({ + notReadCount: state.notReadCount + 1, + })), +})); + +export default useNotificationStore; From 46cbb1edfd75e94288844d294d3c53a03542fd95 Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Mon, 10 Mar 2025 16:19:53 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat=20:=20=EC=95=8C=EB=A6=BC=20=EC=95=88?= =?UTF-8?q?=EC=9D=BD=EC=9D=80=20=EB=A9=94=EC=8B=9C=EC=A7=80=20UI=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20+=20SSE=EB=A1=9C=EC=A7=81=201=EC=B0=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AlarmButton.tsx | 16 ------ src/components/NotificationButton.tsx | 21 +++++++ src/hooks/useServerSentEvents.tsx | 56 ++++++++++++++----- src/layouts/Header.tsx | 4 +- src/pages/Home/components/HomeHeader.tsx | 6 +- .../Notifications/components/WarningModal.tsx | 8 +-- src/pages/Notifications/index.tsx | 7 ++- src/stores/notification.ts | 15 ----- src/stores/notificationStore.ts | 25 +++++++++ src/types/notifications.d.ts | 4 +- 10 files changed, 102 insertions(+), 60 deletions(-) delete mode 100644 src/components/AlarmButton.tsx create mode 100644 src/components/NotificationButton.tsx delete mode 100644 src/stores/notification.ts create mode 100644 src/stores/notificationStore.ts diff --git a/src/components/AlarmButton.tsx b/src/components/AlarmButton.tsx deleted file mode 100644 index b2f60bd..0000000 --- a/src/components/AlarmButton.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { AlarmIcon } from '@/assets/icons'; -import useNotificationStore from '@/stores/notification'; -import { Link } from 'react-router'; - -export default function AlarmButton() { - const notReadCount = useNotificationStore((state) => state.notReadCount); - - return ( - - {notReadCount > 0 && ( -
{notReadCount}
- )} - - - ); -} diff --git a/src/components/NotificationButton.tsx b/src/components/NotificationButton.tsx new file mode 100644 index 0000000..b2cb054 --- /dev/null +++ b/src/components/NotificationButton.tsx @@ -0,0 +1,21 @@ +import { AlarmIcon } from '@/assets/icons'; +import useNotificationStore from '@/stores/notificationStore'; +import { Link } from 'react-router'; +import { twMerge } from 'tailwind-merge'; + +export default function NotificationButton() { + const notReadCount = useNotificationStore((state) => state.notReadCount); + const notReadStyle = twMerge( + `absolute -right-1 -bottom-1 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-red-400 text-[8px] text-white`, + notReadCount >= 100 && 'w-4 h-4', + ); + + return ( + + {notReadCount > 0 && ( +
{notReadCount < 100 ? notReadCount : '99+'}
+ )} + + + ); +} diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index 8eee494..c596fbf 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -1,20 +1,54 @@ import { EventSourcePolyfill } from 'event-source-polyfill'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import useAuthStore from '@/stores/authStore'; import useToastStore from '@/stores/toastStore'; import { useNavigate } from 'react-router'; -import useNotificationStore from '@/stores/notification'; +import useNotificationStore from '@/stores/notificationStore'; +import { getNewToken } from '@/apis/auth'; + +interface MessageEventData { + title: string; + alarmType: AlarmType | 'TEST'; +} export const useServerSentEvents = () => { + let reconnect: number | undefined; + const navigate = useNavigate(); const accessToken = useAuthStore((state) => state.accessToken); + const setAccessToken = useAuthStore((state) => state.setAccessToken); const sourceRef = useRef(null); const setToastActive = useToastStore((state) => state.setToastActive); - const addNotReadCount = useNotificationStore((state) => state.addNotReadCount); + const incrementNotReadCount = useNotificationStore((state) => state.incrementNotReadCount); + + const handleOnMessage = async (event: MessageEvent) => { + const data: MessageEventData = await JSON.parse(event.data); + if (data.alarmType === 'TEST') return; + incrementNotReadCount(); + setToastActive({ + toastType: 'Info', + title: data.title, + position: 'Top', + time: 5, + onClick: () => navigate('/mypage/notifications'), + }); + }; + + // 토큰 재발급 함수 + const callReissue = async () => { + try { + const response = await getNewToken(); + if (response?.status !== 200) throw new Error('error while fetching newToken'); + const newToken = response?.data.data.accessToken; + return setAccessToken(newToken); + } catch (e) { + return Promise.reject(e); + } + }; useEffect(() => { if (!accessToken) { @@ -37,27 +71,20 @@ export const useServerSentEvents = () => { sourceRef.current.onmessage = (event) => { console.log(event); console.log('알림 수신'); - addNotReadCount(); - - setToastActive({ - toastType: 'Info', - title: '새 알림이 도착했어요!', - position: 'Top', - time: 5, - onClick: () => navigate('/mypage/notifications'), - }); + handleOnMessage(event); // 나중에 코드 수정해야함 }; sourceRef.current.onerror = (error) => { + callReissue(); console.log(error); console.log('에러 발생함'); - addNotReadCount(); closeSSE(); // 재연결 로직 추가 가능 - setTimeout(connectSSE, 5000); // 5초 후 재연결 시도 + reconnect = setTimeout(connectSSE, 5000); }; } catch (error) { + console.log('에러', error); console.error(error); } }; @@ -71,6 +98,7 @@ export const useServerSentEvents = () => { }, [accessToken]); const closeSSE = () => { + if (reconnect) clearTimeout(reconnect); sourceRef.current?.close(); sourceRef.current = null; }; diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx index 14e0cce..ae58d9c 100644 --- a/src/layouts/Header.tsx +++ b/src/layouts/Header.tsx @@ -1,7 +1,7 @@ import { Link, useNavigate } from 'react-router'; import { ArrowLeftIcon, PersonIcon } from '@/assets/icons'; -import AlarmButton from '@/components/AlarmButton'; +import NotificationButton from '@/components/NotificationButton'; const Header = () => { // TODO: 뒤로 가기 버튼이 보이는 조건 추가 @@ -14,7 +14,7 @@ const Header = () => {
- + diff --git a/src/pages/Home/components/HomeHeader.tsx b/src/pages/Home/components/HomeHeader.tsx index e2cbf28..a1ea28f 100644 --- a/src/pages/Home/components/HomeHeader.tsx +++ b/src/pages/Home/components/HomeHeader.tsx @@ -1,15 +1,13 @@ import { Link } from 'react-router'; import { PersonIcon } from '@/assets/icons'; -import AlarmButton from '@/components/AlarmButton'; +import NotificationButton from '@/components/NotificationButton'; const HomeHeader = () => { return (
- - - + diff --git a/src/pages/Notifications/components/WarningModal.tsx b/src/pages/Notifications/components/WarningModal.tsx index 4470eb3..3b6ea46 100644 --- a/src/pages/Notifications/components/WarningModal.tsx +++ b/src/pages/Notifications/components/WarningModal.tsx @@ -34,13 +34,7 @@ const WarningModal = ({ isOpen, reportContent, onClose }: WarningModalProps) =>

{`${divideContents[1]} 회`}

경고 규칙

-

- 1회 경고: 주의 안내 -
- 2회 경고: 7일 동안 서비스 이용 제한 -
- 3회 경고: 서비스 이용 불가능 -

+

3회 경고: 서비스 이용 불가능

diff --git a/src/pages/Notifications/index.tsx b/src/pages/Notifications/index.tsx index a3c0cdc..f9a3e55 100644 --- a/src/pages/Notifications/index.tsx +++ b/src/pages/Notifications/index.tsx @@ -7,10 +7,13 @@ import PageTitle from '@/components/PageTitle'; import NotificationItem from './components/NotificationItem'; import WarningModal from './components/WarningModal'; import SendingModal from './components/SendingModal'; +import useNotificationStore from '@/stores/notificationStore'; const NotificationsPage = () => { const navigate = useNavigate(); + const setNotReadCount = useNotificationStore((state) => state.setNotReadCount); + const [noti, setNoti] = useState([]); const [isOpenWarningModal, setIsOpenWarningModal] = useState(false); @@ -51,7 +54,8 @@ const NotificationsPage = () => { if (res?.status === 200) { setNoti((curNoti) => curNoti.map((noti) => { - if (noti.timelineId === timelineId) { + if (noti.timelineId === timelineId && !noti.read) { + setNotReadCount(0); return { ...noti, read: true }; } return noti; @@ -73,6 +77,7 @@ const NotificationsPage = () => { return noti; }); }); + setNotReadCount(0); } else { console.log('모두 읽음처리 에러 발생'); } diff --git a/src/stores/notification.ts b/src/stores/notification.ts deleted file mode 100644 index 2a701b9..0000000 --- a/src/stores/notification.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { create } from 'zustand'; - -interface NotificationStore { - notReadCount: number; - addNotReadCount: () => void; -} -const useNotificationStore = create((set) => ({ - notReadCount: 0, - addNotReadCount: () => - set((state) => ({ - notReadCount: state.notReadCount + 1, - })), -})); - -export default useNotificationStore; diff --git a/src/stores/notificationStore.ts b/src/stores/notificationStore.ts new file mode 100644 index 0000000..0a789a5 --- /dev/null +++ b/src/stores/notificationStore.ts @@ -0,0 +1,25 @@ +import { create } from 'zustand'; + +interface NotificationStore { + notReadCount: number; + incrementNotReadCount: () => void; + decrementNotReadCount: () => void; + setNotReadCount: (updateCount: number) => void; +} +const useNotificationStore = create((set) => ({ + notReadCount: 0, + incrementNotReadCount: () => + set((state) => ({ + notReadCount: state.notReadCount + 1, + })), + decrementNotReadCount: () => + set((state) => ({ + notReadCount: state.notReadCount - 1, + })), + setNotReadCount: (updateCount) => + set(() => ({ + notReadCount: updateCount, + })), +})); + +export default useNotificationStore; diff --git a/src/types/notifications.d.ts b/src/types/notifications.d.ts index 672864a..6c131a2 100644 --- a/src/types/notifications.d.ts +++ b/src/types/notifications.d.ts @@ -1,6 +1,8 @@ +type AlarmType = 'SENDING' | 'LETTER' | 'REPORT' | 'SHARE' | 'POSTED'; + interface Noti { timelineId: number; - alarmType: string; + alarmType: AlarmType; content: string | number; title: string; read: boolean; From 928952a00bfd94f47508ecbb5b91aec21e0f2037 Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Mon, 10 Mar 2025 16:55:41 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat=20:=20=EC=95=8C=EB=A6=BC=20=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8UI=EC=97=90=20=EB=8B=B4=EA=B8=B0=EB=8A=94=20?= =?UTF-8?q?=EC=A0=9C=EB=AA=A9=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useServerSentEvents.tsx | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index c596fbf..9e4f85f 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -1,5 +1,5 @@ import { EventSourcePolyfill } from 'event-source-polyfill'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef } from 'react'; import useAuthStore from '@/stores/authStore'; import useToastStore from '@/stores/toastStore'; @@ -9,7 +9,7 @@ import { getNewToken } from '@/apis/auth'; interface MessageEventData { title: string; - alarmType: AlarmType | 'TEST'; + alarmType: AlarmType; } export const useServerSentEvents = () => { @@ -25,13 +25,14 @@ export const useServerSentEvents = () => { const incrementNotReadCount = useNotificationStore((state) => state.incrementNotReadCount); - const handleOnMessage = async (event: MessageEvent) => { - const data: MessageEventData = await JSON.parse(event.data); - if (data.alarmType === 'TEST') return; + const ALARM_TYPE: AlarmType[] = ['SENDING', 'LETTER', 'REPORT', 'SHARE', 'POSTED']; + const handleOnMessage = async (data: string) => { + const message: MessageEventData = await JSON.parse(data); + if (ALARM_TYPE.includes(message.alarmType)) return; incrementNotReadCount(); setToastActive({ toastType: 'Info', - title: data.title, + title: message.title, position: 'Top', time: 5, onClick: () => navigate('/mypage/notifications'), @@ -71,14 +72,12 @@ export const useServerSentEvents = () => { sourceRef.current.onmessage = (event) => { console.log(event); console.log('알림 수신'); - handleOnMessage(event); // 나중에 코드 수정해야함 + handleOnMessage(event.data); }; - sourceRef.current.onerror = (error) => { + sourceRef.current.onerror = () => { + // 에러 발생시 해당 에러가 45초를 넘어서 발생한 에러인지, 401에러인지 판단할 수 있는게 없어서 그냥 에러 발생하면 reissue 넣는걸로 때움 callReissue(); - console.log(error); - console.log('에러 발생함'); - closeSSE(); // 재연결 로직 추가 가능 reconnect = setTimeout(connectSSE, 5000); From d9d0d01e9fcd935aceb6d887000ff80d052f3283 Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Mon, 10 Mar 2025 16:58:46 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat=20:=20=EC=95=8C=EB=A6=BC=20=EB=B0=9B?= =?UTF-8?q?=EC=9D=84=EC=8B=9C=EC=9D=98=20=EC=BD=94=EB=93=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useServerSentEvents.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index 9e4f85f..4467543 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -28,15 +28,16 @@ export const useServerSentEvents = () => { const ALARM_TYPE: AlarmType[] = ['SENDING', 'LETTER', 'REPORT', 'SHARE', 'POSTED']; const handleOnMessage = async (data: string) => { const message: MessageEventData = await JSON.parse(data); - if (ALARM_TYPE.includes(message.alarmType)) return; - incrementNotReadCount(); - setToastActive({ - toastType: 'Info', - title: message.title, - position: 'Top', - time: 5, - onClick: () => navigate('/mypage/notifications'), - }); + if (ALARM_TYPE.includes(message.alarmType)) { + incrementNotReadCount(); + setToastActive({ + toastType: 'Info', + title: message.title, + position: 'Top', + time: 5, + onClick: () => navigate('/mypage/notifications'), + }); + } }; // 토큰 재발급 함수 From f300943aa97a3ef1e6ae28e287002d2a9b4bbcd7 Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Mon, 10 Mar 2025 18:14:42 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat=20:=20SSE=20=EC=97=90=EB=9F=AC=205?= =?UTF-8?q?=EB=B2=88=20=EB=B0=9C=EC=83=9D=EC=8B=9C=20=EC=9E=AC=EA=B7=80=20?= =?UTF-8?q?=EC=A4=91=EC=A7=80=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useServerSentEvents.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index 4467543..160a97e 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -16,6 +16,7 @@ export const useServerSentEvents = () => { let reconnect: number | undefined; const navigate = useNavigate(); + const recallCountRef = useRef(1); const accessToken = useAuthStore((state) => state.accessToken); const setAccessToken = useAuthStore((state) => state.setAccessToken); @@ -80,8 +81,15 @@ export const useServerSentEvents = () => { // 에러 발생시 해당 에러가 45초를 넘어서 발생한 에러인지, 401에러인지 판단할 수 있는게 없어서 그냥 에러 발생하면 reissue 넣는걸로 때움 callReissue(); closeSSE(); + recallCountRef.current += 1; + console.log(recallCountRef.current); + // 재연결 로직 추가 가능 - reconnect = setTimeout(connectSSE, 5000); + if (recallCountRef.current < 5) { + reconnect = setTimeout(connectSSE, 5000); + } else { + console.log('5회 이상 에러발생으로 구독기능 제거'); + } }; } catch (error) { console.log('에러', error); From c59001cf0814edda3633efaa22f36774df04effd Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Mon, 10 Mar 2025 20:53:36 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat=20:=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC,=20UI=20=EA=B5=AC=ED=98=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20+=20=EC=8B=A0=EA=B3=A0=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=ED=86=A0=EC=8A=A4=ED=8A=B8UI=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/admin.ts | 1 + src/apis/notification.ts | 13 ++++++++++++- src/components/NotificationButton.tsx | 15 +++++++++++++++ src/components/ReportModal.tsx | 9 ++++++--- src/components/ToastItem.tsx | 2 +- src/hooks/useServerSentEvents.tsx | 3 +-- src/pages/Notifications/index.tsx | 3 ++- 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/apis/admin.ts b/src/apis/admin.ts index 5eb1580..f768098 100644 --- a/src/apis/admin.ts +++ b/src/apis/admin.ts @@ -7,6 +7,7 @@ const postReports = async (postReportRequest: PostReportRequest) => { return res; } catch (error) { console.error(error); + return null; } }; diff --git a/src/apis/notification.ts b/src/apis/notification.ts index 869df0a..3453135 100644 --- a/src/apis/notification.ts +++ b/src/apis/notification.ts @@ -31,4 +31,15 @@ const patchReadNotificationAll = async () => { } }; -export { getTimeLines, patchReadNotification, patchReadNotificationAll }; +const getNotReadCount = async () => { + try { + const res = await client.get('/api/notifications/not-read'); + if (!res) throw new Error('안 읽은 알림 수를 가져오는 도중 오류가 발생했습니다.'); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +export { getTimeLines, patchReadNotification, patchReadNotificationAll, getNotReadCount }; diff --git a/src/components/NotificationButton.tsx b/src/components/NotificationButton.tsx index b2cb054..0047067 100644 --- a/src/components/NotificationButton.tsx +++ b/src/components/NotificationButton.tsx @@ -1,15 +1,30 @@ +import { getNotReadCount } from '@/apis/notification'; import { AlarmIcon } from '@/assets/icons'; import useNotificationStore from '@/stores/notificationStore'; +import { useEffect } from 'react'; import { Link } from 'react-router'; import { twMerge } from 'tailwind-merge'; export default function NotificationButton() { const notReadCount = useNotificationStore((state) => state.notReadCount); + const setNotReadCount = useNotificationStore((state) => state.setNotReadCount); const notReadStyle = twMerge( `absolute -right-1 -bottom-1 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-red-400 text-[8px] text-white`, notReadCount >= 100 && 'w-4 h-4', ); + const handleGetNotReadCount = async () => { + const res = await getNotReadCount(); + if (res?.status === 200) { + const updateNotReadCount: number = res.data.data.notReadCount; + setNotReadCount(updateNotReadCount); + } + }; + + useEffect(() => { + handleGetNotReadCount(); + }); + return ( {notReadCount > 0 && ( diff --git a/src/components/ReportModal.tsx b/src/components/ReportModal.tsx index b4dd2c8..ce84c91 100644 --- a/src/components/ReportModal.tsx +++ b/src/components/ReportModal.tsx @@ -5,6 +5,7 @@ import { postReports } from '@/apis/admin'; import ConfirmModal from './ConfirmModal'; import TextareaField from './TextareaField'; +import useToastStore from '@/stores/toastStore'; interface ReportModalProps { reportType: ReportType; @@ -40,14 +41,16 @@ const ReportModal = ({ reportType, letterId, onClose }: ReportModalProps) => { else setPostReportRequest((cur) => ({ ...cur, reasonType: reason })); }; + const setToastActive = useToastStore((state) => state.setToastActive); + const handleSubmit = async () => { const res = await postReports(postReportRequest); if (res?.status === 200) { - alert('신고 처리되었습니다.'); + setToastActive({ title: '신고가 접수되었습니다.', toastType: 'Success' }); console.log(res); onClose(); - } else if (res?.status === 409) { - alert('신고한 이력이 있습니다.'); + } else { + setToastActive({ title: '신고한 이력이 있습니다.', toastType: 'Error' }); onClose(); } }; diff --git a/src/components/ToastItem.tsx b/src/components/ToastItem.tsx index 2465e5b..75a459d 100644 --- a/src/components/ToastItem.tsx +++ b/src/components/ToastItem.tsx @@ -26,7 +26,7 @@ export default function ToastItem({ toastObj, index }: { toastObj: ToastObj; ind const animation = `toast-blink ${toastObj.time}s ease-in-out forwards`; const toastStyle = twMerge( - 'fixed bottom-5 left-1/2 z-40 flex h-[40px] max-w-150 min-w-[300px] w-[100%] -translate-1/2 items-center justify-center rounded-lg caption-sb shadow-[0_1px_6px_rgba(200,200,200,0.2)]', + 'fixed bottom-5 left-1/2 z-40 flex h-[40px] max-w-150 min-w-[300px] w-[80%] -translate-1/2 items-center justify-center rounded-lg caption-sb shadow-[0_1px_6px_rgba(200,200,200,0.2)]', TOAST_POSITION[toastObj.position], TOAST_DESIGN[toastObj.toastType].style, ); diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index 160a97e..b33996c 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -82,7 +82,7 @@ export const useServerSentEvents = () => { callReissue(); closeSSE(); recallCountRef.current += 1; - console.log(recallCountRef.current); + console.log('SSE연결 에러 발생'); // 재연결 로직 추가 가능 if (recallCountRef.current < 5) { @@ -92,7 +92,6 @@ export const useServerSentEvents = () => { } }; } catch (error) { - console.log('에러', error); console.error(error); } }; diff --git a/src/pages/Notifications/index.tsx b/src/pages/Notifications/index.tsx index f9a3e55..257453e 100644 --- a/src/pages/Notifications/index.tsx +++ b/src/pages/Notifications/index.tsx @@ -12,6 +12,7 @@ import useNotificationStore from '@/stores/notificationStore'; const NotificationsPage = () => { const navigate = useNavigate(); + const decrementNotReadCount = useNotificationStore((state) => state.decrementNotReadCount); const setNotReadCount = useNotificationStore((state) => state.setNotReadCount); const [noti, setNoti] = useState([]); @@ -55,7 +56,7 @@ const NotificationsPage = () => { setNoti((curNoti) => curNoti.map((noti) => { if (noti.timelineId === timelineId && !noti.read) { - setNotReadCount(0); + decrementNotReadCount(); return { ...noti, read: true }; } return noti; From 9e9bccf9ca6ea28e8694b3d39abeb653ee1d6c92 Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Mon, 10 Mar 2025 21:36:52 +0900 Subject: [PATCH 7/7] =?UTF-8?q?chore=20:=20=ED=8E=B8=EC=A7=80=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=EC=98=AC=EB=96=84=EB=A7=88=EB=8B=A4=20console=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=ED=95=98=EB=8A=94=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useServerSentEvents.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useServerSentEvents.tsx b/src/hooks/useServerSentEvents.tsx index b33996c..a0b406b 100644 --- a/src/hooks/useServerSentEvents.tsx +++ b/src/hooks/useServerSentEvents.tsx @@ -72,8 +72,8 @@ export const useServerSentEvents = () => { ); sourceRef.current.onmessage = (event) => { - console.log(event); - console.log('알림 수신'); + // console.log(event); + // console.log('알림 수신'); handleOnMessage(event.data); };