diff --git a/apps/web/src/apis/MyPage/getProfile.ts b/apps/web/src/apis/MyPage/getProfile.ts index 3ca32d82..a0dd0b53 100644 --- a/apps/web/src/apis/MyPage/getProfile.ts +++ b/apps/web/src/apis/MyPage/getProfile.ts @@ -7,10 +7,15 @@ type UseGetMyInfoResult = Omit, "data data: MyInfoResponse | undefined; }; -const useGetMyInfo = (): UseGetMyInfoResult => { +type UseGetMyInfoParams = { + enabled?: boolean; +}; + +const useGetMyInfo = ({ enabled = true }: UseGetMyInfoParams = {}): UseGetMyInfoResult => { const queryResult = useQuery({ queryKey: [QueryKeys.MyPage.profile], queryFn: () => myPageApi.getProfile(), + enabled, // staleTime을 무한으로 설정하여 불필요한 자동 refetch를 방지합니다. staleTime: Infinity, gcTime: 1000 * 60 * 30, // 예: 30분 diff --git a/apps/web/src/app/my/_ui/MyProfileContent/index.tsx b/apps/web/src/app/my/_ui/MyProfileContent/index.tsx index 39ecace3..bae8183e 100644 --- a/apps/web/src/app/my/_ui/MyProfileContent/index.tsx +++ b/apps/web/src/app/my/_ui/MyProfileContent/index.tsx @@ -1,8 +1,11 @@ "use client"; import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; import { useDeleteUserAccount, usePostLogout } from "@/apis/Auth"; -import { type MyInfoResponse, useGetMyInfo } from "@/apis/MyPage"; +import { useGetMyInfo } from "@/apis/MyPage"; +import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage"; import LinkedTextWithIcon from "@/components/ui/LinkedTextWithIcon"; import ProfileWithBadge from "@/components/ui/ProfileWithBadge"; import { showIconToast } from "@/lib/toast/showIconToast"; @@ -23,11 +26,40 @@ import { openKakaoOpenChat } from "@/utils/openKakaoOpenChat"; const NEXT_PUBLIC_CONTACT_LINK = process.env.NEXT_PUBLIC_CONTACT_LINK; const MyProfileContent = () => { - const { data: profileData = {} as MyInfoResponse } = useGetMyInfo(); + const router = useRouter(); + const isInitialized = useAuthStore((state) => state.isInitialized); + const isAuthenticated = useAuthStore((state) => state.isAuthenticated); + const shouldFetchProfile = isInitialized && isAuthenticated; + const { data: profileData, isLoading, isFetching, isError, refetch } = useGetMyInfo({ enabled: shouldFetchProfile }); const { mutate: deleteUserAccount } = useDeleteUserAccount(); const { mutate: postLogout } = usePostLogout(); const clientRole = useAuthStore((state) => state.clientRole); + useEffect(() => { + if (!isInitialized || isAuthenticated) return; + + router.replace("/login"); + }, [isInitialized, isAuthenticated, router]); + + if (isError) { + return ( +
+

마이페이지 정보를 불러오지 못했어요.

+ +
+ ); + } + + if (!shouldFetchProfile || isLoading || (isFetching && !profileData) || !profileData) { + return ; + } + const { nickname, email, profileImageUrl } = profileData; const isMentor = profileData.role === UserRole.MENTOR || profileData.role === UserRole.ADMIN; const viewAsMentor = clientRole ? clientRole === UserRole.MENTOR : isMentor; diff --git a/apps/web/src/utils/axiosInstance.ts b/apps/web/src/utils/axiosInstance.ts index be7ddfa8..0b9631f8 100644 --- a/apps/web/src/utils/axiosInstance.ts +++ b/apps/web/src/utils/axiosInstance.ts @@ -1,5 +1,7 @@ import axios, { type AxiosError, type AxiosInstance } from "axios"; import { postReissueToken } from "@/apis/Auth/server"; +import { QueryKeys } from "@/apis/queryKeys"; +import queryClient from "@/lib/react-query/queryClient"; import { showIconToast } from "@/lib/toast/showIconToast"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { isTokenExpired } from "@/utils/jwtUtils"; @@ -18,17 +20,22 @@ export class AuthenticationRequiredError extends Error { // --- 상태 관리 변수 --- let isRedirecting = false; +const clearAuthState = () => { + useAuthStore.getState().clearAccessToken(); + queryClient.removeQueries({ queryKey: [QueryKeys.MyPage.profile] }); +}; + // --- 유틸리티 함수 --- const redirectToLogin = (message: string) => { if (typeof window !== "undefined" && !isRedirecting) { isRedirecting = true; // Zustand 스토어 및 쿠키 상태 초기화 - useAuthStore.getState().clearAccessToken(); + clearAuthState(); try { // 쿠키 유틸이 클라이언트에서만 동작하므로 window 가드 내에서 호출 } catch {} showIconToast("logo", message); - window.location.href = "/login"; + window.location.replace("/login"); } }; @@ -40,7 +47,7 @@ const tryReissueAccessToken = async (): Promise => { return useAuthStore.getState().accessToken; } - const { setLoading, clearAccessToken, setInitialized, setRefreshStatus } = useAuthStore.getState(); + const { setLoading, setInitialized, setRefreshStatus } = useAuthStore.getState(); reissuePromise = (async () => { setRefreshStatus("refreshing"); @@ -49,7 +56,7 @@ const tryReissueAccessToken = async (): Promise => { await postReissueToken(); setRefreshStatus("success"); } catch { - clearAccessToken(); + clearAuthState(); setRefreshStatus("failed"); } finally { setLoading(false);