Skip to content

Commit

Permalink
feature(comments): create and retrieve comments
Browse files Browse the repository at this point in the history
- create and retrieve comments

[Delivers #160609520]
  • Loading branch information
MuhanguziDavid committed Nov 16, 2018
1 parent 34f5bff commit ce0b06d
Show file tree
Hide file tree
Showing 19 changed files with 704 additions and 30 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
"enzyme-adapter-react-16": "^1.6.0",
"history": "^4.7.2",
"jwt-decode": "^2.2.0",
"moment": "^2.22.2",
"node-sass": "^4.9.4",
"prop-types": "^15.6.2",
"react": "^16.6.0",
"react-dom": "^16.6.0",
"react-facebook-login": "^4.1.1",
"react-google-login": "^3.2.1",
"react-loader": "^2.4.5",
"react-moment": "^0.8.3",
"react-notify-toast": "^0.5.0",
"react-quill": "^1.3.2",
"react-redux": "^5.1.0",
Expand Down
23 changes: 23 additions & 0 deletions src/actions/actionCreators.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {
CREATE_ARTICLE_SUCCESS,
CREATE_ARTICLE_ERROR,
CREATE_ARTICLE_INITIATED,
GET_COMMENT_INITIATED,
ADD_COMMENT_SUCCESS,
GET_COMMENTS_SUCCESS,
LOGOUT_USER,
} from './types';

export const socialLoginInitiated = () => ({
Expand All @@ -31,3 +35,22 @@ export const createArticleInititated = payload => ({
type: CREATE_ARTICLE_INITIATED,
payload,
});

export const addCommentSuccess = payload => ({
type: ADD_COMMENT_SUCCESS,
payload,
});

export const getCommentInititated = payload => ({
type: GET_COMMENT_INITIATED,
payload,
});

export const getCommentsSuccess = payload => ({
type: GET_COMMENTS_SUCCESS,
payload,
});
export const logoutUser = payload => ({
type: LOGOUT_USER,
payload,
});
26 changes: 26 additions & 0 deletions src/actions/articleActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {
createArticleSuccess,
createArticleError,
createArticleInititated,
addCommentSuccess,
getCommentInititated,
getCommentsSuccess,
logoutUser,
} from './actionCreators';

export const fetchArticles = () => dispatch => {
Expand Down Expand Up @@ -34,3 +38,25 @@ export const postArticle = postData => dispatch => {
toast.error(errorMessage, { autoClose: false, hideProgressBar: true });
});
};

export const addComment = (postData, article) => dispatch => {
axiosInstance
.post(`/api/articles/${article}/comments/`, postData)
.then(() => {
dispatch(addCommentSuccess(true));
});
};

export const fetchComments = article => dispatch => {
dispatch(getCommentInititated(true));
axiosInstance
.get(`/api/articles/${article}/comments/`)
.then((response) => {
dispatch(getCommentsSuccess(response.data));
})
.catch(() => {
localStorage.removeItem('auth_token');
dispatch(logoutUser(false));
toast.error('Please login to view comments', { autoClose: false, hideProgressBar: true });
});
};
3 changes: 3 additions & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ export const CREATE_ARTICLE_SUCCESS = 'CREATE_ARTICLE_SUCCESS';
export const CREATE_ARTICLE_INITIATED = 'CREATE_ARTICLE_INITIATED';
export const CREATE_ARTICLE_ERROR = 'CREATE_ARTICLE_ERROR';
export const LOGOUT_USER = 'LOGOUT_USER';
export const ADD_COMMENT_SUCCESS = 'ADD_COMMENT_SUCCESS';
export const GET_COMMENT_INITIATED = 'GET_COMMENT_INITIATED';
export const GET_COMMENTS_SUCCESS = 'GET_COMMENTS_SUCCESS';
15 changes: 15 additions & 0 deletions src/assets/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ header.uvp {
.bigger-font{
font-size: 20px;
}
.quill-height-comments {
height: 80px;
}
.m-r-10{
margin-right: 10px;
}
Expand All @@ -363,3 +366,15 @@ header.uvp {
.m-t-10{
margin-top: 100px;
}
.quill-div {
border: solid;
border-width: thin;
border-radius: 10px;
}

.comment{
margin: 0 160px;
}
.no-gutters-comments {
margin-left: 5%;
}
61 changes: 34 additions & 27 deletions src/components/Articles/SingleArticle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom';
import articleImg from '../../assets/images/articleImg.jpeg';
import user from '../../assets/images/user.png';

Expand All @@ -9,26 +10,31 @@ function articleCreated(articleDate) {
return dateOnly;
}

const SingleArticle = ({ article }) => (
<div className="row article">
<div className="col-12">
<div className="row entire-article">
<div className="col-sm-1">
<img src={user} className="img-thumbnail author-thumbnail" alt="" />
</div>
<div className="col-sm-11 article-essentials">
<div className="row">
<div className="col-sm-5 author-name">
authorname
</div>
const returnArticleURL = slug => `/articles/${slug}/comments/`;

const SingleArticle = ({ article }) => {
localStorage.setItem('current_article', article.slug);
return (
<div className="row article">
<div className="col-12">
<div className="row entire-article">
<div className="col-sm-1">
<img src={user} className="img-thumbnail author-thumbnail" alt="" />
</div>
<div className="row">
<div className="col-md-3 article-time">
{articleCreated(article.createdAt)}
&nbsp;&nbsp;|&nbsp;3 Min
<div className="col-sm-11 article-essentials">
<div className="row">
<div className="col-sm-5 author-name">
authorname
</div>
</div>
<div className="col-sm-8">
<i className="fas fa-tags" />
<div className="row">
<div className="col-md-3 article-time">
{articleCreated(article.createdAt)}
&nbsp;&nbsp;|&nbsp;3 Min
</div>
<div className="col-sm-8">
<i className="fas fa-tags" />
</div>
</div>
</div>
</div>
Expand All @@ -45,18 +51,19 @@ const SingleArticle = ({ article }) => (
<p dangerouslySetInnerHTML={{ __html: article.body }} className="my-img" />
</div>
</div>
</div>
<div className="card-footer w-100 text-muted">
<i className="far fa-thumbs-down icon" />
<i className="far fa-thumbs-up icon" />
<i className="far fa-star icon" />
<i className="far fa-bookmark icon" />
<i className="fas fa-share-alt icon" />
<div className="card-footer w-100 text-muted">
<i className="far fa-thumbs-down icon" />
<i className="far fa-thumbs-up icon" />
<i className="far fa-star icon" />
<i className="far fa-bookmark icon" />
<i className="fas fa-share-alt icon" />
<NavLink to={returnArticleURL(article.slug)}><i className="far fa-comment icon" /></NavLink>
</div>
</div>
</div>
</div>
</div>
);
);
};

SingleArticle.propTypes = {
article: PropTypes.object.isRequired,
Expand Down
21 changes: 21 additions & 0 deletions src/components/comments/CommentList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import RetrieveCommentForm from './RetrieveCommentForm';

export const CommentList = ({ comments }) => (
<div>
{comments.map(comment => (
<RetrieveCommentForm key={comment.id} comment={comment} />
))}
</div>
);

CommentList.propTypes = {
comments: PropTypes.array,
};

CommentList.defaultProps = {
comments: [],
};

export default CommentList;
118 changes: 118 additions & 0 deletions src/components/comments/Comments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import Loader from 'react-loader';
import CreateCommentForm from './CreateCommentForm';
import CommentList from './CommentList';
import { fetchComments, addComment } from '../../actions/articleActions';

export class Comments extends Component {
constructor(props) {
super(props);
this.state = {
body: '',
};
}

componentDidMount() {
const {
match: {
params: { article },
},
} = this.props;
const { fetchComments } = this.props;
fetchComments(article);
}

componentWillReceiveProps(nextProps) {
if (nextProps.addCommentSuccess === true) {
window.location.reload();
}
if (nextProps.isLoggedIn === false) {
const { history } = this.props;
history.push('/login');
}
}

handleSubmit = event => {
event.preventDefault();
const {
match: {
params: { article },
},
} = this.props;
const { body } = this.state;
const payload = {
comment: {
body,
},
};
const { addComment } = this.props;
addComment(payload, article);
}

resetForm = () => {
this.setState({
body: '',
});
};

handleEditorChange = (value) => {
this.setState({ body: value });
}

render() {
const { body } = this.state;
const { loading, commentsPayload } = this.props;
return (
<div>
<CreateCommentForm
onSubmit={this.handleSubmit}
onChange={this.handleEditorChange}
onClick={this.resetForm}
body={body}
/>
<Loader loaded={!loading}>
{Object.keys(commentsPayload).length > 0
&& <CommentList comments={commentsPayload.comments} />
}
</Loader>
</div>
);
}
}

const matchDispatchToProps = (dispatch) => bindActionCreators({
fetchComments,
addComment,
}, dispatch);

const mapStateToProps = (state) => ({
commentsPayload: state.article.commentsPayload,
addCommentSuccess: state.article.addCommentSuccess,
isLoggedIn: state.user.isLoggedIn,
loading: state.user.loading,
});

Comments.propTypes = {
addComment: PropTypes.func.isRequired,
match: PropTypes.object.isRequired,
fetchComments: PropTypes.func.isRequired,
commentsPayload: PropTypes.object.isRequired,
addCommentSuccess: PropTypes.bool,
loading: PropTypes.bool,
isLoggedIn: PropTypes.bool,
history: PropTypes.object.isRequired,
};

Comments.defaultProps = {
addCommentSuccess: false,
loading: false,
isLoggedIn: true,
};

export default connect(
mapStateToProps,
matchDispatchToProps,
)(Comments);
48 changes: 48 additions & 0 deletions src/components/comments/CreateCommentForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';

export const CreateCommentForm = ({
onSubmit,
onChange,
onClick,
body,
}) => (
<div className="container quill-container" style={{ paddingTop: '50px' }}>
<div className="row">
<div className="col-12">
<form id="add-comment-form" onSubmit={onSubmit}>
<div className="container">
<div className="form-group">
<div className="text-editor">
<div className="quill-div">
<ReactQuill
id="text-editor"
theme=""
placeholder="Please add a comment"
className="quill-height-comments"
value={body}
onChange={onChange}
required
/>
</div>
</div>
</div>
<button id="comment-submit-button" type="submit" className="btn btn-outline-primary m-r-10">Save</button>
<button type="button" id="clear-button" className="btn btn-outline-warning m-r-10" onClick={onClick}>Clear</button>
</div>
</form>
</div>
</div>
</div>
);

CreateCommentForm.propTypes = {
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
body: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};

export default CreateCommentForm;
Loading

0 comments on commit ce0b06d

Please sign in to comment.