From b17ebb7da0e0cf77322962da2df9deb7c2b3b71d Mon Sep 17 00:00:00 2001 From: Lee JaeJun Date: Fri, 29 Mar 2024 16:53:26 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Remove:=20=EC=97=91=EC=84=B8=EC=8A=A4=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20header=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/authApi.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/api/authApi.ts b/src/api/authApi.ts index a6266fa..d3fa4bb 100644 --- a/src/api/authApi.ts +++ b/src/api/authApi.ts @@ -45,13 +45,10 @@ const authInstance = axios.create({ }); export const getNewAccessToken = async (accessToken: string, refreshToken: string) => { - const response = await authInstance.post( - `${ENDPOINTS.refreshToken}`, - { accessToken, refreshToken }, - { - headers: { Authorization: `Bearer ${refreshToken}` }, - } - ); + const response = await authInstance.post(`${ENDPOINTS.refreshToken}`, { + accessToken, + refreshToken, + }); return response.data; }; From b51eaa48469b3c5d655de62efbee9049e81d0a7e Mon Sep 17 00:00:00 2001 From: Lee JaeJun Date: Fri, 29 Mar 2024 16:53:50 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Add:=20=EB=8C=80=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EB=A1=9C=EB=94=A9=20=EC=8A=A4=ED=94=BC=EB=84=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/ChildComments.module.scss | 20 +++++++++++++++++++ .../components/comment/ChildComments.tsx | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/pages/articleDetail/components/comment/ChildComments.module.scss b/src/pages/articleDetail/components/comment/ChildComments.module.scss index 7c6d35f..9da57a4 100644 --- a/src/pages/articleDetail/components/comment/ChildComments.module.scss +++ b/src/pages/articleDetail/components/comment/ChildComments.module.scss @@ -1,3 +1,23 @@ .childCommentWrapper { padding-left: 1rem; } + +.loadingSpinner { + width: 1rem; + height: 1rem; + border: 2px solid #c1c1c1; + border-top: 2px solid transparent; + border-radius: 50%; + margin-left: 1rem; + + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/src/pages/articleDetail/components/comment/ChildComments.tsx b/src/pages/articleDetail/components/comment/ChildComments.tsx index 4862b4f..a7541e1 100644 --- a/src/pages/articleDetail/components/comment/ChildComments.tsx +++ b/src/pages/articleDetail/components/comment/ChildComments.tsx @@ -6,10 +6,14 @@ interface ChildCommentsProps { } const ChildComments = ({ parentCommentId }: ChildCommentsProps) => { - const { data } = useChildComments(parentCommentId); + const { data, isLoading } = useChildComments(parentCommentId); const childComments = data?.pages.flatMap((page) => page.comments); + if (isLoading) { + return
; + } + return (
{childComments?.map((comment) =>

{comment.content}

)} From 72db886b68e354bd7594cc0570b7c4d86b895f27 Mon Sep 17 00:00:00 2001 From: Lee JaeJun Date: Fri, 29 Mar 2024 16:54:46 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Add:=20=EA=B0=99=EC=9D=80=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=9D=98=20=EC=97=90=EB=9F=AC=EA=B0=80=20=EC=97=AC?= =?UTF-8?q?=EB=9F=AC=EA=B0=9C=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20=ED=95=98?= =?UTF-8?q?=EB=82=98=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/client.ts | 76 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/src/api/client.ts b/src/api/client.ts index 1e19a9a..c9de6a7 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -2,7 +2,7 @@ import { getTokenFromLocalStorage, setTokenToLocalStorage, } from '@/components/signIn/utils/getToken'; -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; import { getNewAccessToken } from './authApi'; import toast from 'react-hot-toast'; @@ -26,6 +26,26 @@ instance.interceptors.request.use((config) => { return config; }); +interface FailedRequest { + resolve: (token: string | null) => void; + reject: (reason?: any) => void; +} + +let isRefreshing = false; +let failedQueue: FailedRequest[] = []; + +const processQueue = (error: AxiosError | null, token: string | null = null) => { + failedQueue.forEach((prom) => { + if (error) { + prom.reject(error); + } else { + prom.resolve(token); + } + }); + + failedQueue = []; +}; + instance.interceptors.response.use( async (response) => { return response; @@ -33,36 +53,50 @@ instance.interceptors.response.use( async (error) => { const { config, - response: { status, data }, + response: { status }, } = error; if (status === 403 || status === 401) { const originalRequest = config; - const { accessToken, refreshToken } = getTokenFromLocalStorage(); + if (!isRefreshing) { + isRefreshing = true; + const { accessToken, refreshToken } = getTokenFromLocalStorage(); + + if (!accessToken || !refreshToken) return; - if (accessToken && refreshToken) { try { - const { data, code } = await getNewAccessToken(accessToken, refreshToken); - if (code === 200) { - const { accessToken: newAccessToken, refreshToken: newRefreshToken } = data.token; - setTokenToLocalStorage(newAccessToken, newRefreshToken); - axios.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`; - originalRequest.headers.authorization = `Bearer ${newAccessToken}`; + const { data } = await getNewAccessToken(accessToken, refreshToken); + + const { accessToken: newAccessToken, refreshToken: newRefreshToken } = data.token; + setTokenToLocalStorage(newAccessToken, newRefreshToken); + axios.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`; + originalRequest.headers.authorization = `Bearer ${newAccessToken}`; - return axios(originalRequest); + processQueue(null, newAccessToken); + return axios(originalRequest); + } catch (refreshError) { + if (refreshError instanceof AxiosError) { + processQueue(refreshError, null); + toast.error('접근 권한이 없습니다.'); } - } catch (error) { - // TODO: 토스트 한 번만 뜨게하기, 리다이렉트 효과적으로 처리할 방법 - toast.error('접근 권한이 없습니다.'); + } finally { + isRefreshing = false; } } - // 리프레시 토큰이 없거나 만료되었을 때 - if (data.path.startsWith('/api/v2/articles')) { - toast.error('접근 권한이 없습니다.'); - return Promise.reject(error); - } - toast.error('다시 로그인 해주세요.'); - return Promise.reject(error); + + return new Promise((resolve, reject) => { + failedQueue.push({ + resolve: () => { + originalRequest.headers.authorization = `Bearer ${ + getTokenFromLocalStorage().accessToken + }`; + resolve(axios(originalRequest)); + }, + reject: () => { + reject(error); + }, + }); + }); } return Promise.reject(error); }