Skip to content

Commit f2d95f3

Browse files
authored
Merge pull request #191 from SOPT-36-NINEDOT/feat/#187/loginState
[Feat] 로그인 상태 관리
2 parents bc861a7 + 38d38a8 commit f2d95f3

File tree

13 files changed

+205
-48
lines changed

13 files changed

+205
-48
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"react": "^19.1.0",
3131
"react-dom": "^19.1.0",
3232
"react-redux": "^9.2.0",
33-
"react-router-dom": "^7.6.3"
33+
"react-router-dom": "^7.6.3",
34+
"zustand": "^5.0.8"
3435
},
3536
"devDependencies": {
3637
"@chromatic-com/storybook": "^4.0.1",

pnpm-lock.yaml

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/auth/refreshToken/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import axiosInstance from '@/api/axiosInstance';
2+
import { END_POINT } from '@/api/constant/endPoint';
3+
import type { BaseResponse } from '@/type/api';
4+
5+
export type RefreshResponse = {
6+
accessToken: string;
7+
message: string;
8+
};
9+
10+
export const postRefreshToken = async () => {
11+
const { data } = await axiosInstance.post<BaseResponse<RefreshResponse>>(
12+
`/${END_POINT.AUTH}/refresh`,
13+
);
14+
return data.data;
15+
};

src/api/axiosInstance.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import axios from 'axios';
22

33
import { HTTP_STATUS } from '@/api/constant/httpStatus';
4+
import { postRefreshToken } from '@/api/auth/refreshToken';
45

56
const axiosInstance = axios.create({
67
baseURL: import.meta.env.VITE_API_BASE_URL,
@@ -24,12 +25,23 @@ axiosInstance.interceptors.request.use(
2425
// 응답 인터셉터
2526
axiosInstance.interceptors.response.use(
2627
(response) => response,
27-
(error) => {
28+
async (error) => {
29+
const originalRequest = error.config;
30+
2831
if (error.response) {
2932
const { status } = error.response;
3033

3134
if (status === HTTP_STATUS.UNAUTHORIZED) {
32-
// 인증 실패 처리
35+
try {
36+
const { accessToken } = await postRefreshToken();
37+
localStorage.setItem('accessToken', accessToken);
38+
39+
axiosInstance.defaults.headers.Authorization = `Bearer ${accessToken}`;
40+
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
41+
} catch (refreshError) {
42+
console.error('리프레시 토큰 만료, 재로그인 필요');
43+
return Promise.reject(refreshError);
44+
}
3345
}
3446

3547
if (status === HTTP_STATUS.INTERNAL_SERVER_ERROR) {

src/api/domain/signup/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { END_POINT } from '@/api/constant/endPoint';
33
import type { JobResponse } from '@/api/domain/signup/type/JobResponse';
44
import type { PersonaResponse } from '@/api/domain/signup/type/PersonaResponse';
55
import type { SignupResponse } from '@/api/domain/signup/type/SignupResponse';
6-
import type { UserInfoResponse } from '@/api/domain/signup/type/UserInfoResponse';
6+
import type { UserType } from '@/store/types/authTypes';
77
import type { BaseResponse } from '@/type/api';
88

99
export const getJobList = async () => {
@@ -21,7 +21,7 @@ export const postSignUp = async (payload: SignupResponse) => {
2121
};
2222

2323
export const getUser = async () => {
24-
const { data } = await axiosInstance.get<BaseResponse<UserInfoResponse>>('/users/info');
24+
const { data } = await axiosInstance.get<BaseResponse<UserType>>('/users/info');
2525
return data.data;
2626
};
2727

src/api/domain/signup/type/UserInfoResponse.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/common/component/UserModal/UserModal.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@ import { useCallback, useEffect, useRef } from 'react';
22

33
import { IcDivider } from '@/assets/svg';
44
import * as styles from '@/common/component/UserModal/UserModal.css';
5-
import { useGetUser } from '@/api/domain/signup/hook/useGetUser';
65
import { usePostLogout } from '@/api/domain/signup/hook/usePostLogout';
6+
import { useAuthStore } from '@/store/useAuthStore';
7+
import { useNavigate } from 'react-router-dom';
8+
import { PATH } from '@/route';
79

810
interface UserModalProps {
911
onClose: () => void;
1012
}
1113

1214
const UserModal = ({ onClose }: UserModalProps) => {
1315
const modalRef = useRef<HTMLDivElement>(null);
14-
const { data: user, isLoading, isError } = useGetUser();
16+
const navigate = useNavigate();
17+
18+
const user = useAuthStore((state) => state.user);
19+
const resetUser = useAuthStore((state) => state.resetUser);
20+
1521
const { mutate: logoutMutate } = usePostLogout();
1622

1723
const handleClickOutside = useCallback(
@@ -26,9 +32,9 @@ const UserModal = ({ onClose }: UserModalProps) => {
2632
const handleLogout = () => {
2733
logoutMutate(undefined, {
2834
onSuccess: () => {
29-
localStorage.removeItem('accessToken');
35+
resetUser();
3036
onClose();
31-
window.location.reload();
37+
navigate(PATH.ROOT);
3238
},
3339
onError: (error) => {
3440
console.error('로그아웃 실패:', error);
@@ -44,7 +50,7 @@ const UserModal = ({ onClose }: UserModalProps) => {
4450
};
4551
}, [handleClickOutside]);
4652

47-
if (isLoading || isError || !user) {
53+
if (!user) {
4854
return null;
4955
}
5056

src/common/hook/useGoogleAuth.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,10 @@ import getGoogleAuthCode from '@/api/auth/googleLogin/util/getGoogleAuthCode';
44
import getAccessToken from '@/api/auth/googleLogin/util/getAccessToken';
55

66
interface UserData {
7-
email: string;
8-
name: string;
9-
profileImageUrl: string;
10-
socialProvider: string;
11-
socialToken: string;
127
exists: boolean;
8+
userId?: number;
139
accessToken?: string;
14-
onboardingCompleted?: boolean;
10+
onboardingPage: string;
1511
}
1612

1713
export const useGoogleAuth = (): UserData | null => {

src/page/callback/GoogleCallback.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ const GoogleCallback = () => {
1313
return;
1414
}
1515

16-
const isExistingUser = userData.exists;
16+
const { exists, onboardingPage } = userData;
1717

18-
if (isExistingUser) {
19-
navigate(PATH.INTRO, {
20-
state: { isWritten: userData.onboardingCompleted },
21-
});
22-
} else {
18+
if (!exists) {
2319
navigate(PATH.SIGNUP, {
2420
state: { userData },
2521
});
22+
return;
23+
}
24+
25+
if (onboardingPage === 'ONBOARDING_COMPLETED') {
26+
navigate(PATH.MANDAL);
27+
} else {
28+
navigate(PATH.INTRO, { state: { pageState: onboardingPage } });
2629
}
2730
}, [userData, navigate]);
2831

src/page/intro/Intro.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { useLocation, useNavigate } from 'react-router-dom';
22

33
import * as styles from '@/page/intro/Intro.css';
4+
import { PATH } from '@/route';
5+
6+
type PageStateType = 'MANDALART' | 'CORE_GOAL' | 'SUB_GOALS';
7+
8+
const ROUTE_BY_STATE: Record<PageStateType, string> = {
9+
MANDALART: PATH.TODO,
10+
CORE_GOAL: PATH.TODO_UPPER,
11+
SUB_GOALS: PATH.TODO_LOWER,
12+
};
413

514
const MESSAGE = {
615
START: {
@@ -16,13 +25,19 @@ const MESSAGE = {
1625
const Intro = () => {
1726
const navigate = useNavigate();
1827
const location = useLocation();
19-
const isWritten = location.state?.isWritten ?? false; // 기본값 false
2028

21-
const handleNavigateToTodo = () => {
22-
navigate('/todo');
23-
};
29+
const pageState = (location.state as { pageState?: PageStateType })?.pageState;
30+
const isStart = pageState === 'MANDALART';
31+
32+
const content = isStart ? MESSAGE.START : MESSAGE.CONTINUE;
2433

25-
const content = isWritten ? MESSAGE.CONTINUE : MESSAGE.START;
34+
const handleGoTodo = () => {
35+
if (!pageState) {
36+
navigate(PATH.TODO);
37+
return;
38+
}
39+
navigate(ROUTE_BY_STATE[pageState]);
40+
};
2641

2742
const renderTitle = content.title.split('<br/>').map((line, index) => (
2843
<span key={index}>
@@ -34,7 +49,7 @@ const Intro = () => {
3449
return (
3550
<main className={styles.introContainer}>
3651
<h1 className={styles.introText}>{renderTitle}</h1>
37-
<button className={styles.buttonContainer} onClick={handleNavigateToTodo}>
52+
<button className={styles.buttonContainer} onClick={handleGoTodo}>
3853
{content.button}
3954
</button>
4055
</main>

0 commit comments

Comments
 (0)