mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 15:47:53 -05:00
Add validation messages for title, image and description
This commit is contained in:
parent
827dad461e
commit
d35c539529
9 changed files with 90 additions and 26 deletions
|
@ -22,7 +22,7 @@ const ValidationMessage = props => (
|
|||
ValidationMessage.propTypes = {
|
||||
className: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
mode: PropTypes.string
|
||||
mode: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||
};
|
||||
|
||||
module.exports = ValidationMessage;
|
||||
|
|
|
@ -18,7 +18,7 @@ const Errors = keyMirror({
|
|||
INAPPROPRIATE: null,
|
||||
PERMISSION: null,
|
||||
THUMBNAIL_TOO_LARGE: null,
|
||||
THUMBNAIL_MISSING: null,
|
||||
THUMBNAIL_INVALID: null,
|
||||
TEXT_TOO_LONG: null,
|
||||
REQUIRED_FIELD: null,
|
||||
UNHANDLED: null
|
||||
|
@ -111,7 +111,7 @@ const normalizeError = (err, body, res) => {
|
|||
switch (body.errors[0]) {
|
||||
case 'inappropriate-generic': return Errors.INAPPROPRIATE;
|
||||
case 'thumbnail-too-large': return Errors.THUMBNAIL_TOO_LARGE;
|
||||
case 'thumbnail-missing': return Errors.THUMBNAIL_MISSING;
|
||||
case 'image-invalid': return Errors.THUMBNAIL_INVALID;
|
||||
case 'editable-text-too-long': return Errors.TEXT_TOO_LONG;
|
||||
case 'This field is required.': return Errors.REQUIRED_FIELD;
|
||||
default: return Errors.UNHANDLED;
|
||||
|
|
|
@ -7,6 +7,12 @@
|
|||
"studio.title": "Title",
|
||||
"studio.description": "Description",
|
||||
"studio.thumbnail": "Thumbnail",
|
||||
"studio.updateErrors.generic": "Something went wrong updating the studio.",
|
||||
"studio.updateErrors.inappropriate": "That seems inappropriate. Please be respectful.",
|
||||
"studio.updateErrors.textTooLong": "That is too long.",
|
||||
"studio.updateErrors.requiredField": "This cannot be blank.",
|
||||
"studio.updateErrors.thumbnailTooLarge": "Maximum file size is 512 KB and less than 500x500 pixels.",
|
||||
"studio.updateErrors.thumbnailInvalid": "Upload a valid image. The file you uploaded was either not an image or a corrupted image.",
|
||||
|
||||
"studio.projectsHeader": "Projects",
|
||||
"studio.addProjectsHeader": "Add Projects",
|
||||
|
|
|
@ -44,7 +44,7 @@ const StudioCuratorInviter = ({onSubmit}) => {
|
|||
/>
|
||||
</div>}
|
||||
<input
|
||||
className={classNames({'studio-adder-invalid-input': error})}
|
||||
className={classNames({'mod-form-error': error})}
|
||||
disabled={submitting}
|
||||
type="text"
|
||||
placeholder="<username>"
|
||||
|
|
|
@ -2,23 +2,36 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectStudioDescription, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {selectCanEditInfo} from '../../redux/studio-permissions';
|
||||
import {
|
||||
mutateStudioDescription, selectIsMutatingDescription, selectDescriptionMutationError
|
||||
Errors, mutateStudioDescription, selectIsMutatingDescription, selectDescriptionMutationError
|
||||
} from '../../redux/studio-mutations';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
|
||||
const errorToMessageId = error => {
|
||||
switch (error) {
|
||||
case Errors.INAPPROPRIATE: return 'studio.updateErrors.inappropriate';
|
||||
case Errors.TEXT_TOO_LONG: return 'studio.updateErrors.textTooLong';
|
||||
case Errors.REQUIRED_FIELD: return 'studio.updateErrors.requiredField';
|
||||
default: return 'studio.updateErrors.generic';
|
||||
}
|
||||
};
|
||||
|
||||
const StudioDescription = ({
|
||||
descriptionError, isFetching, isMutating, description, canEditInfo, handleUpdate
|
||||
}) => {
|
||||
const fieldClassName = classNames('studio-description', {
|
||||
'mod-fetching': isFetching,
|
||||
'mod-mutating': isMutating
|
||||
'mod-mutating': isMutating,
|
||||
'mod-form-error': !!descriptionError
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="studio-info-section">
|
||||
<textarea
|
||||
rows="20"
|
||||
className={fieldClassName}
|
||||
|
@ -27,8 +40,11 @@ const StudioDescription = ({
|
|||
onBlur={e => e.target.value !== description &&
|
||||
handleUpdate(e.target.value)}
|
||||
/>
|
||||
{descriptionError && <div>Error mutating description: {descriptionError}</div>}
|
||||
</React.Fragment>
|
||||
{descriptionError && <ValidationMessage
|
||||
mode="error"
|
||||
message={<FormattedMessage id={errorToMessageId(descriptionError)} />}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -2,19 +2,30 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectStudioImage, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {selectCanEditInfo} from '../../redux/studio-permissions';
|
||||
import {
|
||||
mutateStudioImage, selectIsMutatingImage, selectImageMutationError
|
||||
Errors, mutateStudioImage, selectIsMutatingImage, selectImageMutationError
|
||||
} from '../../redux/studio-mutations';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
|
||||
const errorToMessageId = error => {
|
||||
switch (error) {
|
||||
case Errors.THUMBNAIL_INVALID: return 'studio.updateErrors.thumbnailInvalid';
|
||||
case Errors.THUMBNAIL_TOO_LARGE: return 'studio.updateErrors.thumbnailTooLarge';
|
||||
default: return 'studio.updateErrors.generic';
|
||||
}
|
||||
};
|
||||
|
||||
const blankImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
||||
const StudioImage = ({
|
||||
imageError, isFetching, isMutating, image, canEditInfo, handleUpdate
|
||||
}) => {
|
||||
const fieldClassName = classNames('studio-image', {
|
||||
const fieldClassName = classNames('studio-info-section', {
|
||||
'mod-fetching': isFetching,
|
||||
'mod-mutating': isMutating
|
||||
});
|
||||
|
@ -36,7 +47,10 @@ const StudioImage = ({
|
|||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
{imageError && <div>Error mutating image: {imageError}</div>}
|
||||
{imageError && <ValidationMessage
|
||||
mode="error"
|
||||
message={<FormattedMessage id={errorToMessageId(imageError)} />}
|
||||
/>}
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,7 @@ const StudioProjectAdder = ({onSubmit}) => {
|
|||
/>
|
||||
</div>}
|
||||
<input
|
||||
className={classNames({'studio-adder-invalid-input': error})}
|
||||
className={classNames({'mod-form-error': error})}
|
||||
disabled={submitting}
|
||||
type="text"
|
||||
placeholder="<project id>"
|
||||
|
|
|
@ -2,21 +2,38 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectStudioTitle, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {selectCanEditInfo} from '../../redux/studio-permissions';
|
||||
import {mutateStudioTitle, selectIsMutatingTitle, selectTitleMutationError} from '../../redux/studio-mutations';
|
||||
import classNames from 'classnames';
|
||||
import {Errors, mutateStudioTitle, selectIsMutatingTitle, selectTitleMutationError} from '../../redux/studio-mutations';
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
/*
|
||||
TODO
|
||||
- no newlines in studio title
|
||||
- Correct display in read-only mode
|
||||
- validation message
|
||||
*/
|
||||
const errorToMessageId = error => {
|
||||
switch (error) {
|
||||
case Errors.INAPPROPRIATE: return 'studio.updateErrors.inappropriate';
|
||||
case Errors.TEXT_TOO_LONG: return 'studio.updateErrors.textTooLong';
|
||||
case Errors.REQUIRED_FIELD: return 'studio.updateErrors.requiredField';
|
||||
default: return 'studio.updateErrors.generic';
|
||||
}
|
||||
};
|
||||
|
||||
const StudioTitle = ({
|
||||
titleError, isFetching, isMutating, title, canEditInfo, handleUpdate
|
||||
}) => {
|
||||
const fieldClassName = classNames('studio-title', {
|
||||
'mod-fetching': isFetching,
|
||||
'mod-mutating': isMutating
|
||||
'mod-mutating': isMutating,
|
||||
'mod-form-error': !!titleError
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="studio-info-section">
|
||||
<textarea
|
||||
className={fieldClassName}
|
||||
disabled={isMutating || !canEditInfo || isFetching}
|
||||
|
@ -24,8 +41,11 @@ const StudioTitle = ({
|
|||
onBlur={e => e.target.value !== title &&
|
||||
handleUpdate(e.target.value)}
|
||||
/>
|
||||
{titleError && <div>Error mutating title: {titleError}</div>}
|
||||
</React.Fragment>
|
||||
{titleError && <ValidationMessage
|
||||
mode="error"
|
||||
message={<FormattedMessage id={errorToMessageId(titleError)} />}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -53,14 +53,22 @@ $radius: 8px;
|
|||
border: 2px dashed $ui-blue-25percent;
|
||||
border-radius: $radius;
|
||||
resize: none;
|
||||
width: 300px;
|
||||
|
||||
&:disabled { border-color: transparent; }
|
||||
}
|
||||
|
||||
.studio-info-section {
|
||||
position: relative;
|
||||
.validation-message {
|
||||
margin-top: .5rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.studio-title {
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.studio-description:disabled {
|
||||
background: $ui-blue-10percent;
|
||||
}
|
||||
|
@ -258,10 +266,6 @@ $radius: 8px;
|
|||
padding: 1em 1.25em;
|
||||
font-size: .8rem;
|
||||
margin-inline-end: 6px;
|
||||
|
||||
&.studio-adder-invalid-input {
|
||||
border: 1px solid $ui-orange;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
|
@ -345,3 +349,7 @@ $radius: 8px;
|
|||
.mod-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mod-form-error { /* When a field contains a value is causing an error */
|
||||
border-color: $ui-orange !important;
|
||||
}
|
Loading…
Reference in a new issue