From d3a8592540e770323b3e16baf7196ece4ba34ccc Mon Sep 17 00:00:00 2001 From: shimdokite Date: Sat, 4 May 2024 19:53:25 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[FE]=20=F0=9F=90=9B=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EC=9A=B4=20=EC=B1=84=ED=8C=85=20=EC=8B=9C=20=EC=9E=85=EC=9E=A5?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84=EC=86=A1=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/inquiry/Chat.tsx | 62 ++++++++++++++++--- client/src/components/inquiry/NewChat.tsx | 72 ++++++++++++++++++++--- client/src/stores/chatStore.ts | 9 --- 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/client/src/components/inquiry/Chat.tsx b/client/src/components/inquiry/Chat.tsx index c68799ac..eb60d0a8 100644 --- a/client/src/components/inquiry/Chat.tsx +++ b/client/src/components/inquiry/Chat.tsx @@ -1,31 +1,42 @@ 'use client'; import { useRouter } from 'next/navigation'; -import { useEffect } from 'react'; +import { useEffect, useRef, useState } from 'react'; import InfiniteScroll from 'react-infinite-scroller'; +import { CompatClient, Stomp, StompSubscription } from '@stomp/stompjs'; +import SockJS from 'sockjs-client'; + import useUserStore from '@/stores/userStore'; import useChatStore from '@/stores/chatStore'; +import useScrollToBottom from '@/hooks/useScrollToBottom'; + import useChatMessageQuery from '@/hooks/query/useChatMessageQuery'; -import useNewChatAndExistChatConnect from '@/hooks/useNewChatAndExistChatConnect'; import { ChatInput, ChatBox } from '.'; import checkForToken from '@/utils/checkForToken'; +import { ChatInfo } from '@/types/data'; + interface ChatProps { role: 'user' | 'admin'; } export default function Chat({ role }: ChatProps) { + const client = useRef(); + + const [connected, setConnected] = useState(false); + const [chat, setChat] = useState([]); + const router = useRouter(); - const { roomId, message, isNewChatConnect, setMessage } = useChatStore(); - const { userId, displayName, setClear } = useUserStore(); + const { roomId, message, setMessage } = useChatStore(); + const { accessToken, refreshToken, userId, displayName, setClear } = + useUserStore(); - const { setConnected, setChat, client, scrollRef, chat, connected } = - useNewChatAndExistChatConnect(isNewChatConnect); + const scrollRef = useScrollToBottom(chat); const { data: messageList, @@ -35,6 +46,10 @@ export default function Chat({ role }: ChatProps) { const { authVerify } = checkForToken(); + const url = process.env.NEXT_PUBLIC_API_URL; + + let subscription: StompSubscription | undefined; + const newMessge = { senderId: +userId, chatRoomId: roomId, @@ -49,7 +64,9 @@ export default function Chat({ role }: ChatProps) { authVerify() === 'Refresh Token Expired' ) { return ( - alert('토큰이 만료되었습니다. 다시 로그인 해주시길 바랍니다.'), + alert( + '토큰이 만료되었습니다. 로그아웃 후 다시 로그인 해주시길 바랍니다.', + ), setConnected(false), setClear(), setMessage(''), @@ -58,7 +75,6 @@ export default function Chat({ role }: ChatProps) { } client?.current?.send(`/pub/chatRoom/send`, {}, JSON.stringify(newMessge)); - setMessage(''); }; @@ -68,6 +84,36 @@ export default function Chat({ role }: ChatProps) { } }, [messageList]); + useEffect(() => { + client.current = Stomp.over(() => new SockJS(`${url}/wss`)); + client.current.debug = () => {}; + client.current.connect( + { + Authorization: accessToken, + refresh: refreshToken, + }, + () => { + subscription = client?.current?.subscribe( + `/sub/chatRoom/${roomId}`, + (payload) => { + const receivedMessage: ChatInfo = JSON.parse(payload.body); + + setChat((previousChat) => [...previousChat, receivedMessage]); + }, + ); + + setConnected(true); + }, + ); + + return () => { + client.current?.disconnect(() => { + subscription?.unsubscribe(); + setConnected(false); + }); + }; + }, []); + return (
(); + + const [connected, setConnected] = useState(false); + const [chat, setChat] = useState([]); + const router = useRouter(); - const { message, roomId, isNewChatConnect, setMessage } = useChatStore(); - const { displayName, userId, setClear } = useUserStore(); + const { message, setMessage, roomId } = useChatStore(); + const { accessToken, refreshToken, displayName, userId, setClear } = + useUserStore(); - const { setConnected, client, scrollRef, chat, connected } = - useNewChatAndExistChatConnect(isNewChatConnect); + const scrollRef = useScrollToBottom(chat); const { authVerify } = checkForToken(); + const url = process.env.NEXT_PUBLIC_API_URL; + + let subscription: StompSubscription | undefined; + + const entryMessage = () => { + const adminId = 101; + + client?.current?.send( + `/pub/chatRoom/enter`, + {}, + JSON.stringify({ senderId: +userId, chatRoomId: +roomId, adminId }), + ); + }; + const newMessge = { senderId: +userId, chatRoomId: roomId, @@ -40,7 +64,9 @@ export default function NewChat({ role }: NewChatProps) { authVerify() === 'Refresh Token Expired' ) { return ( - alert('토큰이 만료되었습니다. 다시 로그인 해주시길 바랍니다.'), + alert( + '토큰이 만료되었습니다. 로그아웃 후 다시 로그인 해주시길 바랍니다.', + ), setConnected(false), setClear(), setMessage(''), @@ -49,10 +75,42 @@ export default function NewChat({ role }: NewChatProps) { } client?.current?.send(`/pub/chatRoom/send`, {}, JSON.stringify(newMessge)); - setMessage(''); }; + useEffect(() => { + if (roomId) { + client.current = Stomp.over(() => new SockJS(`${url}/wss`)); + client.current.debug = () => {}; + client.current.connect( + { + Authorization: accessToken, + refresh: refreshToken, + }, + () => { + subscription = client?.current?.subscribe( + `/sub/chatRoom/${roomId}`, + (payload) => { + const receivedMessage: ChatInfo = JSON.parse(payload.body); + + setChat((previousChat) => [...previousChat, receivedMessage]); + }, + ); + + entryMessage(); + setConnected(true); + }, + ); + } + + return () => { + client.current?.disconnect(() => { + subscription?.unsubscribe(); + setConnected(false); + }); + }; + }, [roomId]); + return (
void; - setSelected: (selected: string) => void; setMessage: (message: string) => void; setTitle: (title: string) => void; setRoomId: (roomId: string) => void; setQuestionerId: (questionerId: string) => void; setIsOpen: (isOpen: boolean) => void; - setIsNewChatConnect: (isNewChat: boolean) => void; } const useChatStore = create((set) => ({ @@ -34,7 +30,6 @@ const useChatStore = create((set) => ({ questionerId: '', isOpen: false, - isNewChatConnect: false, setChatList: (chatList) => { set(() => ({ chatList })); @@ -63,10 +58,6 @@ const useChatStore = create((set) => ({ setIsOpen(isOpen) { set({ isOpen, selected: 'home' }); }, - - setIsNewChatConnect(isNewChatConnect) { - set({ isNewChatConnect }); - }, })); export default useChatStore; From d49e420fef65445f0b5af90b1549c9c42f01282c Mon Sep 17 00:00:00 2001 From: shimdokite Date: Sat, 4 May 2024 19:55:30 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EC=8A=A4=ED=81=AC=EB=A1=A4=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20useScrollToBottom=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/useScrollToBottom.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 client/src/hooks/useScrollToBottom.ts diff --git a/client/src/hooks/useScrollToBottom.ts b/client/src/hooks/useScrollToBottom.ts new file mode 100644 index 00000000..49ae2150 --- /dev/null +++ b/client/src/hooks/useScrollToBottom.ts @@ -0,0 +1,17 @@ +import { useEffect, useRef } from 'react'; + +import { ChatInfo } from '@/types/data'; + +const useScrollToBottom = (chat: ChatInfo[]) => { + const scrollRef = useRef(null); + + useEffect(() => { + if (!scrollRef.current) return; + + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + }, [chat]); + + return scrollRef; +}; + +export default useScrollToBottom; From 6a4e0fdd7ad343a02e22f67770f7c8abab819918 Mon Sep 17 00:00:00 2001 From: shimdokite Date: Sat, 4 May 2024 19:55:47 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[FE]=20=F0=9F=94=A5=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20hook=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/useNewChatAndExistChatConnect.ts | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 client/src/hooks/useNewChatAndExistChatConnect.ts diff --git a/client/src/hooks/useNewChatAndExistChatConnect.ts b/client/src/hooks/useNewChatAndExistChatConnect.ts deleted file mode 100644 index 482860cf..00000000 --- a/client/src/hooks/useNewChatAndExistChatConnect.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; - -import { CompatClient, Stomp, StompSubscription } from '@stomp/stompjs'; -import SockJS from 'sockjs-client'; - -import useChatStore from '@/stores/chatStore'; -import useUserStore from '@/stores/userStore'; - -import { ChatInfo } from '@/types/data'; - -const useNewChatAndExistChatConnect = (isNewChatConnect: boolean) => { - const client = useRef(); - const scrollRef = useRef(null); - - const [connected, setConnected] = useState(false); - const [chat, setChat] = useState([]); - - const { roomId, setIsNewChatConnect } = useChatStore(); - const { accessToken, refreshToken, userId } = useUserStore(); - - const url = process.env.NEXT_PUBLIC_API_URL; - - let subscription: StompSubscription | undefined; - - const entryMessage = () => { - const adminId = process.env.NEXT_PUBLIC_ADMIN_ID; - - client?.current?.send( - `/pub/chatRoom/enter`, - {}, - JSON.stringify({ senderId: +userId, chatRoomId: +roomId, adminId }), - ); - }; - - useEffect(() => { - if (!roomId) return; - - client.current = Stomp.over(() => new SockJS(`${url}/wss`)); - client.current.debug = () => {}; - client.current.connect( - { - Authorization: accessToken, - refresh: refreshToken, - }, - () => { - subscription = client?.current?.subscribe( - `/sub/chatRoom/${roomId}`, - (payload) => { - const receivedMessage: ChatInfo = JSON.parse(payload.body); - - setChat((previousChat) => [...previousChat, receivedMessage]); - }, - ); - - if (isNewChatConnect) { - entryMessage(); - } - - setConnected(true); - }, - ); - - return () => { - client.current?.disconnect(() => { - subscription?.unsubscribe(); - setConnected(false); - setIsNewChatConnect(false); - }); - }; - }, [roomId]); - - useEffect(() => { - if (!scrollRef.current) return; - - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - }, [chat]); - - return { setConnected, setChat, client, scrollRef, chat, connected }; -}; - -export default useNewChatAndExistChatConnect; From a1ed8844eb20e01d228a2e48ee00beeb68934294 Mon Sep 17 00:00:00 2001 From: shimdokite Date: Mon, 6 May 2024 22:55:40 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20localStorage.ts?= =?UTF-8?q?=20utils=20=ED=8F=B4=EB=8D=94=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/{api => utils}/localStorage.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/src/{api => utils}/localStorage.ts (100%) diff --git a/client/src/api/localStorage.ts b/client/src/utils/localStorage.ts similarity index 100% rename from client/src/api/localStorage.ts rename to client/src/utils/localStorage.ts From cb5fa29990d7de77418be345527fa44f63d6bbbe Mon Sep 17 00:00:00 2001 From: shimdokite Date: Mon, 6 May 2024 22:55:58 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EA=B4=80=EB=A0=A8=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/api/axios.ts | 9 +++------ client/src/utils/checkForToken.ts | 8 +++----- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/client/src/api/axios.ts b/client/src/api/axios.ts index b4ae54ec..fa596df3 100644 --- a/client/src/api/axios.ts +++ b/client/src/api/axios.ts @@ -1,15 +1,12 @@ import axios, { AxiosResponse } from 'axios'; -import LocalStorage from './localStorage'; - import checkForToken from '@/utils/checkForToken'; +import LocalStorage from '@/utils/localStorage'; const { authVerify, storageData } = checkForToken(); -const accessToken = - typeof window !== 'undefined' ? storageData.state.accessToken : null; -const refreshToken = - typeof window !== 'undefined' ? storageData.state.refreshToken : null; +const accessToken = storageData?.state.accessToken; +const refreshToken = storageData?.state.refreshToken; export const instance = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL, diff --git a/client/src/utils/checkForToken.ts b/client/src/utils/checkForToken.ts index b31c1223..854c15ea 100644 --- a/client/src/utils/checkForToken.ts +++ b/client/src/utils/checkForToken.ts @@ -1,12 +1,10 @@ -import LocalStorage from '@/api/localStorage'; +import LocalStorage from './localStorage'; const checkForToken = () => { const storageData = LocalStorage.getItem('user-key'); - const accessToken = - typeof window !== 'undefined' ? storageData.state.accessToken : null; - const refreshToken = - typeof window !== 'undefined' ? storageData.state.refreshToken : null; + const accessToken = storageData?.state.accessToken; + const refreshToken = storageData?.state.refreshToken; const parseJWT = (token: string | null) => { if (token) return JSON.parse(atob(token.split('.')[1]));