Skip to content
This repository has been archived by the owner on Oct 2, 2021. It is now read-only.

Commit

Permalink
Merge pull request #41 from GNU-CS/enhancement#32
Browse files Browse the repository at this point in the history
enhancement#32
  • Loading branch information
hatchling13 authored Feb 12, 2021
2 parents ac214eb + 78cdef7 commit 782d699
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 17 deletions.
14 changes: 14 additions & 0 deletions catlas/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions catlas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"redux-thunk": "^2.3.0",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^2.0.1",
"suneditor-react": "^2.15.2",
"web-vitals": "^0.2.4"
},
"scripts": {
Expand Down
9 changes: 4 additions & 5 deletions catlas/src/pages/Auth/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const schema = Joi.object({
password: Joi.string().alphanum().required()
});

// How to show register/reset
function Login() {
const { url } = useRouteMatch();

Expand All @@ -34,8 +33,8 @@ function Login() {
});

const handleChange = (_, { name, value }) => setData({ ...data, [name]: value });

// Apply schema description library
// Refactor with try-catch statement
const handleSubmit = async () => {
setError(false);

Expand Down Expand Up @@ -77,11 +76,11 @@ function Login() {
</Form>
<Divider />
<Button.Group fluid>
<Button as={Link} to={`${url}/register`} onClick={() => console.log(`${url}/register`)} name='register'>
<Button as={Link} to={`${url}/register`} name='register'>
회원가입
</Button>
<Button.Or />
<Button as={Link} to={`${url}/reset`} onClick={() => console.log(`${url}/reset`)} name='reset'>
<Button as={Link} to={`${url}/reset`} name='reset'>
비밀번호 재설정
</Button>
</Button.Group>
Expand Down
23 changes: 13 additions & 10 deletions catlas/src/pages/Talks/Board.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from "react";
import { Link } from "react-router-dom";
import { Link, useRouteMatch } from "react-router-dom";
import { Button, Container, Dropdown, Grid, Input, Item, Pagination, Segment } from "semantic-ui-react";

function Board() {
const { url } = useRouteMatch();

const searchOptions = [
{ key: 'title', text: '제목', value: 'title' },
{ key: 'author', text: '글쓴이', value: 'author' }
Expand All @@ -23,9 +25,10 @@ function Board() {
label={<Dropdown defaultValue='title' options={searchOptions} />}
labelPosition='left'
action={<Button primary>검색</Button>}
actionPosition='right'
/>
<Button icon='write' content='글쓰기' />
</Grid.Row>
<Grid.Row centered>
<Button positive as={Link} to={`${url}/new`} icon='write' content='글쓰기' />
</Grid.Row>
</Grid>
</Segment>
Expand All @@ -35,18 +38,18 @@ function Board() {

function PostList() {
const elements = [
{ title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id rhoncus magna, at lobortis leo. Curabitur finibus sit amet mi a venenatis. Maecenas ac ipsum eu arcu vulputate posuere. Nunc ut cursus velit. Suspendisse a ornare massa. Aliquam nibh metus, efficitur et dapibus a, hendrerit ac enim.', date: '2021-01-27' },
{ title: 'Donec sit amet ipsum vitae lectus finibus sollicitudin eget eget sapien.', date: '2021-01-27' },
{ title: 'Nunc rutrum mollis libero, et iaculis velit molestie nec. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nullam dignissim elit vel erat scelerisque, sed aliquet risus iaculis. Praesent odio enim, convallis eget dui id, auctor sodales lectus. Vestibulum non nunc ut augue iaculis molestie.', date: '2021-01-27' },
{ title: 'Suspendisse tempus nunc varius magna imperdiet, quis commodo mauris lacinia.', date: '2021-01-27' },
{ title: 'Cras tincidunt odio sit amet tellus tempor, nec fringilla dolor dignissim.', date: '2021-01-27' }
{ id: 1, title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id rhoncus magna, at lobortis leo. Curabitur finibus sit amet mi a venenatis. Maecenas ac ipsum eu arcu vulputate posuere. Nunc ut cursus velit. Suspendisse a ornare massa. Aliquam nibh metus, efficitur et dapibus a, hendrerit ac enim.', date: '2021-01-27' },
{ id: 2, title: 'Donec sit amet ipsum vitae lectus finibus sollicitudin eget eget sapien.', date: '2021-01-27' },
{ id: 3, title: 'Nunc rutrum mollis libero, et iaculis velit molestie nec. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nullam dignissim elit vel erat scelerisque, sed aliquet risus iaculis. Praesent odio enim, convallis eget dui id, auctor sodales lectus. Vestibulum non nunc ut augue iaculis molestie.', date: '2021-01-27' },
{ id: 4, title: 'Suspendisse tempus nunc varius magna imperdiet, quis commodo mauris lacinia.', date: '2021-01-27' },
{ id: 5, title: 'Cras tincidunt odio sit amet tellus tempor, nec fringilla dolor dignissim.', date: '2021-01-27' }
];

return (
<Item.Group divided>
{
elements.map((value, _) => {
return <Post title={value.title} date={value.date} />
return <Post key={value.id} title={value.title} date={value.date} />
})
}
</Item.Group>
Expand All @@ -57,7 +60,7 @@ function Post(props) {
return (
<Item>
<Item.Content>
<Item.Header as={Link}>{props.title}</Item.Header>
<Item.Header as={Link} to='#'>{props.title}</Item.Header>
<Item.Description>글쓴이</Item.Description>
<Item.Extra>{props.date}</Item.Extra>
</Item.Content>
Expand Down
112 changes: 112 additions & 0 deletions catlas/src/pages/Talks/NewPost.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import Joi from "joi";
import React, { useEffect, useState } from "react";
import { Button, Container, Divider, Input, Segment } from "semantic-ui-react";
import { Link, useHistory } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";

import SunEditor from "suneditor-react";
import "suneditor/dist/css/suneditor.min.css";

import { post } from "../../redux/modules/post";

const PLACEHOLDER_MESSAGES = [
"자유게시판을 이용하실 때의 주의사항!",
"1. 불특정 다수에게 보여지는 글인만큼 예의를 지켜주세요!",
"2. 타인에게 불쾌감을 줄 수 있는 사진/영상 등의 업로드를 자제해주세요!",
"3. 영상의 경우, 에디터 화면에서는 재생이 불가합니다! 에디터 메뉴의 미리보기를 이용해주세요!"
];

const schema = Joi.object({
title: Joi.string().required(),
content: Joi.string().required()
});

// TODO
// 서버에 이미지 저장 API 날리기 - 추가 이슈 작성

function NewPost() {
const buttonList = [
['undo', 'redo'],
['fontSize', 'formatBlock'],
['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript'],
['fontColor', 'hiliteColor'],
['removeFormat'],
'/', // Line break
['outdent', 'indent'],
['align', 'horizontalRule', 'list'],
['table', 'link', 'image', 'video'],
['fullScreen', 'showBlocks', 'codeView', 'preview']
];

const dispatch = useDispatch();
const history = useHistory();

const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [submit, setSubmit] = useState(false);

const loading = useSelector(state => state.post.loading);
const errorCode = useSelector(state => state.post.errorCode);

const handleTitleChange = (_, data) => {
setTitle(data.value);
}

// SunEditor is different from Semantic UI React Input
const handleEditorChange = content => {
setContent(content);

if (content === '<p><br></p>') setContent('');
}

const handleConfirm = async () => {
const data = { title: title, content: content };

const success = await dispatch(post(data));

console.log(errorCode);

if (success) history.goBack();
else {
switch (errorCode) {
case 'ECONNABORTED':
console.log('서버와 통신할 수 없습니다!');
break;
default:
console.log('해당 게시글을 작성할 수 없습니다!');
}
}
}

useEffect(() => {
const data = { title: title, content: content };

const result = schema.validate(data);

if (!result.error) setSubmit(true);
else setSubmit(false);
}, [title, content]);

return (
<Container>
<Segment loading={loading}>
<Input fluid size='big' placeholder='제목' name='title' onChange={handleTitleChange} />
<Divider hidden />
<SunEditor
height='40vh'
autoFocus={true}
name='content'
placeholder={PLACEHOLDER_MESSAGES.join('\n')}
onChange={handleEditorChange}
setOptions={{
buttonList: buttonList
}}
/>
</Segment>
<Button onClick={handleConfirm} disabled={!submit} floated='right' positive content='확인' />
<Button as={Link} to='/talks' floated='right' content='취소' />
</Container>
)
}

export default NewPost;
2 changes: 2 additions & 0 deletions catlas/src/pages/Talks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import React from "react";
import { Route, useRouteMatch } from "react-router-dom";

import Board from "./Board";
import NewPost from "./NewPost";

function Talks() {
const { path } = useRouteMatch();

return (<>
<Route exact path={`${path}`} component={Board} />
<Route path={`${path}/new`} component={NewPost} />
</>);
}

Expand Down
5 changes: 3 additions & 2 deletions catlas/src/redux/modules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import storage from "redux-persist/lib/storage";

import auth from "./auth";
import register from "./register";
import post from "./post";

const persistConfig = {
key: "root",
storage: storage,
whitelist: ["auth", "register"]
whitelist: ["auth", "register", "post"]
};

const rootReducer = combineReducers({ auth, register });
const rootReducer = combineReducers({ auth, register, post });

export default persistReducer(persistConfig, rootReducer);
73 changes: 73 additions & 0 deletions catlas/src/redux/modules/post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { createInstance } from "../../component/request";

// Ducks pattern

const POST = "catlas/upload/POST";
const POST_SUCCEEDED = "catlas/upload/POST_SUCCEEDED";
const POST_FAILED = "catlas/upload/POST_FAILED";

const initialState = {
loading: false,
errorCode: ''
};

export default function reducer(state = initialState, action) {
switch (action.type) {
case POST:
return {
...state,
loading: true,
errorCode: ''
};
case POST_SUCCEEDED:
return {
...state,
loading: false
};
case POST_FAILED:
return {
...state,
loading: false,
errorCode: action.code
};
default:
return state;
}
}

export const post = data => async dispatch => {
dispatch({ type: POST });

try {
const instance = createInstance();

await instance.post('upload/post/', data);

dispatch(postSuccess());

return true;

} catch (error) {
dispatch(postFail(error));

return false;
}
}

const postSuccess = () => {
return {
type: POST_SUCCEEDED
}
}

const postFail = error => {
let code = '';

if (error.code === 'ECONNABORTED') code = error.code; // axios request timeout
else code = error.response.status;

return {
type: POST_FAILED,
code: code
}
}

0 comments on commit 782d699

Please sign in to comment.