Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/web/src/apis/MyPage/getProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ type UseGetMyInfoResult = Omit<UseQueryResult<MyInfoResponse, AxiosError>, "data
data: MyInfoResponse | undefined;
};

const useGetMyInfo = (): UseGetMyInfoResult => {
type UseGetMyInfoParams = {
enabled?: boolean;
};

const useGetMyInfo = ({ enabled = true }: UseGetMyInfoParams = {}): UseGetMyInfoResult => {
const queryResult = useQuery<MyInfoResponse, AxiosError>({
queryKey: [QueryKeys.MyPage.profile],
queryFn: () => myPageApi.getProfile(),
enabled,
// staleTime을 무한으로 설정하여 불필요한 자동 refetch를 방지합니다.
staleTime: Infinity,
gcTime: 1000 * 60 * 30, // 예: 30분
Expand Down
36 changes: 34 additions & 2 deletions apps/web/src/app/my/_ui/MyProfileContent/index.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 (
<div className="flex min-h-[40vh] flex-col items-center justify-center gap-3 px-4 text-center">
<p className="text-k-700 typo-medium-2">마이페이지 정보를 불러오지 못했어요.</p>
<button
type="button"
onClick={() => refetch()}
className="rounded-full bg-primary px-4 py-2 text-white typo-medium-2"
>
다시 시도
</button>
</div>
);
}

if (!shouldFetchProfile || isLoading || (isFetching && !profileData) || !profileData) {
return <CloudSpinnerPage />;
Comment thread
yoonc01 marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

const { nickname, email, profileImageUrl } = profileData;
const isMentor = profileData.role === UserRole.MENTOR || profileData.role === UserRole.ADMIN;
const viewAsMentor = clientRole ? clientRole === UserRole.MENTOR : isMentor;
Expand Down
15 changes: 11 additions & 4 deletions apps/web/src/utils/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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");
}
};

Expand All @@ -40,7 +47,7 @@ const tryReissueAccessToken = async (): Promise<string | null> => {
return useAuthStore.getState().accessToken;
}

const { setLoading, clearAccessToken, setInitialized, setRefreshStatus } = useAuthStore.getState();
const { setLoading, setInitialized, setRefreshStatus } = useAuthStore.getState();

reissuePromise = (async () => {
setRefreshStatus("refreshing");
Expand All @@ -49,7 +56,7 @@ const tryReissueAccessToken = async (): Promise<string | null> => {
await postReissueToken();
setRefreshStatus("success");
} catch {
clearAccessToken();
clearAuthState();
setRefreshStatus("failed");
} finally {
setLoading(false);
Expand Down
Loading