Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…o-with-me into 20-유저가-작성한-코드-결과-서버로-보내기
  • Loading branch information
dev2820 committed Nov 23, 2023
2 parents 4900e23 + 8069ad2 commit e861bd0
Show file tree
Hide file tree
Showing 14 changed files with 263 additions and 44 deletions.
6 changes: 3 additions & 3 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!doctype html>
<html lang="en">
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Algo With Me</title>
</head>
<body>
<div id="root"></div>
Expand Down
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
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',
});
33 changes: 33 additions & 0 deletions frontend/src/components/Login/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { css } from '@style/css';

interface Props {
onClickLogin: () => void;
}

export default function Login({ onClickLogin }: Props) {
return (
<section className={loginWrapperStyle}>
<header className={loginHeaderStyle}>Algo With Me</header>
<button onClick={onClickLogin}>Github으로 로그인</button>
</section>
);
}

const loginWrapperStyle = css({
border: '1px solid white',
borderRadius: '10px',
width: '100%',
height: '100%',
maxWidth: '500px',
maxHeight: '200px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
});

const loginHeaderStyle = css({
fontSize: '3rem',
fontWeight: 'bold',
textAlign: 'center',
padding: '1rem',
});
25 changes: 17 additions & 8 deletions frontend/src/components/Problem/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,50 @@ import { css } from '@style/css';
import { ReactNode } from 'react';
import ReactMarkdown from 'react-markdown';

import type { Components } from 'hast-util-to-jsx-runtime/lib/components';
import remarkGfm from 'remark-gfm';

interface Components {
table: (props: TableProps) => JSX.Element;
th: (props: ThProps) => JSX.Element;
td: (props: TdProps) => JSX.Element;
code: (props: CodeProps) => JSX.Element;
ul: (props: UlProps) => JSX.Element;
ol: (props: OlProps) => JSX.Element;
li: (props: LiProps) => JSX.Element;
}

interface Props {
markdownContent: string;
}

interface TableProps {
children: ReactNode;
children?: ReactNode;
}

interface ThProps {
children: ReactNode;
children?: ReactNode;
}

interface TdProps {
children: ReactNode;
children?: ReactNode;
}

interface CodeProps {
inline?: boolean;
className?: string;
children: ReactNode;
children?: ReactNode;
}

interface UlProps {
children: ReactNode;
children?: ReactNode;
}

interface OlProps {
children: ReactNode;
children?: ReactNode;
}

interface LiProps {
children: ReactNode;
children?: ReactNode;
}

export default function Markdown(props: Props) {
Expand Down
77 changes: 77 additions & 0 deletions frontend/src/hooks/login/useAuth.ts
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);

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

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>,
);
17 changes: 17 additions & 0 deletions frontend/src/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Login from '@/components/Login';

const GITHUB_AUTH_URL = 'http://101.101.208.240:3000/auths/github';

export default function LoginPage() {
// 넘겨주는 함수는 handle, 함수를 넘길 때의 프로펄티 네임은 on
const handleLogin = () => {
try {
window.location.href = GITHUB_AUTH_URL;
} catch (e) {
const error = e as Error;
console.error(error.message);
}
};

return <Login onClickLogin={handleLogin} />;
}
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' : '',
},
]);
);

export default router;
5 changes: 1 addition & 4 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import path from 'path';
import { defineConfig } from 'vite';

export default defineConfig({
server: {
open: 'frontend/dist/index.html',
},
base: '',
base: './',
resolve: {
alias: [
{
Expand Down

0 comments on commit e861bd0

Please sign in to comment.