diff --git a/web/app/common/api.js b/web/app/common/api.js index 2f648d37..96ea41ef 100644 --- a/web/app/common/api.js +++ b/web/app/common/api.js @@ -100,9 +100,9 @@ export const removeComment = ({ id }) => withCredentials: true, }); -export const blockUser = ({ id }) => +export const blockUser = ({ id, ttl }) => fetcher.put({ - url: `/admin/user/${id}?block=1`, + url: ttl === 'permanently' ? `/admin/user/${id}?block=1` : `/admin/user/${id}?block=1&ttl=${ttl}`, withCredentials: true, }); diff --git a/web/app/common/constants.js b/web/app/common/constants.js index 8a0e38ca..c6fba2ad 100644 --- a/web/app/common/constants.js +++ b/web/app/common/constants.js @@ -18,6 +18,25 @@ const PROVIDER_NAMES = { const LS_COLLAPSE_KEY = '__remarkCollapsed'; const LS_SORT_KEY = '__remarkSort'; +const BLOCKING_DURATIONS = [ + { + label: 'Permanently', + value: 'permanently', + }, + { + label: 'For a month', + value: `${30 * 60 * 24}m`, + }, + { + label: 'For a week', + value: `${7 * 60 * 24}m`, + }, + { + label: 'For a day', + value: `${60 * 24}m`, + }, +]; + module.exports = { BASE_URL, API_BASE, @@ -32,4 +51,5 @@ module.exports = { DEFAULT_SORT, LS_COLLAPSE_KEY, LS_SORT_KEY, + BLOCKING_DURATIONS, }; diff --git a/web/app/components/blocked-users/__user-block-ttl/blocked-users__user-block-ttl.scss b/web/app/components/blocked-users/__user-block-ttl/blocked-users__user-block-ttl.scss new file mode 100644 index 00000000..1ec52c28 --- /dev/null +++ b/web/app/components/blocked-users/__user-block-ttl/blocked-users__user-block-ttl.scss @@ -0,0 +1,3 @@ +.blocked-users__user-id { + color: #888; +} diff --git a/web/app/components/blocked-users/blocked-users.jsx b/web/app/components/blocked-users/blocked-users.jsx index c6752411..77d1e932 100644 --- a/web/app/components/blocked-users/blocked-users.jsx +++ b/web/app/components/blocked-users/blocked-users.jsx @@ -51,6 +51,7 @@ export default class BlockedUsers extends Component {
  • {user.name}{' '} ({user.id}) + until {formatTime(new Date(user.time))} {isUserUnblocked && ( this.block(user))} className="blocked-users__action"> block @@ -70,3 +71,14 @@ export default class BlockedUsers extends Component { ); } } + +function formatTime(time) { + // 'ru-RU' adds a dot as a separator + const date = time.toLocaleDateString(['ru-RU'], { day: '2-digit', month: '2-digit', year: '2-digit' }); + + // do it manually because Intl API doesn't add leading zeros to hours; idk why + const hours = `0${time.getHours()}`.slice(-2); + const mins = `0${time.getMinutes()}`.slice(-2); + + return `${date} at ${hours}:${mins}`; +} diff --git a/web/app/components/blocked-users/index.js b/web/app/components/blocked-users/index.js index 73878021..454cfc95 100644 --- a/web/app/components/blocked-users/index.js +++ b/web/app/components/blocked-users/index.js @@ -6,6 +6,7 @@ require('./__action/blocked-users__action.scss'); require('./__list/blocked-users__list.scss'); require('./__list-item/blocked-users__list-item.scss'); +require('./__user-block-ttl/blocked-users__user-block-ttl.scss'); require('./__list-item/_view/_invisible/blocked-users__list-item_view_invisible.scss'); require('./__user-id/blocked-users__user-id.scss'); diff --git a/web/app/components/comment/__control/_select-label/comment__control_select-label.scss b/web/app/components/comment/__control/_select-label/comment__control_select-label.scss new file mode 100644 index 00000000..8248322d --- /dev/null +++ b/web/app/components/comment/__control/_select-label/comment__control_select-label.scss @@ -0,0 +1,10 @@ +.comment__control_select { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + opacity: 0; + width: 100%; + cursor: pointer; +} diff --git a/web/app/components/comment/__control/_select/comment__control_select.scss b/web/app/components/comment/__control/_select/comment__control_select.scss new file mode 100644 index 00000000..211c70ff --- /dev/null +++ b/web/app/components/comment/__control/_select/comment__control_select.scss @@ -0,0 +1,9 @@ +.comment__control_select-label { + position: relative; + white-space: nowrap; + font-weight: 700; + &::after { + content: '▾'; + margin-left: 2px; + } +} diff --git a/web/app/components/comment/comment.jsx b/web/app/components/comment/comment.jsx index 07efd2ee..72d7b4da 100644 --- a/web/app/components/comment/comment.jsx +++ b/web/app/components/comment/comment.jsx @@ -3,7 +3,7 @@ import { h, Component } from 'preact'; import api from 'common/api'; import { getHandleClickProps } from 'common/accessibility'; -import { API_BASE, BASE_URL, COMMENT_NODE_CLASSNAME_PREFIX } from 'common/constants'; +import { API_BASE, BASE_URL, COMMENT_NODE_CLASSNAME_PREFIX, BLOCKING_DURATIONS } from 'common/constants'; import { url } from 'common/settings'; import store from 'common/store'; @@ -37,6 +37,8 @@ export default class Comment extends Component { this.onEdit = this.onEdit.bind(this); this.onReply = this.onReply.bind(this); this.onDeleteClick = this.onDeleteClick.bind(this); + this.onBlockUserClick = this.onBlockUserClick.bind(this); + this.onUnblockUserClick = this.onUnblockUserClick.bind(this); } componentWillReceiveProps(nextProps) { @@ -164,17 +166,41 @@ export default class Comment extends Component { } } - toggleBlock(isBlocked) { + onBlockUserClick(e) { const { id, user: { id: userId }, } = this.props.data; - const promptMessage = `Do you want to ${isBlocked ? 'unblock' : 'block'} this user?`; + + const ttl = e.target.value; + const duration = BLOCKING_DURATIONS.find(el => el.value === ttl).label; + const promptMessage = + ttl === 'permanently' + ? 'Do you want to permanently block this user?' + : `Do you want to block this user (${duration.toLowerCase()})?`; + if (confirm(promptMessage)) { + this.setState({ userBlocked: true }); + + api + .blockUser({ id: userId, ttl }) + .then(api.getComment({ id })) + .then(comment => store.replaceComment(comment)); + } + } + + onUnblockUserClick() { + const { + id, + user: { id: userId }, + } = this.props.data; + + const promptMessage = `Do you want to unblock this user?`; if (confirm(promptMessage)) { - this.setState({ userBlocked: !isBlocked }); + this.setState({ userBlocked: false }); - (isBlocked ? api.unblockUser : api.blockUser)({ id: userId }) + api + .unblockUser({ id: userId }) .then(api.getComment({ id })) .then(comment => store.replaceComment(comment)); } @@ -501,9 +527,26 @@ export default class Comment extends Component { this.togglePin(pinned))} className="comment__control"> {pinned ? 'Unpin' : 'Pin'} - this.toggleBlock(userBlocked))} className="comment__control"> - {userBlocked ? 'Unblock' : 'Block'} - + + {userBlocked && ( + this.onUnblockUserClick())} className="comment__control"> + Unblock + + )} + + {!userBlocked && ( + + Block + {/* eslint-disable jsx-a11y/no-onchange */} + + + )} {!deleted && ( diff --git a/web/app/components/comment/index.js b/web/app/components/comment/index.js index 33518776..dcb2e024 100644 --- a/web/app/components/comment/index.js +++ b/web/app/components/comment/index.js @@ -13,6 +13,8 @@ require('./__avatar/_default/comment__avatar_default.scss'); require('./__body/comment__body.scss'); require('./__control/comment__control.scss'); +require('./__control/_select/comment__control_select.scss'); +require('./__control/_select-label/comment__control_select-label.scss'); require('./__controls/comment__controls.scss'); require('./__info/comment__info.scss'); require('./__input/comment__input.scss');