diff --git a/src/app/root.tsx b/src/app/root.tsx index 59e6163..862b18e 100644 --- a/src/app/root.tsx +++ b/src/app/root.tsx @@ -12,6 +12,7 @@ import type { Route } from './+types/root'; import './app.css'; import { Slide } from 'react-toastify'; +import useTradeNotification from '~/features/trade/hooks/useTradeNotification'; import StompProvider from './provider/StompProvider'; import UserIdProvider from './provider/UserInfoProvider'; @@ -75,6 +76,11 @@ export function Layout({ children }: { children: React.ReactNode }) { ); } +function TradeNotificationHandler() { + useTradeNotification(); + return null; +} + export default function App() { return ( diff --git a/src/features/trade/hooks/useTradeNotification.tsx b/src/features/trade/hooks/useTradeNotification.tsx new file mode 100644 index 0000000..249f3e2 --- /dev/null +++ b/src/features/trade/hooks/useTradeNotification.tsx @@ -0,0 +1,52 @@ +import { useEffect, useState } from 'react'; +import { toast } from 'react-toastify/unstyled'; + +import { useStompClient } from '~/app/provider/StompProvider'; +import { api as userApi } from '~/entities/user'; +import type { UserInfoResponse } from '~/entities/user/types/user.type'; + +type TradeNotification = { + ticker: string; + price: number; + size: number; + type: 'ask' | 'bid'; + tradedTime: string; +}; + +export default function useTradeNotification() { + const { client, connected } = useStompClient(); + const [userId, setUserId] = useState(null); + + useEffect(() => { + const fetchUserInfo = async () => { + try { + const response = await userApi.getUserInfo(); + const { data } = await (response.json() as Promise); + setUserId(data.userId); + } catch (error) { + console.error('Failed to fetch user info:', error); + toast.error('사용자 정보를 가져오는데 실패했습니다.'); + } + }; + + fetchUserInfo(); + }, []); + + useEffect(() => { + if (!client || !connected || !userId) return; + + const subscription = client.subscribe( + `/topic/tradeNotification/${userId}`, + (message) => { + const parsedData = JSON.parse(message.body) as TradeNotification; + const tradeType = parsedData.type === 'ask' ? '매도' : '매수'; + const toastMessage = `${parsedData.ticker} ${tradeType} 체결 완료 - 가격: ${parsedData.price}, 수량: ${parsedData.size}`; + toast.success(toastMessage); + }, + ); + + return () => { + subscription.unsubscribe(); + }; + }, [client, connected, userId]); +}