diff --git a/frontend/src/auth/RequireAuth.tsx b/frontend/src/auth/RequireAuth.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3556a624f9942e70ce74d0f447ce2f9ec2fceb58 --- /dev/null +++ b/frontend/src/auth/RequireAuth.tsx @@ -0,0 +1,15 @@ +import React, { FC } from "react"; +import { Navigate, useLocation } from "react-router-dom"; +import { useAuth } from "./auth.context"; + +export const RequireAuth: FC<{ children: JSX.Element }> = ({ children }) => { + const { user } = useAuth(); + const location = useLocation(); + + if (!user.isLoggedIn) { + return ( + + ); + } + return children; +}; diff --git a/frontend/src/auth/auth.context.tsx b/frontend/src/auth/auth.context.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0d8bd6605d0e54225d9dcabe5e9cedabd0a13db2 --- /dev/null +++ b/frontend/src/auth/auth.context.tsx @@ -0,0 +1,49 @@ +import React, { + createContext, + FC, + ReactNode, + useContext, + useState, +} from "react"; +import { IUser } from "../types"; +import { getLocalUser, handleLogin, handleLogout } from "./auth.helper"; + +interface IAuthContext { + login: (email: string, password: string, cb?: VoidFunction) => Promise; + logout: (cb?: VoidFunction) => Promise; + user: IUser; +} + +const AuthContext = createContext({ + login: async () => {}, + logout: async () => {}, + user: { isLoggedIn: false }, +}); + +export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => { + const [user, setUser] = useState(getLocalUser()); + + const login = async ( + email: string, + password: string, + cb: VoidFunction = () => {} + ) => { + const user = await handleLogin(email, password); + setUser(user); + cb(); + }; + + const logout = async (cb: VoidFunction = () => {}) => { + await handleLogout(); + setUser({ ...user, isLoggedIn: false }); + cb(); + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); diff --git a/frontend/src/auth/auth.helper.ts b/frontend/src/auth/auth.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..3bcdcc73f4dfc2a587b9a5147a34fdfef64c5d95 --- /dev/null +++ b/frontend/src/auth/auth.helper.ts @@ -0,0 +1,60 @@ +import { authApi } from "../apis"; +import { IUser } from "../types"; + +const LOCAL_USER_INFO = "survey-user-info"; + +/** + * 1. 백엔드 로그인을 호출하여 로그인 정보를 얻습니다. + * 2. 로컬 저장소에 저장합니다. + * 3. 사용자 정보를 반환합니다. + * @param email 이메일 + * @param password 비밀번호 + * @returns 사용자 정보 + */ +export const handleLogin = async (email: string, password: string) => { + const user: IUser = await authApi.login(email, password); + // 로컬 저장소에는 로그인 여부만 저장 + localStorage.setItem( + LOCAL_USER_INFO, + JSON.stringify({ + isLoggedIn: user.isLoggedIn, + }) + ); + return user; +}; + +/** + * 로컬 저장소의 정보를 삭제합니다. + * 백엔드 로그아웃을 호출하여 쿠키를 제거합니다. + */ +export const handleLogout = async () => { + console.log("handle logout called"); + localStorage.removeItem(LOCAL_USER_INFO); + try { + await authApi.logout(); + } catch (error) { + console.log("logout 중에 에러 발생:", error); + } +}; + +/** + * 1. 로컬 저장소에 저장된 사용자 로그인 정보를 반환합니다. + * 2. 로컬 저장소에 정보가 없으면 { isLoggedIn: false }를 반환합니다. + * @returns 로컬 저장소에 저장된 사용자 정보 + */ +export const getLocalUser = () => { + console.log("get local user called"); + const userInfo = localStorage.getItem(LOCAL_USER_INFO); + const user: IUser = { isLoggedIn: false }; + if (!userInfo) { + return user; + } + + const userData = JSON.parse(userInfo); + if (userData.isLoggedIn) { + user.isLoggedIn = true; + } else { + user.isLoggedIn = false; + } + return user; +}; diff --git a/frontend/src/types/index.tsx b/frontend/src/types/index.tsx index 49f5d3af94043f5486e2195a465dbb0150a69f97..5c4c83562f904e95f19fefde582c98b2217c3c70 100644 --- a/frontend/src/types/index.tsx +++ b/frontend/src/types/index.tsx @@ -1,3 +1,9 @@ +export interface IUser { + email?: string; + isLoggedIn: boolean; + _id?: string; +} + export interface PostType { id: string; title: string;