송용우

Merge commit '95c511dc' into develop

......@@ -4,6 +4,7 @@ import './App.css';
import LoginPage from './pages/LoginPage';
import RegisterPage from './pages/RegisterPage';
import HomePage from './pages/HomePage';
import SettingPage from './pages/SettingPage';
function App() {
return (
......@@ -11,6 +12,7 @@ function App() {
<Route component={HomePage} path={['/@:username', '/']} exact />
<Route component={LoginPage} path="/login" />
<Route component={RegisterPage} path="/register" />
<Route component={SettingPage} path="/setting" />
</>
);
}
......
import React from 'react';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';
const categories = [
{
name: 'home',
text: '홈',
},
{
name: 'setting',
text: '설정',
},
];
const CategoriesBlock = styled.div`
display: flex;
padding: 1rem;
margin: 0 auto;
@media screen and (max-width: 768px) {
width: 100%;
overflow-x: auto;
}
`;
const Category = styled(NavLink)`
font-size: 1.2rem;
cursor: pointer;
white-space: pre;
text-decoration: none;
color: inherit;
padding-bottom: 0.25rem;
&:hover {
color: #495057;
}
& + & {
margin-left: 2rem;
}
&.active {
font-weight: 600;
border-bottom: 2px solid #22b8cf;
color: #22b8cf;
&:hover {
color: #3bc9db;
}
}
`;
const Categories = () => {
return (
<CategoriesBlock>
{categories.map((c) => (
<Category
activeClassName="active"
key={c.name}
exact={c.name === 'home'}
to={c.name === 'home' ? '/' : `/${c.name}`}
>
{c.text}
</Category>
))}
</CategoriesBlock>
);
};
export default Categories;
......@@ -3,6 +3,7 @@ import styled from 'styled-components';
import Responsive from './Responsive';
import Button from './Button';
import { Link } from 'react-router-dom';
import Categories from './Categories';
const HeaderBlock = styled.div`
position: fixed;
......@@ -35,7 +36,7 @@ const UserInfo = styled.div`
margin-right: 1rem;
`;
const Header = ({ user, onLogout }) => {
const Header = ({ user, onLogout, category, onSelect }) => {
return (
<>
<HeaderBlock>
......@@ -43,6 +44,11 @@ const Header = ({ user, onLogout }) => {
<Link to="/" className="logo">
작심삼일
</Link>
<Categories
category={category}
onSelect={onSelect}
className="right"
/>
{user ? (
<div className="right">
<UserInfo>{user.username}</UserInfo>
......
import React from 'react';
import styled from 'styled-components';
import Button from '../common/Button';
import palette from '../../lib/styles/palette';
const BJIDFormBlock = styled.div`
width: 100%;
border-top: 1px solid ${palette.gray[2]};
padding-top: 2rem;
h4 {
color: ${palette.gray[8]};
margin-top: 0;
margin-bottom: 0.5rem;
}
`;
const BJIDForm = ({ onChange, onBJIDSubmit, profile, onSyncBJIDSubmit }) => {
return (
<BJIDFormBlock>
<h4>백준 아이디</h4>
<form onSubmit={onBJIDSubmit}>
<input
name="userBJID"
onChange={onChange}
value={profile.userBJID}
placeholder="백준 아이디"
/>
<button type="submit">등록</button>
</form>
<button onClick={onSyncBJIDSubmit}>동기화</button>
</BJIDFormBlock>
);
};
export default BJIDForm;
import React from 'react';
import styled from 'styled-components';
import Button from '../common/Button';
import palette from '../../lib/styles/palette';
import BJIDForm from './BJIDForm';
const SettingFormBlock = styled.div`
h3 {
margin: 0;
color: ${palette.gray[8]};
margin-bottom: 1rem;
}
background: ${palette.gray[2]};
margin: 0 auto;
display: flex;
flex-direction: column;
`;
const StyledInput = styled.input`
font-size: 1rem;
border: none;
border-bottom: 1px solid ${palette.gray[5]};
padding-bottom: 0.5rem;
outline: none;
&:focus {
color: $oc-teal-7;
border-bottom: 1px solid ${palette.gray[7]};
}
& + & {
margin-top: 1rem;
}
`;
const SectionContainer = styled.div`
display: flex;
`;
const SettingForm = ({ onChange, onBJIDSubmit, profile, onSyncBJIDSubmit }) => {
return (
<SettingFormBlock>
<SectionContainer>
<h3>{profile.username}</h3>
<p>입력</p>
</SectionContainer>
<SectionContainer>
<BJIDForm
profile={profile}
onChange={onChange}
onBJIDSubmit={onBJIDSubmit}
onSyncBJIDSubmit={onSyncBJIDSubmit}
/>
</SectionContainer>
<SectionContainer>
<h3>친구</h3>
<StyledInput name="BJID" placeholder="친구 아이디" />
<Button>추가</Button>
</SectionContainer>
<h3>친구 리스트</h3>
</SettingFormBlock>
);
};
export default SettingForm;
......@@ -4,6 +4,7 @@ import Header from '../../components/common/Header';
import { logout } from '../../modules/user';
const HeaderContainer = () => {
const { user } = useSelector(({ user }) => ({ user: user.user }));
const dispatch = useDispatch();
const onLogout = () => {
dispatch(logout());
......
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { withRouter } from 'react-router-dom';
import {
changeField,
setBJID,
getPROFILE,
syncBJID,
} from '../../modules/profile';
import SettingForm from '../../components/setting/SettingForm';
import { sync } from '../../../node_modules/fast-glob/index';
const SettingContainer = ({ history }) => {
const dispatch = useDispatch();
const [error, setError] = useState(null);
const { user, profile } = useSelector(({ user, profile }) => ({
user: user.user,
profile: profile,
}));
const onChange = (e) => {
const { value, name } = e.target;
dispatch(
changeField({
key: name,
value: value,
}),
);
};
const onSyncBJIDSubmit = (e) => {
e.preventDefault();
let username = profile.username;
dispatch(syncBJID({ username }));
};
const onBJIDSubmit = (e) => {
e.preventDefault();
let username = profile.username;
let userBJID = profile.userBJID;
dispatch(setBJID({ username, userBJID }));
};
useEffect(() => {
console.log('1');
let username = JSON.parse(user).username;
dispatch(getPROFILE({ username }));
//Do Init Form
}, [dispatch]);
return (
<SettingForm
type="setting"
onChange={onChange}
onBJIDSubmit={onBJIDSubmit}
onSyncBJIDSubmit={onSyncBJIDSubmit}
profile={profile}
></SettingForm>
);
};
export default withRouter(SettingContainer);
import axios from './axios';
import axios from 'axios';
const client = axios.create();
export default client;
......
import client from './client';
export const setBJID = ({ username, userBJID }) =>
client.post('api/profile/setprofile', {
username: username,
userBJID: userBJID,
});
export const getPROFILE = ({ username }) =>
client.post('api/profile/getprofile', { username });
export const syncBJ = ({ username }) =>
client.patch('api/profile/syncBJ', { username });
......@@ -5,19 +5,19 @@ import createRequestSaga, {
createRequestActionTypes,
} from '../lib/createRequestSaga';
import * as authAPI from '../lib/api/auth';
const CHAGE_FIELD = 'auth/CHANGE_FIELD';
const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';
const REGISTER = 'auth/REGISTER';
const REGISTER_SUCCESS = 'auth/REGISTER_SUCCESS';
const REGISTER_FAILURE = 'auth/REGISTER_FAILURE';
const [REGISTER, REGISTER_SUCCESS, REGISTER_FAILURE] = createRequestActionTypes(
'auth/REGISTER',
);
const LOGIN = 'auth/LOGIN';
const LOGIN_SUCCESS = 'auth/LOGIN_SUCCESS';
const LOGIN_FAILURE = 'auth/LOGIN_FAILURE';
const [LOGIN, LOGIN_SUCCESS, LOGIN_FAILURE] = createRequestActionTypes(
'auth/REGISTER',
);
export const changeField = createAction(
CHAGE_FIELD,
CHANGE_FIELD,
({ form, key, value }) => ({
form,
key,
......@@ -59,7 +59,7 @@ export function* authSaga() {
const auth = handleActions(
{
[CHAGE_FIELD]: (state, { payload: { form, key, value } }) =>
[CHANGE_FIELD]: (state, { payload: { form, key, value } }) =>
produce(state, (draft) => {
draft[form][key] = value;
}),
......
......@@ -3,15 +3,17 @@ import { all } from 'redux-saga/effects';
import auth, { authSaga } from './auth';
import loading from './loading';
import user, { userSaga } from './user';
import profile, { profileSaga } from './profile';
const rootReducer = combineReducers({
auth,
loading,
user,
profile,
});
export function* rootSaga() {
yield all([authSaga(), userSaga()]);
yield all([authSaga(), userSaga(), profileSaga()]);
}
export default rootReducer;
......
import { createAction, handleActions } from 'redux-actions';
import createRequestSaga, {
createRequestActionTypes,
} from '../lib/createRequestSaga';
import produce from 'immer';
import * as profileAPI from '../lib/api/profile';
import { takeLatest } from 'redux-saga/effects';
const INITIALIZE = 'profile/INITIALIZE';
const CHANGE_FIELD = 'profile/CHANGE_FIELD';
const [SET_BJID, SET_BJID_SUCCESS, SET_BJID_FAILURE] = createRequestActionTypes(
'profile/SET_BJID',
);
const [
GET_PROFILE,
GET_PROFILE_SUCCESS,
GET_PROFILE_FAILURE,
] = createRequestActionTypes('profile/GET_PROFILE');
const [
SYNC_BJID,
SYNC_BJID_SUCCESS,
SYNC_BJID_FAILURE,
] = createRequestActionTypes('profile/SYNC_BJID');
export const syncBJID = createAction(SYNC_BJID, ({ username }) => ({
username,
}));
export const setBJID = createAction(SET_BJID, ({ username, userBJID }) => ({
username,
userBJID,
}));
export const changeField = createAction(CHANGE_FIELD, ({ key, value }) => ({
key,
value,
}));
export const getPROFILE = createAction(GET_PROFILE, ({ username }) => ({
username,
}));
const initialState = {
username: '',
userBJID: '',
solvedBJ: '',
friendList: [],
profileError: '',
};
const getPROFILESaga = createRequestSaga(GET_PROFILE, profileAPI.getPROFILE);
const setBJIDSaga = createRequestSaga(SET_BJID, profileAPI.setBJID);
const syncBJIDSaga = createRequestSaga(SYNC_BJID, profileAPI.syncBJ);
export function* profileSaga() {
yield takeLatest(SET_BJID, setBJIDSaga);
yield takeLatest(GET_PROFILE, getPROFILESaga);
yield takeLatest(SYNC_BJID, syncBJIDSaga);
}
export default handleActions(
{
[INITIALIZE]: (state) => initialState,
[CHANGE_FIELD]: (state, { payload: { key, value } }) =>
produce(state, (draft) => {
draft[key] = value;
}),
[GET_PROFILE_SUCCESS]: (
state,
{ payload: { username, userBJID, solvedBJ, friendList } },
) => ({
...state,
username: username,
userBJID: userBJID,
solvedBJ: solvedBJ,
friendList: friendList,
profileError: null,
}),
[GET_PROFILE_FAILURE]: (state, { payload: error }) => ({
...state,
profileError: error,
}),
[SET_BJID_SUCCESS]: (state, { payload: { userBJID } }) => ({
...state,
userBJID: userBJID,
profileError: null,
}),
[SET_BJID_FAILURE]: (state, { payload: error }) => ({
...state,
profileError: error,
}),
[SYNC_BJID_SUCCESS]: (state, { payload: { solvedBJ } }) => ({
...state,
solvedBJ,
profileError: null,
}),
[SYNC_BJID_FAILURE]: (state, { payload: error }) => ({
...state,
profileError: error,
}),
},
initialState,
);
......@@ -6,7 +6,7 @@ const HomePage = () => {
return (
<div>
<HeaderContainer />
<Button>test</Button>
<Button>home</Button>
</div>
);
};
......
import React from 'react';
import HeaderContainer from '../containers/common/HeaderContainer';
import SettingForm from '../components/setting/SettingForm';
import SettingContainer from '../containers/setting/SettingContainer';
const SettingPage = () => {
return (
<div>
<HeaderContainer />
<SettingContainer></SettingContainer>
</div>
);
};
export default SettingPage;
......@@ -18,20 +18,22 @@
## API Table
| group | description | method | URL | Detail | Auth |
| ------- | --------------------------- | ------ | ------------------------ | -------- | --------- |
| user | 유저 등록 | POST | api/user | 바로가기 | JWT Token |
| user | 유저 삭제 | DELETE | api/user:id | 바로가기 | JWT Token |
| user | 특정 유저 조회 | GET | api/user:id | 바로가기 | None |
| user | 전체 유저 조회 | GET | api/user | 바로가기 | JWT Token |
| friend | 유저 친구 등록 | POST | api/friend | 바로가기 | JWT Token |
| friend | 유저의 친구 조회 | GET | api/friend:id | 바로가기 | None |
| profile | 유저가 푼 문제 조회(백준) | GET | api/profile/solvedBJ:id | 바로가기 | None |
| profile | 유저가 푼 문제 동기화(백준) | PATCH | api/profile/syncBJ | 바로가기 | None |
| profile | 유저 정보 수정 | POST | api/profile/setprofile | 바로가기 | JWT TOKEN |
| profile | 추천 문제 조회 | GET | api/profile/recommend:id | 바로가기 | None |
| notify | 슬랙 메시지 전송 요청 | POST | api/notify/slack | 바로가기 | Jwt Token |
| auth | 로그인 | POST | api/auth/login | 바로가기 | None |
| auth | 로그아웃 | POST | api/auth/logout | 바로가기 | JWT Token |
| auth | 회원가입 | POST | api/auth/register | 바로가기 | None |
| auth | 로그인 확인 | GET | api/auth/check | 바로가기 | None |
| group | description | method | URL | Detail | Auth |
| ------- | --------------------------- | --------- | ------------------------ | -------- | --------- |
| user | 유저 등록 | POST | api/user | 바로가기 | JWT Token |
| user | 유저 삭제 | DELETE | api/user:id | 바로가기 | JWT Token |
| user | 특정 유저 조회 | GET | api/user:id | 바로가기 | None |
| user | 전체 유저 조회 | GET | api/user | 바로가기 | JWT Token |
| friend | 유저 친구 등록 | POST | api/friend | 바로가기 | JWT Token |
| friend | 유저의 친구 조회 | GET | api/friend:id | 바로가기 | None |
| profile | 유저가 푼 문제 조회(백준) | GET | api/profile/solvedBJ:id | 바로가기 | None |
| profile | 유저가 푼 문제 동기화(백준) | PATCH | api/profile/syncBJ | 바로가기 | None |
| profile | 유저 정보 수정 | POST | api/profile/setprofile | 바로가기 | JWT TOKEN |
| profile | 유저 정보 받아오기 | POST | api/profile/getprofile | 바로가기 | JWT |
| profile | 추천 문제 조회 | GET | api/profile/recommend:id | 바로가기 | None |
| notify | 슬랙 메시지 전송 요청 | POST | api/notify/ |
| slack | 바로가기 | Jwt Token |
| auth | 로그인 | POST | api/auth/login | 바로가기 | None |
| auth | 로그아웃 | POST | api/auth/logout | 바로가기 | JWT Token |
| auth | 회원가입 | POST | api/auth/register | 바로가기 | None |
| auth | 로그인 확인 | GET | api/auth/check | 바로가기 | None |
......
......@@ -88,7 +88,6 @@ exports.login = async (ctx) => {
GET api/auth/check
*/
exports.check = async (ctx) => {
console.log(ctx.state);
const { user } = ctx.state;
if (!user) {
ctx.status = 401;
......
......@@ -7,4 +7,5 @@ profile.get("/solvednum:id");
profile.get("/recommendps:id");
profile.patch("/syncBJ", profileCtrl.syncBJ);
profile.post("/setprofile", profileCtrl.setProfile);
profile.post("/getprofile", profileCtrl.getProfile);
module.exports = profile;
......
......@@ -13,6 +13,24 @@ exports.checkObjectId = (ctx, next) => {
}
return next();
};
/*POST /api/profile/getprofile
{
username: "username"
}
*/
exports.getProfile = async (ctx) => {
try {
const { username } = ctx.request.body;
const profile = await Profile.findByUsername(username);
if (!profile) {
ctx.status = 401;
return;
}
ctx.body = profile;
} catch (e) {
ctx.throw(500, e);
}
};
/*
POST /api/proflie/setprofile
{
......
......@@ -3,14 +3,11 @@ const User = require("../models/user");
const jwtMiddleware = async (ctx, next) => {
const token = ctx.cookies.get("access_token");
console.log("1");
console.log(token);
if (!token) {
console.log("1");
return next();
}
try {
console.log("1");
const decoded = jwt.verify(token, process.env.JWT_SECRET);
ctx.state.user = {
_id: decoded._id,
......@@ -25,7 +22,7 @@ const jwtMiddleware = async (ctx, next) => {
httpOnly: true,
});
}
console.log(decoded);
return next();
} catch (e) {
return next();
......