diff --git a/frontend/src/api/authentication.ts b/frontend/src/api/authentication.ts index 4a11a5530..58f2ed2cb 100644 --- a/frontend/src/api/authentication.ts +++ b/frontend/src/api/authentication.ts @@ -5,6 +5,7 @@ const API_URL = process.env.REACT_APP_API_URL; export const SIGNUP_API_URL = `${API_URL}/signup`; export const LOGIN_API_URL = `${API_URL}/login`; +export const LOGOUT_API_URL = `${API_URL}/logout`; export const LOGIN_STATE_API_URL = `${API_URL}/login/check`; export const CHECK_USERNAME_API_URL = `${API_URL}/check-username`; export const CHECK_EMAIL_API_URL = `${API_URL}/check-email`; @@ -26,10 +27,24 @@ export const postLogin = async (loginInfo: LoginRequest) => { return response; }; +export const postLogout = async () => { + const response = await customFetch({ + method: 'POST', + url: `${LOGOUT_API_URL}`, + }); + + return response; +}; + export const getLoginState = async () => { const url = `${LOGIN_STATE_API_URL}`; - const response = await customFetch({ url }); + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); if (response.status === 401) { throw new Error('로그인을 해주세요.'); @@ -46,7 +61,12 @@ export const checkEmail = async (email: string) => { const params = new URLSearchParams({ email }); const url = `${CHECK_EMAIL_API_URL}?${params}`; - const response = await customFetch({ url }); + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); if (response.status === 409) { throw new Error('중복된 이메일입니다.'); @@ -63,7 +83,12 @@ export const checkUsername = async (username: string) => { const params = new URLSearchParams({ username }); const url = `${CHECK_USERNAME_API_URL}?${params}`; - const response = await customFetch({ url }); + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); if (response.status === 409) { throw new Error('중복된 닉네임입니다.'); diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 891fbf634..8654cf40f 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -15,4 +15,5 @@ export { LOGIN_API_URL, LOGIN_STATE_API_URL, SIGNUP_API_URL, + LOGOUT_API_URL, } from './authentication'; diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx index 5d90ebd0c..ed0b79a44 100644 --- a/frontend/src/components/Header/Header.tsx +++ b/frontend/src/components/Header/Header.tsx @@ -1,12 +1,16 @@ import { Link } from 'react-router-dom'; import { logoIcon, newTemplateIcon, userMenuIcon } from '@/assets/images'; -import { Flex, Heading, Text } from '@/components'; +import { Button, Flex, Heading, Text } from '@/components'; import { useCheckLoginState } from '@/hooks/authentication'; -import { theme } from '@/style/theme'; +import { useAuth } from '@/hooks/authentication/useAuth'; +import { useLogoutMutation } from '@/queries/authentication/useLogoutMutation'; +import { theme } from '../../style/theme'; import * as S from './Header.style'; const Header = ({ headerRef }: { headerRef: React.RefObject }) => { + const { isLogin } = useAuth(); + useCheckLoginState(); return ( @@ -19,8 +23,12 @@ const Header = ({ headerRef }: { headerRef: React.RefObject }) = - - + + + + {isLogin ? : } @@ -46,21 +54,26 @@ const NavOption = ({ route, name }: { route: string; name: string }) => ( ); -const NewTemplateButton = () => ( - - - - - 새 템플릿 - - - -); +const UserMenuButton = () => { + const { mutateAsync } = useLogoutMutation(); -const UserMenuButton = () => ( - - 사용자 메뉴 - + const handleLogoutButton = async () => { + await mutateAsync(); + }; + + return ( + + 사용자 메뉴 + + ); +}; + +const LoginButton = () => ( + + + ); export default Header; diff --git a/frontend/src/contexts/authContext.tsx b/frontend/src/contexts/authContext.tsx new file mode 100644 index 000000000..599cf71aa --- /dev/null +++ b/frontend/src/contexts/authContext.tsx @@ -0,0 +1,18 @@ +import { createContext, useState, ReactNode } from 'react'; + +interface AuthContextProps { + isLogin: boolean; + handleLoginState: (state: boolean) => void; +} + +export const AuthContext = createContext(undefined); + +export const AuthProvider = ({ children }: { children: ReactNode }) => { + const [isLogin, setIsLogin] = useState(false); + + const handleLoginState = (state: boolean) => { + setIsLogin(state); + }; + + return {children}; +}; diff --git a/frontend/src/hooks/authentication/useAuth.ts b/frontend/src/hooks/authentication/useAuth.ts new file mode 100644 index 000000000..14072089c --- /dev/null +++ b/frontend/src/hooks/authentication/useAuth.ts @@ -0,0 +1,13 @@ +import { useContext } from 'react'; + +import { AuthContext } from '@/contexts/authContext'; + +export const useAuth = () => { + const context = useContext(AuthContext); + + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + + return context; +}; diff --git a/frontend/src/hooks/authentication/useCheckLoginState.ts b/frontend/src/hooks/authentication/useCheckLoginState.ts index 1c15e4334..163faeb92 100644 --- a/frontend/src/hooks/authentication/useCheckLoginState.ts +++ b/frontend/src/hooks/authentication/useCheckLoginState.ts @@ -2,10 +2,12 @@ import { useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useLoginStateQuery } from '@/queries/authentication/useLoginStateQuery'; +import { useAuth } from './useAuth'; export const useCheckLoginState = () => { - const { error, isError } = useLoginStateQuery(); + const { error, isError, isSuccess } = useLoginStateQuery(); const navigate = useNavigate(); + const { handleLoginState } = useAuth(); const handleLoginNavigate = useCallback(() => { navigate('/login'); @@ -15,6 +17,11 @@ export const useCheckLoginState = () => { if (isError) { alert(error.message); handleLoginNavigate(); + handleLoginState(false); } - }, [error, isError, handleLoginNavigate]); + + if (isSuccess) { + handleLoginState(true); + } + }, [error, isError, isSuccess, handleLoginNavigate, handleLoginState]); }; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 21741a185..0327286b6 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -7,6 +7,7 @@ import { RouterProvider } from 'react-router-dom'; import { HeaderProvider } from './context/HeaderContext'; import { ToastProvider } from './context/ToastContext'; +import { AuthProvider } from './contexts/authContext'; import router from './routes/router'; import GlobalStyles from './style/GlobalStyles'; import { theme } from './style/theme'; @@ -44,12 +45,14 @@ enableMocking().then(() => { - - - - - - + + + + + + + + , diff --git a/frontend/src/mocks/handlers.ts b/frontend/src/mocks/handlers.ts index 088efd1c4..fc4f226af 100644 --- a/frontend/src/mocks/handlers.ts +++ b/frontend/src/mocks/handlers.ts @@ -7,6 +7,7 @@ import { CHECK_USERNAME_API_URL, LOGIN_API_URL, LOGIN_STATE_API_URL, + LOGOUT_API_URL, SIGNUP_API_URL, } from '@/api'; import mockCategoryList from './categoryList.json'; @@ -62,6 +63,14 @@ const authenticationHandler = [ status: 200, }), ), + http.post( + `${LOGOUT_API_URL}`, + () => + new HttpResponse(null, { + status: 204, + statusText: 'AUTHORIZED', + }), + ), http.get( `${CHECK_EMAIL_API_URL}`, () => diff --git a/frontend/src/queries/authentication/useLogoutMutation.ts b/frontend/src/queries/authentication/useLogoutMutation.ts new file mode 100644 index 000000000..2b372d97d --- /dev/null +++ b/frontend/src/queries/authentication/useLogoutMutation.ts @@ -0,0 +1,18 @@ +import { useMutation } from '@tanstack/react-query'; + +import { postLogout } from '@/api/authentication'; +import { useAuth } from '@/hooks/authentication/useAuth'; + +export const useLogoutMutation = () => { + const { handleLoginState } = useAuth(); + + return useMutation({ + mutationFn: () => postLogout(), + onSuccess: () => { + handleLoginState(false); + }, + onError: () => { + console.log('mutation'); + }, + }); +};