Add validation messages for title, image and description

This commit is contained in:
Paul Kaplan 2021-05-13 10:36:40 -04:00
parent 827dad461e
commit d35c539529
9 changed files with 90 additions and 26 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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",

View file

@ -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>"

View file

@ -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>
);
};

View file

@ -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>

View file

@ -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>"

View file

@ -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>
);
};

View file

@ -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;
}