Refactor report modal

* move all the report modal presentation into the component
* add strings to general l10n file because we don’t handle l10n files in components
* Add current project id and username to the data before submitting
This commit is contained in:
chrisgarrity 2018-05-27 18:00:56 -04:00
parent 34042c9e8e
commit 08cf4f4f3d
3 changed files with 215 additions and 153 deletions

View file

@ -1,34 +1,186 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const React = require('react'); const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const Modal = require('../base/modal.jsx'); const Modal = require('../base/modal.jsx');
const log = require('../../../lib/log.js');
const Form = require('../../forms/form.jsx');
const Button = require('../../forms/button.jsx');
const Select = require('../../forms/select.jsx');
const Spinner = require('../../spinner/spinner.jsx');
const TextArea = require('../../forms/textarea.jsx');
require('../../forms/button.scss'); require('../../forms/button.scss');
require('./modal.scss'); require('./modal.scss');
const ReportModal = props => ( class ReportModal extends React.Component {
<Modal constructor (props) {
className="mod-report" super(props);
{...props} bindAll(this, [
> 'handleReasonSelect',
<div> 'handleSubmit'
<div className="report-modal-header"> ]);
<div className="report-content-label"> this.state = {
{props.contentLabel} prompt: props.intl.formatMessage({id: 'report.promptPlaceholder'}),
reason: '',
waiting: false
};
}
handleReasonSelect (name, value) {
const prompts = [
this.props.intl.formatMessage({id: 'report.promptCopy'}),
this.props.intl.formatMessage({id: 'report.promptUncredited'}),
this.props.intl.formatMessage({id: 'report.promptScary'}),
this.props.intl.formatMessage({id: 'report.promptLanguage'}),
this.props.intl.formatMessage({id: 'report.promptMusic'}),
this.props.intl.formatMessage({id: 'report.promptPersonal'}),
this.props.intl.formatMessage({id: 'report.promptGuidelines'}),
'not used',
this.props.intl.formatMessage({id: 'report.promptImage'})
];
this.setState({prompt: prompts[value], reason: value});
}
handleSubmit (formData) {
this.setState({waiting: true});
this.props.onReport(formData, err => {
if (err) log.error(err);
this.setState({
prompt: this.props.intl.formatMessage({id: 'report.promptPlaceholder'}),
reason: '',
waiting: false
});
});
}
render () {
const {
intl,
onReport, // eslint-disable-line no-unused-vars
type,
...modalProps
} = this.props;
const contentLabel = intl.formatMessage({id: `report.${type}`});
return (
<Modal
className="mod-report"
contentLabel={contentLabel}
{...modalProps}
>
<div>
<div className="report-modal-header">
<div className="report-content-label">
{contentLabel}
</div>
</div>
<div className="report-modal-content">
<FormattedMessage
id={`report.${type}Instructions`}
values={{
CommunityGuidelinesLink: (
<a href="/community_guidelines">
<FormattedMessage id="report.CommunityGuidelinesLinkText" />
</a>
)
}}
/>
<Form
className="report"
onSubmit={this.handleSubmit}
>
<Select
required
name="reason"
options={[
{
value: '',
label: this.props.intl.formatMessage({id: 'report.reasonPlaceHolder'})
},
{
value: '0',
label: this.props.intl.formatMessage({id: 'report.reasonCopy'})
},
{
value: '1',
label: this.props.intl.formatMessage({id: 'report.reasonUncredited'})
},
{
value: '2',
label: this.props.intl.formatMessage({id: 'report.reasonScary'})
},
{
value: '3',
label: this.props.intl.formatMessage({id: 'report.reasonLanguage'})
},
{
value: '4',
label: this.props.intl.formatMessage({id: 'report.reasonMusic'})
},
{
value: '8',
label: this.props.intl.formatMessage({id: 'report.reasonImage'})
},
{
value: '5',
label: this.props.intl.formatMessage({id: 'report.reasonPersonal'})
},
{
value: '6',
label: this.props.intl.formatMessage({id: 'report.reasonOther'})
}
]}
value={this.state.reason}
onChange={this.handleReasonSelect}
/>
<TextArea
required
className="report-text"
name="reportText"
placeholder={this.state.prompt}
validationErrors={{
maxLength: this.props.intl.formatMessage({id: 'report.tooLongError'}),
minLength: this.props.intl.formatMessage({id: 'report.tooShortError'})
}}
validations={{
// TODO find out max and min characters
maxLength: 500,
minLength: 30
}}
/>
{this.state.reportWaiting ? [
<Button
className="submit-button white"
disabled="disabled"
key="submitButton"
type="submit"
>
<Spinner />
</Button>
] : [
<Button
className="submit-button white"
key="submitButton"
type="submit"
>
<FormattedMessage id="report.send" />
</Button>
]}
</Form>
</div>
</div> </div>
</div>
<div className="report-modal-content"> </Modal>
{props.children} );
</div> }
</div> }
</Modal>
);
ReportModal.propTypes = { ReportModal.propTypes = {
children: PropTypes.node, intl: intlShape,
contentLabel: PropTypes.string, onReport: PropTypes.func,
onRequestClose: PropTypes.func onRequestClose: PropTypes.func,
type: PropTypes.string
}; };
module.exports = ReportModal; module.exports = injectIntl(ReportModal);

View file

@ -159,5 +159,29 @@
"registration.welcomeStepPrompt": "To get started, click on the button below.", "registration.welcomeStepPrompt": "To get started, click on the button below.",
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!", "registration.welcomeStepTitle": "Hurray! Welcome to Scratch!",
"thumbnail.by": "by" "thumbnail.by": "by",
"report.project": "Report Project",
"report.projectInstructions": "From the dropdown below, please select the reason why you feel this project is disrespectful or inappropriate or otherwise breaks the {CommunityGuidelinesLink}.",
"report.CommunityGuidelinesLinkText": "Scratch Community Guidelines",
"report.reasonPlaceHolder": "Select a reason",
"report.reasonCopy": "Exact Copy of Project",
"report.reasonUncredited": "Uses Image/Music Without Credit",
"report.reasonScary": "Too Violent or Scary",
"report.reasonLanguage": "Inappropriate Language",
"report.reasonMusic": "Inappropriate Music",
"report.reasonImage": "Inappropriate Images",
"report.reasonPersonal": "Sharing Personal Contact Information",
"report.reasonOther": "Other",
"report.promptPlaceholder": "Select a reason why above.",
"report.promptCopy": "Please provide a link to the original project",
"report.promptUncredited": "Please provide links to the uncredited content",
"report.promptScary": "Please say why the project is too violent or scary",
"report.promptLanguage": "Please say where the inappropriate language occurs in the project (For example: Notes & Credits, sprite name, project text, etc.)",
"report.promptMusic": "Please say the name of the audio file with the inappropriate music",
"report.promptPersonal": "Please say where the personal contact information is shared (For example: Notes & Credits, sprite name, project text, etc.)",
"report.promptGuidelines": "Please be specific about why this project does not follow our Community Guidelines",
"report.promptImage": "Please say the name of the sprite or the backdrop with the inappropriate image",
"report.tooLongError": "That's too long! Please find a way to shorten your text.",
"report.tooShortError": "That's too short. Please describe in detail what's inappropriate or disrespectful about the project.",
"report.send": "Send"
} }

View file

@ -14,11 +14,6 @@ const sessionActions = require('../../redux/session.js');
const decorateText = require('../../lib/decorate-text.jsx'); const decorateText = require('../../lib/decorate-text.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx'); const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Button = require('../../components/forms/button.jsx'); const Button = require('../../components/forms/button.jsx');
const Form = require('../../components/forms/form.jsx');
const Input = require('../../components/forms/input.jsx');
const Select = require('../../components/forms/select.jsx');
const Spinner = require('../../components/spinner/spinner.jsx');
const TextArea = require('../../components/forms/textarea.jsx');
const Avatar = require('../../components/avatar/avatar.jsx'); const Avatar = require('../../components/avatar/avatar.jsx');
const CappedNumber = require('../../components/cappednumber/cappednumber.jsx'); const CappedNumber = require('../../components/cappednumber/cappednumber.jsx');
const ShareBanner = require('../../components/share-banner/share-banner.jsx'); const ShareBanner = require('../../components/share-banner/share-banner.jsx');
@ -26,7 +21,6 @@ const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolum
const InplaceInput = require('../../components/forms/inplace-input.jsx'); const InplaceInput = require('../../components/forms/inplace-input.jsx');
const ReportModal = require('../../components/modal/report/modal.jsx'); const ReportModal = require('../../components/modal/report/modal.jsx');
require('./preview.scss'); require('./preview.scss');
class PreviewPresentation extends React.Component { class PreviewPresentation extends React.Component {
@ -35,13 +29,10 @@ class PreviewPresentation extends React.Component {
bindAll(this, [ bindAll(this, [
'handleReportClick', 'handleReportClick',
'handleReportClose', 'handleReportClose',
'handleReportReasonSelect',
'handleReportSubmit' 'handleReportSubmit'
]); ]);
this.state = { this.state = {
reportOpen: false, reportOpen: false
reportPrompt: 'Select a reason why above.',
reportReason: ''
}; };
} }
handleReportClick (e) { handleReportClick (e) {
@ -51,22 +42,16 @@ class PreviewPresentation extends React.Component {
handleReportClose () { handleReportClose () {
this.setState({reportOpen: false}); this.setState({reportOpen: false});
} }
handleReportReasonSelect (name, value) {
const prompts = [ handleReportSubmit (formData, callback) {
'Please provide a link to the original project', const data = {
'Please provide links to the uncredited content', ...formData,
'Please say why the project is too violent or scary', id: this.props.projectId,
'Please say where the inappropriate language occurs in the project (For example: Notes & Credits, sprite name, project text, etc.)', username: this.props.user.username
'Please say the name of the audio file with the inappropriate music', };
'Please say where the personal contact information is shared (For example: Notes & Credits, sprite name, project text, etc.)', console.log('submit report data', data);
'Please be specific about why this project does not follow our Community Guidelines', // TODO: pass error to modal via callback.
'not used', callback();
'Please say the name of the sprite or the backdrop with the inappropriate image'
];
this.setState({reportPrompt: prompts[value]});
}
handleReportSubmit (formData) {
console.log('submit report data', formData);
this.setState({reportOpen: false}); this.setState({reportOpen: false});
} }
render () { render () {
@ -99,7 +84,7 @@ class PreviewPresentation extends React.Component {
<ShareBanner> <ShareBanner>
<FlexRow className="preview-row"> <FlexRow className="preview-row">
<span className="share-text"> <span className="share-text">
This project is not shared so only you can see it. Click share to let everyone see it! This project is not shared so only you can see it. Click share to let everyone see it!
</span> </span>
<Button className="button share-button"> <Button className="button share-button">
Share Share
@ -130,8 +115,7 @@ class PreviewPresentation extends React.Component {
// }) // })
}} }}
validations={{ validations={{
// TODO: actual 100 maxLength: 100
maxLength: 40
}} }}
value={projectInfo.title} value={projectInfo.title}
/> : /> :
@ -337,114 +321,16 @@ class PreviewPresentation extends React.Component {
Report Report
</Button>, </Button>,
<ReportModal <ReportModal
contentLabel="Report Project"
isOpen={this.state.reportOpen} isOpen={this.state.reportOpen}
key="report-modal" key="report-modal"
type="project"
onReport={this.handleReportSubmit}
onRequestClose={this.handleReportClose} onRequestClose={this.handleReportClose}
onSubmit={this.handleReportSubmit} />
>
From the dropdown below, please select the reason why you feel this project is disrespectful or inappropriate or otherwise breaks the Scratch Community Guidelines.
<Form
className="report"
onSubmit={this.handleReportSubmit}
>
<Input
name="projectId"
type="hidden"
value={projectId}
/>
<Input
name="username"
type="hidden"
value={user.username}
/>
<Select
required
name="reason"
options={[
{
value: '',
label: 'Select a reason'
},
{
value: '0',
label: 'Exact Copy of Project'
},
{
value: '1',
label: 'Uses Image/Music Without Credit'
},
{
value: '2',
label: 'Too Violent or Scary'
},
{
value: '3',
label: 'Inappropriate Language'
},
{
value: '4',
label: 'Inappropriate Music'
},
{
value: '8',
label: 'Inappropriate Images'
},
{
value: '5',
label: 'Sharing Personal Contact Information'
},
{
value: '6',
label: 'Other'
}
]}
value={this.state.reportReason}
onChange={this.handleReportReasonSelect}
/>
<TextArea
required
className="report-text"
name="reportText"
placeholder={this.state.reportPrompt}
validationErrors={{
maxLength: 'That\'s too long! Please find a way to shorten your text.',
minLength: 'That\'s too short. Please describe in detail what\'s inappropriate or disrespectful about the project.'
}}
validations={{
// TODO find out max and min characters
maxLength: 500,
minLength: 30
}}
/>
{this.state.reportWaiting ? [
<Button
className="submit-button white"
disabled="disabled"
key="submitButton"
type="submit"
>
<Spinner />
</Button>
] : [
<Button
className="submit-button white"
key="submitButton"
type="submit"
>
Send
</Button>
]}
</Form>
</ReportModal>
] ]
} }
</FlexRow> </FlexRow>
</FlexRow> </FlexRow>
</FlexRow> </FlexRow>
<FlexRow className="preview-row"> <FlexRow className="preview-row">
<div className="comments-container"> <div className="comments-container">