Report first pass

Report button opens a modal with the report project form. All the content is currently in `preview/presentation` because components cannot have their own l10n files.
This commit is contained in:
chrisgarrity 2018-05-24 13:47:31 -04:00
parent bd391b422a
commit 34042c9e8e
6 changed files with 568 additions and 336 deletions

View file

@ -18,8 +18,9 @@ $ui-border: hsla(0, 0, 85, 1); //#D9D9D9
/* 3.0 colors */
/* Using www naming convention for now, should be consistent with gui */
$ui-green: hsla(163, 83, 40, 1); //#0fbd8c Pen Primary
$ui-coral: hsla(350, 100, 70, 1); //#FF6680 More Priamry
$ui-green: hsla(163, 83, 40, 1); // #0fbd8c Pen Primary
$ui-coral: hsla(350, 100, 70, 1); // #FF6680 More Primary
$ui-coral-dark: hsla(350, 100, 60, 1); // #FF3355 More tertiary
$ui-blue-10percent: hsla(215, 100, 65, .1);
$ui-orange-25percent: hsla(35, 90, 55, .25);

View file

@ -0,0 +1,34 @@
const PropTypes = require('prop-types');
const React = require('react');
const Modal = require('../base/modal.jsx');
require('../../forms/button.scss');
require('./modal.scss');
const ReportModal = props => (
<Modal
className="mod-report"
{...props}
>
<div>
<div className="report-modal-header">
<div className="report-content-label">
{props.contentLabel}
</div>
</div>
<div className="report-modal-content">
{props.children}
</div>
</div>
</Modal>
);
ReportModal.propTypes = {
children: PropTypes.node,
contentLabel: PropTypes.string,
onRequestClose: PropTypes.func
};
module.exports = ReportModal;

View file

@ -0,0 +1,41 @@
@import "../../../colors";
@import "../../../frameless";
.mod-report * {
box-sizing: border-box;
}
.mod-report {
margin: 100px auto;
width: 30rem;
outline: none;
padding: 0;
user-select: none;
overflow: hidden;
}
.report-modal-header {
height: 3rem;
padding-top: .75rem;
box-sizing: border-box;
width: 100%;
background-color: $ui-coral;
box-shadow: inset 0 -1px 0 0 $ui-coral-dark;
}
.report-content-label {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1rem;
font-weight: bold;
color: $type-white;
text-align: center;
}
.report-modal-content {
width: 80%;
margin: 1rem auto;
font-size: .875rem;
line-height: 1.5rem;
}

View file

@ -1,3 +1,4 @@
const bindAll = require('lodash.bindall');
const FormattedDate = require('react-intl').FormattedDate;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
@ -13,15 +14,62 @@ const sessionActions = require('../../redux/session.js');
const decorateText = require('../../lib/decorate-text.jsx');
const FlexRow = require('../../components/flex-row/flex-row.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 CappedNumber = require('../../components/cappednumber/cappednumber.jsx');
const ShareBanner = require('../../components/share-banner/share-banner.jsx');
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
const InplaceInput = require('../../components/forms/inplace-input.jsx');
const ReportModal = require('../../components/modal/report/modal.jsx');
require('./preview.scss');
const PreviewPresentation = props => {
class PreviewPresentation extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleReportClick',
'handleReportClose',
'handleReportReasonSelect',
'handleReportSubmit'
]);
this.state = {
reportOpen: false,
reportPrompt: 'Select a reason why above.',
reportReason: ''
};
}
handleReportClick (e) {
e.preventDefault();
this.setState({reportOpen: true});
}
handleReportClose () {
this.setState({reportOpen: false});
}
handleReportReasonSelect (name, value) {
const prompts = [
'Please provide a link to the original project',
'Please provide links to the uncredited content',
'Please say why the project is too violent or scary',
'Please say where the inappropriate language occurs in the project (For example: Notes & Credits, sprite name, project text, etc.)',
'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.)',
'Please be specific about why this project does not follow our Community Guidelines',
'not used',
'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});
}
render () {
const {
editable,
faved,
@ -43,7 +91,7 @@ const PreviewPresentation = props => {
onSeeInside,
onUpdate
// ...otherProps TBD
} = props;
} = this.props;
const shareDate = (projectInfo.history && projectInfo.history.shared) ? projectInfo.history.shared : '';
return (
<div className="preview">
@ -277,9 +325,123 @@ const PreviewPresentation = props => {
<Button className="action-button copy-link-button">
Copy Link
</Button>
<Button className="action-button report-button">
{
sessionStatus === sessionActions.Status.FETCHED &&
Object.keys(user).length > 0 &&
user.id !== projectInfo.author.id && [
<Button
className="action-button report-button"
key="report-button"
onClick={this.handleReportClick}
>
Report
</Button>,
<ReportModal
contentLabel="Report Project"
isOpen={this.state.reportOpen}
key="report-modal"
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>
@ -291,12 +453,15 @@ const PreviewPresentation = props => {
</div>
</div>
<FlexRow className="column">
{/* hide remixes if there aren't any */}
{remixes && remixes.length !== 0 && (
<FlexRow className="remix-list">
<div className="project-title">
Remixes
</div>
{remixes && remixes.length === 0 ? (
<span>No remixes</span>
// TODO: style remix invitation
<span>Invite user to remix</span>
) : (
<ThumbnailColumn
cards
@ -309,12 +474,16 @@ const PreviewPresentation = props => {
/>
)}
</FlexRow>
)}
{/* hide studios if there aren't any */}
{studios && studios.length !== 0 && (
<FlexRow className="studio-list">
<div className="project-title">
Studios
</div>
{studios && studios.length === 0 ? (
<span>No studios</span>
// TODO: invite user to add to studio?
<span>None </span>
) : (
<ThumbnailColumn
cards
@ -327,6 +496,7 @@ const PreviewPresentation = props => {
/>
)}
</FlexRow>
)}
</FlexRow>
</FlexRow>
</Formsy>
@ -335,7 +505,8 @@ const PreviewPresentation = props => {
</div>
);
};
}
}
PreviewPresentation.propTypes = {
editable: PropTypes.bool,
@ -389,17 +560,6 @@ PreviewPresentation.propTypes = {
root: PropTypes.number
})
}),
editable: PropTypes.bool,
faved: PropTypes.bool,
favoriteCount: PropTypes.number,
intl: intlShape,
isFullScreen: PropTypes.bool,
loveCount: PropTypes.number,
loved: PropTypes.bool,
onFavoriteClicked: PropTypes.func,
onLoveClicked: PropTypes.func,
onSeeInside: PropTypes.func,
onUpdate: PropTypes.func,
projectId: PropTypes.string,
projectInfo: PropTypes.shape({
id: PropTypes.number,

View file

@ -69,6 +69,19 @@ 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);
}
@ -108,19 +121,6 @@ class Preview extends React.Component {
);
}
}
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]
};
}
handleFavoriteToggle () {
this.props.setFavedStatus(
!this.props.faved,
@ -269,15 +269,6 @@ Preview.propTypes = {
root: PropTypes.number
})
}),
faved: PropTypes.bool,
fullScreen: PropTypes.bool,
getCreditInfo: PropTypes.func.isRequired,
getFavedStatus: PropTypes.func.isRequired,
getLovedStatus: PropTypes.func.isRequired,
getProjectInfo: PropTypes.func.isRequired,
getRemixes: PropTypes.func.isRequired,
getStudios: PropTypes.func.isRequired,
loved: PropTypes.bool,
playerMode: PropTypes.bool,
projectInfo: PropTypes.shape({
author: PropTypes.shape({

View file

@ -462,3 +462,8 @@ $stage-height: 406px;
}
}
}
.report-text textarea {
// override min-height from default settings (for teacher registration)
min-height: 8rem;
}