Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/dbaranoff/add posts comments feature #427

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
18 changes: 18 additions & 0 deletions Intl/localizationData/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ export default {
postTitle: 'Post Title',
postContent: 'Post Content',
submit: 'Submit',
addComment: 'Add comment',
editComment: 'Edit comment',
deleteComment: 'Delete comment',
emptyComments: 'No comments added yet. Let\'s write something awesome!',
makeComment: `{count, plural,
=0 {Add first comment}
other {See all}
}`,
commentForm: {
author: {
label: 'Comment Author',
placeholder: 'Write your name here',
},
content: {
label: 'Comment body',
placeholder: 'Place your thoughts here',
},
},
comment: `user {name} {value, plural,
=0 {does not have any comments}
=1 {has # comment}
Expand Down
18 changes: 18 additions & 0 deletions Intl/localizationData/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ export default {
postTitle: 'Titre de l\'article',
postContent: 'Contenu après',
submit: 'Soumettre',
addComent: 'Ajouter un commentaire',
editComment: 'Modifier le commentaire',
deleteComment: 'Supprimer le commentaire',
emptyComments: 'No comments added yet. Let\'s write something awesome!',
makeComment: `{count, plural,
=0 {Add first comment}
other {See all}
}`,
commentForm: {
author: {
label: 'Comment Author',
placeholder: 'Write your name here',
},
content: {
label: 'Comment body',
placeholder: 'Place your thoughts here',
},
},
comment: `user {name} {value, plural,
=0 {does not have any comments}
=1 {has # comment}
Expand Down
29 changes: 29 additions & 0 deletions client/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,32 @@ body {
font-family: 'Lato', sans-serif;
font-size: 16px;
}

:global(.btn) {
display: flex;
justify-content: center;
align-items: center;
margin: 10px auto 0;
background: #41c3fa;
border: 1px solid #fafafa;
outline: none;
border-radius: 12px;
max-width: 240px;
width: 100%;
height: 40px;
font-size: 18px;
color: #fff;
box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.15), inset 1px 2px rgba(255, 255, 255, 0.3);
transition: all 0.15s ease;
cursor: pointer;
}

:global(.btn:hover:not(:disabled)) {
background: #548bfa;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 0 transparent;
}

:global(.btn:disabled) {
background: #6bdcfa;
box-shadow: none;
}
1 change: 1 addition & 0 deletions client/modules/App/AppReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TOGGLE_ADD_POST } from './AppActions';
// Initial State
const initialState = {
showAddPost: false,
showAddComment: false,
};

const AppReducer = (state = initialState, action) => {
Expand Down
11 changes: 11 additions & 0 deletions client/modules/Comment/CommentFormWidget/CommentFormWidget.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.container {
padding: 50px;
margin: 0 -15px;
position: relative;
}

.container.inline {
background: transparent;
padding-bottom: 15px;
padding-top: 25px;
}
110 changes: 110 additions & 0 deletions client/modules/Comment/CommentFormWidget/CommentFormWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl } from 'react-intl';

// Import styles
import styles from './CommentFormWidget.css';

// Import components
import CommentForm from '../components/CommentForm/CommentForm';

// Import actions
import { addCommentRequest, deleteCommentRequest, editCommentRequest } from '../CommentsActions';
import { withRouter } from 'react-router';

export class CommentFormWidget extends React.Component {
constructor(props) {
super(props);

this.state = {
editMode: !!props.initialValues,
isFormShown: false,
comment: props.initialValues,
};
}

componentDidUpdate(prevProps) {
if (
prevProps.initialValues && this.props.initialValues &&
(prevProps.initialValues.author !== this.props.initialValues.author ||
prevProps.initialValues.content !== this.props.initialValues.content)
) {
this.refreshComment();
}
}

showForm = () => this.setState({ isFormShown: true });

closeForm = () => this.setState(
{ isFormShown: false },
() => {
if (this.props.onClose) {
this.props.onClose();
}
},
);

refreshComment = () => this.setState({ comment: this.props.initialValues });

handleFormSubmit = (formData) => {
const { editComment, addComment, submitCallback, params } = this.props;
const { cuid: postId } = params;
if (!postId) {
formData.reject('PostId should be specified.');
return;
}

const payload = { ...formData, postId };

if (this.state.editMode) {
editComment(payload);
} else {
addComment(payload);
}
if (submitCallback) {
submitCallback();
}
this.closeForm();
};

render() {
const initialValues = this.state.comment || { author: '', content: '' };
return this.props.inline || this.state.isFormShown ? (
<div className={`${styles.container} ${this.props.inline ? styles.inline : ''}`}>
<CommentForm
inline={this.props.inline}
onClose={this.closeForm}
initialValues={initialValues}
onSubmit={this.handleFormSubmit}
/>
</div>
) : <button className={`btn ${styles['show-btn']}`} onClick={this.showForm}>
<FormattedMessage id={this.state.editMode ? 'editComment' : 'addComment'} />
</button>;
}
}

CommentFormWidget.propTypes = {
initialValues: PropTypes.shape({
author: PropTypes.string,
content: PropTypes.string,
commentId: PropTypes.string,
}),
onClose: PropTypes.func,
inline: PropTypes.bool,
addComment: PropTypes.func,
editComment: PropTypes.func,
submitCallback: PropTypes.func,
params: PropTypes.object,
};

const mapStateToProps = () => ({});

const mapDispatchToProps = dispatch => ({
addComment: (comment) => dispatch(addCommentRequest(comment)),
editComment: (comment) => dispatch(editCommentRequest(comment)),
deleteComment: (commentId) => dispatch(deleteCommentRequest(commentId)),
});

export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(withRouter(CommentFormWidget)));
14 changes: 14 additions & 0 deletions client/modules/Comment/CommentList/CommentList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.container {
display: flex;
flex-flow: column;
align-items: stretch;
justify-content: flex-start;
margin: 25px auto 0;
max-width: 600px;
width: 100%;
}

.no-comments {
margin-top: 20px;
text-align: center;
}
38 changes: 38 additions & 0 deletions client/modules/Comment/CommentList/CommentList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';

// Import style
import styles from './CommentList.css';

// Import Components
import CommentListItem from '../components/CommentListItem/CommentListItem';

function CommentList({ comments, deleteComment }) {
return (
<div className={styles.container}>
{comments && comments.length ? comments.map(comment => (
<CommentListItem
key={comment.cuid}
comment={comment}
onDelete={deleteComment}
/>
)) : (
<div className={styles['no-comments']}>
<FormattedMessage id="emptyComments" />
</div>
)}
</div>
);
}

CommentList.propTypes = {
comments: PropTypes.arrayOf(PropTypes.shape({
cuid: PropTypes.string,
author: PropTypes.string,
content: PropTypes.string,
})),
deleteComment: PropTypes.func.isRequired,
};

export default CommentList;
91 changes: 91 additions & 0 deletions client/modules/Comment/CommentsActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import callApi from '../../util/apiCaller';

// Export Constants
export const ADD_COMMENT_SUCCESS = 'ADD_COMMENT_SUCCESS';
export const ADD_COMMENT_FAILURE = 'ADD_COMMENT_FAILURE';

export const EDIT_COMMENT_SUCCESS = 'EDIT_COMMENT_SUCCESS';
export const EDIT_COMMENT_FAILURE = 'EDIT_COMMENT_FAILURE';

export const DELETE_COMMENT_SUCCESS = 'DELETE_COMMENT_SUCCESS';
export const DELETE_COMMENT_FAILURE = 'DELETE_COMMENT_FAILURE';

// Export Actions
export function addCommentSuccess(comment) {
return {
type: ADD_COMMENT_SUCCESS,
comment,
};
}

export function addCommentFailure(error) {
return {
type: ADD_COMMENT_FAILURE,
error,
};
}

export function addCommentRequest({ comment, resolve, reject, postId }) {
return (dispatch) => {
return callApi(`comments/${postId}`, 'post', { comment })
.then(res => {
if (resolve) resolve();
dispatch(addCommentSuccess(res.comment));
})
.catch(error => {
if (reject) reject(error);
dispatch(addCommentFailure(error));
});
};
}

export function editCommentSuccess(comment) {
return {
type: EDIT_COMMENT_SUCCESS,
comment,
};
}

export function editCommentFailure(error) {
return {
type: EDIT_COMMENT_FAILURE,
error,
};
}

export function editCommentRequest({ comment, resolve, reject }) {
return (dispatch) => {
return callApi(`comments/${comment.cuid}`, 'put', { comment })
.then(res => {
if (resolve) resolve();
dispatch(editCommentSuccess(res.comment));
})
.catch(error => {
if (reject) reject(error);
dispatch(editCommentFailure(error));
});
};
}


export function deleteCommentSuccess(comment) {
return {
type: DELETE_COMMENT_SUCCESS,
comment,
};
}

export function deleteCommentFailure(error) {
return {
type: DELETE_COMMENT_FAILURE,
error,
};
}

export function deleteCommentRequest(comment) {
return (dispatch) => {
return callApi(`comments/${comment.cuid}`, 'delete')
.then(() => dispatch(deleteCommentSuccess(comment)))
.catch(error => dispatch(deleteCommentFailure(error)));
};
}
Loading