From a9a43de6d1502a6cbb388a5dbcd0e8532c236e64 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 23 Feb 2022 20:03:46 +0100 Subject: [PATCH] Change report modal to include category selection in web UI (#17565) * Change report modal to include category selection in web UI * Various fixes and improvements - Change thank you text to be different based on category - Change starting headline to be different for account and status reports - Change toggle components to have a checkmark when checked - Fix report dialog being cut off on small screens - Fix thank you screen offering mute or block if already muted or blocked - Refactor toggle components in report dialog into one component * Change wording on final screen * Change checkboxes to be square when multiple options are possible --- app/javascript/mastodon/actions/reports.js | 103 ++----- app/javascript/mastodon/actions/rules.js | 27 ++ app/javascript/mastodon/components/check.js | 9 + .../mastodon/features/report/category.js | 93 +++++++ .../mastodon/features/report/comment.js | 83 ++++++ .../features/report/components/option.js | 60 ++++ .../report/components/status_check_box.js | 61 +++- .../containers/status_check_box_container.js | 22 +- .../mastodon/features/report/rules.js | 64 +++++ .../mastodon/features/report/statuses.js | 58 ++++ .../mastodon/features/report/thanks.js | 84 ++++++ .../features/ui/components/report_modal.js | 241 ++++++++++------ app/javascript/mastodon/reducers/index.js | 6 +- app/javascript/mastodon/reducers/rules.js | 13 + .../styles/mastodon/components.scss | 262 +++++++++++++++--- 15 files changed, 954 insertions(+), 232 deletions(-) create mode 100644 app/javascript/mastodon/actions/rules.js create mode 100644 app/javascript/mastodon/components/check.js create mode 100644 app/javascript/mastodon/features/report/category.js create mode 100644 app/javascript/mastodon/features/report/comment.js create mode 100644 app/javascript/mastodon/features/report/components/option.js create mode 100644 app/javascript/mastodon/features/report/rules.js create mode 100644 app/javascript/mastodon/features/report/statuses.js create mode 100644 app/javascript/mastodon/features/report/thanks.js create mode 100644 app/javascript/mastodon/reducers/rules.js diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js index afa0c341218..7a7bed04efb 100644 --- a/app/javascript/mastodon/actions/reports.js +++ b/app/javascript/mastodon/actions/reports.js @@ -1,89 +1,38 @@ import api from '../api'; -import { openModal, closeModal } from './modal'; - -export const REPORT_INIT = 'REPORT_INIT'; -export const REPORT_CANCEL = 'REPORT_CANCEL'; +import { openModal } from './modal'; export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST'; export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS'; export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; -export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; -export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; -export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE'; +export const initReport = (account, status) => dispatch => + dispatch(openModal('REPORT', { + accountId: account.get('id'), + statusId: status.get('id'), + })); -export function initReport(account, status) { - return dispatch => { - dispatch({ - type: REPORT_INIT, - account, - status, - }); +export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => { + dispatch(submitReportRequest()); - dispatch(openModal('REPORT')); - }; + api(getState).post('/api/v1/reports', params).then(response => { + dispatch(submitReportSuccess(response.data)); + if (onSuccess) onSuccess(); + }).catch(error => { + dispatch(submitReportFail(error)); + if (onFail) onFail(); + }); }; -export function cancelReport() { - return { - type: REPORT_CANCEL, - }; -}; +export const submitReportRequest = () => ({ + type: REPORT_SUBMIT_REQUEST, +}); -export function toggleStatusReport(statusId, checked) { - return { - type: REPORT_STATUS_TOGGLE, - statusId, - checked, - }; -}; +export const submitReportSuccess = report => ({ + type: REPORT_SUBMIT_SUCCESS, + report, +}); -export function submitReport() { - return (dispatch, getState) => { - dispatch(submitReportRequest()); - - api(getState).post('/api/v1/reports', { - account_id: getState().getIn(['reports', 'new', 'account_id']), - status_ids: getState().getIn(['reports', 'new', 'status_ids']), - comment: getState().getIn(['reports', 'new', 'comment']), - forward: getState().getIn(['reports', 'new', 'forward']), - }).then(response => { - dispatch(closeModal()); - dispatch(submitReportSuccess(response.data)); - }).catch(error => dispatch(submitReportFail(error))); - }; -}; - -export function submitReportRequest() { - return { - type: REPORT_SUBMIT_REQUEST, - }; -}; - -export function submitReportSuccess(report) { - return { - type: REPORT_SUBMIT_SUCCESS, - report, - }; -}; - -export function submitReportFail(error) { - return { - type: REPORT_SUBMIT_FAIL, - error, - }; -}; - -export function changeReportComment(comment) { - return { - type: REPORT_COMMENT_CHANGE, - comment, - }; -}; - -export function changeReportForward(forward) { - return { - type: REPORT_FORWARD_CHANGE, - forward, - }; -}; +export const submitReportFail = error => ({ + type: REPORT_SUBMIT_FAIL, + error, +}); diff --git a/app/javascript/mastodon/actions/rules.js b/app/javascript/mastodon/actions/rules.js new file mode 100644 index 00000000000..34e60a121e0 --- /dev/null +++ b/app/javascript/mastodon/actions/rules.js @@ -0,0 +1,27 @@ +import api from '../api'; + +export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST'; +export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS'; +export const RULES_FETCH_FAIL = 'RULES_FETCH_FAIL'; + +export const fetchRules = () => (dispatch, getState) => { + dispatch(fetchRulesRequest()); + + api(getState) + .get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules))) + .catch(err => dispatch(fetchRulesFail(err))); +}; + +const fetchRulesRequest = () => ({ + type: RULES_FETCH_REQUEST, +}); + +const fetchRulesSuccess = rules => ({ + type: RULES_FETCH_SUCCESS, + rules, +}); + +const fetchRulesFail = error => ({ + type: RULES_FETCH_FAIL, + error, +}); diff --git a/app/javascript/mastodon/components/check.js b/app/javascript/mastodon/components/check.js new file mode 100644 index 00000000000..ee2ef1595a2 --- /dev/null +++ b/app/javascript/mastodon/components/check.js @@ -0,0 +1,9 @@ +import React from 'react'; + +const Check = () => ( + + + +); + +export default Check; diff --git a/app/javascript/mastodon/features/report/category.js b/app/javascript/mastodon/features/report/category.js new file mode 100644 index 00000000000..122b51c7cdd --- /dev/null +++ b/app/javascript/mastodon/features/report/category.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import Button from 'mastodon/components/button'; +import Option from './components/option'; + +const messages = defineMessages({ + dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' }, + dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' }, + spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' }, + spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' }, + violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' }, + violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' }, + other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' }, + other_description: { id: 'report.reasons.other_description', defaultMessage: 'The issue does not fit into other categories' }, + status: { id: 'report.category.title_status', defaultMessage: 'post' }, + account: { id: 'report.category.title_account', defaultMessage: 'profile' }, +}); + +export default @injectIntl +class Category extends React.PureComponent { + + static propTypes = { + onNextStep: PropTypes.func.isRequired, + category: PropTypes.string, + onChangeCategory: PropTypes.func.isRequired, + startedFrom: PropTypes.oneOf(['status', 'account']), + intl: PropTypes.object.isRequired, + }; + + handleNextClick = () => { + const { onNextStep, category } = this.props; + + switch(category) { + case 'dislike': + onNextStep('thanks'); + break; + case 'violation': + onNextStep('rules'); + break; + default: + onNextStep('statuses'); + break; + } + }; + + handleCategoryToggle = (value, checked) => { + const { onChangeCategory } = this.props; + + if (checked) { + onChangeCategory(value); + } + }; + + render () { + const { category, startedFrom, intl } = this.props; + + const options = [ + 'dislike', + 'spam', + 'violation', + 'other', + ]; + + return ( + +

+

+ +
+ {options.map(item => ( +
+ +
+ +
+ +
+ + ); + } + +} diff --git a/app/javascript/mastodon/features/report/comment.js b/app/javascript/mastodon/features/report/comment.js new file mode 100644 index 00000000000..8d1a4e21fe0 --- /dev/null +++ b/app/javascript/mastodon/features/report/comment.js @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; +import Button from 'mastodon/components/button'; +import Toggle from 'react-toggle'; + +const messages = defineMessages({ + placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' }, +}); + +export default @injectIntl +class Comment extends React.PureComponent { + + static propTypes = { + onSubmit: PropTypes.func.isRequired, + comment: PropTypes.string.isRequired, + onChangeComment: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + isSubmitting: PropTypes.bool, + forward: PropTypes.bool, + isRemote: PropTypes.bool, + domain: PropTypes.string, + onChangeForward: PropTypes.func.isRequired, + }; + + handleClick = () => { + const { onSubmit } = this.props; + onSubmit(); + }; + + handleChange = e => { + const { onChangeComment } = this.props; + onChangeComment(e.target.value); + }; + + handleKeyDown = e => { + if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { + this.handleClick(); + } + }; + + handleForwardChange = e => { + const { onChangeForward } = this.props; + onChangeForward(e.target.checked); + }; + + render () { + const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props; + + return ( + +

+ +