From 80164178c216462a4f94913077d32f985ab5b32f Mon Sep 17 00:00:00 2001 From: riggraz Date: Fri, 27 Sep 2019 16:57:23 +0200 Subject: [PATCH] Implement basic version of likes --- app/controllers/likes_controller.rb | 11 +++-- app/javascript/actions/submitLike.ts | 41 +++++++++++++++++ app/javascript/components/Board/BoardP.tsx | 7 ++- app/javascript/components/Board/PostList.tsx | 14 +++++- .../components/Board/PostListItem.tsx | 46 +++++++++++++++---- .../components/LikeButton/LikeButtonP.tsx | 37 +++++++++++++++ app/javascript/containers/LikeButton.tsx | 16 +++++++ app/javascript/interfaces/IPost.ts | 3 ++ app/javascript/interfaces/json/IPost.ts | 3 ++ app/javascript/reducers/postReducer.ts | 6 +++ app/javascript/reducers/postsReducer.ts | 23 +++++++++- 11 files changed, 189 insertions(+), 18 deletions(-) create mode 100644 app/javascript/actions/submitLike.ts create mode 100644 app/javascript/components/LikeButton/LikeButtonP.tsx create mode 100644 app/javascript/containers/LikeButton.tsx diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb index 29dd6803..8b85d338 100644 --- a/app/controllers/likes_controller.rb +++ b/app/controllers/likes_controller.rb @@ -14,10 +14,12 @@ class LikesController < ApplicationController end def destroy - like = Like.where(like_params) + like = Like.find_by(like_params) + + return if like.nil? if like.destroy - render json: {}, status: :no_content + render json: {}, status: :accepted else render json: { error: I18n.t('errors.likes.destroy', message: like.errors.full_messages) @@ -28,6 +30,9 @@ class LikesController < ApplicationController private def like_params - params.permit(:post_id).merge(user_id: current_user.id) + { + post_id: params[:post_id], + user_id: current_user.id, + } end end diff --git a/app/javascript/actions/submitLike.ts b/app/javascript/actions/submitLike.ts new file mode 100644 index 00000000..26c64ca8 --- /dev/null +++ b/app/javascript/actions/submitLike.ts @@ -0,0 +1,41 @@ +import { Action } from "redux"; +import { ThunkAction } from "redux-thunk"; + +import { State } from "../reducers/rootReducer"; + +export const LIKE_SUBMIT_SUCCESS = 'LIKE_SUBMIT_SUCCESS'; +interface LikeSubmitSuccessAction { + type: typeof LIKE_SUBMIT_SUCCESS, + postId: number; + isLike: boolean; +} + +export type LikeActionTypes = LikeSubmitSuccessAction; + +const likeSubmitSuccess = (postId: number, isLike: boolean): LikeSubmitSuccessAction => ({ + type: LIKE_SUBMIT_SUCCESS, + postId, + isLike, +}); + +export const submitLike = ( + postId: number, + isLike: boolean, + authenticityToken: string, +): ThunkAction> => async (dispatch) => { + try { + const res = await fetch(`/posts/${postId}/likes`, { + method: isLike ? 'POST' : 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'X-CSRF-Token': authenticityToken, + }, + }); + + if (res.status === 201 || res.status === 202) + dispatch(likeSubmitSuccess(postId, isLike)); + } catch (e) { + console.log('An error occurred while liking a post'); + } +} \ No newline at end of file diff --git a/app/javascript/components/Board/BoardP.tsx b/app/javascript/components/Board/BoardP.tsx index ded16311..00675bd3 100644 --- a/app/javascript/components/Board/BoardP.tsx +++ b/app/javascript/components/Board/BoardP.tsx @@ -97,16 +97,19 @@ class BoardP extends React.Component { posts.areLoading ? null : requestPosts(board.id, posts.page + 1, filters.searchQuery, filters.postStatusId) } + + isLoggedIn={isLoggedIn} + authenticityToken={authenticityToken} /> ); diff --git a/app/javascript/components/Board/PostList.tsx b/app/javascript/components/Board/PostList.tsx index 4d862296..01be7d12 100644 --- a/app/javascript/components/Board/PostList.tsx +++ b/app/javascript/components/Board/PostList.tsx @@ -18,8 +18,11 @@ interface Props { areLoading: boolean; error: string; - handleLoadMore(): void; hasMore: boolean; + handleLoadMore(): void; + + isLoggedIn: boolean; + authenticityToken: string; } const PostList = ({ @@ -27,8 +30,10 @@ const PostList = ({ postStatuses, areLoading, error, + hasMore, handleLoadMore, - hasMore + isLoggedIn, + authenticityToken, }: Props) => (
{ error ? {error} : null } @@ -48,8 +53,13 @@ const PostList = ({ title={post.title} description={post.description} postStatus={postStatuses.find(postStatus => postStatus.id === post.postStatusId)} + likesCount={post.likesCount} + liked={post.liked} commentsCount={post.commentsCount} + isLoggedIn={isLoggedIn} + authenticityToken={authenticityToken} + key={i} /> )) diff --git a/app/javascript/components/Board/PostListItem.tsx b/app/javascript/components/Board/PostListItem.tsx index ab46a3b4..05b25cb6 100644 --- a/app/javascript/components/Board/PostListItem.tsx +++ b/app/javascript/components/Board/PostListItem.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; +import LikeButton from '../../containers/LikeButton'; import CommentsNumber from '../shared/CommentsNumber'; import PostStatusLabel from '../shared/PostStatusLabel'; import { DescriptionText } from '../shared/CustomTexts'; @@ -11,21 +12,46 @@ interface Props { title: string; description?: string; postStatus: IPostStatus; + likesCount: number; + liked: number; commentsCount: number; + + isLoggedIn: boolean; + authenticityToken: string; } -const PostListItem = ({ id, title, description, postStatus, commentsCount }: Props) => ( - -
- {title} - {description} +const PostListItem = ({ + id, + title, + description, + postStatus, + likesCount, + liked, + commentsCount, - - + + ); export default PostListItem; \ No newline at end of file diff --git a/app/javascript/components/LikeButton/LikeButtonP.tsx b/app/javascript/components/LikeButton/LikeButtonP.tsx new file mode 100644 index 00000000..3e2db452 --- /dev/null +++ b/app/javascript/components/LikeButton/LikeButtonP.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; + +interface Props { + postId: number; + likesCount: number; + liked: number; + handleLikeSubmit( + postId: number, + isLike: boolean, + authenticityToken: string, + ): void; + authenticityToken: string; + isLoggedIn: boolean; +} + +const LikeButtonP = ({ + postId, + likesCount, + liked, + handleLikeSubmit, + authenticityToken, + isLoggedIn, +}: Props) => ( +
+ + {likesCount} +
+); + +export default LikeButtonP; \ No newline at end of file diff --git a/app/javascript/containers/LikeButton.tsx b/app/javascript/containers/LikeButton.tsx new file mode 100644 index 00000000..b1cb2c78 --- /dev/null +++ b/app/javascript/containers/LikeButton.tsx @@ -0,0 +1,16 @@ +import { connect } from 'react-redux'; + +import LikeButtonP from '../components/LikeButton/LikeButtonP'; + +import { submitLike } from '../actions/submitLike'; + +const mapDispatchToProps = dispatch => ({ + handleLikeSubmit(postId: number, isLike: boolean, authenticityToken: string) { + dispatch(submitLike(postId, isLike, authenticityToken)); + }, +}); + +export default connect( + null, + mapDispatchToProps, +)(LikeButtonP); \ No newline at end of file diff --git a/app/javascript/interfaces/IPost.ts b/app/javascript/interfaces/IPost.ts index 7dc5519c..e038a510 100644 --- a/app/javascript/interfaces/IPost.ts +++ b/app/javascript/interfaces/IPost.ts @@ -4,7 +4,10 @@ interface IPost { description?: string; boardId: number; postStatusId?: number; + likesCount: number; + liked: number; commentsCount: number; + hotness: number; userId: number; createdAt: string; } diff --git a/app/javascript/interfaces/json/IPost.ts b/app/javascript/interfaces/json/IPost.ts index 40e3f2ba..9d97c119 100644 --- a/app/javascript/interfaces/json/IPost.ts +++ b/app/javascript/interfaces/json/IPost.ts @@ -4,7 +4,10 @@ interface IPostJSON { description?: string; board_id: number; post_status_id?: number; + likes_count: number; + liked: number; comments_count: number; + hotness: number; user_id: number; created_at: string; } diff --git a/app/javascript/reducers/postReducer.ts b/app/javascript/reducers/postReducer.ts index 1b4dd6b4..8d37e3db 100644 --- a/app/javascript/reducers/postReducer.ts +++ b/app/javascript/reducers/postReducer.ts @@ -21,7 +21,10 @@ const initialState: IPost = { description: null, boardId: 0, postStatusId: null, + likesCount: 0, + liked: 0, commentsCount: 0, + hotness: 0, userId: 0, createdAt: '', }; @@ -41,7 +44,10 @@ const postReducer = ( description: action.post.description, boardId: action.post.board_id, postStatusId: action.post.post_status_id, + likesCount: action.post.likes_count, + liked: action.post.liked, commentsCount: action.post.comments_count, + hotness: action.post.hotness, userId: action.post.user_id, createdAt: action.post.created_at, }; diff --git a/app/javascript/reducers/postsReducer.ts b/app/javascript/reducers/postsReducer.ts index b9a8b8af..289e9950 100644 --- a/app/javascript/reducers/postsReducer.ts +++ b/app/javascript/reducers/postsReducer.ts @@ -22,6 +22,11 @@ import { SET_POST_STATUS_FILTER, } from '../actions/changeFilters'; +import { + LikeActionTypes, + LIKE_SUBMIT_SUCCESS, +} from '../actions/submitLike'; + export interface PostsState { items: Array; page: number; @@ -42,7 +47,10 @@ const initialState: PostsState = { const postsReducer = ( state = initialState, - action: PostsRequestActionTypes | ChangeFiltersActionTypes, + action: + PostsRequestActionTypes | + ChangeFiltersActionTypes | + LikeActionTypes, ): PostsState => { switch (action.type) { case POSTS_REQUEST_START: @@ -78,6 +86,19 @@ const postsReducer = ( filters: filtersReducer(state.filters, action), }; + case LIKE_SUBMIT_SUCCESS: + return { + ...state, + items: state.items.map(post => { + if (action.postId === post.id) { + return action.isLike ? + { ...post, likesCount: post.likesCount + 1, liked: 1 } + : + { ...post, likesCount: post.likesCount - 1, liked: 0 } + } else return post; + }), + }; + default: return state; }