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 {
constructor (props) {
super(props);
bindAll(this, [
'handleReasonSelect',
'handleSubmit'
]);
this.state = {
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 <Modal
className="mod-report" className="mod-report"
{...props} contentLabel={contentLabel}
{...modalProps}
> >
<div> <div>
<div className="report-modal-header"> <div className="report-modal-header">
<div className="report-content-label"> <div className="report-content-label">
{props.contentLabel} {contentLabel}
</div> </div>
</div> </div>
<div className="report-modal-content"> <div className="report-modal-content">
{props.children} <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>
</Modal> </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 () {
@ -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">