Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#68] 공통 헤더 만들기 #105

Merged
merged 6 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added frontend/public/algo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions frontend/src/components/Auth/AuthContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext } from 'react';

const AuthContext = createContext({
isLoggedin: false,
login: () => {},
logout: () => {},
});

export default AuthContext;
20 changes: 20 additions & 0 deletions frontend/src/components/Auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useState } from 'react';

import AuthContext from './AuthContext';

const AuthProvider = ({ children }: { children: React.ReactNode }) => {
function logout() {
setIsLogined(false);
}
function login() {
setIsLogined(true);
}

const [isLoggedin, setIsLogined] = useState<boolean>(false);

return (
<AuthContext.Provider value={{ isLoggedin, logout, login }}>{children}</AuthContext.Provider>
);
};

export default AuthProvider;
8 changes: 8 additions & 0 deletions frontend/src/components/Common/Logo.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 식으로 할 수 있었네요, 배워갑니다:D

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface Props {
size: string;
}

export default function Logo({ size }: Props) {
// public이란 곳에 리소스 넣어두면 build시에 바로 밑에 생기기 때문에 ./으로 접근 가능합니다.
return <img src="./algo.png" alt="logo" width={size} height={size} />;
}
35 changes: 35 additions & 0 deletions frontend/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { css } from '@style/css';

import Logo from '@/components/Common/Logo';
import useAuth from '@/hooks/login/useAuth';

export default function Header() {
const { changeLoginInfo, changeLogoutInfo, isLoggedin } = useAuth();

const handleLogin = () => {
changeLoginInfo();
};

const handleLogout = () => {
changeLogoutInfo();
};

return (
<header className={headerStyle}>
<Logo size="36px" />
{isLoggedin ? (
<button onClick={handleLogout}> 로그아웃 </button>
) : (
<button onClick={handleLogin}> 로그인 </button>
)}
</header>
);
}

const headerStyle = css({
width: '100%',
height: '40px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
});
77 changes: 77 additions & 0 deletions frontend/src/hooks/login/useAuth.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useAuth로 Provider를 참조하는 것 아주 좋습니다. 👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useContext, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

import AuthContext from '@/components/Auth/AuthContext';

import axios from 'axios';

const TOKEN_KEY = 'accessToken';
const BASE_URL = import.meta.env.VITE_API_URL;
const AUTH_TEST_PATH = '/auths/tests';

const URL = `${BASE_URL}${AUTH_TEST_PATH}`;

const fetchTokenValid = async (token: string) => {
try {
const response = await axios.get(URL, {
headers: { Authorization: `Bearer ${token}` },
});
// {email: '[email protected]', nickname: '[email protected]'}
// 저장할 지 말지는 나중에 결정
const data = await response.data;
console.log(data, '인증 받음.');
if (response.status === 200) return true;
// 올바른 유저라는 검증을 받음.
} catch (e) {
console.log('인증 받지 못함.', e);
return false;
}
};

export default function useAuth() {
const { isLoggedin, login, logout } = useContext(AuthContext);

const location = useLocation();
const navigate = useNavigate();

useEffect(() => {
if (isLoggedin) return;
const queryParams = new URLSearchParams(location.search);
const token = queryParams.get(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분이 로그인 토큰을 가져오는 코드인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 맞습니다. queryParams.get(TOKEN_KEY)이 /?accessToke="여기꺼가져오는코드" 이고 이게 없다면 로컬스토리지에서 찾아라 입니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 로그인 여부를 확인하는 로직이 필요한데, 그러면 이 코드를 사용하면 될까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네네넵!
로그인 여부는 저걸로 체크하면 되고, 인증 관련 문제가 만약에 생긴다면 저한테 물어보시면 바로 알려드릴게요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스크린샷 2023-11-24 오전 12 04 54
정확히 이렇게 보내야하는데, 저도 처음엔 모르겠더라구요 나중에 말씀하면 바로 알려드릴게요

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵, 감사합니다!


if (!token) return;
evaluateToken(token);
}, []);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isLogin에 의해 결정되는 sideeffect 같은데, 의존성에 isLogin이 있는게 좋지 않을까요?

Copy link
Collaborator Author

@mahwin mahwin Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. isLoggedin이 false에서 true가 됐을 경우
    로그인 검증을 금방해서 true가 됐으니 검증할 필요 x

isLoggedin이 �true에서 false가 됐을 경우 => logout을 눌렀을 때임.

  1. url에 accessToken이 있는데,
    logout을 눌러 isLoggedin으로 바꾸면 의존성 배열에 있는 isLoggedin 때문에 다시 useEffect함수가 돌아서 다시 검증해서 로그인으로 만들어버리지 않을까요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api 서버가 꺼져있네요. 서버 켜지는 대로 직접 돌려보겠습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reload가 아니라서 의존성 배열에 추가하니 위 쿼리를 읽고 tokenValid 검증을 다시 해서 로그인 버튼이 서버에서 토큰 검증 후 다시 로그아웃되네요!

2023-11-23.11.49.21.mov

Copy link
Collaborator Author

@mahwin mahwin Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의존성 배열 뺏을 때는 정상작동합니다.

2023-11-23.11.53.29.mov


const evaluateToken = async (token: string) => {
const isValid = await fetchTokenValid(token);
isValid ? saveAuthInfo(token) : removeAuthInfo();
};

const saveAuthInfo = (token: string) => {
localStorage.setItem(TOKEN_KEY, token);
login();
};

const removeAuthInfo = () => {
// accessToken 없애기
// AuthContext 없애기
localStorage.removeItem(TOKEN_KEY);
logout();
};

const changeLoginInfo = () => {
// accessToken 없애기
// AuthContext 없애기
// 로그인 페이지로 이동
removeAuthInfo();
navigate('/login');
};

const changeLogoutInfo = () => {
// accessToken 없애기
// AuthContext 없애기
removeAuthInfo();
};
return { changeLoginInfo, changeLogoutInfo, isLoggedin };
}
5 changes: 4 additions & 1 deletion frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';

import AuthProvider from './components/Auth/AuthProvider';
import router from './router';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
<AuthProvider>
<RouterProvider router={router}></RouterProvider>
</AuthProvider>
</React.StrictMode>,
);
16 changes: 10 additions & 6 deletions frontend/src/pages/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { css } from '@style/css';

import Header from '@/components/Header';
import GoToCreateCompetitionLink from '@/components/Main/Buttons/GoToCreateCompetitionLink';
import CompetitionList from '@/components/Main/CompetitionList';
import { SITE } from '@/constants';

function MainPage() {
return (
<main className={style}>
<span className={ProjectNameStyle}>{SITE.NAME} </span>
<span>{SITE.PAGE_DESCRIPTION} </span>
<GoToCreateCompetitionLink />
<CompetitionList />
</main>
<>
<Header />
<main className={style}>
<span className={ProjectNameStyle}>{SITE.NAME} </span>
<span>{SITE.PAGE_DESCRIPTION} </span>
<GoToCreateCompetitionLink />
<CompetitionList />
</main>
</>
);
}

Expand Down
51 changes: 29 additions & 22 deletions frontend/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,41 @@ import { createBrowserRouter } from 'react-router-dom';

import ContestPage from '@/pages/ContestPage';
import CreateCompetitionPage from '@/pages/CreateCompetitionPage';
import LoginPage from '@/pages/LoginPage';
import MainPage from '@/pages/MainPage';
import ProblemPage from '@/pages/ProblemPage';

import App from './App';

const router = createBrowserRouter([
const router = createBrowserRouter(
[
{
path: '/',
element: <App />,
children: [
{
index: true,
element: <MainPage />,
},
{
path: '/contest/:id',
element: <ContestPage />,
},
{
path: '/problem/:id',
element: <ProblemPage />,
},
{
path: '/contest/create',
element: <CreateCompetitionPage />,
},
{ path: '/login', element: <LoginPage /> },
],
},
],
{
path: '/',
element: <App />,
children: [
{
index: true,
element: <MainPage />,
},
{
path: '/contest/:id',
element: <ContestPage />,
},
{
path: '/problem/:id',
element: <ProblemPage />,
},
{
path: '/contest/create',
element: <CreateCompetitionPage />,
},
],
basename: process.env.NODE_ENV === 'production' ? '/web12-Algo-With-Me' : '',
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 부분이 있어야 pnpm run deploy했을 떄 제대로 경로 찾아집니다

},
]);
);

export default router;
Loading