diff --git a/src/App.tsx b/src/App.tsx
index b00975a..57a96b0 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,6 +3,7 @@ import { Route, Routes } from 'react-router';
import useViewport from './hooks/useViewport';
import Layout from './layouts/Layout';
import MobileLayout from './layouts/MobileLayout';
+import PrivateRoute from './layouts/PrivateRoute';
import AdminPage from './pages/Admin';
import FilteredLetterManage from './pages/Admin/FilteredLetter';
import FilteringManage from './pages/Admin/Filtering';
@@ -30,39 +31,44 @@ const App = () => {
return (
}>
- } />
} />
} />
+ } />
+ } />
+ } />
} />
-
- }>
- } />
- } />
- } />
+
+ }>
+
+ }>
+ } />
+ } />
+ } />
+
+ } />
+ } />
- } />
- } />
-
-
- }>
- } />
- } />
+
+ }>
+ } />
+ } />
+
+ } />
+
+ }>
+ } />
+ } />
+ } />
- } />
-
- }>
- } />
- } />
- } />
- } />
- } />
- }>
- } />
- } />
- } />
+ }>
+ }>
+ } />
+ } />
+ } />
+
);
diff --git a/src/apis/auth.ts b/src/apis/auth.ts
index 6885010..9105f60 100644
--- a/src/apis/auth.ts
+++ b/src/apis/auth.ts
@@ -12,8 +12,10 @@ export const getUserToken = async (stateToken: string) => {
if (userInfo) {
return userInfo;
}
+ return response;
} catch (error) {
console.error(error);
+ throw error;
}
};
@@ -29,7 +31,7 @@ export const postZipCode = async () => {
export const getNewToken = async () => {
try {
- const response = await client.get('/api/reissue', { withCredentials: true });
+ const response = await client.post('/api/reissue', {}, { withCredentials: true });
if (!response) throw new Error('getNewToken: no response data');
return response;
} catch (error) {
@@ -40,7 +42,7 @@ export const getNewToken = async () => {
export const getMydata = async () => {
try {
const response = await client.get('/api/members/me');
- if (!response) throw new Error('getNewTOken: no response data');
+ if (!response) throw new Error('getNewToken: no response data');
return response;
} catch (error) {
console.error(error);
diff --git a/src/apis/client.ts b/src/apis/client.ts
index 6fc9fe4..8ccefbf 100644
--- a/src/apis/client.ts
+++ b/src/apis/client.ts
@@ -6,60 +6,100 @@ import { getNewToken } from './auth';
const client = axios.create({
baseURL: import.meta.env.VITE_API_URL,
+ headers: { 'Content-Type': 'application/json' },
});
+type FailedRequest = {
+ resolve: (token: string) => void;
+ reject: (error: unknown) => void;
+};
+
+let isRefreshing = false;
+let failedQueue: FailedRequest[] = [];
+
+const processQueue = (error: unknown, token: string | null = null) => {
+ failedQueue.forEach((prom) => {
+ if (error) {
+ prom.reject(error);
+ } else {
+ if (token) {
+ prom.resolve(token);
+ }
+ }
+ });
+
+ failedQueue = [];
+};
+
client.interceptors.request.use(
(config) => {
- const accessToken = useAuthStore((state) => state.accessToken);
- console.log(config.url);
- console.log(accessToken);
+ const accessToken = useAuthStore.getState().accessToken;
+
if (config.url !== '/auth/reissue' && accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
- console.log('intercepter', config.headers);
}
+
return config;
},
- (error) => {
- const logout = useAuthStore((state) => state.logout);
- logout();
- window.location.replace('/login');
- return Promise.reject(error);
- },
+ (error) => Promise.reject(error),
);
client.interceptors.response.use(
(response) => response,
async (error) => {
- const setAccessToken = useAuthStore((state) => state.setAccessToken);
- const logout = useAuthStore((state) => state.logout);
-
+ const setAccessToken = useAuthStore.getState().setAccessToken;
+ const logout = useAuthStore.getState().logout;
const originalRequest = error.config;
- if (!originalRequest) {
+ if (!originalRequest) return Promise.reject(error);
+
+ if (
+ originalRequest.url === '/auth/reissue' ||
+ originalRequest.url.includes('/api/auth/token?state=')
+ ) {
return Promise.reject(error);
}
if (
- (error.response.status === 401 ||
- error.response.status === 403 ||
- error.response.data.message === 'Unauthorized') &&
+ (error.response?.status === 401 || error.response?.status === 403) &&
!originalRequest._retry
) {
originalRequest._retry = true;
+ if (isRefreshing) {
+ try {
+ return new Promise((resolve, reject) => {
+ failedQueue.push({
+ resolve: (token: string) => {
+ originalRequest.headers.Authorization = `Bearer ${token}`;
+ resolve(client(originalRequest));
+ },
+ reject: (err: unknown) => reject(err),
+ });
+ });
+ } catch (e) {
+ return Promise.reject(e);
+ }
+ }
+
+ isRefreshing = true;
+
try {
const response = await getNewToken();
const newToken = response?.data.accessToken;
- if (!newToken) throw new Error('Failed to Refresh Token');
+ if (!newToken) throw new Error('Failed to refresh token');
setAccessToken(newToken);
- originalRequest.headers.Authorization = `Bearer ${newToken}`;
+ processQueue(null, newToken);
+ isRefreshing = false;
+ originalRequest.headers.Authorization = `Bearer ${newToken}`;
return client(originalRequest);
} catch (e) {
+ processQueue(e, null);
+ isRefreshing = false;
logout();
- window.location.replace('/login');
return Promise.reject(e);
}
}
diff --git a/src/layouts/PrivateRoute.tsx b/src/layouts/PrivateRoute.tsx
new file mode 100644
index 0000000..bc78b4a
--- /dev/null
+++ b/src/layouts/PrivateRoute.tsx
@@ -0,0 +1,24 @@
+import { useEffect, useState } from 'react';
+import { useNavigate, Outlet } from 'react-router';
+
+import useAuthStore from '@/stores/authStore';
+
+export default function PrivateRoute() {
+ const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
+ const navigate = useNavigate();
+ const [shouldRender, setShouldRender] = useState(false);
+
+ useEffect(() => {
+ if (!isLoggedIn) {
+ navigate('/login', { replace: true });
+ } else {
+ setShouldRender(true);
+ }
+ }, [isLoggedIn, navigate]);
+
+ if (!shouldRender) {
+ return null;
+ }
+
+ return ;
+}
diff --git a/src/pages/Auth/index.tsx b/src/pages/Auth/index.tsx
index cc157c7..0596a77 100644
--- a/src/pages/Auth/index.tsx
+++ b/src/pages/Auth/index.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-unused-expressions */
import { useEffect } from 'react';
import { useNavigate } from 'react-router';
@@ -10,73 +9,75 @@ const AuthCallbackPage = () => {
const redirectURL = new URLSearchParams(window.location.search).get('redirect');
const login = useAuthStore((state) => state.login);
+ const logout = useAuthStore((state) => state.logout);
const setAccessToken = useAuthStore((state) => state.setAccessToken);
const setZipCode = useAuthStore((state) => state.setZipCode);
-
const navigate = useNavigate();
+ const handleError = (error: unknown) => {
+ console.error('AuthCallback Error:', error);
+ logout();
+ navigate('/login', { replace: true });
+ };
+
const setUserInfo = async (stateToken: string) => {
try {
const response = await getUserToken(stateToken);
- if (!response) throw new Error('Error Fetching userInfo');
+ console.log(response);
+ if (!response) throw new Error('Error fetching user token');
const userInfo = response.data;
- if (userInfo) {
- login();
- userInfo.accessToken && setAccessToken(userInfo.accessToken);
-
- if (redirectURL == 'home') {
- const zipCodeResponse = await getMydata();
- if (!zipCodeResponse) throw new Error('Error Fetching userInfo');
- const zipCode = zipCodeResponse.data.data.zipCode;
- zipCode && setZipCode(zipCode);
-
- console.log(
- 'isLoggedIn',
- useAuthStore.getState().isLoggedIn,
- 'access',
- useAuthStore.getState().accessToken,
- 'zipCode',
- useAuthStore.getState().zipCode,
- );
- } else if (redirectURL === 'onboarding') {
- const createZipCodeResponse = await postZipCode();
- if (!createZipCodeResponse) throw new Error('Error creating ZipCode');
- const zipCode = createZipCodeResponse.data.data.zipCode;
- console.log(createZipCodeResponse);
- const newAccessToken = createZipCodeResponse.headers['Authorization'];
- setZipCode(zipCode);
- setAccessToken(newAccessToken);
- console.log(
- 'isLoggedIn',
- useAuthStore.getState().isLoggedIn,
- 'access',
- useAuthStore.getState().accessToken,
- 'zipCode',
- useAuthStore.getState().zipCode,
- );
- }
- } else {
- navigate('/login');
+ if (!userInfo) throw new Error('Invalid user info');
+
+ login();
+ if (userInfo.accessToken) setAccessToken(userInfo.accessToken);
+
+ switch (redirectURL) {
+ case 'home':
+ {
+ const zipCodeResponse = await getMydata();
+ if (!zipCodeResponse) throw new Error('Error fetching user data');
+ setZipCode(zipCodeResponse.data.data.zipCode);
+ }
+ break;
+
+ case 'onboarding':
+ {
+ const createZipCodeResponse = await postZipCode();
+ if (!createZipCodeResponse) throw new Error('Error creating ZipCode');
+
+ setZipCode(createZipCodeResponse.data.data.zipCode);
+ const newAccessToken = createZipCodeResponse.headers['authorization']?.split(' ')[1];
+ if (!newAccessToken) throw new Error('Missing new access token');
+
+ setAccessToken(newAccessToken);
+ }
+ break;
+
+ default:
+ navigate('/notFound');
+ return;
}
+ navigate(redirectURL === 'onboarding' ? '/onboarding' : '/');
} catch (error) {
- console.error(error);
+ handleError(error);
}
};
- const redirection = () => {
- if (redirectURL === 'onboarding') navigate('/onboarding');
- else if (redirectURL === 'home') navigate('/');
- else navigate('/notFound');
- };
-
useEffect(() => {
- if (stateToken) {
- setUserInfo(stateToken as string);
- redirection();
- } else navigate('/notFound');
- }, []);
- return <>>;
+ if (!stateToken) {
+ navigate('/notFound');
+ return;
+ }
+
+ const fetchData = async () => {
+ await setUserInfo(stateToken as string);
+ };
+
+ fetchData();
+ }, [stateToken, navigate]);
+
+ return null;
};
export default AuthCallbackPage;
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
index fee0eb5..68f528f 100644
--- a/src/pages/Home/index.tsx
+++ b/src/pages/Home/index.tsx
@@ -1,6 +1,10 @@
+import { useEffect } from 'react';
+import { useNavigate } from 'react-router';
+
import HomeButton from '@/components/HomeButton';
import NoticeRollingPaper from '@/components/NoticeRollingPaper';
import useViewport from '@/hooks/useViewport';
+import useAuthStore from '@/stores/authStore';
import HomeBackgroundLeft from './components/HomeBackgroundLeft';
import HomeBackgroundRightBottom from './components/HomeBackgroundRightBottom';
@@ -11,7 +15,16 @@ import HomeRight from './components/HomeRight';
import LetterActions from './components/LetterActions';
const HomePage = () => {
+ const isLoggedIn = useAuthStore.getState().isLoggedIn;
+ const navigate = useNavigate();
+
useViewport();
+ useEffect(() => {
+ if (!isLoggedIn) {
+ navigate('/login');
+ }
+ }, []);
+
return (
diff --git a/src/pages/Landing/index.tsx b/src/pages/Landing/index.tsx
index 9db2fc7..b89bfda 100644
--- a/src/pages/Landing/index.tsx
+++ b/src/pages/Landing/index.tsx
@@ -1,13 +1,20 @@
-import { useState } from 'react';
-import { Navigate } from 'react-router';
+import { useState, useEffect } from 'react';
+import { Navigate, useNavigate } from 'react-router';
import { twMerge } from 'tailwind-merge';
import LandingImg from '@/assets/images/landing.png';
+import useAuthStore from '@/stores/authStore';
import { STYLE_CLASS } from './constants';
const Landing = () => {
const [step, setStep] = useState(0);
+ const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (isLoggedIn) navigate('/');
+ }, [isLoggedIn, navigate]);
if (step === 3) return ;
diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx
index ec651c7..2922331 100644
--- a/src/pages/Login/index.tsx
+++ b/src/pages/Login/index.tsx
@@ -1,12 +1,24 @@
+import { useEffect } from 'react';
+import { useNavigate } from 'react-router';
+
import { socialLogin } from '@/apis/auth';
import { GoogleIcon, KakaoIcon, NaverIcon, StampIcon } from '@/assets/icons';
+import useAuthStore from '@/stores/authStore';
import Background from './components/Background';
const LoginPage = () => {
+ const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
+ const navigate = useNavigate();
+
const handleLogin = (loginType: LoginType) => {
socialLogin(loginType);
};
+
+ useEffect(() => {
+ if (isLoggedIn) navigate('/');
+ }, [isLoggedIn]);
+
return (
<>
diff --git a/src/pages/MyPage/index.tsx b/src/pages/MyPage/index.tsx
index d97ec42..4c00707 100644
--- a/src/pages/MyPage/index.tsx
+++ b/src/pages/MyPage/index.tsx
@@ -27,7 +27,7 @@ const MyPage = () => {
const handleLeave = async () => {
try {
const response = await deleteUserInfo();
- if (!response) throw new Error('deletiongi failed');
+ if (!response) throw new Error('deletioning failed');
console.log(response);
} catch (error) {
console.error(error);
@@ -81,7 +81,9 @@ const MyPage = () => {
고객 센터
-
운영자에게 문의하기
+
+
운영자에게 문의하기
+
계정
@@ -92,7 +94,12 @@ const MyPage = () => {
{data.email}
-