diff --git a/backend/app/rest/httperrors.go b/backend/app/rest/httperrors.go index f682b3ec..60d4bcb8 100644 --- a/backend/app/rest/httperrors.go +++ b/backend/app/rest/httperrors.go @@ -25,7 +25,7 @@ const ( ErrReadOnly = 8 // write failed on read only ErrCommentRejected = 9 // general error on rejected comment change ErrCommentEditExpired = 10 // too late for edit - ErrCommentEditChanged = 11 // parent commend changed + ErrCommentEditChanged = 11 // parent comment cannot be changed ErrVoteRejected = 12 // general error on vote rejected ErrVoteSelf = 13 // vote for own comment ErrVoteDbl = 14 // already voted for the comment diff --git a/web/app/components/comment/__vote/comment__vote.scss b/web/app/components/comment/__vote/comment__vote.scss index 12084446..813609c6 100644 --- a/web/app/components/comment/__vote/comment__vote.scss +++ b/web/app/components/comment/__vote/comment__vote.scss @@ -9,3 +9,11 @@ background-size: contain; cursor: pointer; } + +.voting__error { + color: #9a0000; + text-align: right; + font-size: 14px; + line-height: 1; + margin-top: -10px; +} diff --git a/web/app/components/comment/comment.jsx b/web/app/components/comment/comment.jsx index 5d30816a..d7b47b12 100644 --- a/web/app/components/comment/comment.jsx +++ b/web/app/components/comment/comment.jsx @@ -8,6 +8,7 @@ import { url } from 'common/settings'; import store from 'common/store'; import copy from 'common/copy'; import debounce from 'utils/debounce'; +import { extractErrorMessageFromResponse } from 'utils/errorUtils'; import Input from 'components/input'; @@ -23,6 +24,7 @@ export default class Comment extends Component { isEditing: false, isUserVerified: false, editTimeLeft: null, + voteErrorMessage: null, }; this.votingPromise = Promise.resolve(); @@ -49,6 +51,8 @@ export default class Comment extends Component { this.isGuest = this.isGuest.bind(this); this.getUpvoteDisabledReason = this.getUpvoteDisabledReason.bind(this); this.getDownvoteDisabledReason = this.getDownvoteDisabledReason.bind(this); + this.handleVoteError = this.handleVoteError.bind(this); + this.sendVotingRequest = this.sendVotingRequest.bind(this); } componentWillReceiveProps(nextProps) { @@ -264,6 +268,23 @@ export default class Comment extends Component { } } + handleVoteError(e, originalVotingState) { + this.setState({ + ...originalVotingState, + voteErrorMessage: extractErrorMessageFromResponse(e.response), + }); + } + + sendVotingRequest(id, votingValue, originalVotingState) { + this.votingPromise = this.votingPromise + .then(() => { + return api.putCommentVote({ id, url, value: votingValue }).then(() => { + api.getComment({ id }).then(comment => store.replaceComment(comment)); + }); + }) + .catch(e => this.handleVoteError(e, originalVotingState)); + } + increaseScore() { const { score, scoreIncreased, scoreDecreased } = this.state; const { id } = this.props.data; @@ -274,13 +295,10 @@ export default class Comment extends Component { scoreIncreased: !scoreDecreased, scoreDecreased: false, score: score + 1, + voteErrorMessage: null, }); - this.votingPromise = this.votingPromise.then(() => { - return api.putCommentVote({ id, url, value: 1 }).then(() => { - api.getComment({ id }).then(comment => store.replaceComment(comment)); - }); - }); + this.sendVotingRequest(id, 1, { score, scoreIncreased, scoreDecreased }); } decreaseScore() { @@ -293,13 +311,10 @@ export default class Comment extends Component { scoreDecreased: !scoreIncreased, scoreIncreased: false, score: score - 1, + voteErrorMessage: null, }); - this.votingPromise = this.votingPromise.then(() => { - return api.putCommentVote({ id, url, value: -1 }).then(() => { - api.getComment({ id }).then(comment => store.replaceComment(comment)); - }); - }); + this.sendVotingRequest(id, -1, { score, scoreIncreased, scoreDecreased }); } onReply(...rest) { @@ -412,6 +427,7 @@ export default class Comment extends Component { isEditing, isUserVerified, editTimeLeft, + voteErrorMessage, } ) { const { data, mods = {}, isCommentsDisabled } = props; @@ -607,6 +623,12 @@ export default class Comment extends Component { + {!!voteErrorMessage && ( +
+ Voting error: {voteErrorMessage} +
+ )} +
(this.textNode = r)} diff --git a/web/app/components/input/input.jsx b/web/app/components/input/input.jsx index 56f3d817..e3b37234 100644 --- a/web/app/components/input/input.jsx +++ b/web/app/components/input/input.jsx @@ -6,6 +6,7 @@ import { siteId, url, pageTitle } from 'common/settings'; import api from 'common/api'; import store from 'common/store'; +import { extractErrorMessageFromResponse } from 'utils/errorUtils'; import TextareaAutosize from 'components/input/textarea-autosize'; const RSS_THREAD_URL = `${BASE_URL}${API_BASE}/rss/post?site=${siteId}&url=${url}`; @@ -92,16 +93,8 @@ export default class Input extends Component { this.setState({ preview: null, text: '' }); }) .catch(e => { - if ( - e.response && - e.response.data && - typeof e.response.data.error === 'string' && - e.response.data.error.indexOf("parent comment with reply can't be edited") === 0 - ) { - this.setState({ isErrorShown: true, errorMessage: 'Comment has reply, editing is not possible' }); - return; - } - this.setState({ isErrorShown: true, errorMessage: null }); + const errorMessage = extractErrorMessageFromResponse(e.response); + this.setState({ isErrorShown: true, errorMessage }); }) .finally(() => this.setState({ isDisabled: false })); } diff --git a/web/app/utils/errorUtils.js b/web/app/utils/errorUtils.js new file mode 100644 index 00000000..08f8c0ba --- /dev/null +++ b/web/app/utils/errorUtils.js @@ -0,0 +1,44 @@ +const errorMessageForCodes = new Map([ + [0, 'Something went wrong. Please try again a bit later.'], + [1, 'Comment cannot be found. Please refresh the page and try again.'], + [2, 'Failed to unmarshal incoming request.'], + [3, "You don't have permission for this operaton."], + [4, 'Invalid comment data.'], + [5, 'Comment cannot be found. Please refresh the page and try again.'], + [6, 'Site cannot be found. Please refresh the page and try again.'], + [7, 'User has been blocked.'], + [8, 'This post is read only.'], + [9, 'Comment changing failed. Please try again a bit later.'], + [10, 'It is too late to edit the comment.'], + [11, 'Comment already has reply, editing is not possible.'], + [12, 'Cannot save voting result. Please try again a bit later.'], + [13, 'You cannot vote for your own comment.'], + [14, 'You have already voted for the comment.'], + [15, 'Too many votes for the comment.'], + [16, 'Min score reached for the comment.'], + [17, 'Action rejected. Please try again a bit later.'], + [18, 'Requested file cannot be found.'], +]); + +export function extractErrorMessageFromResponse(response) { + const defatulErrorMessage = 'Something went wrong. Please try again a bit later.'; + if (!(response && response.data)) { + return defatulErrorMessage; + } + + const responseData = response.data; + + if (typeof responseData.code === 'number' && errorMessageForCodes.has(responseData.code)) { + return errorMessageForCodes.get(responseData.code); + } + + if (typeof responseData.details === 'string') { + return responseData.details; + } + + if (typeof responseData === 'string') { + return responseData; + } + + return defatulErrorMessage; +}