Skip to content
Open
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
26 changes: 14 additions & 12 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { getToken } from '@utils/api';
import './scss/main.scss';
import Footer from './components/Footer';
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { getToken } from "@utils/api";
import "./scss/main.scss";
import Footer from "./components/Footer";

import Timetable from './views/timetable';
import Login, { LoginBox } from './views/login/index';
import PrivacyPolicy from './views/terms/PrivacyPolicy';
import TermsOfService from './views/terms/TermsOfService';
import Admin from './views/admin';
import usePopup from './components/usePopup';
import Notice from './views/notice';
import Timetable from "./views/timetable";
import Login, { LoginBox } from "./views/login/index";
import PrivacyPolicy from "./views/terms/PrivacyPolicy";
import TermsOfService from "./views/terms/TermsOfService";
import Admin from "./views/admin";
import EmailAuth from "./views/EmailAuth";
import usePopup from "./components/usePopup";
import Notice from "./views/notice";

function ErrorPage() {
return <h1>404 Not Found</h1>;
Expand All @@ -31,6 +32,7 @@ function App() {
<Route exact path="/admin" component={NeedLogin || Admin} />
<Route path="/privacy_policy" component={PrivacyPolicy} />
<Route path="/terms_of_service" component={TermsOfService} />
<Route path="/email_auth" component={EmailAuth} />
<Route path="*" component={ErrorPage} />
</Switch>
</Router>
Expand Down
96 changes: 50 additions & 46 deletions src/utils/api.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
/* eslint-disable no-console */
/* eslint-disable no-unused-vars */
import axios from 'axios';
import usePopup from '@components/usePopup';
import axios from "axios";
import usePopup from "@components/usePopup";

const { API_URL_BASE } = process.env;

const makeConfig = ({ method, url }, needToken = true) => (initialData = {}) => {
const makeConfig = ({ method, url }, needToken = true) => (
initialData = {}
) => {
const config = {
method,
url,
needToken,

setPath: (...path) => {
config.url += ['', ...path].join('/');
config.url += ["", ...path].join("/");
return config;
},

setQuery: query => {
setQuery: (query) => {
config.params = query;
return config;
},

setData: data => {
if (method === 'GET') {
setData: (data) => {
if (method === "GET") {
config.setQuery(data);
} else {
config.data = data;
Expand All @@ -35,66 +37,68 @@ const makeConfig = ({ method, url }, needToken = true) => (initialData = {}) =>
};

// API CONFIG OBJECT
const GET = url => ({ method: 'GET', url });
const POST = url => ({ method: 'POST', url });
const PUT = url => ({ method: 'PUT', url });
const PATCH = url => ({ method: 'PATCH', url });
const DELETE = url => ({ method: 'DELETE', url });
const GET = (url) => ({ method: "GET", url });
const POST = (url) => ({ method: "POST", url });
const PUT = (url) => ({ method: "PUT", url });
const PATCH = (url) => ({ method: "PATCH", url });
const DELETE = (url) => ({ method: "DELETE", url });

// API CONFIG LIST
export const API_LOGIN = makeConfig(POST('/user/login'), false);
export const API_GET_SEMESTER = makeConfig(GET('/semester'));
export const API_GET_SEMESTERS = makeConfig(GET('/semesters'));
export const API_GET_NOTICE = makeConfig(GET('/notice'));
export const API_GET_USE_NOTICE = makeConfig(GET('/notice/use'));
export const API_GET_HOT_NOTICE = makeConfig(GET('/notice/hot'));
export const API_CREATE_NOTICE = makeConfig(POST('/notice'));
export const API_UPDATE_NOTICE = makeConfig(PATCH('/notice'));
export const API_DELETE_NOTICE = makeConfig(DELETE('/notice'));
export const API_GET_ALL_LECTURES = makeConfig(GET('/lecture'));
export const API_UPDATE_LECTURES = makeConfig(PATCH('/lecture'));
export const API_DELETE_TLECTURE = makeConfig(DELETE('/timetable/tlecture'));
export const API_ADD_TLECTURE = makeConfig(POST('/timetable/tlecture'));
export const API_CREATE_TIMETABLE = makeConfig(POST('/timetable'));
export const API_DELETE_TIMETABLE = makeConfig(DELETE('/timetable'));
export const API_GET_TIMETABLES = makeConfig(GET('/timetable'));
export const API_PATCH_TIMETABLE_NAME = makeConfig(PATCH('/timetable/name'));
export const API_GET_HISTORIES = makeConfig(GET('/history'));
export const API_SIGN_UP = makeConfig(POST('/user'), false);
export const API_FIND_ID = makeConfig(GET('/user/id'), false);
export const API_FIND_PW = makeConfig(GET('/user/password'), false);
export const API_LOGIN = makeConfig(POST("/user/login"), false);
export const API_GET_SEMESTER = makeConfig(GET("/semester"));
export const API_GET_SEMESTERS = makeConfig(GET("/semesters"));
export const API_GET_NOTICE = makeConfig(GET("/notice"));
export const API_GET_USE_NOTICE = makeConfig(GET("/notice/use"));
export const API_GET_HOT_NOTICE = makeConfig(GET("/notice/hot"));
export const API_CREATE_NOTICE = makeConfig(POST("/notice"));
export const API_UPDATE_NOTICE = makeConfig(PATCH("/notice"));
export const API_DELETE_NOTICE = makeConfig(DELETE("/notice"));
export const API_GET_ALL_LECTURES = makeConfig(GET("/lecture"));
export const API_UPDATE_LECTURES = makeConfig(PATCH("/lecture"));
export const API_DELETE_TLECTURE = makeConfig(DELETE("/timetable/tlecture"));
export const API_ADD_TLECTURE = makeConfig(POST("/timetable/tlecture"));
export const API_CREATE_TIMETABLE = makeConfig(POST("/timetable"));
export const API_DELETE_TIMETABLE = makeConfig(DELETE("/timetable"));
export const API_GET_TIMETABLES = makeConfig(GET("/timetable"));
export const API_PATCH_TIMETABLE_NAME = makeConfig(PATCH("/timetable/name"));
export const API_GET_HISTORIES = makeConfig(GET("/history"));
export const API_SIGN_UP = makeConfig(POST("/user"), false);
export const API_FIND_ID = makeConfig(GET("/user/id"), false);
export const API_FIND_PW = makeConfig(GET("/user/password"), false);
export const API_MAKE_AUTH_CODE = makeConfig(POST("/user/auth/code"));
export const API_MATCH_AUTH_CODE = makeConfig(POST("/user/auth"));

const axiosInstance = axios.create({
baseURL: `${API_URL_BASE}/api`,
timeout: 20000,
});

axiosInstance.interceptors.request.use(
config => {
(config) => {
const token = getToken();
if (token) {
config.headers.Authorization = token;
}
return config;
},
error => {
(error) => {
const [, showPopup] = usePopup();
console.error(error);
showPopup('에러', '서버를 찾을 수 없어요...');
showPopup("에러", "서버를 찾을 수 없어요...");
return null;
},
}
);

axiosInstance.interceptors.response.use(
config => config,
error => Promise.resolve(error.response),
(config) => config,
(error) => Promise.resolve(error.response)
);

export async function requestAPI(config) {
try {
if (config.needToken && !getToken()) {
// token required but not found: API wouldn't be requested
window.location.href = '/login';
window.location.href = "/login";
return null;
}

Expand All @@ -114,25 +118,25 @@ export async function requestAPI(config) {
}

export function setToken(token) {
localStorage.setItem('token', token);
localStorage.setItem("token", token);
}

export function getToken() {
return localStorage.getItem('token');
return localStorage.getItem("token");
}

export function removeToken() {
localStorage.removeItem('token');
localStorage.removeItem("token");
}

export function setUserID(userID) {
localStorage.setItem('userID', userID);
localStorage.setItem("userID", userID);
}

export function getUserID() {
return localStorage.getItem('userID');
return localStorage.getItem("userID");
}

export function removeUserID() {
localStorage.removeItem('userID');
localStorage.removeItem("userID");
}
145 changes: 145 additions & 0 deletions src/views/EmailAuth.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useEffect, useState } from "react";
import StatusCodes from "http-status-codes";
import {
requestAPI,
API_MAKE_AUTH_CODE,
API_MATCH_AUTH_CODE,
} from "@utils/api";
import {
Button,
Box,
makeStyles,
Container,
Typography,
} from "@material-ui/core";
import UosInput from "@components/UosInput";

export default function EmailAuth() {
const [userEmail, setUserEmail] = useState(null);
const [isCodeSend, setIsCodeSend] = useState(false);
const [code, setCode] = useState(null);

const classes = useStyles();

const codeOnChange = (e) => {
setCode(e.target.value);
};

const emailOnChange = (e) => {
setUserEmail(e.target.value);
};

const codeOnEnterPress = (e) => {
if (e.key == "Enter") {
handleAuth();
}
};

const emailOnEnterPress = (e) => {
if (e.key == "Enter") {
handleSendCode();
}
};

const handleSendCode = async () => {
try {
const res = await requestAPI(API_MAKE_AUTH_CODE({ email: userEmail }));
if (res.status === StatusCodes.NO_CONTENT) setIsCodeSend(true);
} catch (err) {
alert(err.message);
throw err;
}
};

const handleAuth = async () => {
try {
const userId = window.localStorage.getItem("userID");
const res = await requestAPI(API_MATCH_AUTH_CODE().setPath(userId, code));
if (res.status === StatusCodes.NO_CONTENT) {
alert("이메일이 성공적으로 인증되었습니다!");
}
} catch (err) {
alert(err);
throw err;
}
};

return (
<Container className={classes.container}>
<Typography className={classes.title}>이메일 인증</Typography>
<Typography className={classes.subTitle}>
서울시립대학교 포털 이메일 인증을 하신 후 강의 교환 서비스를 이용하실 수
있습니다.
</Typography>
<Container className={classes.inputWrapper}>
<UosInput
className={classes.input}
type="email"
name="email"
label="email"
onChange={emailOnChange}
onKeyPress={emailOnEnterPress}
value={userEmail}
placeholder="서울시립대학교 이메일을 입력해주세요."
/>
<Button className={classes.button} onClick={handleSendCode}>
인증 코드 전송
</Button>
</Container>
{isCodeSend && (
<Container className={classes.inputWrapper}>
<UosInput
className={classes.input}
type="password"
name="code"
label="인증코드"
onChange={codeOnChange}
onKeyPress={codeOnEnterPress}
value={code}
/>
<Button className={classes.button} onClick={handleAuth}>
인증
</Button>
</Container>
)}
</Container>
);
}

const useStyles = makeStyles({
container: {
margin: "auto",
},
title: {
alignItems: "center",
textAlign: "center",
fontSize: "1.5rem",
fontWeight: "700",
marginBottom: "1rem",
},
subTitle: {
alignItems: "center",
textAlign: "center",
fontSize: "1rem",
marginButtom: "1rem",
color: "#A3A2A2",
},
inputWrapper: {
display: "flex",
width: "60%",
flexDirection: "row",
marginTop: "2rem",
},
input: {
flex: 9,
},
button: {
flex: 3,
marginLeft: "1rem",
backgroundColor: "#CFCFCF",
textAlign: "center",
boxShadow: "3px 4px 4px rgba(0, 0, 0, 0.25)",
borderRadius: "1.5rem",
color: "#FFF",
},
});
Loading