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

[FEAT] Advanced Key Word Search #411

Merged
merged 3 commits into from
Aug 16, 2019
Merged
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
2 changes: 2 additions & 0 deletions fact-bounty-client/src/pages/Dashboard/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Posts from '../Posts'
import PostDetailView from '../PostDetailView'
import TwitterGraph from '../TwitterGraph'
import DashboardSideNav from '../../components/DashboardSideNav'
import Search from '../Search'

class Dashboard extends Component {
render() {
Expand Down Expand Up @@ -42,6 +43,7 @@ class Dashboard extends Component {
path={`${match.url}/twitter`}
component={TwitterGraph}
/>
<Route exact path={`${match.url}/search`} component={Search} />
</Switch>
</div>
</div>
Expand Down
132 changes: 132 additions & 0 deletions fact-bounty-client/src/pages/Search/Search.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import {
getPostsFromKeyword,
loadUserVotes
} from '../../redux/actions/postActions'
import PostItem from '../../components/PostItem'
import { Button } from '@material-ui/core'
import './style.sass'

class Search extends Component {
constructor(props) {
super(props)
this.state = {
keyword: '',
currentKeyword: ''
}
}

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

onSearchPressed = e => {
e.preventDefault()
const { keyword } = this.state
this.props.getPostsFromKeyword(keyword)
this.setState({ currentKeyword: keyword })
}

renderPostList = () => {
const { currentKeyword } = this.state
const { posts, userVotes } = this.props
const validPosts = posts ? posts : []

if (validPosts.length === 0) {
return (
<div>
{currentKeyword === '' ? (
<h3>Type a keyword and press search!</h3>
) : (
<h3>
No result found for: <b>{currentKeyword}</b>
</h3>
)}
</div>
)
}
return (
<div>
<h3 style={{ marginBottom: 25 }}>
Showing results for: <b>{currentKeyword}</b>
</h3>
{validPosts.map((post, index) => {
console.log(userVotes)
const userVote = userVotes.filter(uv => uv.story_id === post._id)
return (
<PostItem
key={index}
post={post}
userVote={userVote[0] ? userVote[0].value : null}
/>
)
})}
</div>
)
}

render() {
const { keyword } = this.state
const { loadingPosts } = this.props
return (
<Fragment>
<div className="container search-wrapper">
<div className="header">
<h1>Search</h1>
</div>
<div className="search-form-container">
<form onSubmit={this.onSearchPressed}>
<input
type="text"
name="Keyword"
placeholder="Keyword"
required
value={keyword}
onChange={e => {
this.setState({ keyword: e.target.value })
}}
/>
<Button
variant="contained"
color="primary"
style={{ width: 120 }}
type="submit"
disabled={loadingPosts}
>
SEARCH
</Button>
</form>
</div>
<hr className="hr" />
{loadingPosts ? <h2>Loading...</h2> : this.renderPostList()}
</div>
</Fragment>
)
}
}

Search.propTypes = {
posts: PropTypes.array,
loadingPosts: PropTypes.bool,
userVotes: PropTypes.array,
getPostsFromKeyword: PropTypes.func,
loadUserVotes: PropTypes.func
}

const mapStatetoProps = state => ({
posts: state.posts.searchedPosts,
userVotes: state.posts.userVotes,
loadingPosts: state.posts.searchedPostsLoading
})

const mapDispatchToProps = dispatch => ({
getPostsFromKeyword: keyword => dispatch(getPostsFromKeyword(keyword)),
loadUserVotes: () => dispatch(loadUserVotes())
})

export default connect(
mapStatetoProps,
mapDispatchToProps
)(Search)
2 changes: 2 additions & 0 deletions fact-bounty-client/src/pages/Search/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Search from './Search'
export default Search
28 changes: 28 additions & 0 deletions fact-bounty-client/src/pages/Search/style.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.search-wrapper
.header
padding: 20px 0px 10px 0px
display: flex
justify-content: space-between
align-items: center
input
width: 300px;
box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
border: none
border-radius: 3px
padding: 10px
.hr
margin-bottom: 40px
.search-form-container
text-align: center
form
display: flex
flex-direction: row
input
z-index: 1
box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
border: none
border-radius: 3px
padding: 12px
resize: none
width: 500px
margin-right: 15px
2 changes: 2 additions & 0 deletions fact-bounty-client/src/redux/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const LOADING_USER_VOTES = 'LOADING_USER_VOTES'
export const SET_USER_VOTES = 'SET_USER_VOTES'
export const UPDATE_USER_VOTES = 'UPDATE_USER_VOTES'
export const UPDATE_POST_VOTES = 'UPDATE_POST_VOTES'
export const START_POSTS_SEARCH = 'START_POSTS_SEARCH'
export const SET_POSTS_ON_SEARCH = 'SET_POSTS_ON_SEARCH'

export const START_CONTACT_FORM_SUBMIT = 'START_CONTACT_FORM_SUBMIT'
export const CONTACT_FORM_SUBMIT_SUCCESS = 'CONTACT_FORM_SUBMIT_SUCCESS'
Expand Down
18 changes: 17 additions & 1 deletion fact-bounty-client/src/redux/actions/postActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
LOADING_USER_VOTES,
SET_USER_VOTES,
UPDATE_USER_VOTES,
UPDATE_POST_VOTES
UPDATE_POST_VOTES,
SET_POSTS_ON_SEARCH,
START_POSTS_SEARCH
} from './actionTypes'
import PostsService from '../../services/PostsService'

Expand Down Expand Up @@ -78,3 +80,17 @@ export const loadUserVotes = () => dispatch => {
console.error('Server response invalid:', err)
})
}

export const getPostsFromKeyword = keyword => dispatch => {
dispatch({ type: START_POSTS_SEARCH })
PostsService.getPostsFromKeyword(keyword)
.then(res => {
dispatch({
type: SET_POSTS_ON_SEARCH,
payload: res.data.stories ? res.data.stories : null
})
})
.catch(err => {
console.error('Server response invalid:', err)
})
}
85 changes: 53 additions & 32 deletions fact-bounty-client/src/redux/reducers/postReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ import {
LOADING_USER_VOTES,
SET_USER_VOTES,
UPDATE_USER_VOTES,
UPDATE_POST_VOTES
UPDATE_POST_VOTES,
SET_POSTS_ON_SEARCH,
START_POSTS_SEARCH
} from '../actions/actionTypes'

const initialState = {
items: [], // the posts
searchedPosts: [], // the posts in search
currentPost: null, // the current post in the post detail view
page: 1,
userVotes: [],
loadingPosts: false,
loadingUserVotes: false
loadingUserVotes: false,
searchedPostsLoading: false
}

export default function(state = initialState, action) {
Expand Down Expand Up @@ -78,42 +82,59 @@ export default function(state = initialState, action) {
}
}
case UPDATE_POST_VOTES: {
const { story_id, value, userVote } = action.payload
const updatedItems = state.items.map(item => {
if (item._id === story_id) {
let approved_count_diffValue = 0
let mixedvote_count_diffValue = 0
let fake_count_diffValue = 0

if (value === 1) {
approved_count_diffValue = userVote === 1 ? 0 : 1
mixedvote_count_diffValue = userVote === 0 ? -1 : 0
fake_count_diffValue = userVote === -1 ? -1 : 0
} else if (value === 0) {
approved_count_diffValue = userVote === 1 ? -1 : 0
mixedvote_count_diffValue = userVote === 0 ? 0 : 1
fake_count_diffValue = userVote === -1 ? -1 : 0
} else if (value === -1) {
approved_count_diffValue = userVote === 1 ? -1 : 0
mixedvote_count_diffValue = userVote === 0 ? -1 : 0
fake_count_diffValue = userVote === -1 ? 0 : 1
}
return {
...item,
approved_count: item.approved_count + approved_count_diffValue,
mixedvote_count: item.mixedvote_count + mixedvote_count_diffValue,
fake_count: item.fake_count + fake_count_diffValue
}
}
return item
})
return {
...state,
items: updatedItems
items: getUpdatedItemsAfterVote(state.items, action),
searchedPosts: getUpdatedItemsAfterVote(state.searchedPosts, action)
}
}
case START_POSTS_SEARCH: {
return {
...state,
searchedPostsLoading: true
}
}
case SET_POSTS_ON_SEARCH: {
return {
...state,
searchedPosts: action.payload,
searchedPostsLoading: false
}
}
default: {
return state
}
}
}

const getUpdatedItemsAfterVote = (items, action) => {
const { story_id, value, userVote } = action.payload
return items.map(item => {
if (item._id === story_id) {
let approved_count_diffValue = 0
let mixedvote_count_diffValue = 0
let fake_count_diffValue = 0

if (value === 1) {
approved_count_diffValue = userVote === 1 ? 0 : 1
mixedvote_count_diffValue = userVote === 0 ? -1 : 0
fake_count_diffValue = userVote === -1 ? -1 : 0
} else if (value === 0) {
approved_count_diffValue = userVote === 1 ? -1 : 0
mixedvote_count_diffValue = userVote === 0 ? 0 : 1
fake_count_diffValue = userVote === -1 ? -1 : 0
} else if (value === -1) {
approved_count_diffValue = userVote === 1 ? -1 : 0
mixedvote_count_diffValue = userVote === 0 ? -1 : 0
fake_count_diffValue = userVote === -1 ? 0 : 1
}
return {
...item,
approved_count: item.approved_count + approved_count_diffValue,
mixedvote_count: item.mixedvote_count + mixedvote_count_diffValue,
fake_count: item.fake_count + fake_count_diffValue
}
}
return item
})
}
12 changes: 11 additions & 1 deletion fact-bounty-client/src/services/PostsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,19 @@ const loadUserVotes = () => {
return ApiBuilder.API.post(`/api/stories/load-user-votes`, {})
}

/**
*
* GET : getPostsFromKeyword
*
*/
const getPostsFromKeyword = keyword => {
return ApiBuilder.API.get(`/api/stories/search/${keyword}`)
}

export default {
fetchPosts,
fetchPostById,
changeVoteCount,
loadUserVotes
loadUserVotes,
getPostsFromKeyword
}