@@ -104,68 +126,115 @@ class ReportModal extends React.Component {
@@ -176,15 +245,26 @@ class ReportModal extends React.Component {
ReportModal.propTypes = {
intl: intlShape,
+ isConfirmed: PropTypes.bool,
+ isError: PropTypes.bool,
+ isOpen: PropTypes.bool,
+ isWaiting: PropTypes.bool,
onReport: PropTypes.func,
onRequestClose: PropTypes.func,
- report: PropTypes.shape({
- category: PropTypes.string,
- notes: PropTypes.string,
- open: PropTypes.bool,
- waiting: PropTypes.bool
- }),
type: PropTypes.string
};
-module.exports = injectIntl(ReportModal);
+const mapStateToProps = state => ({
+ isConfirmed: state.preview.status.report === previewActions.Status.FETCHED,
+ isError: state.preview.status.report === previewActions.Status.ERROR,
+ isWaiting: state.preview.status.report === previewActions.Status.FETCHING
+});
+
+const mapDispatchToProps = () => ({});
+
+const ConnectedReportModal = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ReportModal);
+
+module.exports = injectIntl(ConnectedReportModal);
diff --git a/src/components/modal/report/modal.scss b/src/components/modal/report/modal.scss
index 6a9fe3abf..375ac6227 100644
--- a/src/components/modal/report/modal.scss
+++ b/src/components/modal/report/modal.scss
@@ -9,7 +9,7 @@
margin: 100px auto;
outline: none;
padding: 0;
- width: 30rem;
+ width: 36.25rem; /* 580px; */
user-select: none;
}
@@ -34,26 +34,45 @@
.report-modal-content {
margin: 1rem auto;
width: 80%;
- line-height: 1.5rem;
font-size: .875rem;
+ .instructions {
+ line-height: 1.5rem;
+ }
+
+ .received {
+ margin: 0 auto;
+ width: 90%;
+ text-align: center;
+ line-height: 1.65rem;
+
+ .received-header {
+ font-weight: bold;
+ }
+ }
+
+ .error-text {
+ margin-top: .9375rem;
+ }
+
.validation-message {
$arrow-border-width: 1rem;
display: block;
position: absolute;
top: 0;
- left: 0;
- transform: translate(23.5rem, 0);
+ left: 100%; /* position to the right of parent */
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
padding: 1rem;
+ min-width: 12rem;
max-width: 18.75rem;
min-height: 1rem;
overflow: visible;
color: $type-white;
+ /* arrow on box that points to the left */
&:before {
display: block;
position: absolute;
@@ -78,3 +97,13 @@
.report-modal-field {
position: relative;
}
+
+.form-group.has-error {
+ .textarea, select {
+ border: 1px solid $ui-orange;
+ }
+}
+
+.report-text .textarea {
+ margin-bottom: 0;
+}
diff --git a/src/l10n.json b/src/l10n.json
index 2c59f68de..a399ad6b2 100644
--- a/src/l10n.json
+++ b/src/l10n.json
@@ -172,6 +172,7 @@
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!",
"thumbnail.by": "by",
+ "report.error": "Something went wrong when trying to send your message. Please try again.",
"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",
@@ -181,8 +182,11 @@
"report.reasonScary": "Too Violent or Scary",
"report.reasonLanguage": "Inappropriate Language",
"report.reasonMusic": "Inappropriate Music",
+ "report.reasonMissing": "Please select a reason",
"report.reasonImage": "Inappropriate Images",
"report.reasonPersonal": "Sharing Personal Contact Information",
+ "report.receivedHeader": "We have received your report!",
+ "report.receivedBody": "The Scratch Team will review the project based on the Scratch community guidelines.",
"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",
@@ -194,5 +198,7 @@
"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"
+ "report.send": "Send",
+ "report.sending": "Sending...",
+ "report.textMissing": "Please tell us why you are reporting this project"
}
diff --git a/src/redux/preview.js b/src/redux/preview.js
index 6ab017bf8..109635a12 100644
--- a/src/redux/preview.js
+++ b/src/redux/preview.js
@@ -1,3 +1,4 @@
+const defaults = require('lodash.defaults');
const keyMirror = require('keymirror');
const async = require('async');
const merge = require('lodash.merge');
@@ -21,6 +22,7 @@ module.exports.getInitialState = () => ({
original: module.exports.Status.NOT_FETCHED,
parent: module.exports.Status.NOT_FETCHED,
remixes: module.exports.Status.NOT_FETCHED,
+ report: module.exports.Status.NOT_FETCHED,
projectStudios: module.exports.Status.NOT_FETCHED,
curatedStudios: module.exports.Status.NOT_FETCHED,
studioRequests: {}
@@ -324,6 +326,7 @@ module.exports.getReplies = (projectId, commentIds) => (dispatch => {
});
module.exports.setFavedStatus = (faved, id, username, token) => (dispatch => {
+ dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHING));
if (faved) {
api({
uri: `/projects/${id}/favorites/user/${username}`,
@@ -383,6 +386,7 @@ module.exports.getLovedStatus = (id, username, token) => (dispatch => {
});
module.exports.setLovedStatus = (loved, id, username, token) => (dispatch => {
+ dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHING));
if (loved) {
api({
uri: `/projects/${id}/loves/user/${username}`,
@@ -531,6 +535,7 @@ module.exports.leaveStudio = (studioId, projectId, token) => (dispatch => {
});
module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
+ dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
api({
uri: `/projects/${id}`,
authentication: token,
@@ -556,3 +561,27 @@ module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
dispatch(module.exports.setProjectInfo(body));
});
});
+
+module.exports.reportProject = (id, jsonData) => (dispatch => {
+ dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING));
+ // scratchr2 will fail if no thumbnail base64 string provided. We don't yet have
+ // a way to get the actual project thumbnail in www/gui, so for now just submit
+ // a minimal base64 png string.
+ defaults(jsonData, {
+ thumbnail: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC' +
+ '0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII='
+ });
+ api({
+ host: '',
+ uri: `/site-api/projects/all/${id}/report/`,
+ method: 'POST',
+ json: jsonData,
+ useCsrf: true
+ }, (err, body, res) => {
+ if (err || res.statusCode !== 200) {
+ dispatch(module.exports.setFetchStatus('report', module.exports.Status.ERROR));
+ return;
+ }
+ dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHED));
+ });
+});
diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx
index b204eb831..a62aa663f 100644
--- a/src/views/preview/presentation.jsx
+++ b/src/views/preview/presentation.jsx
@@ -43,7 +43,7 @@ const PreviewPresentation = ({
projectId,
projectInfo,
remixes,
- report,
+ reportOpen,
replies,
addToStudioOpen,
projectStudios,
@@ -278,8 +278,8 @@ const PreviewPresentation = ({
Report
,