mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 00:21:20 -05:00
Refactor report-modal
* addresses comments for https://github.com/LLK/scratch-www/pull/1900 * renames report fields to match scratchr2 expectations * restyle modal to allow display of validation messages * removed cruft
This commit is contained in:
parent
47b90d449b
commit
74c76fb1cf
5 changed files with 160 additions and 103 deletions
|
@ -5,7 +5,6 @@ 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 log = require('../../../lib/log.js');
|
||||
|
||||
const Form = require('../../forms/form.jsx');
|
||||
const Button = require('../../forms/button.jsx');
|
||||
|
@ -20,44 +19,70 @@ class ReportModal extends React.Component {
|
|||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleReasonSelect',
|
||||
'handleSubmit'
|
||||
'handleReportCategorySelect'
|
||||
]);
|
||||
this.state = {
|
||||
prompt: props.intl.formatMessage({id: 'report.promptPlaceholder'}),
|
||||
reason: '',
|
||||
waiting: false
|
||||
reportCategory: this.props.report.category,
|
||||
options: [
|
||||
{
|
||||
value: '',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonPlaceHolder'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptPlaceholder'})
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonCopy'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptCopy'})
|
||||
},
|
||||
{
|
||||
value: '1',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonUncredited'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptUncredited'})
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonScary'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptScary'})
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonLanguage'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptLanguage'})
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonMusic'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptMusic'})
|
||||
},
|
||||
{
|
||||
value: '8',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonImage'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptImage'})
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonPersonal'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptPersonal'})
|
||||
},
|
||||
{
|
||||
value: '6',
|
||||
label: this.props.intl.formatMessage({id: 'general.other'}),
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptGuidelines'})
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
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});
|
||||
handleReportCategorySelect (name, value) {
|
||||
this.setState({reportCategory: 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
|
||||
});
|
||||
});
|
||||
lookupPrompt (value) {
|
||||
return this.state.options.find(item => item.value === value).prompt;
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
intl,
|
||||
onReport, // eslint-disable-line no-unused-vars
|
||||
report,
|
||||
type,
|
||||
...modalProps
|
||||
} = this.props;
|
||||
|
@ -66,6 +91,7 @@ class ReportModal extends React.Component {
|
|||
<Modal
|
||||
className="mod-report"
|
||||
contentLabel={contentLabel}
|
||||
isOpen={report.open}
|
||||
{...modalProps}
|
||||
>
|
||||
<div>
|
||||
|
@ -88,68 +114,35 @@ class ReportModal extends React.Component {
|
|||
/>
|
||||
<Form
|
||||
className="report"
|
||||
onSubmit={this.handleSubmit}
|
||||
onSubmit={onReport}
|
||||
>
|
||||
<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: 'general.other'})
|
||||
}
|
||||
]}
|
||||
value={this.state.reason}
|
||||
onChange={this.handleReasonSelect}
|
||||
elementWrapperClassName="report-modal-field"
|
||||
label={null}
|
||||
name="report_category"
|
||||
options={this.state.options}
|
||||
value={this.state.reportCategory}
|
||||
onChange={this.handleReportCategorySelect}
|
||||
/>
|
||||
<TextArea
|
||||
required
|
||||
className="report-text"
|
||||
name="reportText"
|
||||
placeholder={this.state.prompt}
|
||||
elementWrapperClassName="report-modal-field"
|
||||
label={null}
|
||||
name="notes"
|
||||
placeholder={this.lookupPrompt(this.state.reportCategory)}
|
||||
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
|
||||
minLength: 20
|
||||
}}
|
||||
value={report.notes}
|
||||
/>
|
||||
{this.state.reportWaiting ? [
|
||||
{report.waiting ? [
|
||||
<Button
|
||||
className="submit-button white"
|
||||
disabled="disabled"
|
||||
|
@ -180,6 +173,12 @@ ReportModal.propTypes = {
|
|||
intl: intlShape,
|
||||
onReport: PropTypes.func,
|
||||
onRequestClose: PropTypes.func,
|
||||
report: PropTypes.shape({
|
||||
category: PropTypes.string,
|
||||
notes: PropTypes.string,
|
||||
open: PropTypes.bool,
|
||||
waiting: PropTypes.bool
|
||||
}),
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
outline: none;
|
||||
padding: 0;
|
||||
width: 30rem;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.report-modal-header {
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
box-shadow: inset 0 -1px 0 0 $ui-coral-dark;
|
||||
background-color: $ui-coral;
|
||||
padding-top: .75rem;
|
||||
|
@ -36,4 +36,45 @@
|
|||
width: 80%;
|
||||
line-height: 1.5rem;
|
||||
font-size: .875rem;
|
||||
|
||||
.validation-message {
|
||||
$arrow-border-width: 1rem;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translate(23.5rem, 0);
|
||||
margin-left: $arrow-border-width;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $ui-orange;
|
||||
padding: 1rem;
|
||||
max-width: 18.75rem;
|
||||
min-height: 1rem;
|
||||
overflow: visible;
|
||||
color: $type-white;
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: -$arrow-border-width / 2;
|
||||
|
||||
transform: rotate(45deg);
|
||||
|
||||
border-bottom: 1px solid $active-gray;
|
||||
border-left: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
|
||||
background-color: $ui-orange;
|
||||
width: $arrow-border-width;
|
||||
height: $arrow-border-width;
|
||||
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-modal-field {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class PreviewPresentation extends React.Component {
|
|||
faved,
|
||||
favoriteCount,
|
||||
isFullScreen,
|
||||
isReportOpen,
|
||||
isLoggedIn,
|
||||
isShared,
|
||||
loved,
|
||||
loveCount,
|
||||
|
@ -41,6 +41,7 @@ class PreviewPresentation extends React.Component {
|
|||
projectId,
|
||||
projectInfo,
|
||||
remixes,
|
||||
report,
|
||||
studios,
|
||||
userOwnsProject,
|
||||
onFavoriteClicked,
|
||||
|
@ -244,7 +245,7 @@ class PreviewPresentation extends React.Component {
|
|||
<Button className="action-button copy-link-button">
|
||||
Copy Link
|
||||
</Button>
|
||||
{(!userOwnsProject) &&
|
||||
{(isLoggedIn && !userOwnsProject) &&
|
||||
<React.Fragment>
|
||||
<Button
|
||||
className="action-button report-button"
|
||||
|
@ -254,8 +255,8 @@ class PreviewPresentation extends React.Component {
|
|||
Report
|
||||
</Button>,
|
||||
<ReportModal
|
||||
isOpen={isReportOpen}
|
||||
key="report-modal"
|
||||
report={report}
|
||||
type="project"
|
||||
onReport={onReportSubmit}
|
||||
onRequestClose={onReportClose}
|
||||
|
@ -288,7 +289,7 @@ PreviewPresentation.propTypes = {
|
|||
faved: PropTypes.bool,
|
||||
favoriteCount: PropTypes.number,
|
||||
isFullScreen: PropTypes.bool,
|
||||
isReportOpen: PropTypes.bool,
|
||||
isLoggedIn: PropTypes.bool,
|
||||
isShared: PropTypes.bool,
|
||||
loveCount: PropTypes.number,
|
||||
loved: PropTypes.bool,
|
||||
|
@ -304,6 +305,12 @@ PreviewPresentation.propTypes = {
|
|||
projectId: PropTypes.string,
|
||||
projectInfo: projectShape,
|
||||
remixes: PropTypes.arrayOf(PropTypes.object),
|
||||
report: PropTypes.shape({
|
||||
category: PropTypes.string,
|
||||
notes: PropTypes.string,
|
||||
open: PropTypes.bool,
|
||||
waiting: PropTypes.bool
|
||||
}),
|
||||
studios: PropTypes.arrayOf(PropTypes.object),
|
||||
userOwnsProject: PropTypes.bool
|
||||
};
|
||||
|
|
|
@ -44,7 +44,12 @@ class Preview extends React.Component {
|
|||
favoriteCount: 0,
|
||||
loveCount: 0,
|
||||
projectId: parts[1] === 'editor' ? 0 : parts[1],
|
||||
reportOpen: false
|
||||
report: {
|
||||
category: '',
|
||||
notes: '',
|
||||
open: false,
|
||||
waiting: false
|
||||
}
|
||||
};
|
||||
this.addEventListeners();
|
||||
}
|
||||
|
@ -86,19 +91,6 @@ class Preview extends React.Component {
|
|||
componentWillUnmount () {
|
||||
this.removeEventListeners();
|
||||
}
|
||||
initState () {
|
||||
const pathname = window.location.pathname.toLowerCase();
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
// parts[0]: 'preview'
|
||||
// parts[1]: either :id or 'editor'
|
||||
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
|
||||
return {
|
||||
editable: false,
|
||||
favoriteCount: 0,
|
||||
loveCount: 0,
|
||||
projectId: parts[1] === 'editor' ? 0 : parts[1]
|
||||
};
|
||||
}
|
||||
addEventListeners () {
|
||||
window.addEventListener('popstate', this.handlePopState);
|
||||
}
|
||||
|
@ -106,19 +98,31 @@ class Preview extends React.Component {
|
|||
window.removeEventListener('popstate', this.handlePopState);
|
||||
}
|
||||
handleReportClick () {
|
||||
this.setState({reportOpen: true});
|
||||
this.setState({report: {...this.state.report, open: true}});
|
||||
}
|
||||
handleReportClose () {
|
||||
this.setState({reportOpen: false});
|
||||
this.setState({report: {...this.state.report, open: false}});
|
||||
}
|
||||
handleReportSubmit (formData) {
|
||||
this.setState({report: {
|
||||
category: formData.report_category,
|
||||
notes: formData.notes,
|
||||
open: this.state.report.open,
|
||||
waiting: true}
|
||||
});
|
||||
|
||||
const data = {
|
||||
...formData,
|
||||
id: this.state.projectId,
|
||||
username: this.props.user.username
|
||||
user: this.props.user.username
|
||||
};
|
||||
console.log('submit report data', data); // eslint-disable-line no-console
|
||||
this.setState({reportOpen: false});
|
||||
this.setState({report: {
|
||||
category: '',
|
||||
notes: '',
|
||||
open: false,
|
||||
waiting: false}
|
||||
});
|
||||
}
|
||||
handlePopState () {
|
||||
const path = window.location.pathname.toLowerCase();
|
||||
|
@ -219,10 +223,15 @@ class Preview extends React.Component {
|
|||
)
|
||||
);
|
||||
}
|
||||
userOwnsProject () {
|
||||
isLoggedIn () {
|
||||
return (
|
||||
this.props.sessionStatus === sessionActions.Status.FETCHED &&
|
||||
Object.keys(this.props.user).length > 0 &&
|
||||
Object.keys(this.props.user).length > 0
|
||||
);
|
||||
}
|
||||
userOwnsProject () {
|
||||
return (
|
||||
this.isLoggedIn() &&
|
||||
Object.keys(this.props.projectInfo).length > 0 &&
|
||||
this.props.user.id === this.props.projectInfo.author.id
|
||||
);
|
||||
|
@ -237,7 +246,7 @@ class Preview extends React.Component {
|
|||
faved={this.props.faved}
|
||||
favoriteCount={this.state.favoriteCount}
|
||||
isFullScreen={this.state.isFullScreen}
|
||||
isReportOpen={this.state.reportOpen}
|
||||
isLoggedIn={this.isLoggedIn()}
|
||||
isShared={this.isShared()}
|
||||
loveCount={this.state.loveCount}
|
||||
loved={this.props.loved}
|
||||
|
@ -246,6 +255,7 @@ class Preview extends React.Component {
|
|||
projectId={this.state.projectId}
|
||||
projectInfo={this.props.projectInfo}
|
||||
remixes={this.props.remixes}
|
||||
report={this.state.report}
|
||||
studios={this.props.studios}
|
||||
user={this.props.user}
|
||||
userOwnsProject={this.userOwnsProject()}
|
||||
|
|
|
@ -237,9 +237,9 @@ $stage-height: 404px;
|
|||
background-color: $ui-blue-10percent;
|
||||
padding: .5rem;
|
||||
width: calc(100% - (1rem + 2px));
|
||||
overflow: auto;
|
||||
white-space: pre-line;
|
||||
font-size: 1rem;
|
||||
overflow: auto;
|
||||
// flex-grow
|
||||
flex: 1;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue