From f2f772ee4ab77f5a325073ec67bf43610495b6e7 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Tue, 14 Jan 2025 22:06:11 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EA=B5=AC=EC=A1=B0=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=9A=94=EC=B2=AD=20=EA=B0=9C=EC=84=A0=20#163?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/chat.ts | 5 ++++- src/pages/ChatPage/index.tsx | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/apis/chat.ts b/src/apis/chat.ts index 9017b54e..b976c784 100644 --- a/src/apis/chat.ts +++ b/src/apis/chat.ts @@ -8,8 +8,11 @@ import { authInstance } from '.'; // 메시지 기록 조회 export const getMessage = async ( roomId: number, + date: Date, ): Promise<ChatMessageResponse[]> => { - const response = await authInstance.get(`/api/v1/chat/room/${roomId}`); + const response = await authInstance.get(`/api/v1/chat/room/${roomId}`, { + params: { cursor: date }, + }); return response.data; }; diff --git a/src/pages/ChatPage/index.tsx b/src/pages/ChatPage/index.tsx index 23218c0b..2d80836f 100644 --- a/src/pages/ChatPage/index.tsx +++ b/src/pages/ChatPage/index.tsx @@ -43,7 +43,7 @@ const ChatPage = () => { // 채팅 내용 불러오기 const loadMessage = async () => { - const messageList = await getMessage(Number(roomId)); + const messageList = await getMessage(Number(roomId), new Date()); setMessages(messageList); }; @@ -54,6 +54,9 @@ const ChatPage = () => { brokerURL: WS_URL, webSocketFactory: () => new SockJS(WS_URL), reconnectDelay: 5000, // 자동 재연결 + connectHeaders: { + chatRoomId: roomId as string, // 헤더에 chatRoomId 추가 + }, }); // 구독 @@ -96,6 +99,9 @@ const ChatPage = () => { stompClient.publish({ destination: '/pub/sendMessage', body: JSON.stringify(chatMessage), + headers: { + chatRoomId: roomId as string, // 헤더에 chatRoomId 추가 + }, }); } }; From dc41c922c706c3bbbe53c57b6ba7ce2877d3830f Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Tue, 14 Jan 2025 22:11:47 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=EC=86=8C=EC=BC=93=20=ED=97=A4=EB=8D=94=EC=97=90=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EA=B0=92=20=EC=B6=94=EA=B0=80=20#163?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/ChatPage/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/ChatPage/index.tsx b/src/pages/ChatPage/index.tsx index 2d80836f..848b1b42 100644 --- a/src/pages/ChatPage/index.tsx +++ b/src/pages/ChatPage/index.tsx @@ -55,7 +55,9 @@ const ChatPage = () => { webSocketFactory: () => new SockJS(WS_URL), reconnectDelay: 5000, // 자동 재연결 connectHeaders: { - chatRoomId: roomId as string, // 헤더에 chatRoomId 추가 + nickName: user, + senderType: role === 'ROLE_USER' ? 'MEMBER' : 'BUSINESS', + chatRoomId: roomId as string, }, }); @@ -100,7 +102,9 @@ const ChatPage = () => { destination: '/pub/sendMessage', body: JSON.stringify(chatMessage), headers: { - chatRoomId: roomId as string, // 헤더에 chatRoomId 추가 + nickName: user, + senderType: role === 'ROLE_USER' ? 'MEMBER' : 'BUSINESS', + chatRoomId: roomId as string, }, }); } From 294105d09e2827a8229af2cb41d432519d131217 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Tue, 14 Jan 2025 22:45:35 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=92=84=20Design=20:=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EB=B2=84=ED=8A=BC=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PaymentPage/components/PaymentButton.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pages/PaymentPage/components/PaymentButton.tsx b/src/pages/PaymentPage/components/PaymentButton.tsx index a3b0134f..7702573a 100644 --- a/src/pages/PaymentPage/components/PaymentButton.tsx +++ b/src/pages/PaymentPage/components/PaymentButton.tsx @@ -163,14 +163,16 @@ const PaymentButton = (props: PaymentButtonProps) => { }; return ( - <div className='fixed bottom-0 z-10 flex h-[94px] w-[375px] items-center justify-between border-t-[1px] border-t-subfont bg-white px-[30px] pb-[30px] pt-[18px]'> - <button - type='button' - onClick={handlePaymentButton} - className='btn-primary' - > - {totalAmount.toLocaleString('ko-KR')}원 결제하기 - </button> + <div className='fixed bottom-0 z-10 flex h-[94px] w-[375px] items-center justify-center border-t-[1px] border-t-subfont bg-white pb-[16px]'> + <div className='flex items-center gap-2'> + <button + type='button' + onClick={handlePaymentButton} + className='btn-primary w-custom px-1' + > + {totalAmount.toLocaleString('ko-KR')}원 결제하기 + </button> + </div> </div> ); }; From 11da7b9a576a8f89624f1a50d4a28868305156c5 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Wed, 15 Jan 2025 00:21:27 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EC=9A=94=EC=B2=AD=20params=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81=20#163?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/index.ts | 72 +++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/apis/index.ts b/src/apis/index.ts index 4cfd8516..49729de1 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -1,16 +1,16 @@ import axios, { AxiosError, AxiosInstance, - AxiosRequestConfig, + // AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, } from 'axios'; import { BASE_URL } from '@constants/constants'; import { getAuthToken, - removeAuthToken, - removeRole, - setAuthToken, + // removeAuthToken, + // removeRole, + // setAuthToken, } from '@utils/auth'; import { toast } from 'react-toastify'; @@ -44,40 +44,40 @@ const authInstance: AxiosInstance = axios.create({ */ // response interceptor (토큰 갱신) -authInstance.interceptors.response.use( - (response: AxiosResponse) => response, - // 에러 처리 함수 - async (error: AxiosError) => { - // 401 Unauthorized 에러 시 token 갱신하기 - if (error.response && error.response.status === 401) { - try { - const response = await defaultInstance.post('/reissue'); +// authInstance.interceptors.response.use( +// (response: AxiosResponse) => response, +// // 에러 처리 함수 +// async (error: AxiosError) => { +// // 401 Unauthorized 에러 시 token 갱신하기 +// if (error.response && error.response.status === 401) { +// try { +// const response = await defaultInstance.post('/reissue'); - // reissue 요청 성공 시 - if (response.status === 200) { - const token = response.headers.authorization; - setAuthToken(token); +// // reissue 요청 성공 시 +// if (response.status === 200) { +// const token = response.headers.authorization; +// setAuthToken(token); - const originalRequest = error.config as AxiosRequestConfig; - if (originalRequest.headers) { - originalRequest.headers.Authorization = `Bearer ${token}`; - } - return await authInstance(originalRequest); // 실패했던 요청 재시도 - } - } catch (refreshError) { - // reissue 요청 실패 시 - const reissueError = refreshError as AxiosError; - if (reissueError.response?.status === 401) { - // 로그아웃 - removeAuthToken(); - removeRole(); - window.location.replace('/start'); - } - } - } - return Promise.reject(error); - }, -); +// const originalRequest = error.config as AxiosRequestConfig; +// if (originalRequest.headers) { +// originalRequest.headers.Authorization = `Bearer ${token}`; +// } +// return await authInstance(originalRequest); // 실패했던 요청 재시도 +// } +// } catch (refreshError) { +// // reissue 요청 실패 시 +// const reissueError = refreshError as AxiosError; +// if (reissueError.response?.status === 401) { +// // 로그아웃 +// removeAuthToken(); +// removeRole(); +// window.location.replace('/start'); +// } +// } +// } +// return Promise.reject(error); +// }, +// ); // request interceptor authInstance.interceptors.request.use( From 35cef9166ac768df212eb42ad69c83a3aae4bbbd Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Wed, 15 Jan 2025 00:23:02 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=EC=97=90=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EB=84=98=EA=B2=A8=EC=A3=BC=EA=B8=B0=20=EA=B0=9C=EC=84=A0=20#16?= =?UTF-8?q?3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/ChatPage/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/ChatPage/index.tsx b/src/pages/ChatPage/index.tsx index 848b1b42..c62c4bce 100644 --- a/src/pages/ChatPage/index.tsx +++ b/src/pages/ChatPage/index.tsx @@ -113,11 +113,13 @@ const ChatPage = () => { useEffect(() => { getUserNickName(); loadMessage(); - connect(); + if (user !== '') { + connect(); + } return () => disConnect(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [user]); return ( <> From aa450866fb741a2bce85e263bd0b944613109ef2 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Wed, 15 Jan 2025 10:26:00 +0900 Subject: [PATCH 06/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EC=9A=94=EC=B2=AD=EC=97=90=20params=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81=20?= =?UTF-8?q?#163?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/chat.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/apis/chat.ts b/src/apis/chat.ts index b976c784..6a6022ee 100644 --- a/src/apis/chat.ts +++ b/src/apis/chat.ts @@ -17,14 +17,22 @@ export const getMessage = async ( }; // 채팅방 목록 조회 (사용자) -export const getChatListMember = async (): Promise<ChatListMember[]> => { - const response = await authInstance.get('/api/v1/chat/room'); +export const getChatListMember = async ( + date: Date, +): Promise<ChatListMember[]> => { + const response = await authInstance.get('/api/v1/chat/room', { + params: { cursor: date }, + }); return response.data; }; // 채팅방 목록 조회 (사업자) -export const getChatListBusiness = async (): Promise<ChatListBusiness[]> => { - const response = await authInstance.get('/api/v1/chat/room'); +export const getChatListBusiness = async ( + date: Date, +): Promise<ChatListBusiness[]> => { + const response = await authInstance.get('/api/v1/chat/room', { + params: { cursor: date }, + }); return response.data; }; From f20792b5d51554f3b54ddf2bf8561f60027002ff Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Wed, 15 Jan 2025 10:27:45 +0900 Subject: [PATCH 07/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EB=A7=8C=EB=A3=8C=20=EC=8B=9C=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=84=ED=99=98=20=EA=B0=9C=EC=84=A0=20=EC=A4=91?= =?UTF-8?q?=20#164?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TokenRefresher.tsx | 73 +++++++++++++++++++++++++++++++ src/pages/UserMypage/index.tsx | 19 ++++---- 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 src/components/TokenRefresher.tsx diff --git a/src/components/TokenRefresher.tsx b/src/components/TokenRefresher.tsx new file mode 100644 index 00000000..78872a7c --- /dev/null +++ b/src/components/TokenRefresher.tsx @@ -0,0 +1,73 @@ +import { authInstance, defaultInstance } from '@apis/index'; +import { removeAuthToken, removeRole, setAuthToken } from '@utils/auth'; +import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; +import { useEffect, useState } from 'react'; +import { SyncLoader } from 'react-spinners'; + +const TokenRefresher = ({ children }: { children: React.ReactNode }) => { + const [isRefreshing, setIsRefreshing] = useState(true); + + const handleLogout = () => { + removeAuthToken(); + removeRole(); + window.location.replace('/start'); + }; + + useEffect(() => { + const interceptor = authInstance.interceptors.response.use( + (response: AxiosResponse) => { + setIsRefreshing(false); + return response; + }, + // 에러 처리 + async (error: AxiosError) => { + if (error.response && error.response.status === 401) { + setIsRefreshing(true); + try { + const res = await defaultInstance.post('/reissue'); + + if (res.status === 200) { + const token = res.headers.authorization; + setAuthToken(token); + + const originalRequest = error.config as AxiosRequestConfig; + if (originalRequest.headers) { + originalRequest.headers.Authorization = `Bearer ${token}`; + } + // 실패했던 요청 재시도 + setIsRefreshing(false); + return authInstance(originalRequest); + } + } catch (refreshError) { + // 토큰 갱신 실패 시 로그아웃 처리 + const reissueError = refreshError as AxiosError; + if (reissueError.response?.status === 401) { + // 로그아웃 + handleLogout(); + } + } + } + setIsRefreshing(true); + return Promise.reject(error); + }, + ); + setIsRefreshing(false); + return () => { + authInstance.interceptors.response.eject(interceptor); // 인터셉터 해제 + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (isRefreshing) { + return ( + <div className='flex h-[300px] w-full items-center justify-center'> + <SyncLoader color='#50BEAD' /> + </div> + ); + } + + return <>{children}</>; +}; + +export default TokenRefresher; diff --git a/src/pages/UserMypage/index.tsx b/src/pages/UserMypage/index.tsx index 974234e7..b5e1475b 100644 --- a/src/pages/UserMypage/index.tsx +++ b/src/pages/UserMypage/index.tsx @@ -1,20 +1,23 @@ import HeaderNoTitle from '@layouts/HeaderNoTitle'; import MainLayout from '@layouts/MainLayout'; import BottomNavigation from '@layouts/BottomNavigation'; +import TokenRefresher from '@components/TokenRefresher'; import UserButtonContainer from './components/UserButtonContainer'; import UserInfo from './components/UserInfo'; const UserMypage = () => { return ( <> - <MainLayout> - <HeaderNoTitle /> - <div className='flex h-[263px] w-[375px] flex-col items-center bg-primary'> - <UserInfo /> - <UserButtonContainer /> - </div> - <BottomNavigation /> - </MainLayout> + <TokenRefresher> + <MainLayout> + <HeaderNoTitle /> + <div className='flex h-[263px] w-[375px] flex-col items-center bg-primary'> + <UserInfo /> + <UserButtonContainer /> + </div> + <BottomNavigation /> + </MainLayout> + </TokenRefresher> </> ); }; From 7fa15d94f5548134524595ca091191ec23d53161 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Thu, 16 Jan 2025 14:35:10 +0900 Subject: [PATCH 08/13] =?UTF-8?q?=F0=9F=94=A7=20Fix=20:=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EC=9A=94=EC=B2=AD=20cursor=20=EA=B0=92=20=EC=9E=98?= =?UTF-8?q?=EB=AA=BB=20=EB=84=98=EA=B2=A8=EC=A3=BC=EB=8A=94=20=EA=B2=83=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20#163?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/chat.ts | 15 +++++---------- src/pages/ChatPage/index.tsx | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/apis/chat.ts b/src/apis/chat.ts index 6a6022ee..0e6f98db 100644 --- a/src/apis/chat.ts +++ b/src/apis/chat.ts @@ -8,30 +8,25 @@ import { authInstance } from '.'; // 메시지 기록 조회 export const getMessage = async ( roomId: number, - date: Date, ): Promise<ChatMessageResponse[]> => { const response = await authInstance.get(`/api/v1/chat/room/${roomId}`, { - params: { cursor: date }, + params: { cursor: new Date().toISOString() }, }); return response.data; }; // 채팅방 목록 조회 (사용자) -export const getChatListMember = async ( - date: Date, -): Promise<ChatListMember[]> => { +export const getChatListMember = async (): Promise<ChatListMember[]> => { const response = await authInstance.get('/api/v1/chat/room', { - params: { cursor: date }, + params: { cursor: new Date().toISOString() }, }); return response.data; }; // 채팅방 목록 조회 (사업자) -export const getChatListBusiness = async ( - date: Date, -): Promise<ChatListBusiness[]> => { +export const getChatListBusiness = async (): Promise<ChatListBusiness[]> => { const response = await authInstance.get('/api/v1/chat/room', { - params: { cursor: date }, + params: { cursor: new Date().toISOString() }, }); return response.data; }; diff --git a/src/pages/ChatPage/index.tsx b/src/pages/ChatPage/index.tsx index c62c4bce..fefc35ab 100644 --- a/src/pages/ChatPage/index.tsx +++ b/src/pages/ChatPage/index.tsx @@ -43,7 +43,7 @@ const ChatPage = () => { // 채팅 내용 불러오기 const loadMessage = async () => { - const messageList = await getMessage(Number(roomId), new Date()); + const messageList = await getMessage(Number(roomId)); setMessages(messageList); }; From ced8b406d846ff24524f48f3bf027e71b5652080 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Thu, 16 Jan 2025 14:55:05 +0900 Subject: [PATCH 09/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20S3=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD=EC=A0=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20#151?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/workplace.ts | 10 ++++++++++ src/pages/ModifySpace/components/RoomModify.tsx | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/apis/workplace.ts b/src/apis/workplace.ts index d24caab7..dbbb61c3 100644 --- a/src/apis/workplace.ts +++ b/src/apis/workplace.ts @@ -51,6 +51,16 @@ export const deleteStudyRoom = async (studyRoomId: string): Promise<void> => { return authInstance.delete(`/api/v1/studyroom/${studyRoomId}`); }; +// 스터디룸 이미지 삭제 +export const deleteStudyRoomImage = async ( + fileName: string, + fileLocation: string, +): Promise<void> => { + await authInstance.delete(`/api/delete-object`, { + params: { fileName, fileLocation }, + }); +}; + // 사업장의 스터디룸 찾기 export const getWorkplaceStudyRoom = async ( workplaceId: number, diff --git a/src/pages/ModifySpace/components/RoomModify.tsx b/src/pages/ModifySpace/components/RoomModify.tsx index 6344ef67..78c1f3da 100644 --- a/src/pages/ModifySpace/components/RoomModify.tsx +++ b/src/pages/ModifySpace/components/RoomModify.tsx @@ -6,7 +6,7 @@ import CountPeople from '@components/CountPeople'; import { ERROR_MESSAGE } from '@constants/constants'; import { validate } from 'uuid'; import { useParams } from 'react-router-dom'; -import { getS3URL } from '@apis/workplace'; +import { deleteStudyRoomImage, getS3URL } from '@apis/workplace'; import axios from 'axios'; import useGetRoomListInfo from '../hooks/useGetRoomListInfo'; import usePostRoom from '../hooks/usePostRoom'; @@ -155,6 +155,11 @@ const RoomModify = ({ room, updateRoomData, completeAdd }: RoomModifyProps) => { // 삭제된 이미지가 있을 경우 if (deletedImage) { // 룸 사진 삭제 + deletedImage.forEach((img) => { + const fileName = img; + const fileLocation = `workplace-${workplaceId}/studyroom-${room.id}`; + deleteStudyRoomImage(fileName, fileLocation); + }); } } From fab9227094a9282112362a795c5bd4cfa7e709f9 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Thu, 16 Jan 2025 14:57:00 +0900 Subject: [PATCH 10/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EB=A7=8C=EB=A3=8C=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#164?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/index.ts | 72 +++++++++++++++++----------------- src/pages/UserMypage/index.tsx | 19 ++++----- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/apis/index.ts b/src/apis/index.ts index 49729de1..4cfd8516 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -1,16 +1,16 @@ import axios, { AxiosError, AxiosInstance, - // AxiosRequestConfig, + AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, } from 'axios'; import { BASE_URL } from '@constants/constants'; import { getAuthToken, - // removeAuthToken, - // removeRole, - // setAuthToken, + removeAuthToken, + removeRole, + setAuthToken, } from '@utils/auth'; import { toast } from 'react-toastify'; @@ -44,40 +44,40 @@ const authInstance: AxiosInstance = axios.create({ */ // response interceptor (토큰 갱신) -// authInstance.interceptors.response.use( -// (response: AxiosResponse) => response, -// // 에러 처리 함수 -// async (error: AxiosError) => { -// // 401 Unauthorized 에러 시 token 갱신하기 -// if (error.response && error.response.status === 401) { -// try { -// const response = await defaultInstance.post('/reissue'); +authInstance.interceptors.response.use( + (response: AxiosResponse) => response, + // 에러 처리 함수 + async (error: AxiosError) => { + // 401 Unauthorized 에러 시 token 갱신하기 + if (error.response && error.response.status === 401) { + try { + const response = await defaultInstance.post('/reissue'); -// // reissue 요청 성공 시 -// if (response.status === 200) { -// const token = response.headers.authorization; -// setAuthToken(token); + // reissue 요청 성공 시 + if (response.status === 200) { + const token = response.headers.authorization; + setAuthToken(token); -// const originalRequest = error.config as AxiosRequestConfig; -// if (originalRequest.headers) { -// originalRequest.headers.Authorization = `Bearer ${token}`; -// } -// return await authInstance(originalRequest); // 실패했던 요청 재시도 -// } -// } catch (refreshError) { -// // reissue 요청 실패 시 -// const reissueError = refreshError as AxiosError; -// if (reissueError.response?.status === 401) { -// // 로그아웃 -// removeAuthToken(); -// removeRole(); -// window.location.replace('/start'); -// } -// } -// } -// return Promise.reject(error); -// }, -// ); + const originalRequest = error.config as AxiosRequestConfig; + if (originalRequest.headers) { + originalRequest.headers.Authorization = `Bearer ${token}`; + } + return await authInstance(originalRequest); // 실패했던 요청 재시도 + } + } catch (refreshError) { + // reissue 요청 실패 시 + const reissueError = refreshError as AxiosError; + if (reissueError.response?.status === 401) { + // 로그아웃 + removeAuthToken(); + removeRole(); + window.location.replace('/start'); + } + } + } + return Promise.reject(error); + }, +); // request interceptor authInstance.interceptors.request.use( diff --git a/src/pages/UserMypage/index.tsx b/src/pages/UserMypage/index.tsx index b5e1475b..974234e7 100644 --- a/src/pages/UserMypage/index.tsx +++ b/src/pages/UserMypage/index.tsx @@ -1,23 +1,20 @@ import HeaderNoTitle from '@layouts/HeaderNoTitle'; import MainLayout from '@layouts/MainLayout'; import BottomNavigation from '@layouts/BottomNavigation'; -import TokenRefresher from '@components/TokenRefresher'; import UserButtonContainer from './components/UserButtonContainer'; import UserInfo from './components/UserInfo'; const UserMypage = () => { return ( <> - <TokenRefresher> - <MainLayout> - <HeaderNoTitle /> - <div className='flex h-[263px] w-[375px] flex-col items-center bg-primary'> - <UserInfo /> - <UserButtonContainer /> - </div> - <BottomNavigation /> - </MainLayout> - </TokenRefresher> + <MainLayout> + <HeaderNoTitle /> + <div className='flex h-[263px] w-[375px] flex-col items-center bg-primary'> + <UserInfo /> + <UserButtonContainer /> + </div> + <BottomNavigation /> + </MainLayout> </> ); }; From aa228979b8f74d04e8f953419add8fed953842c8 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Thu, 16 Jan 2025 15:30:02 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20curso?= =?UTF-8?q?r=20=EA=B0=92=20=ED=95=9C=EA=B5=AD=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0=20#163?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/chat.ts | 11 ++++------- src/pages/ChatPage/index.tsx | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/apis/chat.ts b/src/apis/chat.ts index 0e6f98db..7c4c1f37 100644 --- a/src/apis/chat.ts +++ b/src/apis/chat.ts @@ -9,25 +9,22 @@ import { authInstance } from '.'; export const getMessage = async ( roomId: number, ): Promise<ChatMessageResponse[]> => { + const cursor = new Date(Date.now() + 9 * 60 * 60 * 1000); const response = await authInstance.get(`/api/v1/chat/room/${roomId}`, { - params: { cursor: new Date().toISOString() }, + params: { cursor }, }); return response.data; }; // 채팅방 목록 조회 (사용자) export const getChatListMember = async (): Promise<ChatListMember[]> => { - const response = await authInstance.get('/api/v1/chat/room', { - params: { cursor: new Date().toISOString() }, - }); + const response = await authInstance.get('/api/v1/chat/room'); return response.data; }; // 채팅방 목록 조회 (사업자) export const getChatListBusiness = async (): Promise<ChatListBusiness[]> => { - const response = await authInstance.get('/api/v1/chat/room', { - params: { cursor: new Date().toISOString() }, - }); + const response = await authInstance.get('/api/v1/chat/room'); return response.data; }; diff --git a/src/pages/ChatPage/index.tsx b/src/pages/ChatPage/index.tsx index fefc35ab..cc429b42 100644 --- a/src/pages/ChatPage/index.tsx +++ b/src/pages/ChatPage/index.tsx @@ -112,8 +112,8 @@ const ChatPage = () => { useEffect(() => { getUserNickName(); - loadMessage(); if (user !== '') { + loadMessage(); connect(); } From 50c0231d608edf859474872436dc9dc5d6788134 Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Thu, 16 Jan 2025 16:04:48 +0900 Subject: [PATCH 12/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EB=A7=8C=EB=A3=8C=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0=20#164?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TokenRefresher.tsx | 73 ------------------------------- src/pages/MainPage/index.tsx | 21 ++++++++- src/utils/auth.ts | 18 ++++++-- 3 files changed, 34 insertions(+), 78 deletions(-) delete mode 100644 src/components/TokenRefresher.tsx diff --git a/src/components/TokenRefresher.tsx b/src/components/TokenRefresher.tsx deleted file mode 100644 index 78872a7c..00000000 --- a/src/components/TokenRefresher.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { authInstance, defaultInstance } from '@apis/index'; -import { removeAuthToken, removeRole, setAuthToken } from '@utils/auth'; -import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; -import { useEffect, useState } from 'react'; -import { SyncLoader } from 'react-spinners'; - -const TokenRefresher = ({ children }: { children: React.ReactNode }) => { - const [isRefreshing, setIsRefreshing] = useState(true); - - const handleLogout = () => { - removeAuthToken(); - removeRole(); - window.location.replace('/start'); - }; - - useEffect(() => { - const interceptor = authInstance.interceptors.response.use( - (response: AxiosResponse) => { - setIsRefreshing(false); - return response; - }, - // 에러 처리 - async (error: AxiosError) => { - if (error.response && error.response.status === 401) { - setIsRefreshing(true); - try { - const res = await defaultInstance.post('/reissue'); - - if (res.status === 200) { - const token = res.headers.authorization; - setAuthToken(token); - - const originalRequest = error.config as AxiosRequestConfig; - if (originalRequest.headers) { - originalRequest.headers.Authorization = `Bearer ${token}`; - } - // 실패했던 요청 재시도 - setIsRefreshing(false); - return authInstance(originalRequest); - } - } catch (refreshError) { - // 토큰 갱신 실패 시 로그아웃 처리 - const reissueError = refreshError as AxiosError; - if (reissueError.response?.status === 401) { - // 로그아웃 - handleLogout(); - } - } - } - setIsRefreshing(true); - return Promise.reject(error); - }, - ); - setIsRefreshing(false); - return () => { - authInstance.interceptors.response.eject(interceptor); // 인터셉터 해제 - }; - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (isRefreshing) { - return ( - <div className='flex h-[300px] w-full items-center justify-center'> - <SyncLoader color='#50BEAD' /> - </div> - ); - } - - return <>{children}</>; -}; - -export default TokenRefresher; diff --git a/src/pages/MainPage/index.tsx b/src/pages/MainPage/index.tsx index a56e329c..b6159bfe 100644 --- a/src/pages/MainPage/index.tsx +++ b/src/pages/MainPage/index.tsx @@ -4,7 +4,12 @@ import BottomNavigation from '@layouts/BottomNavigation'; import usePositionStore from '@store/positionStore'; import useAuthStore from '@store/authStore'; import { useEffect, useState } from 'react'; -import { getRole } from '@utils/auth'; +import { + getRole, + getTokenExpiration, + removeAuthToken, + removeRole, +} from '@utils/auth'; import MainList from './components/MainList'; import KakaoMap from './components/KakaoMap'; import { @@ -28,9 +33,21 @@ const MainPage = () => { ); // 비로그인 / 사업자 / 사용자 확인 - const { isLogin } = useAuthStore(); + const { isLogin, storeLogout } = useAuthStore(); const [isUser, setIsUser] = useState<boolean>(false); + // 토큰 만료 확인 + useEffect(() => { + const isExpiration = getTokenExpiration(); + if (isExpiration && isExpiration < new Date().getTime()) { + removeAuthToken(); + removeRole(); + storeLogout(); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { if (isLogin) { const role = getRole(); diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 27b8aacd..bad8f05d 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,10 +1,21 @@ const setAuthToken = (accessToken: string) => { - localStorage.setItem('accessToken', accessToken); + const expiration = new Date().getTime() + 5 * 60 * 1000; // 만료시간 5분 + localStorage.setItem( + 'accessToken', + JSON.stringify({ accessToken, expiration }), + ); }; const getAuthToken = () => { - const token = localStorage.getItem('accessToken'); - return token; + const tokenData = localStorage.getItem('accessToken'); + const accessToken = tokenData ? JSON.parse(tokenData).accessToken : ''; + return accessToken; +}; + +const getTokenExpiration = () => { + const tokenData = localStorage.getItem('accessToken'); + const expiration = tokenData ? JSON.parse(tokenData).expiration : ''; + return expiration; }; const removeAuthToken = () => { @@ -29,6 +40,7 @@ const removeRole = () => { export { setAuthToken, getAuthToken, + getTokenExpiration, removeAuthToken, setRole, getRole, From c0d69d335e94b24f68eda115083beb33c5ca0e2f Mon Sep 17 00:00:00 2001 From: eunjju2 <graceun309@gmail.com> Date: Thu, 16 Jan 2025 17:51:07 +0900 Subject: [PATCH 13/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20cursor=20=EA=B0=92=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EC=85=98=201=EC=B0=A8=20=EB=B0=98=EC=98=81=20#163?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/chat.ts | 5 +++-- src/pages/ChatPage/index.tsx | 28 ++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/apis/chat.ts b/src/apis/chat.ts index 7c4c1f37..eb984308 100644 --- a/src/apis/chat.ts +++ b/src/apis/chat.ts @@ -8,10 +8,11 @@ import { authInstance } from '.'; // 메시지 기록 조회 export const getMessage = async ( roomId: number, + cursor?: string, ): Promise<ChatMessageResponse[]> => { - const cursor = new Date(Date.now() + 9 * 60 * 60 * 1000); + const now = new Date(Date.now() + 9 * 60 * 60 * 1000); const response = await authInstance.get(`/api/v1/chat/room/${roomId}`, { - params: { cursor }, + params: { cursor: cursor || now }, }); return response.data; }; diff --git a/src/pages/ChatPage/index.tsx b/src/pages/ChatPage/index.tsx index cc429b42..3613b6fb 100644 --- a/src/pages/ChatPage/index.tsx +++ b/src/pages/ChatPage/index.tsx @@ -42,9 +42,21 @@ const ChatPage = () => { }; // 채팅 내용 불러오기 - const loadMessage = async () => { - const messageList = await getMessage(Number(roomId)); - setMessages(messageList); + const loadMessage = async (cursor?: string) => { + try { + const messageList = await getMessage(Number(roomId), cursor); + setMessages((prevMessages) => [...messageList, ...prevMessages]); + } catch (error) { + toast.error('메시지를 불러오는 중 오류가 발생했습니다.'); + } + }; + + // 채팅 무한 스크롤 + const loadMoreMessages = () => { + if (messages.length > 0) { + const lastMessageTimestamp = messages[0].timestamp; // 첫 번째 메시지의 타임스탬프 사용 + loadMessage(lastMessageTimestamp); + } }; // 소켓 연결 @@ -126,7 +138,15 @@ const ChatPage = () => { <MainLayout> <HeaderOnlyTitle title={chatTitle} /> <div className='fixed left-1/2 top-[93px] flex h-[calc(100vh-93px-94px)] w-custom -translate-x-1/2 overflow-hidden'> - <div className='overflow-y-auto'> + <div + className='overflow-y-auto' + onScroll={(e) => { + const target = e.target as HTMLDivElement; + if (target.scrollTop === 0) { + loadMoreMessages(); + } + }} + > <MessageContainer messages={messages} user={user}