Skip to content

Commit

Permalink
feat(articlePage): serve comment on article functionality
Browse files Browse the repository at this point in the history
- ensure only logged in users can enter and post comments
- ensure users can post comments on an article

[Finishes #161290969]
  • Loading branch information
Andela-Jalil committed Jan 1, 2019
1 parent a0e41e2 commit 13f22ee
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 113 deletions.
Binary file added public/images/avatar-placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 59 additions & 4 deletions public/styles/ArticlePage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -297,18 +297,73 @@
margin-bottom: 1em;
}

input {
textarea {
@include placeholder($text-grey, initial);
border: 0;
margin-left: 1em;
font-family: "Zilla Slab", serif;
font-size: 1em;
font-weight: 500;
height: 9em;
outline: 0;
padding: 0.5em;
width: 100%;
resize: none;
}

.new-comment {
@include outer-box-shadow(0, 0, 3px, 0, $shadow-comment-box);
@include outer-box-shadow(0, 0, 2px, 0, $shadow-comment-box);
border: solid 1px $color-border;
border-radius: 0.2em;
display: flex;
flex-direction: column;
padding: 0.6em 1em;

.post-comment-btn {
align-self: flex-end;
background: $color-accent;
border: 1px solid $color-accent;
border-radius: 3em;
color: $text-white;
cursor: pointer;
font-size: 0.9em;
outline: 0;
padding: 0.5em 2.5em;
position: relative;

&:hover {
background: $text-white;
border: 1px solid $color-accent;
bottom: 1px;
color: $color-accent;
}

&:active {
bottom: 0;
}
}

.author-container {
line-height: 3em;

span {
font-size: 0.9em;
}

img {
margin: 0 1em 0 0;
}

a {
img {
border: 0;
}
}
}

.author-name {
line-height: 3em;
margin: 0 1em;
width: 70%;
}
}

.comment-box {
Expand Down
36 changes: 36 additions & 0 deletions src/actions/commentActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { toastr } from 'react-redux-toastr';
import { globalLoading, globalFailure } from './globalActions';
import fetchArticle from './singleArticleActions';
import requestOptions from '../utils/requestOptions';
import getToken from '../utils/getToken';

export const postComment = (articleId, body) => (dispatch) => {
dispatch(globalLoading(true));
const commentBody = {
body
};
return fetch(
`${process.env.API_BASE_URL}/articles/${articleId}/comments`,
requestOptions(commentBody, 'POST', getToken())
).then(
res => res.json(),
error => dispatch(globalFailure(error))
).then((commentResponse) => {
if (commentResponse.status === 'success') {
// successfully posted article
toastr.success(commentResponse.message);
dispatch(fetchArticle(articleId));
}
if (commentResponse.status === 'unauthorized') {
// no token provided or invalid token
toastr.error('Sorry, you need to relogin to perform that action', 'Unauthorized');
}
if (commentResponse.errors) {
// invalid body
toastr.error('Failed to post comment...', commentResponse.errors.body[0]);
}
dispatch(globalLoading(false));
});
};

export const dummyExport = null;
25 changes: 12 additions & 13 deletions src/actions/singleArticleActions.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { toastr } from 'react-redux-toastr';
import * as types from './actionTypes';
import { RECEIVE_ARTICLE_SUCCESS } from './actionTypes';
import { globalLoading, globalFailure } from './globalActions';


const receiveArticleSuccess = (articleId, response) => ({
type: types.RECEIVE_ARTICLE_SUCCESS,
articleId,
const receiveArticleSuccess = response => ({
type: RECEIVE_ARTICLE_SUCCESS,
item: response
});


const fetchArticle = articleId => (dispatch) => {
dispatch(globalLoading(true));
return fetch(`${process.env.API_BASE_URL}/articles/${articleId}`)
.then(res => res.json())
.then(
res => res.json(),
error => dispatch(globalFailure(error))
)
.then((body) => {
if (body.errors) {
dispatch(globalFailure(body.errors));
toastr.error(body.status.toUpperCase(), body.errors.message[0]);
} else {
dispatch(globalLoading(false));
dispatch(receiveArticleSuccess(articleId, body));
if (body.status === 'success') {
dispatch(receiveArticleSuccess(body));
return dispatch(globalLoading(false));
}
toastr.error(body.status.toUpperCase(), body.errors.message[0]);
return dispatch(globalFailure(body.errors));
});
};

Expand Down
107 changes: 84 additions & 23 deletions src/components/ArticlePage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import HtmlToReact from 'html-to-react';
import { NavLink } from 'react-router-dom';

import articlePageScript from '../../../public/js/articlePageScript';
import { sampleReportTypes, articleSample } from '../../../mockdata/samplebody';
import { sampleReportTypes } from '../../../mockdata/samplebody';
import { formatDate, formatReadTime } from '../../utils';
import CommentBox from './CommentBox';
import fetchArticle from '../../actions/singleArticleActions';
import { postComment } from '../../actions/commentActions';

const HtmlToReactParser = new HtmlToReact.Parser();

Expand All @@ -24,26 +26,24 @@ export const SelectList = (props) => {
);
};

SelectList.propTypes = {
types: PropTypes.array.isRequired
};

export class ArticlePage extends React.Component {
constructor(props) {
super(props);
this.state = {
article: articleSample,
articleLikeStatus: null,
bookmarkState: false,
commentLikeState: [null, null],
reportTypes: sampleReportTypes,
commentInput: '',
};

this.handleLikeClick = this.handleLikeClick.bind(this);
this.handleDislikeClick = this.handleDislikeClick.bind(this);
this.handleBookmarkClick = this.handleBookmarkClick.bind(this);
this.handleCommentLikeClick = this.handleCommentLikeClick.bind(this);
this.handleCommentDislikeClick = this.handleCommentDislikeClick.bind(this);
this.handleCommentInput = this.handleCommentInput.bind(this);
this.handleCommentPost = this.handleCommentPost.bind(this);
}

componentDidMount() {
Expand Down Expand Up @@ -95,22 +95,48 @@ export class ArticlePage extends React.Component {
});
}

handleCommentInput(event) {
this.setState({
[event.target.name]: event.target.value
});
}

handleCommentPost() {
const { commentInput } = this.state;
const { payload, postArticleComment } = this.props;
postArticleComment(payload.id, commentInput);
this.setState({
commentInput: ''
});
}

render() {
const {
articleLikeStatus, bookmarkState, commentLikeState, reportTypes
articleLikeStatus, bookmarkState, commentLikeState, reportTypes, commentInput,
} = this.state;
let { article } = this.state;
const { payload } = this.props;
article = payload;
const articleBody = HtmlToReactParser.parse(article.body);

const { payload, isLoggedIn } = this.props;

const article = payload;

// get author information
const { author } = article;

// parse html article body to react
const articleBody = HtmlToReactParser.parse(article.body);

// get user information from local storage
const user = JSON.parse(localStorage.getItem('user'));

// resolve button classes based on state
const likeClass = articleLikeStatus
? 'fas fa-thumbs-up fa-2x' : 'far fa-thumbs-up fa-2x';
const dislikeClass = !articleLikeStatus && articleLikeStatus !== null
? 'fas fa-thumbs-down fa-2x' : 'far fa-thumbs-down fa-2x';
const bookmarkClass = bookmarkState
? 'fas fa-bookmark fa-2x' : 'far fa-bookmark fa-2x';


return (
(Object.keys(payload).length < 1) ? (<div />)
: (
Expand Down Expand Up @@ -184,12 +210,41 @@ export class ArticlePage extends React.Component {
<hr />
<div className="comment-content">
<span>Comments</span>

<div className="new-comment">
<div className="author-container">
<img src={author.avatarUrl} alt="author" />
</div>
<input placeholder="Add a comment..." />
{(isLoggedIn) ? (
<Fragment>
<div className="author-container">
<img src="https://bit.ly/2Bzc7wF" alt="author" />
<span id="author-name">{user.fullName}</span>
</div>
<textarea
name="commentInput"
placeholder="Add a comment..."
value={commentInput}
onChange={this.handleCommentInput} />
<button
className="post-comment-btn"
type="button"
onClick={this.handleCommentPost}>
POST
</button>
</Fragment>
) : (
<div className="author-container">
<NavLink to="/signup">
<img src="https://bit.ly/2Bzc7wF" alt="author" />
Sign up&nbsp;
</NavLink>
or&nbsp;
<NavLink to="/login">
login&nbsp;
</NavLink>
to post a comment
</div>
)}
</div>

<div className="comment-list">
{article.comments
.map((commentItem, index) => (
Expand All @@ -210,20 +265,26 @@ export class ArticlePage extends React.Component {
}
}

SelectList.propTypes = {
types: PropTypes.array.isRequired
};

ArticlePage.propTypes = {
fetchSingleArticle: PropTypes.func.isRequired,
postArticleComment: PropTypes.func.isRequired,
match: PropTypes.object.isRequired,
payload: PropTypes.object.isRequired
payload: PropTypes.object.isRequired,
isLoggedIn: PropTypes.bool.isRequired,
};

export const mapStateToProps = state => ({
payload: state.article.item
payload: state.article.item,
isLoggedIn: state.global.isLoggedIn,
});

export const mapDispatchToProps = dispatch => ({
fetchSingleArticle: (articleId) => {
dispatch(fetchArticle(articleId));
}
});
const mapActionsToProps = {
postArticleComment: postComment,
fetchSingleArticle: fetchArticle,
};

export default connect(mapStateToProps, mapDispatchToProps)(ArticlePage);
export default connect(mapStateToProps, mapActionsToProps)(ArticlePage);
1 change: 0 additions & 1 deletion src/reducers/singleArticleReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const singleArticleReducer = (state = article, action) => {
case RECEIVE_ARTICLE_SUCCESS:
return {
...state,
isLoading: action.isLoading,
item: action.item.article
};
default:
Expand Down
8 changes: 6 additions & 2 deletions src/store/initialState.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ export default {
error: []
},
article: {
isLoading: false,
item: {}
item: {
createdAt: '',
readTime: '',
comments: [],
author: {}
}
},
categoryTitles: [],
tagTitles: [],
Expand Down
Loading

0 comments on commit 13f22ee

Please sign in to comment.