Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client";

import MenteePageTabs from "../MenteePageTabs";
import MentorFindSection from "../MentorFindSection";

/**
* 멘티 페이지 컴포넌트
* - 나의 멘토 탭 (진행 중, 대기 중)
* - 멘토 찾기 섹션
*/
const MenteePage = () => {
return (
<>
{/* 탭 및 나의 멘토, 멘티요청 리스트 채팅카드 */}
<MenteePageTabs />
{/* 멘토찾기 섹션 */}
<MentorFindSection />
</>
);
};

export default MenteePage;
75 changes: 53 additions & 22 deletions apps/web/src/app/mentor/_ui/MentorClient/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,65 @@
"use client";

import { useState } from "react";
import { lazy, Suspense, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { postReissueToken } from "@/apis/Auth";
import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import useAuthStore from "@/lib/zustand/useAuthStore";
import { UserRole } from "@/types/mentor";
import { tokenParse } from "@/utils/jwtUtils";
import MenteePageTabs from "./_ui/MenteePageTabs";
import MentorFindSection from "./_ui/MentorFindSection";
import MentorPage from "./_ui/MentorPage";

// 레이지 로드 컴포넌트
const MenteePage = lazy(() => import("./_ui/MenteePage"));
const MentorPage = lazy(() => import("./_ui/MentorPage"));

const MentorClient = () => {
const { isLoading, accessToken } = useAuthStore();
const parsedToken = tokenParse(accessToken);
const userRole = parsedToken?.role;
const isMentor = userRole === UserRole.MENTOR || userRole === UserRole.ADMIN;
const isAdmin = userRole === UserRole.ADMIN;
const router = useRouter();
const { isLoading, accessToken, isInitialized, refreshStatus, setRefreshStatus } = useAuthStore();
const [isRefreshing, setIsRefreshing] = useState(false);

// 어드민 전용: 뷰 전환 상태 (true: 멘토 뷰, false: 멘티 뷰)
const [showMentorView, setShowMentorView] = useState<boolean>(true);

if (isLoading || !accessToken) return <CloudSpinnerPage />; // 로딩 중일 때 스피너 표시
// 토큰 재발급 로직
useEffect(() => {
const attemptTokenRefresh = async () => {
// 이미 초기화되었고 토큰이 없는 경우에만 재발급 시도
if (!isInitialized || accessToken || isRefreshing || refreshStatus === "refreshing") {
return;
}

setIsRefreshing(true);
setRefreshStatus("refreshing");

try {
await postReissueToken();
setRefreshStatus("success");
} catch {
// 재발급 실패 시 로그인 페이지로 리다이렉트
setRefreshStatus("failed");
router.push("/login?redirect=/mentor");
} finally {
setIsRefreshing(false);
}
};

attemptTokenRefresh();
}, [isInitialized, accessToken, isRefreshing, refreshStatus, setRefreshStatus, router]);

// 초기화 전이거나 로딩 중이거나 재발급 중일 때 스피너 표시
if (!isInitialized || isLoading || refreshStatus === "refreshing" || isRefreshing) {
return <CloudSpinnerPage />;
}

// 초기화 완료 후에도 토큰이 없으면 리다이렉트 (useEffect에서 처리되지만 fallback)
if (!accessToken) {
return <CloudSpinnerPage />;
}

const parsedToken = tokenParse(accessToken);
const userRole = parsedToken?.role;
const isMentor = userRole === UserRole.MENTOR || userRole === UserRole.ADMIN;
const isAdmin = userRole === UserRole.ADMIN;

// 어드민이 아닌 경우 기존 로직대로
const shouldShowMentorView = isAdmin ? showMentorView : isMentor;
Expand Down Expand Up @@ -48,18 +88,9 @@ const MentorClient = () => {
</div>
)}

{shouldShowMentorView ? (
// 멘토페이지
<MentorPage />
) : (
// 멘티페이지
<>
{/* 탭 및 나의 멘토 , 멘티요청 리스트 채팅카드 */}
<MenteePageTabs />
{/* 멘토찾기 섹션 */}
<MentorFindSection />
</>
)}
<Suspense fallback={<CloudSpinnerPage />}>
{shouldShowMentorView ? <MentorPage /> : <MenteePage />}
</Suspense>
</>
);
};
Expand Down
15 changes: 13 additions & 2 deletions apps/web/src/lib/zustand/useAuthStore.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";

type RefreshStatus = "idle" | "refreshing" | "success" | "failed";

interface AuthState {
accessToken: string | null;
isAuthenticated: boolean;
isLoading: boolean;
isInitialized: boolean;
refreshStatus: RefreshStatus;
setAccessToken: (token: string) => void;
clearAccessToken: () => void;
setLoading: (loading: boolean) => void;
setInitialized: (initialized: boolean) => void;
setRefreshStatus: (status: RefreshStatus) => void;
}

const useAuthStore = create<AuthState>()(
Expand All @@ -19,13 +23,15 @@ const useAuthStore = create<AuthState>()(
isAuthenticated: false,
isLoading: false,
isInitialized: false,
refreshStatus: "idle",

setAccessToken: (token) => {
set({
accessToken: token,
isAuthenticated: true,
isLoading: false,
isInitialized: true,
refreshStatus: "success",
});
},

Expand All @@ -35,6 +41,7 @@ const useAuthStore = create<AuthState>()(
isAuthenticated: false,
isLoading: false,
isInitialized: true,
refreshStatus: "idle",
});
},

Expand All @@ -45,13 +52,17 @@ const useAuthStore = create<AuthState>()(
setInitialized: (initialized) => {
set({ isInitialized: initialized });
},

setRefreshStatus: (status) => {
set({ refreshStatus: status });
},
}),
{
name: "auth-storage", // localStorage에 저장될 키 이름
name: "auth-storage",
partialize: (state) => ({
accessToken: state.accessToken,
isAuthenticated: state.isAuthenticated,
}), // accessToken과 isAuthenticated만 localStorage에 저장
}),
},
),
);
Expand Down
6 changes: 3 additions & 3 deletions apps/web/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,9 @@ const config: Config = {
height: "height",
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
lg: "0.5rem",
md: "0.375rem",
sm: "0.25rem",
},
animation: {
"slide-up": "slideUp 0.3s ease-out",
Expand Down
Loading