<repo_name>BridgeNB/Redux-Project-Readable<file_sep>/src/components/NewComment.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { guid } from '../api/util';
import { addComment } from '../actions/commentActions'

class NewComment extends Component {
  handleSubmit = (e) => {
    e.preventDefault()
    const postId = this.props.match.params.postId
    const commendBody = e.target.body.value
    const author = e.target.author.value
    const submitComment = {
        id: guid(),
        parentId: postId,
        timestamp: Date.now(),
        body: commendBody,
        author: author
      }
    this.props.addComment(submitComment, postId,
        () => this.props.history.push(`/posts/${postId}`))
    }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <ul className='comment-form'>
          <li>
            <label>Name <span className="required">*</span></label>
            <input type="text" name="author" className="field-long" />
          </li>
          <li>
            <label>Comment <span className="required">*</span></label>
            <textarea name="body" id="field5" className="field-long field-textarea"></textarea>
          </li>
          <button>Submit</button>
        </ul>
      </form>
    )
  }
}

function mapStateToProps(comment) {
  return {
    comment: comment
  }
}

export default connect(mapStateToProps, {addComment}) (NewComment);
<file_sep>/src/components/SinglePost.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import { fetchCommentsForPost } from '../actions/commentActions'
import { votePost, fetchAllPosts, deletePost } from '../actions/postActions'
import { Button, Well } from 'react-bootstrap'

class SinglePost extends Component {
  componentDidMount() {
    this.props.fetchCommentsForPost(this.props.post.id)
  }
  afterPostDelete = (postId) => {
    const id = this.props.post.id;
    this.props.deletePost(id, () => {});
  }
  render() {
    let { post, votePost, fetchAllPosts } = this.props;
    return (
      <div className="single-post">
        <div className="single-post-detail">
          <Link to={`/${post.category}/${post.id}`}>
            <div className="post-title"><h3>{ post.title }</h3></div>
          </Link>
          <div className="post-body"><Well bsSize="large">{ post.body }</Well></div>
          <div className="post-author">Author: <Button bsStyle="info">{ post.author }</Button></div>
          <div className="post-category">Category: <Button bsStyle="info">{ post.category }</Button></div>
          <div className="post-action-button">
            <Link to={`/${post.category}/${post.id}/edit`}><Button>Edit</Button></Link>
            <Link to={`/${post.category}/${post.id}/comment`}><Button>Comment</Button></Link>
            <Button onClick={(e) => this.afterPostDelete(e)}>Delete</Button>
          </div>
        </div>
        <div className="single-post-bottom">
          <div className="comments-summary">
            <span>{ post.commentCount } <strong>Comments</strong></span>
            <span>{ post.voteScore } <strong>Votes</strong></span>
          </div>
          <div className="post-votes">
            <Button onClick={() => {
              votePost(post.id, "upVote")
              fetchAllPosts()
            }}>Vote up</Button>
            <Button onClick={() => {
              votePost(post.id, "downVote")
              fetchAllPosts()
            }}>Vote down</Button>
          </div>
        </div>
      </div>
    );
  }
}
function mapStateToProps({ comments }, { post }) {
  return {
    comments: comments[post.id]
  }
}
export default connect(mapStateToProps, {votePost, fetchAllPosts, fetchCommentsForPost, deletePost})(SinglePost)
<file_sep>/src/reducers/comments.js
import * as Types from '../actions/types'

function comments(state = {}, action) {
  const { comments, commentId, postId, editedComment } = action
  switch(action.type) {
    case Types.ADD_COMMENT:
      return Object.assign({}, state, {[postId]: comments})
    case Types.FETCH_COMMENTS: {
      let newState = { ...state }
      if (action.comments.length > 0) {
       const key = action.comments[0].parentId
       newState[key] = action.comments
      } else {
       newState[action.postId] = []
      }
      return newState;
    }
    case Types.EDIT_COMMENT:
      return {
        ...state,
        [postId]: state[postId].map(comment => {
          if(comment.id === commentId) {
            comment = editedComment
          }
          return comment
        })
      }
    case Types.VOTE_COMMENT:
      return {
        ...state,
        [postId]: state[postId].map(comment => {
          if(comment.id === commentId) {
            comment = editedComment
          }
          return comment
        })
      }
    default:
      return state;
  }
}

export default comments
<file_sep>/src/actions/commentActions.js
import * as API from '../api/index';
import * as Types from './types';

export const fetchCommentsForPost = (postId) => {
  return (dispatch) => {
    API.fetchComment(postId).then(comments => {
      dispatch({type: Types.FETCH_COMMENTS, postId, comments})
    })
  }
}

export const addComment = (comment, postId, callback) => {
  return (dispatch) => {
    API.addComment(comment).then(() => callback())
    dispatch({ type: Types.ADD_COMMENT, postId, comment})
  }
}

export const deleteComment = (commentId, callback) => {
  return (dispatch) => {
    API.deleteComment(commentId).then(() => callback())
    dispatch({ type: Types.DELETE_COMMENT, commentId})
  }
}

export const editComment = (commentId, postId, timestamp, body, callback) => {
  return (dispatch) => {
    API.editComment(commentId, timestamp, body)
      .then(editedComment => {
        dispatch({ type: Types.EDIT_COMMENT, editedComment, commentId, postId })
      }).then(() => callback())
  }
}

export const voteComment = (commentId, postId, option) => {
  return (dispatch) => {
    API.voteComment(commentId, option).then((editedComment) => {
      dispatch({type: Types.VOTE_COMMENT, editedComment, commentId, postId})
    })
  }
}
<file_sep>/src/actions/postActions.js
import * as API from '../api/index';
import * as Types from './types';

export const fetchAllPosts = () => {
  return (dispatch) => {
    API.fetchPosts().then(posts => {
      dispatch({ type: Types.FETCH_POSTS, posts })
    })
  }
}

export const addPost = (post, callback) => {
  return (dispatch) => {
    API.addPost(post).then(() => callback())
    dispatch({ type: Types.ADD_POST, post})
  }
}

export const deletePost = (postId, callback) => {
  return (dispatch) => {
    API.deletePost(postId).then(() => callback())
    dispatch({ type: Types.DELETE_POST, postId})
  }
}

export const editPost = (postId, title, body, callback) => {
  return (dispatch) => {
    API.editPost(postId, title, body).then(editedPost => {
      dispatch({ type: Types.EDIT_POST, editedPost, postId })
    }).then(() => callback())
  }
}

export const fetchPostsByCategory = (category) => {
  return (dispatch) => {
    API.fetchPostsByCategory(category).then(posts => {
      dispatch({ type: Types.FETCH_POSTS_BY_CATEGORY, posts})
    })
  }
}

export const votePost = (postId, option) => {
  return (dispatch) => {
    API.votePost(postId, option).then( post => {
      dispatch({ type: Types.VOTE_POST, postId, option})
    })
  }
}

export const sortPost = (option) => {
  return (dispatch) => {
    dispatch({ type: Types.SORT_POST, option})
  }
}
<file_sep>/src/reducers/posts.js
import * as Types from '../actions/types'
import sortBy from "sort-by"

function posts(state = [], action) {
  const { post, postId, editedPost, posts, option } = action
  switch(action.type) {
    case Types.FETCH_POSTS:
      return posts.filter(post => !(post.deleted))
    case Types.ADD_POST:
      return state.concat([post])
    case Types.DELETE_POST:
      return state.filter((post) => post.id !== postId)
    case Types.EDIT_POST:
      return state.map(post => {
        if (post.id === postId) {
          post = editedPost
        }
        return post
      })
    case Types.FETCH_POSTS_BY_CATEGORY:
      return posts.filter(post => !(post.deleted))
    case Types.VOTE_POST:
      return state.map(post => {
        if (post.id === action.postId) {
          if (action.option === "upVote") {
            post.voteScore += 1
          }
          if (action.option === "downVote") {
            post.voteScore -= 1
          }
        }
        return post
      })
    case Types.SORT_POST:
      return [].concat(state.sort(sortBy(option)).reverse())
    default:
      return state
  }
}

export default posts
<file_sep>/src/components/App.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { Grid, Button } from 'react-bootstrap';
import { Route, Switch, withRouter, Link} from 'react-router-dom';
import {connect} from 'react-redux';
import * as actions from '../actions/categoryActions';

import CategoryList from './CategoryList';
import NewPost from './NewPost';
import DetailedPost from './DetailedPost';
import EditPost from './EditPost';
import NewComment from './NewComment';
import EditComment from './EditComment';

import "../App.css";

class App extends Component {
  static propTypes = {
    categories: PropTypes.array,
    posts: PropTypes.array
  }

  componentDidMount() {
    this.props.fetchCategories()
  }

  render() {
    const {categories} = this.props
    return (
      <div className="whole-app">
        <div className="app-head">
          <h1>Bridge's Forum</h1>
        </div>
        <div className="category-changer">
          <h2>Choose Posts by Category</h2>
          <div className="category-list">
            {categories && categories.map(category => {
              const { name, path } = category;
              return (
                <Link key={name} to={`/${path}`}>
                  <Button className="category-tag">{name.charAt(0).toUpperCase() + name.slice(1)}</Button>
                </Link>
              )
            })}
          </div>
        </div>

        <div>
          <Grid>
            <Switch>
              <Route path="/" exact component={CategoryList}/>
              <Route path="/create" exact component={NewPost}/>
              <Route path="/:category" exact component={CategoryList}/>
              <Route path="/:category/:postId" exact component={DetailedPost}/>
              <Route path="/:category/:postId/edit" exact component={EditPost}/>
              <Route path="/:category/:postId/comment" exact component={NewComment}/>
              <Route path="/:category/:postId/:commentId/edit" exact component={EditComment}/>
            </Switch>
          </Grid>
        </div>
    </div>
    );
  }
}

function mapStateToProps({categories}) {
  return {categories: categories}
}

export default withRouter(connect(mapStateToProps, actions)(App));
<file_sep>/src/actions/categoryActions.js
import * as API from '../api/index'
import * as Types from './types'

export const fetchCategories = () => {
  return (dispatch) => {
    API.fetchCategories().then(res => {
      dispatch({ type: Types.FETCH_CATEGORIES, res})
    })
  }
}
