diff --git a/src/apis/domains/users/api.ts b/src/apis/domains/users/api.ts index d8fbd665..13b1dc6e 100644 --- a/src/apis/domains/users/api.ts +++ b/src/apis/domains/users/api.ts @@ -3,7 +3,7 @@ import { components } from "@typings/api/schema"; import { ApiResponseType } from "@typings/commonType"; import { AxiosResponse } from "axios"; -type LoginSuccessResponse = components["schemas"]["LoginSuccessResponse"]; +type LoginSuccessResponse = components["schemas"]["MemberLoginResponse"]; export const postKakaoLogin = async (authCode: string): Promise => { try { diff --git a/src/apis/domains/users/queries.ts b/src/apis/domains/users/queries.ts index 82d85994..a51b39e7 100644 --- a/src/apis/domains/users/queries.ts +++ b/src/apis/domains/users/queries.ts @@ -8,7 +8,7 @@ const QUERY_KEY = { KAKAO_LOGIN: "kakaoLogin", }; -type LoginSuccessResponse = components["schemas"]["LoginSuccessResponse"]; +type LoginSuccessResponse = components["schemas"]["MemberLoginResponse"]; export const usePostKakaoLogin = () => { const [, setUserData] = useAtom(userAtom); diff --git a/src/hooks/useTokenRefresher.tsx b/src/hooks/useTokenRefresher.tsx index 4695916a..ac5d30b4 100644 --- a/src/hooks/useTokenRefresher.tsx +++ b/src/hooks/useTokenRefresher.tsx @@ -1,29 +1,32 @@ import { instance } from "@apis/index"; +import axios, { AxiosResponse } from "axios"; import { useEffect } from "react"; -import { useNavigate } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import useModal from "./useModal"; export default function TokenRefresher() { + const location = useLocation(); const navigate = useNavigate(); const { openAlert } = useModal(); useEffect(() => { - const user = localStorage.getItem("user"); - - const getCookie = (name: string): string | undefined => { - const value = `; ${document.cookie}`; - const parts = value.split(`; ${name}=`); - if (parts.length === 2) { - return parts.pop()?.split(";").shift(); - } - }; - - if (user) { - if (!JSON.parse(user)?.role) { + const userString = localStorage.getItem("user"); + if (userString) { + if (!JSON.parse(userString)?.role) { // 기존에 존재하던 유저 role 유무로 임시로 토큰 제거 후 리로드 localStorage.clear(); - openAlert({ title: "다시 로그인 해주세요." }); - window.location.reload(); + openAlert({ + title: "다시 로그인 해주세요.", + okCallback: () => { + localStorage.clear(); + if (location.pathname === "/main") { + window.location.reload(); + } else { + navigate("/main"); + } + }, + }); + return; } } @@ -34,53 +37,45 @@ export default function TokenRefresher() { const originalConfig = error.config; const status = error?.response?.status; - // if (status === 401 && user) { - // try { - // const refreshToken = getCookie("refreshToken"); - - // const response: AxiosResponse<{ data: { accessToken: string } }> = await axios.get( - // `${import.meta.env.VITE_API_BASE_URL}/users/refresh-token`, - // { - // headers: { - // Authorization_Refresh: `Bearer ${refreshToken}`, - // }, - // } - // ); + if (status === 401 && userString) { + try { + const response: AxiosResponse<{ data: { accessToken: string } }> = await axios.get( + `${import.meta.env.VITE_API_BASE_URL}/users/refresh-token`, + { + withCredentials: true, + } + ); - // const newAccessToken = response.data?.data?.accessToken; + const newAccessToken = response.data?.data?.accessToken; - // if (newAccessToken) { - // localStorage.setItem("accessToken", `Bearer ${newAccessToken}`); - // originalConfig.headers["Authorization"] = `Bearer ${newAccessToken}`; - // return instance(originalConfig); - // } - // throw new Error("Failed to refresh access token"); - // } catch (refreshError) { - // if (refreshError.response?.status === 404) { - // // 리프레시 토큰도 없다? 다시 로그인 - // // 그렇지 않으면 요청이 성공하겠찌.. ? - // } - // console.error("Token refresh failed:", refreshError); - // // localStorage.clear(); - // // navigate("/main"); - // openAlert({ title: "장시간 미활동으로 인해 \n자동으로 로그아웃 되었습니다." }); - // // window.location.reload(); - // } - // } else if (status === 500) { - // openAlert({ - // title: "서버에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.", - // }); - // } - - if (status === 401) { + const user = JSON.parse(userString); + user.accessToken = newAccessToken; + if (newAccessToken) { + localStorage.setItem("user", JSON.stringify(user)); + originalConfig.headers["Authorization"] = `Bearer ${newAccessToken}`; + return instance(originalConfig); + } + throw new Error("Failed to refresh access token"); + } catch (refreshError) { + if (refreshError.response?.status === 401) { + openAlert({ + title: "장시간 미활동으로 인해 \n자동으로 로그아웃 되었습니다.", + okCallback: () => { + localStorage.clear(); + if (location.pathname === "/main") { + window.location.reload(); + } else { + navigate("/main"); + } + }, + }); + } + } + } else if (status === 500) { openAlert({ title: "서버에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.", }); - localStorage.clear(); - navigate("/main"); } - - console.error("응답 에러:", error); return Promise.reject(error); } ); diff --git a/src/typings/api/schema/index.ts b/src/typings/api/schema/index.ts index 67e0a120..f0adc6dc 100644 --- a/src/typings/api/schema/index.ts +++ b/src/typings/api/schema/index.ts @@ -790,17 +790,16 @@ export interface components { /** @enum {string} */ socialType: "KAKAO"; }; - LoginSuccessResponse: { + MemberLoginResponse: { accessToken?: string; - refreshToken?: string; nickname?: string; role?: string; }; - SuccessResponseLoginSuccessResponse: { + SuccessResponseMemberLoginResponse: { /** Format: int32 */ status?: number; message?: string; - data?: components["schemas"]["LoginSuccessResponse"]; + data?: components["schemas"]["MemberLoginResponse"]; }; CastRequest: { castName?: string; @@ -1095,14 +1094,14 @@ export interface components { message?: string; data?: components["schemas"]["BookingCancelResponse"]; }; - AccessTokenGetSuccess: { + AccessTokenGenerateResponse: { accessToken?: string; }; - SuccessResponseAccessTokenGetSuccess: { + SuccessResponseAccessTokenGenerateResponse: { /** Format: int32 */ status?: number; message?: string; - data?: components["schemas"]["AccessTokenGetSuccess"]; + data?: components["schemas"]["AccessTokenGenerateResponse"]; }; SuccessResponseTicketRetrieveResponse: { /** Format: int32 */ @@ -1596,7 +1595,7 @@ export interface operations { "application/json;charset=UTF-8": components["schemas"]["SuccessResponsePerformanceModifyResponse"]; }; }; - /** @description 예매자가 존재하여 가격을 수정할 수 없습니다. */ + /** @description 티켓 가격은 음수일 수 없습니다. */ 400: { headers: { [name: string]: unknown; @@ -1718,7 +1717,7 @@ export interface operations { "application/json;charset=UTF-8": components["schemas"]["SuccessResponseCarouselHandleAllResponse"]; }; }; - /** @description 회원이 없습니다. */ + /** @description 해당 공연 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown; @@ -1750,7 +1749,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json;charset=UTF-8": components["schemas"]["SuccessResponseLoginSuccessResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseMemberLoginResponse"]; }; }; /** @description 로그인 요청이 유효하지 않습니다. */ @@ -1824,7 +1823,7 @@ export interface operations { "application/json;charset=UTF-8": components["schemas"]["SuccessResponseMemberBookingResponse"]; }; }; - /** @description 잘못된 데이터 형식입니다. */ + /** @description 필수 데이터가 누락되었습니다. */ 400: { headers: { [name: string]: unknown; @@ -1833,7 +1832,7 @@ export interface operations { "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; - /** @description 회차 정보를 찾을 수 없습니다. */ + /** @description 회원 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown; @@ -1866,7 +1865,7 @@ export interface operations { "application/json;charset=UTF-8": components["schemas"]["SuccessResponseGuestBookingResponse"]; }; }; - /** @description 잘못된 데이터 형식입니다. */ + /** @description 필수 데이터가 누락되었습니다. */ 400: { headers: { [name: string]: unknown; @@ -2008,11 +2007,11 @@ export interface operations { issueAccessTokenUsingRefreshToken: { parameters: { query?: never; - header: { - Authorization_Refresh: string; - }; + header?: never; path?: never; - cookie?: never; + cookie: { + refreshToken: string; + }; }; requestBody?: never; responses: { @@ -2022,7 +2021,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json;charset=UTF-8": components["schemas"]["SuccessResponseAccessTokenGetSuccess"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseAccessTokenGenerateResponse"]; }; }; /** @description 유효하지 않은 토큰입니다. */ @@ -2059,7 +2058,7 @@ export interface operations { "application/json;charset=UTF-8": components["schemas"]["SuccessResponseTicketRetrieveResponse"]; }; }; - /** @description 입력하신 정보와 일치하는 예매자 목록이 없습니다. */ + /** @description 회차 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown; @@ -2094,7 +2093,7 @@ export interface operations { "application/json;charset=UTF-8": components["schemas"]["SuccessResponseTicketRetrieveResponse"]; }; }; - /** @description 입력하신 정보와 일치하는 예매자 목록이 없습니다. */ + /** @description 회차 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown;