1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-14 09:05:08 +01:00

Finalize submission flow with validation and thumbnail creation

This commit is contained in:
Tim Daubenschütz 2015-11-10 17:35:57 +01:00
parent 447ccabf45
commit ea321a4a77
10 changed files with 281 additions and 176 deletions

View File

@ -236,12 +236,12 @@ let Form = React.createClass({
},
renderChildren() {
return ReactAddons.Children.map(this.props.children, (child) => {
return ReactAddons.Children.map(this.props.children, (child, i) => {
if (child) {
return ReactAddons.addons.cloneWithProps(child, {
handleChange: this.handleChangeChild,
ref: child.props.name,
key: i,
// We need this in order to make editable be overridable when setting it directly
// on Property
editable: child.props.overrideForm ? child.props.editable : !this.props.disabled
@ -269,7 +269,13 @@ let Form = React.createClass({
}
},
_validateRef(refToValidate) {
/**
* Validates a single ref and returns a human-readable error message
* @param {object} refToValidate A customly constructed object to check
* @return {oneOfType([arrayOf(string), bool])} Either an error message or false, saying that
* everything is valid
*/
_hasRefErrors(refToValidate) {
let error = [];
Object
@ -280,13 +286,13 @@ let Form = React.createClass({
if(!contraintValue) {
switch(constraintKey) {
case 'min' || 'max':
error.push(getLangText('The field you defined is not in the valid range.'));
error.push(getLangText('The field you defined is not in the valid range'));
break;
case 'pattern':
error.push(getLangText('The value you defined is not matching the valid pattern'));
break;
case 'required':
error.push(getLangText('This field is required.'));
error.push(getLangText('This field is required'));
break;
}
}
@ -295,7 +301,22 @@ let Form = React.createClass({
return error.length ? error : false;
},
valid() {
/**
* This method validates all child inputs of the form.
*
* As of now, it only considers
* - `max`
* - `min`
* - `pattern`
* - `required`
*
* The idea is to enhance this method everytime we need more thorough validation.
* So feel free to add props that additionally should be checked, if they're present
* in the input's props.
*
* @return {[type]} [description]
*/
validate() {
this.clearErrors();
const validatedFormInputs = {};
@ -317,12 +338,12 @@ let Form = React.createClass({
refToValidate.max = type === 'number' ? parseInt(value) <= max : true;
refToValidate.min = type === 'number' ? parseInt(value) >= min : true;
const validatedRef = this._validateRef(refToValidate);
const validatedRef = this._hasRefErrors(refToValidate);
validatedFormInputs[refName] = validatedRef;
});
const errorMessagesForFields = sanitize(validatedFormInputs, (val) => !val);
this.handleError({ json: { errors: errorMessagesForFields } });
return !Object.keys(errorMessagesForFields).length;
const errorMessagesForRefs = sanitize(validatedFormInputs, (val) => !val);
this.handleError({ json: { errors: errorMessagesForRefs } });
return !Object.keys(errorMessagesForRefs).length;
},
render() {

View File

@ -3,64 +3,82 @@
import React from 'react';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import FileDragAndDrop from '../ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop';
import AppConstants from '../../constants/application_constants';
import { getCookie } from '../../utils/fetch_api_utils';
let InputFineUploader = React.createClass({
const { func, bool, object, shape, string, number, arrayOf } = React.PropTypes;
const InputFineUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
submitFileName: React.PropTypes.func,
setIsUploadReady: func,
isReadyForFormSubmission: func,
submitFileName: func,
fileInputElement: func,
areAssetsDownloadable: React.PropTypes.bool,
areAssetsDownloadable: bool,
onClick: React.PropTypes.func,
keyRoutine: React.PropTypes.shape({
url: React.PropTypes.string,
fileClass: React.PropTypes.string
onClick: func,
keyRoutine: shape({
url: string,
fileClass: string
}),
createBlobRoutine: React.PropTypes.shape({
url: React.PropTypes.string
createBlobRoutine: shape({
url: string
}),
validation: React.PropTypes.shape({
itemLimit: React.PropTypes.number,
sizeLimit: React.PropTypes.string,
allowedExtensions: React.PropTypes.arrayOf(React.PropTypes.string)
validation: shape({
itemLimit: number,
sizeLimit: string,
allowedExtensions: arrayOf(string)
}),
// isFineUploaderActive is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
isFineUploaderActive: React.PropTypes.bool,
onLoggedOut: React.PropTypes.func,
isFineUploaderActive: bool,
onLoggedOut: func,
enableLocalHashing: React.PropTypes.bool,
enableLocalHashing: bool,
// provided by Property
disabled: React.PropTypes.bool,
disabled: bool,
// A class of a file the user has to upload
// Needs to be defined both in singular as well as in plural
fileClassToUpload: React.PropTypes.shape({
singular: React.PropTypes.string,
plural: React.PropTypes.string
fileClassToUpload: shape({
singular: string,
plural: string
}),
location: React.PropTypes.object
location: object
},
getDefaultProps() {
return {
fileInputElement: FileDragAndDrop
};
},
getInitialState() {
return {
value: null
value: null,
file: null
};
},
submitFile(file){
submitFile(file) {
console.log(file);
this.setState({
file,
value: file.key
});
if(this.state.value && typeof this.props.onChange === 'function') {
this.props.onChange({ target: { value: this.state.value } });
}
if(typeof this.props.submitFileName === 'function') {
this.props.submitFileName(file.originalName);
}
@ -70,7 +88,26 @@ let InputFineUploader = React.createClass({
this.refs.fineuploader.reset();
},
createBlobRoutine() {
const { fineuploader } = this.refs;
const { file } = this.state;
fineuploader.createBlob(file);
},
render() {
const { fileInputElement,
onClick,
keyRoutine,
createBlobRoutine,
validation,
setIsUploadReady,
isReadyForFormSubmission,
areAssetsDownloadable,
onLoggedOut,
enableLocalHashing,
fileClassToUpload,
location } = this.props;
let editable = this.props.isFineUploaderActive;
// if disabled is actually set by property, we want to override
@ -82,14 +119,15 @@ let InputFineUploader = React.createClass({
return (
<ReactS3FineUploader
ref="fineuploader"
onClick={this.props.onClick}
keyRoutine={this.props.keyRoutine}
createBlobRoutine={this.props.createBlobRoutine}
validation={this.props.validation}
fileInputElement={fileInputElement}
onClick={onClick}
keyRoutine={keyRoutine}
createBlobRoutine={createBlobRoutine}
validation={validation}
submitFile={this.submitFile}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={this.props.areAssetsDownloadable}
setIsUploadReady={setIsUploadReady}
isReadyForFormSubmission={isReadyForFormSubmission}
areAssetsDownloadable={areAssetsDownloadable}
areAssetsEditable={editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
@ -105,10 +143,10 @@ let InputFineUploader = React.createClass({
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
onInactive={this.props.onLoggedOut}
enableLocalHashing={this.props.enableLocalHashing}
fileClassToUpload={this.props.fileClassToUpload}
location={this.props.location}/>
onInactive={onLoggedOut}
enableLocalHashing={enableLocalHashing}
fileClassToUpload={fileClassToUpload}
location={location}/>
);
}
});

View File

@ -255,8 +255,10 @@ let Property = React.createClass({
placement="top"
overlay={tooltip}>
<div className={'ascribe-property ' + this.props.className}>
{this.state.errors}
<span>{this.props.label}</span>
<p>
<span className="pull-left">{this.props.label}</span>
<span className="pull-right">{this.state.errors}</span>
</p>
{this.renderChildren(style)}
{footer}
</div>

View File

@ -92,8 +92,14 @@ let UploadButton = React.createClass({
allowedExtensions
} = this.props;
/*
* We do not want a button that submits here.
* As UploadButton could be used in forms that want to be submitted independent
* of clicking the selector.
* Therefore the wrapping component needs to be an `anchor` tag instead of a `button`
*/
return (
<button
<a
onClick={this.handleOnClick}
className="btn btn-default btn-sm margin-left-2px"
disabled={this.getUploadingFiles().length !== 0 || !!this.getUploadedFile()}>
@ -109,7 +115,7 @@ let UploadButton = React.createClass({
}}
onChange={this.handleDrop}
accept={allowedExtensions}/>
</button>
</a>
);
}
});

View File

@ -7,7 +7,8 @@ import Form from '../../../../../ascribe_forms/form';
import Property from '../../../../../ascribe_forms/property';
import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
import UploadFileButton from '../../../../../ascribe_buttons/upload_file_button';
import UploadButton from '../../../../../ascribe_uploader/ascribe_upload_button/upload_button';
import InputFineuploader from '../../../../../ascribe_forms/input_fineuploader';
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
@ -19,6 +20,7 @@ import requests from '../../../../../../utils/requests';
import { getLangText } from '../../../../../../utils/lang_utils';
import { setCookie } from '../../../../../../utils/fetch_api_utils';
import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils';
const { object } = React.PropTypes;
@ -45,20 +47,28 @@ const PRRegisterPieceForm = React.createClass({
* second adding all the additional details
*/
submit() {
if(!this.validateForms()) {
return;
}
const { currentUser } = this.props;
const { registerPieceForm,
digitalWorkForm,
proofOfPaymentForm,
supportingMaterialsForm,
additionalDataForm,
thumbnailForm } = this.refs;
uploadersForm } = this.refs;
const { digitalWorkKey,
thumbnailKey,
supportingMaterials,
proofOfPayment } = uploadersForm.refs;
const additionalDataFormData = additionalDataForm.getFormData();
// composing data for piece registration
let registerPieceFormData = registerPieceForm.getFormData();
registerPieceFormData.digital_work_key = digitalWorkForm.state.file ? digitalWorkForm.state.file.key : '';
registerPieceFormData.digital_work_key = digitalWorkKey.state.value;
registerPieceFormData.thumbnail_file = thumbnailKey.state.value;
registerPieceFormData.terms = true;
console.log(registerPieceFormData);
// submitting the piece
requests
.post(ApiUrls.pieces_list, { body: registerPieceFormData })
@ -67,9 +77,8 @@ const PRRegisterPieceForm = React.createClass({
this.setState({
piece
}, () => {
supportingMaterialsForm.createBlobRoutine();
proofOfPaymentForm.createBlobRoutine();
thumbnailForm.createBlobRoutine();
supportingMaterials.refs.input.createBlobRoutine();
proofOfPayment.refs.input.createBlobRoutine();
});
//setCookie(currentUser.email, piece.id);
@ -90,24 +99,28 @@ const PRRegisterPieceForm = React.createClass({
.catch((err) => {
console.log(err);
});
},
getCreateBlobRoutine(fileClass) {
validateForms() {
const { registerPieceForm,
additionalDataForm,
uploadersForm } = this.refs;
const registerPieceFormValidation = registerPieceForm.validate();
const additionalDataFormValidation = additionalDataForm.validate();
const uploaderFormValidation = uploadersForm.validate();
return registerPieceFormValidation && additionalDataFormValidation && uploaderFormValidation;
},
getCreateBlobRoutine() {
const { piece } = this.state;
if(piece && piece.id) {
if(fileClass === 'other_data') {
return {
url: ApiUrls.blob_otherdatas,
pieceId: piece.id
};
} else if(fileClass === 'thumbnail') {
return {
url: ApiUrls.blob_thumbnails,
pieceId: piece.id
};
}
return {
url: ApiUrls.blob_otherdatas,
pieceId: piece.id
};
} else {
return null;
}
@ -174,92 +187,116 @@ const PRRegisterPieceForm = React.createClass({
placeholder={getLangText('Enter exhibitions and publication history')}/>
</Property>
</Form>
<div className="input-upload-file-button-property">
{getLangText('Select the PDF with your work')}
<UploadFileButton
ref="digitalWorkForm"
createBlobRoutine={{
url: ApiUrls.blob_digitalworks
}}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
allowedExtensions: ['pdf']
}}
location={location}
fileClassToUpload={{
singular: getLangText('Upload the Portfolio'),
plural: getLangText('Upload the Portfolios')
}}/>
</div>
<div className="input-upload-file-button-property">
{getLangText('Featured Cover photo')}
<UploadFileButton
ref="thumbnailForm"
createBlobRoutine={this.getCreateBlobRoutine('thumbnail')}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'thumbnail'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
allowedExtensions: ['png', 'jpg']
}}
location={location}
fileClassToUpload={{
singular: getLangText('Upload cover photo'),
plural: getLangText('Upload cover photos')
}}/>
</div>
<div className="input-upload-file-button-property">
{getLangText('Supporting Materials (Optional)')}
<UploadFileButton
ref="supportingMaterialsForm"
createBlobRoutine={this.getCreateBlobRoutine('other_data')}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'other_data'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit
}}
location={location}
fileClassToUpload={{
singular: getLangText('Upload'),
plural: getLangText('Upload')
}}/>
</div>
<div className="input-upload-file-button-property">
{getLangText('Proof of payment')}
<UploadFileButton
ref="proofOfPaymentForm"
createBlobRoutine={this.getCreateBlobRoutine('other_data')}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'other_data'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
allowedExtensions: ['png', 'jpg']
}}
location={location}
fileClassToUpload={{
singular: getLangText('Upload Screenshot'),
plural: getLangText('Upload Screenshots')
}}/>
</div>
<Form
buttons={{}}
className="ascribe-form-bordered"
ref="uploadersForm">
<Property
name="digitalWorkKey"
className="input-upload-file-button-property">
<span>{getLangText('Select the PDF with your work')}</span>
<InputFineuploader
fileInputElement={UploadButton}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}}
createBlobRoutine={{
url: ApiUrls.blob_digitalworks
}}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
allowedExtensions: ['pdf']
}}
location={location}
fileClassToUpload={{
singular: getLangText('Upload the Portfolio'),
plural: getLangText('Upload the Portfolios')
}}
required/>
</Property>
<Property
name="thumbnailKey"
className="input-upload-file-button-property">
<span>{getLangText('Featured Cover photo')}</span>
<InputFineuploader
fileInputElement={UploadButton}
createBlobRoutine={{
url: ApiUrls.blob_thumbnails
}}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'thumbnail'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
allowedExtensions: ['png', 'jpg']
}}
location={location}
fileClassToUpload={{
singular: getLangText('Upload cover photo'),
plural: getLangText('Upload cover photos')
}}/>
</Property>
<Property
name="supportingMaterials"
className="input-upload-file-button-property">
<span>{getLangText('Supporting Materials (Optional)')}</span>
<InputFineuploader
fileInputElement={UploadButton}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}}
createBlobRoutine={this.getCreateBlobRoutine()}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'other_data'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit
}}
location={location}
fileClassToUpload={{
singular: getLangText('Upload'),
plural: getLangText('Upload')
}}/>
</Property>
<Property
name="proofOfPayment"
className="input-upload-file-button-property">
<span>{getLangText('Proof of payment')}</span>
<InputFineuploader
fileInputElement={UploadButton}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}}
createBlobRoutine={this.getCreateBlobRoutine()}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'other_data'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
allowedExtensions: ['png', 'jpg']
}}
location={location}
fileClassToUpload={{
singular: getLangText('Upload Screenshot'),
plural: getLangText('Upload Screenshots')
}}
required/>
</Property>
</Form>
<Form
buttons={{}}
className="ascribe-form-bordered">
<Property
ref="termsForm"
name="terms"
className="ascribe-property-collapsible-toggle"
style={{paddingBottom: 0}}>

View File

@ -56,7 +56,7 @@ const PRRegisterPiece = React.createClass({
const { currentUser } = this.state;
const { location } = this.props;
setDocumentTitle(getLangText('Submission form'));
setDocumentTitle(getLangText('Submit to Portfolio Review'));
return (
<Row>
<Col xs={6}>

View File

@ -508,7 +508,10 @@ fieldset[disabled] .btn-secondary.active {
> pre,
> select,
> span:not(.glyphicon),
> p,
> p > span,
> textarea {
color: $ascribe-dark-blue;
font-family: $ascribe--font;
font-weight: $ascribe--font-weight-light;
}

View File

@ -30,11 +30,14 @@ $ascribe-red-error: rgb(169, 68, 66);
border-left: 3px solid rgba($ascribe-red-error, 1);
> div {
> span {
color: rgba($ascribe-red-error, 1);
font-size: .9em;
margin-right: 1em;
> p {
> span {
color: rgba($ascribe-red-error, 1);
font-size: .9em;
margin-right: 1em;
}
}
> input,
> textarea {
@ -74,6 +77,7 @@ $ascribe-red-error: rgb(169, 68, 66);
padding-left: 1.5em;
padding-right: 1.5em;
padding-top: 1em;
padding-bottom: 1em;
text-align: left;
width: 100%;
@ -86,10 +90,13 @@ $ascribe-red-error: rgb(169, 68, 66);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
> span {
color: rgba(0, 0, 0, .5);
font-size: .9em;
font-weight: normal;
> p {
height: 20px;
margin-bottom: 0;
> span {
font-size: .9em;
font-weight: normal;
}
}
> div {
@ -139,7 +146,7 @@ $ascribe-red-error: rgb(169, 68, 66);
padding: 0;
}
> textarea{
> textarea {
color: #666;
margin-top: 1em;
padding: 0;
@ -221,16 +228,14 @@ $ascribe-red-error: rgb(169, 68, 66);
}
.input-upload-file-button-property {
background-color: white;
padding: 1em 0 1em 0;
text-align: right;
button {
.btn {
font-size: 1em;
margin-right: 1em;
}
span + button {
span + .btn {
margin-left: 1em;
}
}

View File

@ -35,14 +35,7 @@
form {
border-top: none;
}
form:first-child {
border-bottom: none;
}
form + form, form:last-child {
border-bottom: 1px solid rgba(0, 0, 0, .05);
}
}
}

View File

@ -178,7 +178,7 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important;
border: none;
}
.ascribe-property > span {
.ascribe-property > p > span {
color: white;
}