mirror of
https://github.com/astuto/astuto.git
synced 2025-12-16 11:47:56 +01:00
Add button to toggle comment is post update
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
class CommentsController < ApplicationController
|
class CommentsController < ApplicationController
|
||||||
before_action :authenticate_user!, only: [:create]
|
before_action :authenticate_user!, only: [:create, :update]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
comments = Comment
|
comments = Comment
|
||||||
@@ -33,12 +33,32 @@ class CommentsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
comment = Comment.find_by(post_id: params[:post_id])
|
||||||
|
comment.assign_attributes(comment_params)
|
||||||
|
|
||||||
|
if !current_user.power_user? && current_user.id != post.user_id
|
||||||
|
render json: I18n.t('errors.unauthorized'), status: :unauthorized
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if comment.save
|
||||||
|
render json: comment.attributes.merge(
|
||||||
|
{ user_full_name: current_user.full_name, user_email: current_user.email}
|
||||||
|
)
|
||||||
|
else
|
||||||
|
render json: {
|
||||||
|
error: I18n.t('errors.comment.update', message: comment.errors.full_messages)
|
||||||
|
}, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def comment_params
|
def comment_params
|
||||||
params
|
params
|
||||||
.require(:comment)
|
.require(:comment)
|
||||||
.permit(:body, :parent_id)
|
.permit(:body, :parent_id, :is_post_update)
|
||||||
.merge(
|
.merge(
|
||||||
user_id: current_user.id,
|
user_id: current_user.id,
|
||||||
post_id: params[:post_id]
|
post_id: params[:post_id]
|
||||||
|
|||||||
45
app/javascript/actions/updateComment.ts
Normal file
45
app/javascript/actions/updateComment.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { ThunkAction } from "redux-thunk";
|
||||||
|
import { State } from "../reducers/rootReducer";
|
||||||
|
import { Action } from "redux";
|
||||||
|
|
||||||
|
export const TOGGLE_COMMENT_IS_UPDATE_SUCCESS = 'TOGGLE_COMMENT_IS_UPDATE_SUCCESS';
|
||||||
|
export interface ToggleIsUpdateSuccessAction {
|
||||||
|
type: typeof TOGGLE_COMMENT_IS_UPDATE_SUCCESS;
|
||||||
|
commentId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleIsUpdateSuccess = (
|
||||||
|
commentId: number,
|
||||||
|
): ToggleIsUpdateSuccessAction => ({
|
||||||
|
type: TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
||||||
|
commentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const toggleCommentIsUpdate = (
|
||||||
|
postId: number,
|
||||||
|
commentId: number,
|
||||||
|
currentIsPostUpdate: boolean,
|
||||||
|
authenticityToken: string,
|
||||||
|
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/posts/${postId}/comments/${commentId}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': authenticityToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
comment: {
|
||||||
|
is_post_update: !currentIsPostUpdate,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
dispatch(toggleIsUpdateSuccess(commentId));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import friendlyDate from '../../helpers/friendlyDate';
|
|||||||
interface Props {
|
interface Props {
|
||||||
id: number;
|
id: number;
|
||||||
body: string;
|
body: string;
|
||||||
|
isPostUpdate: boolean;
|
||||||
userFullName: string;
|
userFullName: string;
|
||||||
userEmail: string;
|
userEmail: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -19,6 +20,7 @@ interface Props {
|
|||||||
replyForm: ReplyFormState;
|
replyForm: ReplyFormState;
|
||||||
handleToggleCommentReply(): void;
|
handleToggleCommentReply(): void;
|
||||||
handleCommentReplyBodyChange(e: React.FormEvent): void;
|
handleCommentReplyBodyChange(e: React.FormEvent): void;
|
||||||
|
handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void;
|
||||||
handleSubmitComment(body: string, parentId: number): void;
|
handleSubmitComment(body: string, parentId: number): void;
|
||||||
|
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
@@ -29,6 +31,7 @@ interface Props {
|
|||||||
const Comment = ({
|
const Comment = ({
|
||||||
id,
|
id,
|
||||||
body,
|
body,
|
||||||
|
isPostUpdate,
|
||||||
userFullName,
|
userFullName,
|
||||||
userEmail,
|
userEmail,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
@@ -36,6 +39,7 @@ const Comment = ({
|
|||||||
replyForm,
|
replyForm,
|
||||||
handleToggleCommentReply,
|
handleToggleCommentReply,
|
||||||
handleCommentReplyBodyChange,
|
handleCommentReplyBodyChange,
|
||||||
|
handleToggleIsCommentUpdate,
|
||||||
handleSubmitComment,
|
handleSubmitComment,
|
||||||
|
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
@@ -46,17 +50,26 @@ const Comment = ({
|
|||||||
<div className="commentHeader">
|
<div className="commentHeader">
|
||||||
<Gravatar email={userEmail} size={24} className="gravatar" />
|
<Gravatar email={userEmail} size={24} className="gravatar" />
|
||||||
<span className="commentAuthor">{userFullName}</span>
|
<span className="commentAuthor">{userFullName}</span>
|
||||||
|
{ isPostUpdate ? <span className="postUpdateBadge">Post update</span> : null }
|
||||||
</div>
|
</div>
|
||||||
<p className="commentBody">{body}</p>
|
<p className="commentBody">{body}</p>
|
||||||
<div className="commentFooter">
|
<div className="commentFooter">
|
||||||
<a className="commentReplyButton" onClick={handleToggleCommentReply}>
|
<a className="commentReplyButton commentLink" onClick={handleToggleCommentReply}>
|
||||||
{ replyForm.isOpen ? 'Cancel' : 'Reply' }
|
{ replyForm.isOpen ? 'Cancel' : 'Reply' }
|
||||||
</a>
|
</a>
|
||||||
{
|
{
|
||||||
isPowerUser ?
|
isPowerUser ?
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
<Separator />
|
||||||
|
<a
|
||||||
|
onClick={() => handleToggleIsCommentUpdate(id, isPostUpdate)}
|
||||||
|
className="commentLink"
|
||||||
|
>
|
||||||
|
{ isPostUpdate ? 'No post update' : 'Post update' }
|
||||||
|
</a>
|
||||||
<Separator />
|
<Separator />
|
||||||
<a href={`/admin/comments/${id}`} data-turbolinks="false">Edit</a>
|
<a href={`/admin/comments/${id}`} data-turbolinks="false">Edit</a>
|
||||||
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ interface Props {
|
|||||||
|
|
||||||
toggleCommentReply(commentId: number): void;
|
toggleCommentReply(commentId: number): void;
|
||||||
setCommentReplyBody(commentId: number, body: string): void;
|
setCommentReplyBody(commentId: number, body: string): void;
|
||||||
|
handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void;
|
||||||
handleSubmitComment(body: string, parentId: number): void;
|
handleSubmitComment(body: string, parentId: number): void;
|
||||||
|
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
@@ -28,6 +29,7 @@ const CommentList = ({
|
|||||||
|
|
||||||
toggleCommentReply,
|
toggleCommentReply,
|
||||||
setCommentReplyBody,
|
setCommentReplyBody,
|
||||||
|
handleToggleIsCommentUpdate,
|
||||||
handleSubmitComment,
|
handleSubmitComment,
|
||||||
|
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
@@ -47,6 +49,7 @@ const CommentList = ({
|
|||||||
setCommentReplyBody(comment.id, (e.target as HTMLTextAreaElement).value)
|
setCommentReplyBody(comment.id, (e.target as HTMLTextAreaElement).value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
handleToggleIsCommentUpdate={handleToggleIsCommentUpdate}
|
||||||
handleSubmitComment={handleSubmitComment}
|
handleSubmitComment={handleSubmitComment}
|
||||||
{...comment}
|
{...comment}
|
||||||
|
|
||||||
@@ -63,6 +66,7 @@ const CommentList = ({
|
|||||||
|
|
||||||
toggleCommentReply={toggleCommentReply}
|
toggleCommentReply={toggleCommentReply}
|
||||||
setCommentReplyBody={setCommentReplyBody}
|
setCommentReplyBody={setCommentReplyBody}
|
||||||
|
handleToggleIsCommentUpdate={handleToggleIsCommentUpdate}
|
||||||
handleSubmitComment={handleSubmitComment}
|
handleSubmitComment={handleSubmitComment}
|
||||||
|
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ interface Props {
|
|||||||
requestComments(postId: number, page?: number): void;
|
requestComments(postId: number, page?: number): void;
|
||||||
toggleCommentReply(commentId: number): void;
|
toggleCommentReply(commentId: number): void;
|
||||||
setCommentReplyBody(commentId: number, body: string): void;
|
setCommentReplyBody(commentId: number, body: string): void;
|
||||||
|
toggleCommentIsPostUpdate(
|
||||||
|
postId: number,
|
||||||
|
commentId: number,
|
||||||
|
currentIsPostUpdate: boolean,
|
||||||
|
authenticityToken: string,
|
||||||
|
): void;
|
||||||
submitComment(
|
submitComment(
|
||||||
postId: number,
|
postId: number,
|
||||||
body: string,
|
body: string,
|
||||||
@@ -36,6 +42,15 @@ class CommentsP extends React.Component<Props> {
|
|||||||
this.props.requestComments(this.props.postId);
|
this.props.requestComments(this.props.postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleToggleIsCommentUpdate = (commentId: number, currentIsPostUpdate: boolean) => {
|
||||||
|
this.props.toggleCommentIsPostUpdate(
|
||||||
|
this.props.postId,
|
||||||
|
commentId,
|
||||||
|
currentIsPostUpdate,
|
||||||
|
this.props.authenticityToken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_handleSubmitComment = (body: string, parentId: number) => {
|
_handleSubmitComment = (body: string, parentId: number) => {
|
||||||
this.props.submitComment(
|
this.props.submitComment(
|
||||||
this.props.postId,
|
this.props.postId,
|
||||||
@@ -92,6 +107,7 @@ class CommentsP extends React.Component<Props> {
|
|||||||
replyForms={replyForms}
|
replyForms={replyForms}
|
||||||
toggleCommentReply={toggleCommentReply}
|
toggleCommentReply={toggleCommentReply}
|
||||||
setCommentReplyBody={setCommentReplyBody}
|
setCommentReplyBody={setCommentReplyBody}
|
||||||
|
handleToggleIsCommentUpdate={this._handleToggleIsCommentUpdate}
|
||||||
handleSubmitComment={this._handleSubmitComment}
|
handleSubmitComment={this._handleSubmitComment}
|
||||||
parentId={null}
|
parentId={null}
|
||||||
level={1}
|
level={1}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
toggleCommentReply,
|
toggleCommentReply,
|
||||||
setCommentReplyBody,
|
setCommentReplyBody,
|
||||||
} from '../actions/handleCommentReplies';
|
} from '../actions/handleCommentReplies';
|
||||||
|
import { toggleCommentIsUpdate } from '../actions/updateComment';
|
||||||
import { submitComment } from '../actions/submitComment';
|
import { submitComment } from '../actions/submitComment';
|
||||||
|
|
||||||
import { State } from '../reducers/rootReducer';
|
import { State } from '../reducers/rootReducer';
|
||||||
@@ -31,6 +32,15 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setCommentReplyBody(commentId, body));
|
dispatch(setCommentReplyBody(commentId, body));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleCommentIsPostUpdate(
|
||||||
|
postId: number,
|
||||||
|
commentId: number,
|
||||||
|
currentIsPostUpdate: boolean,
|
||||||
|
authenticityToken: string,
|
||||||
|
) {
|
||||||
|
dispatch(toggleCommentIsUpdate(postId, commentId, currentIsPostUpdate, authenticityToken));
|
||||||
|
},
|
||||||
|
|
||||||
submitComment(
|
submitComment(
|
||||||
postId: number,
|
postId: number,
|
||||||
body: string,
|
body: string,
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ import {
|
|||||||
COMMENT_SUBMIT_FAILURE,
|
COMMENT_SUBMIT_FAILURE,
|
||||||
} from '../actions/submitComment';
|
} from '../actions/submitComment';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ToggleIsUpdateSuccessAction,
|
||||||
|
TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
||||||
|
} from '../actions/updateComment';
|
||||||
|
|
||||||
import commentReducer from './commentReducer';
|
import commentReducer from './commentReducer';
|
||||||
import replyFormsReducer from './replyFormsReducer';
|
import replyFormsReducer from './replyFormsReducer';
|
||||||
|
|
||||||
@@ -47,7 +52,8 @@ const commentsReducer = (
|
|||||||
action:
|
action:
|
||||||
CommentsRequestActionTypes |
|
CommentsRequestActionTypes |
|
||||||
HandleCommentRepliesType |
|
HandleCommentRepliesType |
|
||||||
CommentSubmitActionTypes
|
CommentSubmitActionTypes |
|
||||||
|
ToggleIsUpdateSuccessAction
|
||||||
): CommentsState => {
|
): CommentsState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case COMMENTS_REQUEST_START:
|
case COMMENTS_REQUEST_START:
|
||||||
@@ -95,6 +101,18 @@ const commentsReducer = (
|
|||||||
replyForms: replyFormsReducer(state.replyForms, action),
|
replyForms: replyFormsReducer(state.replyForms, action),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case TOGGLE_COMMENT_IS_UPDATE_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
items:
|
||||||
|
state.items.map(comment => {
|
||||||
|
if (comment.id === action.commentId) {
|
||||||
|
comment.isPostUpdate = !comment.isPostUpdate;
|
||||||
|
return comment;
|
||||||
|
} else return comment;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ import {
|
|||||||
COMMENT_SUBMIT_FAILURE,
|
COMMENT_SUBMIT_FAILURE,
|
||||||
} from '../actions/submitComment';
|
} from '../actions/submitComment';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ToggleIsUpdateSuccessAction,
|
||||||
|
TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
||||||
|
} from '../actions/updateComment';
|
||||||
|
|
||||||
import postReducer from './postReducer';
|
import postReducer from './postReducer';
|
||||||
import likesReducer from './likesReducer';
|
import likesReducer from './likesReducer';
|
||||||
import commentsReducer from './commentsReducer';
|
import commentsReducer from './commentsReducer';
|
||||||
@@ -82,7 +87,8 @@ const currentPostReducer = (
|
|||||||
LikeActionTypes |
|
LikeActionTypes |
|
||||||
CommentsRequestActionTypes |
|
CommentsRequestActionTypes |
|
||||||
HandleCommentRepliesType |
|
HandleCommentRepliesType |
|
||||||
CommentSubmitActionTypes
|
CommentSubmitActionTypes |
|
||||||
|
ToggleIsUpdateSuccessAction
|
||||||
): CurrentPostState => {
|
): CurrentPostState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case POST_REQUEST_START:
|
case POST_REQUEST_START:
|
||||||
@@ -130,6 +136,7 @@ const currentPostReducer = (
|
|||||||
case COMMENT_SUBMIT_START:
|
case COMMENT_SUBMIT_START:
|
||||||
case COMMENT_SUBMIT_SUCCESS:
|
case COMMENT_SUBMIT_SUCCESS:
|
||||||
case COMMENT_SUBMIT_FAILURE:
|
case COMMENT_SUBMIT_FAILURE:
|
||||||
|
case TOGGLE_COMMENT_IS_UPDATE_SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
comments: commentsReducer(state.comments, action),
|
comments: commentsReducer(state.comments, action),
|
||||||
|
|||||||
@@ -58,6 +58,13 @@
|
|||||||
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.postUpdateBadge {
|
||||||
|
@extend
|
||||||
|
.badge,
|
||||||
|
.badgeLight,
|
||||||
|
.ml-2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.commentBody {
|
.commentBody {
|
||||||
@@ -67,7 +74,7 @@
|
|||||||
.commentFooter {
|
.commentFooter {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
.commentReplyButton {
|
.commentLink {
|
||||||
color: $astuto-black;
|
color: $astuto-black;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ en:
|
|||||||
create: 'Like create error: %{message}'
|
create: 'Like create error: %{message}'
|
||||||
comment:
|
comment:
|
||||||
create: 'Comment create error: %{message}'
|
create: 'Comment create error: %{message}'
|
||||||
|
update: 'Comment update error: %{message}'
|
||||||
@@ -17,7 +17,7 @@ Rails.application.routes.draw do
|
|||||||
resources :posts, only: [:index, :create, :show, :update] do
|
resources :posts, only: [:index, :create, :show, :update] do
|
||||||
resource :likes, only: [:create, :destroy]
|
resource :likes, only: [:create, :destroy]
|
||||||
resources :likes, only: [:index]
|
resources :likes, only: [:index]
|
||||||
resources :comments, only: [:index, :create]
|
resources :comments, only: [:index, :create, :update]
|
||||||
end
|
end
|
||||||
resources :boards, only: [:show]
|
resources :boards, only: [:show]
|
||||||
resources :post_statuses, only: [:index]
|
resources :post_statuses, only: [:index]
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ RSpec.describe 'comments routing', :aggregate_failures, type: :routing do
|
|||||||
expect(post: '/posts/1/comments').to route_to(
|
expect(post: '/posts/1/comments').to route_to(
|
||||||
controller: 'comments', action: 'create', post_id: "1"
|
controller: 'comments', action: 'create', post_id: "1"
|
||||||
)
|
)
|
||||||
|
expect(patch: '/posts/1/comments/1').to route_to(
|
||||||
|
controller: 'comments', action: 'update', post_id: "1", id: "1"
|
||||||
|
)
|
||||||
|
|
||||||
expect(get: '/posts/1/comments/1').not_to be_routable
|
expect(get: '/posts/1/comments/1').not_to be_routable
|
||||||
expect(get: '/posts/1/comments/new').not_to be_routable
|
expect(get: '/posts/1/comments/new').not_to be_routable
|
||||||
expect(get: '/posts/1/comments/1/edit').not_to be_routable
|
expect(get: '/posts/1/comments/1/edit').not_to be_routable
|
||||||
expect(patch: '/posts/1/comments/1').not_to be_routable
|
|
||||||
expect(delete: '/posts/1/comments/1').not_to be_routable
|
expect(delete: '/posts/1/comments/1').not_to be_routable
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
Reference in New Issue
Block a user