From bc15140512cff901cdc05a0d4cf2ff00394ce197 Mon Sep 17 00:00:00 2001 From: Riccardo Graziosi <31478034+riggraz@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:17:42 +0200 Subject: [PATCH] Add edit and delete actions to posts and comments (#125) --- app/controllers/comments_controller.rb | 54 +++-- .../post_status_changes_controller.rb | 4 +- app/controllers/posts_controller.rb | 86 ++++--- .../actions/Comment/deleteComment.ts | 72 ++++++ .../actions/Comment/updateComment.ts | 78 ++++-- .../actions/Post/changePostBoard.ts | 40 --- .../actions/Post/changePostEditForm.ts | 61 +++++ .../actions/Post/changePostStatus.ts | 40 --- app/javascript/actions/Post/deletePost.ts | 69 ++++++ .../actions/Post/togglePostEditMode.ts | 9 + app/javascript/actions/Post/updatePost.ts | 83 +++++++ .../components/Comments/Comment.tsx | 223 +++++++++-------- .../components/Comments/CommentEditForm.tsx | 100 ++++++++ .../components/Comments/CommentFooter.tsx | 78 ++++++ .../components/Comments/CommentList.tsx | 16 +- .../components/Comments/CommentsP.tsx | 51 ++-- .../components/Comments/NewComment.tsx | 4 +- .../components/Post/PostEditForm.tsx | 107 ++++++++ app/javascript/components/Post/PostFooter.tsx | 53 ++++ app/javascript/components/Post/PostP.tsx | 228 +++++++++++------- .../components/Post/PostUpdateList.tsx | 2 +- app/javascript/components/Post/index.tsx | 12 +- .../SiteSettings/Boards/BoardEditable.tsx | 5 +- .../PostStatuses/PostStatusEditable.tsx | 5 +- app/javascript/components/common/Box.tsx | 2 +- .../components/common/SidebarBox.tsx | 2 +- app/javascript/containers/Comments.tsx | 34 ++- app/javascript/containers/Post.tsx | 71 ++++-- app/javascript/interfaces/IComment.ts | 1 + app/javascript/interfaces/IPost.ts | 2 + .../interfaces/IPostStatusChange.ts | 2 +- app/javascript/interfaces/json/IComment.ts | 1 + app/javascript/interfaces/json/IPost.ts | 2 + .../interfaces/json/IPostStatusChange.ts | 2 +- app/javascript/reducers/commentReducer.ts | 10 +- app/javascript/reducers/commentsReducer.ts | 32 ++- app/javascript/reducers/currentPostReducer.ts | 120 +++++++-- app/javascript/reducers/postReducer.ts | 31 +-- .../reducers/postStatusChangesReducer.ts | 2 +- app/javascript/reducers/postsReducer.ts | 12 + .../stylesheets/components/Comments.scss | 60 +++-- .../stylesheets/components/LikeButton.scss | 3 +- .../stylesheets/components/Post.scss | 43 +++- app/policies/comment_policy.rb | 20 ++ app/policies/post_policy.rb | 16 ++ app/views/posts/show.html.erb | 4 +- config/locales/en.yml | 5 +- config/locales/it.yml | 5 +- config/routes.rb | 4 +- script/rspec-no-system-specs.sh | 0 spec/routing/comments_routing_spec.rb | 4 +- spec/routing/post_routing_spec.rb | 6 +- 52 files changed, 1495 insertions(+), 481 deletions(-) create mode 100644 app/javascript/actions/Comment/deleteComment.ts delete mode 100644 app/javascript/actions/Post/changePostBoard.ts create mode 100644 app/javascript/actions/Post/changePostEditForm.ts delete mode 100644 app/javascript/actions/Post/changePostStatus.ts create mode 100644 app/javascript/actions/Post/deletePost.ts create mode 100644 app/javascript/actions/Post/togglePostEditMode.ts create mode 100644 app/javascript/actions/Post/updatePost.ts create mode 100644 app/javascript/components/Comments/CommentEditForm.tsx create mode 100644 app/javascript/components/Comments/CommentFooter.tsx create mode 100644 app/javascript/components/Post/PostEditForm.tsx create mode 100644 app/javascript/components/Post/PostFooter.tsx mode change 100644 => 100755 script/rspec-no-system-specs.sh diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 8b1631e0..e4c2fbd3 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,5 +1,5 @@ class CommentsController < ApplicationController - before_action :authenticate_user!, only: [:create, :update] + before_action :authenticate_user!, only: [:create, :update, :destroy] def index comments = Comment @@ -8,58 +8,80 @@ class CommentsController < ApplicationController :body, :parent_id, :is_post_update, + :created_at, :updated_at, 'users.full_name as user_full_name', 'users.email as user_email', ) .where(post_id: params[:post_id]) .left_outer_joins(:user) - .order(updated_at: :desc) + .order(created_at: :desc) render json: comments end def create - comment = Comment.new(comment_params) + @comment = Comment.new + @comment.assign_attributes(comment_create_params) - if comment.save - SendNotificationForCommentWorkflow.new(comment: comment).run + if @comment.save + SendNotificationForCommentWorkflow.new(comment: @comment).run - render json: comment.attributes.merge( + render json: @comment.attributes.merge( { user_full_name: current_user.full_name, user_email: current_user.email } ), status: :created else render json: { - error: comment.errors.full_messages + error: @comment.errors.full_messages }, status: :unprocessable_entity end end def update - comment = Comment.find(params[:id]) - authorize comment - comment.assign_attributes(comment_params) + @comment = Comment.find(params[:id]) + authorize @comment - if comment.save - render json: comment.attributes.merge( - { user_full_name: current_user.full_name, user_email: current_user.email } + if @comment.update(comment_update_params) + render json: @comment.attributes.merge( + { user_full_name: @comment.user.full_name, user_email: @comment.user.email } ) else render json: { - error: comment.errors.full_messages + error: @comment.errors.full_messages + }, status: :unprocessable_entity + end + end + + def destroy + @comment = Comment.find(params[:id]) + authorize @comment + + if @comment.destroy + render json: { + id: @comment.id, + }, status: :accepted + else + render json: { + error: @comment.errors.full_messages }, status: :unprocessable_entity end end private - def comment_params + def comment_create_params params .require(:comment) - .permit(:body, :parent_id, :is_post_update) + .permit(policy(@comment).permitted_attributes_for_create) .merge( user_id: current_user.id, post_id: params[:post_id] ) end + + def comment_update_params + params + .require(:comment) + .permit(policy(@comment).permitted_attributes_for_update) + end end diff --git a/app/controllers/post_status_changes_controller.rb b/app/controllers/post_status_changes_controller.rb index 68980cdf..044292e4 100644 --- a/app/controllers/post_status_changes_controller.rb +++ b/app/controllers/post_status_changes_controller.rb @@ -3,13 +3,13 @@ class PostStatusChangesController < ApplicationController post_status_changes = PostStatusChange .select( :post_status_id, - :updated_at, + :created_at, 'users.full_name as user_full_name', 'users.email as user_email', ) .where(post_id: params[:post_id]) .left_outer_joins(:user) - .order(updated_at: :asc) + .order(created_at: :asc) render json: post_status_changes end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index a2ce7eed..97e3362a 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -1,5 +1,5 @@ class PostsController < ApplicationController - before_action :authenticate_user!, only: [:create, :update] + before_action :authenticate_user!, only: [:create, :update, :destroy] def index posts = Post @@ -25,21 +25,37 @@ class PostsController < ApplicationController end def create - post = Post.new(post_params) + @post = Post.new + @post.assign_attributes(post_create_params) - if post.save - Follow.create(post_id: post.id, user_id: current_user.id) + if @post.save + Follow.create(post_id: @post.id, user_id: current_user.id) - render json: post, status: :created + render json: @post, status: :created else render json: { - error: post.errors.full_messages + error: @post.errors.full_messages }, status: :unprocessable_entity end end def show - @post = Post.find(params[:id]) + @post = Post + .select( + :id, + :title, + :description, + :board_id, + :user_id, + :post_status_id, + :created_at, + :updated_at, + 'users.email as user_email', + 'users.full_name as user_full_name' + ) + .joins(:user) + .find(params[:id]) + @post_statuses = PostStatus.select(:id, :name, :color).order(order: :asc) @board = @post.board @@ -51,35 +67,41 @@ class PostsController < ApplicationController end def update - post = Post.find(params[:id]) - authorize post - - post.board_id = params[:post][:board_id] if params[:post].has_key?(:board_id) + @post = Post.find(params[:id]) + authorize @post - post_status_changed = false - - if params[:post].has_key?(:post_status_id) and - params[:post][:post_status_id] != post.post_status_id - - post_status_changed = true - post.post_status_id = params[:post][:post_status_id] - end + @post.assign_attributes(post_update_params) - if post.save - if post_status_changed + if @post.save + if @post.post_status_id_previously_changed? PostStatusChange.create( user_id: current_user.id, - post_id: post.id, - post_status_id: post.post_status_id + post_id: @post.id, + post_status_id: @post.post_status_id ) - - send_notifications(post) + + UserMailer.notify_followers_of_post_status_change(post: @post).deliver_later end - render json: post, status: :no_content + render json: @post else render json: { - error: post.errors.full_messages + error: @post.errors.full_messages + }, status: :unprocessable_entity + end + end + + def destroy + @post = Post.find(params[:id]) + authorize @post + + if @post.destroy + render json: { + id: @post.id, + }, status: :accepted + else + render json: { + error: @post.errors.full_messages }, status: :unprocessable_entity end end @@ -95,14 +117,16 @@ class PostsController < ApplicationController .except(:page, :search) end - def post_params + def post_create_params params .require(:post) - .permit(:title, :description, :board_id) + .permit(policy(@post).permitted_attributes_for_create) .merge(user_id: current_user.id) end - def send_notifications(post) - UserMailer.notify_followers_of_post_status_change(post: post).deliver_later + def post_update_params + params + .require(:post) + .permit(policy(@post).permitted_attributes_for_update) end end diff --git a/app/javascript/actions/Comment/deleteComment.ts b/app/javascript/actions/Comment/deleteComment.ts new file mode 100644 index 00000000..e07e9799 --- /dev/null +++ b/app/javascript/actions/Comment/deleteComment.ts @@ -0,0 +1,72 @@ +import { Action } from "redux"; +import { ThunkAction } from "redux-thunk"; +import HttpStatus from "../../constants/http_status"; +import buildRequestHeaders from "../../helpers/buildRequestHeaders"; +import { State } from "../../reducers/rootReducer"; + +export const COMMENT_DELETE_START = 'COMMENT_DELETE_START'; +interface CommentDeleteStartAction { + type: typeof COMMENT_DELETE_START; +} + +export const COMMENT_DELETE_SUCCESS = 'COMMENT_DELETE_SUCCESS'; +interface CommentDeleteSuccessAction { + type: typeof COMMENT_DELETE_SUCCESS; + postId: number; + commentId: number; +} + +export const COMMENT_DELETE_FAILURE = 'COMMENT_DELETE_FAILURE'; +interface CommentDeleteFailureAction { + type: typeof COMMENT_DELETE_FAILURE; + error: string; +} + +export type CommentDeleteActionTypes = + CommentDeleteStartAction | + CommentDeleteSuccessAction | + CommentDeleteFailureAction; + +const commentDeleteStart = (): CommentDeleteStartAction => ({ + type: COMMENT_DELETE_START, +}); + +const commentDeleteSuccess = ( + postId: number, + commentId: number, +): CommentDeleteSuccessAction => ({ + type: COMMENT_DELETE_SUCCESS, + postId, + commentId, +}); + +const commentDeleteFailure = (error: string): CommentDeleteFailureAction => ({ + type: COMMENT_DELETE_FAILURE, + error, +}); + +export const deleteComment = ( + postId: number, + commentId: number, + authenticityToken: string, +): ThunkAction> => ( + async (dispatch) => { + dispatch(commentDeleteStart()); + + try { + const res = await fetch(`/posts/${postId}/comments/${commentId}`, { + method: 'DELETE', + headers: buildRequestHeaders(authenticityToken), + }); + const json = await res.json(); + + if (res.status === HttpStatus.Accepted) { + dispatch(commentDeleteSuccess(postId, commentId)); + } else { + dispatch(commentDeleteFailure(json.error)); + } + } catch (e) { + dispatch(commentDeleteFailure(e)); + } + } +); \ No newline at end of file diff --git a/app/javascript/actions/Comment/updateComment.ts b/app/javascript/actions/Comment/updateComment.ts index 5bc18a7e..292ddb6a 100644 --- a/app/javascript/actions/Comment/updateComment.ts +++ b/app/javascript/actions/Comment/updateComment.ts @@ -1,43 +1,81 @@ -import { ThunkAction } from "redux-thunk"; -import { State } from "../../reducers/rootReducer"; import { Action } from "redux"; +import { ThunkAction } from "redux-thunk"; +import HttpStatus from "../../constants/http_status"; import buildRequestHeaders from "../../helpers/buildRequestHeaders"; +import ICommentJSON from "../../interfaces/json/IComment"; +import { State } from "../../reducers/rootReducer"; -export const TOGGLE_COMMENT_IS_UPDATE_SUCCESS = 'TOGGLE_COMMENT_IS_UPDATE_SUCCESS'; -export interface ToggleIsUpdateSuccessAction { - type: typeof TOGGLE_COMMENT_IS_UPDATE_SUCCESS; - commentId: number; +export const COMMENT_UPDATE_START = 'COMMENT_UPDATE_START'; +interface CommentUpdateStartAction { + type: typeof COMMENT_UPDATE_START; } -const toggleIsUpdateSuccess = ( - commentId: number, -): ToggleIsUpdateSuccessAction => ({ - type: TOGGLE_COMMENT_IS_UPDATE_SUCCESS, - commentId, +export const COMMENT_UPDATE_SUCCESS = 'COMMENT_UPDATE_SUCCESS'; +interface CommentUpdateSuccessAction { + type: typeof COMMENT_UPDATE_SUCCESS; + comment: ICommentJSON; +} + +export const COMMENT_UPDATE_FAILURE = 'COMMENT_UPDATE_FAILURE'; +interface CommentUpdateFailureAction { + type: typeof COMMENT_UPDATE_FAILURE; + error: string; +} + +export type CommentUpdateActionTypes = + CommentUpdateStartAction | + CommentUpdateSuccessAction | + CommentUpdateFailureAction; + +const commentUpdateStart = (): CommentUpdateStartAction => ({ + type: COMMENT_UPDATE_START, }); -export const toggleCommentIsUpdate = ( +const commentUpdateSuccess = ( + commentJSON: ICommentJSON, +): CommentUpdateSuccessAction => ({ + type: COMMENT_UPDATE_SUCCESS, + comment: commentJSON, +}); + +const commentUpdateFailure = (error: string): CommentUpdateFailureAction => ({ + type: COMMENT_UPDATE_FAILURE, + error, +}); + +export const updateComment = ( postId: number, commentId: number, - currentIsPostUpdate: boolean, + body: string, + isPostUpdate: boolean, authenticityToken: string, ): ThunkAction> => async (dispatch) => { + dispatch(commentUpdateStart()); + try { - const response = await fetch(`/posts/${postId}/comments/${commentId}`, { + const res = await fetch(`/posts/${postId}/comments/${commentId}`, { method: 'PATCH', headers: buildRequestHeaders(authenticityToken), body: JSON.stringify({ comment: { - is_post_update: !currentIsPostUpdate, + body, + is_post_update: isPostUpdate, }, - }) + }), }); + const json = await res.json(); - if (response.status === 200) { - dispatch(toggleIsUpdateSuccess(commentId)); + if (res.status === HttpStatus.OK) { + dispatch(commentUpdateSuccess(json)); + } else { + dispatch(commentUpdateFailure(json.error)); } + + return Promise.resolve(res); } catch (e) { - console.log(e); + dispatch(commentUpdateFailure(e)); + + return Promise.resolve(null); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/app/javascript/actions/Post/changePostBoard.ts b/app/javascript/actions/Post/changePostBoard.ts deleted file mode 100644 index 965a3984..00000000 --- a/app/javascript/actions/Post/changePostBoard.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Action } from 'redux'; -import { ThunkAction } from 'redux-thunk'; -import { State } from '../../reducers/rootReducer'; - -import buildRequestHeaders from '../../helpers/buildRequestHeaders'; - -export const CHANGE_POST_BOARD_SUCCESS = 'CHANGE_POST_BOARD_SUCCESS'; -export interface ChangePostBoardSuccessAction { - type: typeof CHANGE_POST_BOARD_SUCCESS; - newBoardId: number; -} - -const changePostBoardSuccess = (newBoardId: number): ChangePostBoardSuccessAction => ({ - type: CHANGE_POST_BOARD_SUCCESS, - newBoardId, -}); - -export const changePostBoard = ( - postId: number, - newBoardId: number, - authenticityToken: string, -): ThunkAction> => async (dispatch) => { - try { - const response = await fetch(`/posts/${postId}`, { - method: 'PATCH', - headers: buildRequestHeaders(authenticityToken), - body: JSON.stringify({ - post: { - board_id: newBoardId, - }, - }) - }); - - if (response.status === 204) { - dispatch(changePostBoardSuccess(newBoardId)); - } - } catch (e) { - console.log(e); - } -} \ No newline at end of file diff --git a/app/javascript/actions/Post/changePostEditForm.ts b/app/javascript/actions/Post/changePostEditForm.ts new file mode 100644 index 00000000..463fe5f3 --- /dev/null +++ b/app/javascript/actions/Post/changePostEditForm.ts @@ -0,0 +1,61 @@ +export const POST_CHANGE_EDIT_FORM_TITLE = 'POST_CHANGE_EDIT_FORM_TITLE'; + +interface PostChangeEditFormTitle { + type: typeof POST_CHANGE_EDIT_FORM_TITLE, + title: string, +} + +export const changePostEditFormTitle = ( + title: string +): PostChangeEditFormTitle => ({ + type: POST_CHANGE_EDIT_FORM_TITLE, + title, +}); + +export const POST_CHANGE_EDIT_FORM_DESCRIPTION = 'POST_CHANGE_EDIT_FORM_DESCRIPTION'; + +interface PostChangeEditFormDescription { + type: typeof POST_CHANGE_EDIT_FORM_DESCRIPTION, + description: string, +} + +export const changePostEditFormDescription = ( + description: string +): PostChangeEditFormDescription => ({ + type: POST_CHANGE_EDIT_FORM_DESCRIPTION, + description, +}); + +export const POST_CHANGE_EDIT_FORM_BOARD = 'POST_CHANGE_EDIT_FORM_BOARD'; + +interface PostChangeEditFormBoard { + type: typeof POST_CHANGE_EDIT_FORM_BOARD, + boardId: number, +} + +export const changePostEditFormBoard = ( + boardId: number +): PostChangeEditFormBoard => ({ + type: POST_CHANGE_EDIT_FORM_BOARD, + boardId, +}); + +export const POST_CHANGE_EDIT_FORM_POST_STATUS = 'POST_CHANGE_EDIT_FORM_POST_STATUS'; + +interface PostChangeEditFormPostStatus { + type: typeof POST_CHANGE_EDIT_FORM_POST_STATUS, + postStatusId: number, +} + +export const changePostEditFormPostStatus = ( + postStatusId: number +): PostChangeEditFormPostStatus => ({ + type: POST_CHANGE_EDIT_FORM_POST_STATUS, + postStatusId, +}); + +export type ChangePostEditFormActionTypes = + PostChangeEditFormTitle | + PostChangeEditFormDescription | + PostChangeEditFormBoard | + PostChangeEditFormPostStatus; \ No newline at end of file diff --git a/app/javascript/actions/Post/changePostStatus.ts b/app/javascript/actions/Post/changePostStatus.ts deleted file mode 100644 index f1218b81..00000000 --- a/app/javascript/actions/Post/changePostStatus.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Action } from 'redux'; -import { ThunkAction } from 'redux-thunk'; -import { State } from '../../reducers/rootReducer'; - -import buildRequestHeaders from '../../helpers/buildRequestHeaders'; - -export const CHANGE_POST_STATUS_SUCCESS = 'CHANGE_POST_STATUS_SUCCESS'; -export interface ChangePostStatusSuccessAction { - type: typeof CHANGE_POST_STATUS_SUCCESS; - newPostStatusId: number; -} - -const changePostStatusSuccess = (newPostStatusId: number): ChangePostStatusSuccessAction => ({ - type: CHANGE_POST_STATUS_SUCCESS, - newPostStatusId, -}); - -export const changePostStatus = ( - postId: number, - newPostStatusId: number, - authenticityToken: string, -): ThunkAction> => async (dispatch) => { - try { - const response = await fetch(`/posts/${postId}`, { - method: 'PATCH', - headers: buildRequestHeaders(authenticityToken), - body: JSON.stringify({ - post: { - post_status_id: newPostStatusId, - }, - }) - }); - - if (response.status === 204) { - dispatch(changePostStatusSuccess(newPostStatusId)); - } - } catch (e) { - console.log(e); - } -} \ No newline at end of file diff --git a/app/javascript/actions/Post/deletePost.ts b/app/javascript/actions/Post/deletePost.ts new file mode 100644 index 00000000..4131878d --- /dev/null +++ b/app/javascript/actions/Post/deletePost.ts @@ -0,0 +1,69 @@ +import { Action } from "redux"; +import { ThunkAction } from "redux-thunk"; + +import HttpStatus from "../../constants/http_status"; +import buildRequestHeaders from "../../helpers/buildRequestHeaders"; +import { State } from "../../reducers/rootReducer"; + +export const POST_DELETE_START = 'POST_DELETE_START'; +interface PostDeleteStartAction { + type: typeof POST_DELETE_START; +} + +export const POST_DELETE_SUCCESS = 'POST_DELETE_SUCCESS'; +interface PostDeleteSuccessAction { + type: typeof POST_DELETE_SUCCESS; + postId: number; +} + +export const POST_DELETE_FAILURE = 'POST_DELETE_FAILURE'; +interface PostDeleteFailureAction { + type: typeof POST_DELETE_FAILURE; + error: string; +} + +export type PostDeleteActionTypes = + PostDeleteStartAction | + PostDeleteSuccessAction | + PostDeleteFailureAction; + +const postDeleteStart = (): PostDeleteStartAction => ({ + type: POST_DELETE_START, +}); + +const postDeleteSuccess = ( + postId: number, +): PostDeleteSuccessAction => ({ + type: POST_DELETE_SUCCESS, + postId, +}); + +const postDeleteFailure = (error: string): PostDeleteFailureAction => ({ + type: POST_DELETE_FAILURE, + error, +}); + +export const deletePost = ( + postId: number, + authenticityToken: string, +): ThunkAction> => ( + async (dispatch) => { + dispatch(postDeleteStart()); + + try { + const res = await fetch(`/posts/${postId}`, { + method: 'DELETE', + headers: buildRequestHeaders(authenticityToken), + }); + const json = await res.json(); + + if (res.status === HttpStatus.Accepted) { + dispatch(postDeleteSuccess(postId)); + } else { + dispatch(postDeleteFailure(json.error)); + } + } catch (e) { + dispatch(postDeleteFailure(e)); + } + } +); \ No newline at end of file diff --git a/app/javascript/actions/Post/togglePostEditMode.ts b/app/javascript/actions/Post/togglePostEditMode.ts new file mode 100644 index 00000000..1e9357da --- /dev/null +++ b/app/javascript/actions/Post/togglePostEditMode.ts @@ -0,0 +1,9 @@ +export const POST_TOGGLE_EDIT_MODE = 'POST_TOGGLE_EDIT_MODE'; + +export interface PostToggleEditMode { + type: typeof POST_TOGGLE_EDIT_MODE; +} + +export const togglePostEditMode = (): PostToggleEditMode => ({ + type: POST_TOGGLE_EDIT_MODE, +}); \ No newline at end of file diff --git a/app/javascript/actions/Post/updatePost.ts b/app/javascript/actions/Post/updatePost.ts new file mode 100644 index 00000000..ace6c64a --- /dev/null +++ b/app/javascript/actions/Post/updatePost.ts @@ -0,0 +1,83 @@ +import { Action } from "redux"; +import { ThunkAction } from "redux-thunk"; +import HttpStatus from "../../constants/http_status"; +import buildRequestHeaders from "../../helpers/buildRequestHeaders"; +import IPostJSON from "../../interfaces/json/IPost"; +import { State } from "../../reducers/rootReducer"; + +export const POST_UPDATE_START = 'POST_UPDATE_START'; +interface PostUpdateStartAction { + type: typeof POST_UPDATE_START; +} + +export const POST_UPDATE_SUCCESS = 'POST_UPDATE_SUCCESS'; +interface PostUpdateSuccessAction { + type: typeof POST_UPDATE_SUCCESS; + post: IPostJSON; +} + +export const POST_UPDATE_FAILURE = 'POST_UPDATE_FAILURE'; +interface PostUpdateFailureAction { + type: typeof POST_UPDATE_FAILURE; + error: string; +} + +export type PostUpdateActionTypes = + PostUpdateStartAction | + PostUpdateSuccessAction | + PostUpdateFailureAction; + +const postUpdateStart = (): PostUpdateStartAction => ({ + type: POST_UPDATE_START, +}); + +const postUpdateSuccess = ( + postJSON: IPostJSON, +): PostUpdateSuccessAction => ({ + type: POST_UPDATE_SUCCESS, + post: postJSON, +}); + +const postUpdateFailure = (error: string): PostUpdateFailureAction => ({ + type: POST_UPDATE_FAILURE, + error, +}); + +export const updatePost = ( + id: number, + title: string, + description: string, + boardId: number, + postStatusId: number, + authenticityToken: string, +): ThunkAction> => async (dispatch) => { + dispatch(postUpdateStart()); + + try { + const res = await fetch(`/posts/${id}`, { + method: 'PATCH', + headers: buildRequestHeaders(authenticityToken), + body: JSON.stringify({ + post: { + title, + description, + board_id: boardId, + post_status_id: postStatusId, + } + }), + }); + const json = await res.json(); + + if (res.status === HttpStatus.OK) { + dispatch(postUpdateSuccess(json)); + } else { + dispatch(postUpdateFailure(json.error)); + } + + return Promise.resolve(res); + } catch (e) { + dispatch(postUpdateFailure(e)); + + return Promise.resolve(null); + } +}; \ No newline at end of file diff --git a/app/javascript/components/Comments/Comment.tsx b/app/javascript/components/Comments/Comment.tsx index 194d5937..fcba9d36 100644 --- a/app/javascript/components/Comments/Comment.tsx +++ b/app/javascript/components/Comments/Comment.tsx @@ -4,12 +4,11 @@ import Gravatar from 'react-gravatar'; import I18n from 'i18n-js'; import NewComment from './NewComment'; -import Separator from '../common/Separator'; -import { MutedText } from '../common/CustomTexts'; import { ReplyFormState } from '../../reducers/replyFormReducer'; -import friendlyDate from '../../helpers/datetime'; +import CommentEditForm from './CommentEditForm'; +import CommentFooter from './CommentFooter'; interface Props { id: number; @@ -17,119 +16,147 @@ interface Props { isPostUpdate: boolean; userFullName: string; userEmail: string; + createdAt: string; updatedAt: string; replyForm: ReplyFormState; handleToggleCommentReply(): void; handleCommentReplyBodyChange(e: React.FormEvent): void; - handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void; + handleSubmitComment(body: string, parentId: number, isPostUpdate: boolean): void; + handleUpdateComment(commentId: number, body: string, isPostUpdate: boolean, onSuccess: Function): void; + handleDeleteComment(id: number): void; isLoggedIn: boolean; isPowerUser: boolean; currentUserEmail: string; } -const Comment = ({ - id, - body, - isPostUpdate, - userFullName, - userEmail, - updatedAt, +interface State { + editMode: boolean; +} - replyForm, - handleToggleCommentReply, - handleCommentReplyBodyChange, - handleToggleIsCommentUpdate, - handleSubmitComment, +class Comment extends React.Component { + constructor(props: Props) { + super(props); - isLoggedIn, - isPowerUser, - currentUserEmail, -}: Props) => ( -
-
- - {userFullName} - { - isPostUpdate ? - - {I18n.t('post.comments.post_update_badge')} - - : - null - } -
+ this.state = { + editMode: false, + }; - - {body} - + this.toggleEditMode = this.toggleEditMode.bind(this); + this._handleUpdateComment = this._handleUpdateComment.bind(this); + } -
- + toggleEditMode() { + this.setState({editMode: !this.state.editMode}); + } + + _handleUpdateComment(body: string, isPostUpdate: boolean) { + this.props.handleUpdateComment( + this.props.id, + body, + isPostUpdate, + this.toggleEditMode, + ); + } + + render() { + const { + id, + body, + isPostUpdate, + userFullName, + userEmail, + createdAt, + updatedAt, + + replyForm, + handleToggleCommentReply, + handleCommentReplyBodyChange, + + handleSubmitComment, + handleDeleteComment, + + isLoggedIn, + isPowerUser, + currentUserEmail, + } = this.props; + + return ( + - { - replyForm.isOpen ? - null} - handleSubmit={handleSubmitComment} - - isLoggedIn={isLoggedIn} - isPowerUser={isPowerUser} - userEmail={currentUserEmail} - /> - : - null - } -
-); +
+ ); + } +} export default Comment; \ No newline at end of file diff --git a/app/javascript/components/Comments/CommentEditForm.tsx b/app/javascript/components/Comments/CommentEditForm.tsx new file mode 100644 index 00000000..2a855fd5 --- /dev/null +++ b/app/javascript/components/Comments/CommentEditForm.tsx @@ -0,0 +1,100 @@ +import * as React from 'react'; +import I18n from 'i18n-js'; +import Button from '../common/Button'; + +interface Props { + id: number; + initialBody: string; + initialIsPostUpdate: boolean; + + isPowerUser: boolean; + + handleUpdateComment(body: string, isPostUpdate: boolean): void; + toggleEditMode(): void; +} + +interface State { + body: string; + isPostUpdate: boolean; +} + +class CommentEditForm extends React.Component { + constructor(props) { + super(props); + + this.state = { + body: '', + isPostUpdate: false, + }; + + this.handleCommentBodyChange = this.handleCommentBodyChange.bind(this); + this.handleCommentIsPostUpdateChange = this.handleCommentIsPostUpdateChange.bind(this); + } + + componentDidMount() { + this.setState({ + body: this.props.initialBody, + isPostUpdate: this.props.initialIsPostUpdate, + }); + } + + handleCommentBodyChange(newCommentBody: string) { + this.setState({ body: newCommentBody }); + } + + handleCommentIsPostUpdateChange(newIsPostUpdate: boolean) { + this.setState({ isPostUpdate: newIsPostUpdate }); + } + + render() { + const { id, isPowerUser, handleUpdateComment, toggleEditMode } = this.props; + const { body, isPostUpdate } = this.state; + + return ( +
+