Skip to content

Commit c75fa51

Browse files
authored
Cache onboarding + Logout 추가 (#45)
* fix: improve onboarding redirect logic and implement logout - Add logout functionality in MyPage component using auth store - Optimize onboarding page redirect by using useGoals hook with loading state - Replace window.location.href with Next.js router.replace() for better performance - Add loading spinner during goal data fetch in onboarding - Remove SSR/CSR conflicts by moving redirect logic to useEffect * fix: handle Zustand hydration delay in onboarding page - Add hydration state check to prevent premature login state evaluation - Wait 50ms for Zustand persist data to hydrate before checking login status - Show loading spinner during hydration process - Add debug console logs for hydration and auth states - Fix redirect logic to only execute after store has been properly hydrated * fix: resolve 401 error and smooth onboarding page transitions - Fix premature API call issue where useGoals was called before login completion - Add error handling and retry logic for 401 authentication errors - Use SWR mutate function to retry goals API after successful login - Improve loading state logic to prevent flickering during page transitions - Set shouldRetryOnError to false to prevent automatic retries on auth errors - Show loading spinner until proper authentication and data fetching is complete * fix: improve loading spinner visibility and eliminate page flash - Change spinner color from border-primary-500 to border-gray-800 for better visibility on white background - Add additional loading state for when goals exist and redirect is about to happen - Prevent brief flash of onboarding content during page transitions - Show consistent loading spinner during all redirect scenarios
1 parent 550a888 commit c75fa51

File tree

2 files changed

+81
-7
lines changed

2 files changed

+81
-7
lines changed

app/onboarding/page.tsx

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,68 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useState, useEffect } from "react";
4+
import { useRouter } from "next/navigation";
45
import LoginScreen from "./_components/LoginScreen";
56
import GoalInputScreen from "./_components/GoalInputScreen";
67
import PeriodSelectionScreen from "./_components/PeriodSelectionScreen";
78
import CompletionScreen from "./_components/CompletionScreen";
89
import useAuthStore from "@/stores/useAuthStore";
10+
import { useGoals } from "@/api/hooks";
911

1012
export default function OnboardingPage() {
13+
const router = useRouter();
1114
const [currentStep, setCurrentStep] = useState(0);
12-
const { setHasCompletedOnboarding } = useAuthStore();
15+
const [hasHydrated, setHasHydrated] = useState(false);
16+
const { setHasCompletedOnboarding, isLoggedIn, hasCompletedOnboarding } =
17+
useAuthStore();
18+
19+
// 클라이언트 사이드에서만 hydration 체크
20+
useEffect(() => {
21+
// 짧은 지연 후 hydration 완료로 간주
22+
const timer = setTimeout(() => {
23+
setHasHydrated(true);
24+
}, 50);
25+
26+
return () => clearTimeout(timer);
27+
}, []);
28+
29+
console.log("hasHydrated:", hasHydrated);
30+
console.log("isLoggedIn:", isLoggedIn);
31+
console.log("hasCompletedOnboarding:", hasCompletedOnboarding);
32+
33+
// hasCompletedOnboarding이 true면 즉시 redirect (hydration 후에만)
34+
useEffect(() => {
35+
if (hasHydrated && hasCompletedOnboarding) {
36+
router.replace("/");
37+
return;
38+
}
39+
}, [hasHydrated, hasCompletedOnboarding, router]);
40+
41+
// goals 가져오기
42+
const { data: { goals } = {}, isLoading, error, mutate } = useGoals({
43+
revalidateOnFocus: false,
44+
revalidateOnReconnect: false,
45+
shouldRetryOnError: false, // 401 에러 시 재시도 방지
46+
});
47+
48+
// 로그인 상태가 true로 변경될 때 goals API 재호출
49+
useEffect(() => {
50+
if (hasHydrated && isLoggedIn && error) {
51+
console.log("Retrying goals API after login...");
52+
mutate();
53+
}
54+
}, [hasHydrated, isLoggedIn, error, mutate]);
55+
56+
console.log("goals:", goals);
57+
console.log("isLoading:", isLoading);
58+
console.log("error:", error);
59+
60+
useEffect(() => {
61+
if (hasHydrated && isLoggedIn && goals && goals.length > 0) {
62+
setHasCompletedOnboarding(true);
63+
router.replace("/");
64+
}
65+
}, [hasHydrated, isLoggedIn, goals, setHasCompletedOnboarding, router]);
1366

1467
const nextStep = () => {
1568
setCurrentStep((prev) => prev + 1);
@@ -33,7 +86,7 @@ export default function OnboardingPage() {
3386
onComplete={() => {
3487
// Navigate to main app
3588
setHasCompletedOnboarding(true);
36-
window.location.href = "/";
89+
router.replace("/");
3790
}}
3891
/>
3992
);
@@ -42,9 +95,22 @@ export default function OnboardingPage() {
4295
}
4396
};
4497

45-
const { hasCompletedOnboarding } = useAuthStore();
46-
if (hasCompletedOnboarding) {
47-
window.location.href = "/";
98+
// hydration이 완료되지 않았거나 로그인된 상태에서 처리 중이면 스피너 표시
99+
if (!hasHydrated || (hasHydrated && isLoggedIn && (isLoading || (!goals && !error)))) {
100+
return (
101+
<div className="min-h-screen bg-background-normal flex items-center justify-center">
102+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-800"></div>
103+
</div>
104+
);
105+
}
106+
107+
// 로그인된 상태에서 goals가 있으면 빈 화면 표시 (redirect 준비)
108+
if (hasHydrated && isLoggedIn && goals && goals.length > 0) {
109+
return (
110+
<div className="min-h-screen bg-background-normal flex items-center justify-center">
111+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-800"></div>
112+
</div>
113+
);
48114
}
49115

50116
return (

components/mypage/MyPage.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface MyPageProps {
2727

2828
export function MyPage({ className = "" }: MyPageProps) {
2929
const router = useSafeRouter();
30-
const { isLoggedIn, login } = useAuthStore();
30+
const { isLoggedIn, login, logout } = useAuthStore();
3131
const { data: user, isLoading, error } = useMyProfile();
3232

3333
const handleLogin = () => {
@@ -56,6 +56,14 @@ export function MyPage({ className = "" }: MyPageProps) {
5656
hasIcon: false,
5757
onClick: () => {},
5858
},
59+
{
60+
label: "로그아웃",
61+
hasIcon: false,
62+
onClick: () => {
63+
logout();
64+
router.push("/");
65+
},
66+
},
5967
]
6068
: [
6169
{

0 commit comments

Comments
 (0)