diff --git a/js/components/ascribe_detail/further_details.js b/js/components/ascribe_detail/further_details.js index 57c6ebe1..36e79811 100644 --- a/js/components/ascribe_detail/further_details.js +++ b/js/components/ascribe_detail/further_details.js @@ -15,6 +15,8 @@ import GlobalNotificationActions from '../../actions/global_notification_actions import FurtherDetailsFileuploader from './further_details_fileuploader'; +import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils'; + let FurtherDetails = React.createClass({ propTypes: { editable: React.PropTypes.bool, @@ -48,15 +50,6 @@ let FurtherDetails = React.createClass({ }); }, - isReadyForFormSubmission(files) { - files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled'); - if(files.length > 0 && files[0].status === 'upload successful') { - return true; - } else { - return false; - } - }, - render() { //return (); return ( @@ -88,7 +81,7 @@ let FurtherDetails = React.createClass({ file.status !== 'deleted' && file.status !== 'canceled'); - if (files.length > 0 && files[0].status === 'upload successful') { - return true; - } else { - return false; - } - }, - render() { let currentUser = this.state.currentUser; let enableLocalHashing = currentUser && currentUser.profile ? currentUser.profile.hash_locally : false; @@ -117,7 +110,7 @@ let RegisterPieceForm = React.createClass({ {this.props.breadcrumbs.map((breadcrumb, i) => { return ( - +
{this.props.breadcrumbs[i]} diff --git a/js/components/ascribe_uploader/file_drag_and_drop_dialog.js b/js/components/ascribe_uploader/file_drag_and_drop_dialog.js index 306eb6f1..f5399f9c 100644 --- a/js/components/ascribe_uploader/file_drag_and_drop_dialog.js +++ b/js/components/ascribe_uploader/file_drag_and_drop_dialog.js @@ -57,7 +57,13 @@ let FileDragAndDropDialog = React.createClass({ if(this.props.multipleFiles) { return ( - {getLangText('Click or drag to add files')} +

{getLangText('Drag files here')}

+

{getLangText('or')}

+ + {getLangText('choose files to upload')} +
); } else { diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js index d6d2f939..90c76667 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js @@ -435,7 +435,9 @@ var ReactS3FineUploader = React.createClass({ }, onCancel(id) { - this.removeFileWithIdFromFilesToUpload(id); + + // when a upload is canceled, we need to update this components file array + this.setStatusOfFile(id, 'canceled'); let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000); GlobalNotificationActions.appendGlobalNotification(notification); @@ -455,6 +457,7 @@ var ReactS3FineUploader = React.createClass({ }, onProgress(id, name, uploadedBytes, totalBytes) { + let newState = React.addons.update(this.state, { filesToUpload: { [id]: { progress: { $set: (uploadedBytes / totalBytes) * 100} } @@ -498,7 +501,9 @@ var ReactS3FineUploader = React.createClass({ let notification = new GlobalNotificationModel(getLangText('Couldn\'t delete file'), 'danger', 10000); GlobalNotificationActions.appendGlobalNotification(notification); } else { - this.removeFileWithIdFromFilesToUpload(id); + + // To hide the file in this component, we need to set it's status to "deleted" + this.setStatusOfFile(id, 'deleted'); let notification = new GlobalNotificationModel(getLangText('File deleted'), 'success', 5000); GlobalNotificationActions.appendGlobalNotification(notification); @@ -521,7 +526,8 @@ var ReactS3FineUploader = React.createClass({ }, handleDeleteFile(fileId) { - // In some instances (when the file was already uploaded and is just displayed to the user) + // In some instances (when the file was already uploaded and is just displayed to the user + // - for example in the loan contract or additional files dialog) // fineuploader does not register an id on the file (we do, don't be confused by this!). // Since you can only delete a file by its id, we have to implement this method ourselves // @@ -532,7 +538,7 @@ var ReactS3FineUploader = React.createClass({ if(this.state.filesToUpload[fileId].status !== 'online') { // delete file from server this.state.uploader.deleteFile(fileId); - // this is being continues in onDeleteFile, as + // this is being continued in onDeleteFile, as // fineuploaders deleteFile does not return a correct callback or // promise } else { @@ -739,31 +745,23 @@ var ReactS3FineUploader = React.createClass({ this.setState(newState); }, - removeFileWithIdFromFilesToUpload(fileId) { - // also, sync files from state with the ones from fineuploader - let filesToUpload = JSON.parse(JSON.stringify(this.state.filesToUpload)); - - // splice because I can - filesToUpload.splice(fileId, 1); - - // set state - let newState = React.addons.update(this.state, { - filesToUpload: { $set: filesToUpload } - }); - this.setState(newState); - }, - setStatusOfFile(fileId, status) { // also, sync files from state with the ones from fineuploader let filesToUpload = JSON.parse(JSON.stringify(this.state.filesToUpload)); - // splice because I can filesToUpload[fileId].status = status; + // is status is set to deleted or canceled, we also need to reset the progress + // back to zero + if(status === 'deleted' || status === 'canceled') { + filesToUpload[fileId].progress = 0; + } + // set state let newState = React.addons.update(this.state, { filesToUpload: { $set: filesToUpload } }); + this.setState(newState); }, diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js b/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js new file mode 100644 index 00000000..dbf62619 --- /dev/null +++ b/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js @@ -0,0 +1,16 @@ +'use strict'; + +/** + * Returns a boolean if there has been at least one file uploaded + * successfully without it being deleted or canceled. + * @param {array of files} files provided by react fine uploader + * @return {Boolean} + */ +export function isReadyForFormSubmission(files) { + files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled'); + if (files.length > 0 && files[0].status === 'upload successful') { + return true; + } else { + return false; + } +} \ No newline at end of file diff --git a/js/components/whitelabel/wallet/components/cyland/ascribe_buttons/cyland_submit_button.js b/js/components/whitelabel/wallet/components/cyland/ascribe_buttons/cyland_submit_button.js index a19a73c6..2bb35719 100644 --- a/js/components/whitelabel/wallet/components/cyland/ascribe_buttons/cyland_submit_button.js +++ b/js/components/whitelabel/wallet/components/cyland/ascribe_buttons/cyland_submit_button.js @@ -3,16 +3,42 @@ import React from 'react'; import classNames from 'classnames'; +import Moment from 'moment'; + +import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../../stores/whitelabel_store'; + import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; -import CylandPieceSubmitForm from '../ascribe_forms/cyland_form_submit'; +import LoanForm from '../../../../../ascribe_forms/form_loan'; + +import ApiUrls from '../../../../../../constants/api_urls'; import { getLangText } from '../../../../../../utils/lang_utils'; +import { getAclFormMessage } from '../../../../../../utils/form_utils'; let CylandSubmitButton = React.createClass({ propTypes: { className: React.PropTypes.string, handleSuccess: React.PropTypes.func, - piece: React.PropTypes.object.isRequired + piece: React.PropTypes.object.isRequired, + username: React.PropTypes.string + }, + + getInitialState() { + return WhitelabelStore.getState(); + }, + + componentDidMount() { + WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); + }, + + componentWillUnmount() { + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); }, getSubmitButton() { @@ -25,16 +51,26 @@ let CylandSubmitButton = React.createClass({ }, render() { + let today = new Moment(); + let loanEndDate = new Moment(); + loanEndDate.add(1000, 'years'); + return ( - - ); } }); diff --git a/js/components/whitelabel/wallet/components/cyland/ascribe_forms/cyland_additional_data_form.js b/js/components/whitelabel/wallet/components/cyland/ascribe_forms/cyland_additional_data_form.js index 364360c8..cddabb0c 100644 --- a/js/components/whitelabel/wallet/components/cyland/ascribe_forms/cyland_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/cyland/ascribe_forms/cyland_additional_data_form.js @@ -43,6 +43,7 @@ let CylandAdditionalDataForm = React.createClass({ extradata: extradata, piece_id: this.props.piece.id }; + }, setIsUploadReady(isReady) { @@ -51,8 +52,15 @@ let CylandAdditionalDataForm = React.createClass({ }); }, - isReadyForFormSubmission() { - return true; + isReadyForFormSubmission(files) { + let uploadedFiles = files.filter((file) => file.status === 'upload successful'); + let uploadingFiles = files.filter((file) => file.status === 'submitting'); + + if (uploadedFiles.length > 0 && uploadingFiles.length === 0) { + return true; + } else { + return false; + } }, render() { diff --git a/js/components/whitelabel/wallet/components/cyland/ascribe_forms/cyland_form_submit.js b/js/components/whitelabel/wallet/components/cyland/ascribe_forms/cyland_form_submit.js deleted file mode 100644 index bbc5f889..00000000 --- a/js/components/whitelabel/wallet/components/cyland/ascribe_forms/cyland_form_submit.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -import React from 'react'; - -import Form from '../../../../../ascribe_forms/form'; -import Property from '../../../../../ascribe_forms/property'; -import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable'; -import InputCheckbox from '../../../../../ascribe_forms/input_checkbox'; - -import Alert from 'react-bootstrap/lib/Alert'; - -import AppConstants from '../../../../../../constants/application_constants'; -import ApiUrls from '../../../../../../constants/api_urls'; - -import { getLangText } from '../../../../../../utils/lang_utils.js'; - -import requests from '../../../../../../utils/requests'; - -let CylandPieceSubmitForm = React.createClass({ - propTypes: { - piece: React.PropTypes.object, - handleSuccess: React.PropTypes.func - }, - - render() { - //return ( - //
- //

- // - //

- //
} - // spinner={ - //
- // - //
}> - // - // - // - // - // - // - // - // - // - // {' ' + getLangText('I agree to the Terms of Service the art price') + ' '} - // (
- // {getLangText('read')} - // ) - //
- // - // - // - //

{getLangText('Are you sure you want to submit to the prize?')}

- //

{getLangText('This is an irrevocable action%s', '.')}

- //
- // - //); - return null; - } -}); - - -export default CylandPieceSubmitForm; diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_hero.js b/js/components/whitelabel/wallet/components/cyland/cyland_hero.js new file mode 100644 index 00000000..b98f407e --- /dev/null +++ b/js/components/whitelabel/wallet/components/cyland/cyland_hero.js @@ -0,0 +1,20 @@ +'use strict'; + +import React from 'react'; +import constants from '../../../../constants/application_constants'; + + +let Hero = React.createClass({ + render() { + return ( +
+ Sluice Art Prize +
+ ); + } +}); + +export default Hero; diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js new file mode 100644 index 00000000..1512587e --- /dev/null +++ b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js @@ -0,0 +1,77 @@ +'use strict'; + +import React from 'react'; +import Router from 'react-router'; + +import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; +import ButtonGroup from 'react-bootstrap/lib/ButtonGroup'; + +import UserStore from '../../../../../stores/user_store'; +import UserActions from '../../../../../actions/user_actions'; + +import { mergeOptions } from '../../../../../utils/general_utils'; + + +let CylandLanding = React.createClass({ + + mixins: [Router.Navigation], + + getInitialState() { + return mergeOptions( + UserStore.getState() + ); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + + // if user is already logged in, redirect him to piece list + if(this.state.currentUser && this.state.currentUser.email) { + // FIXME: hack to redirect out of the dispatch cycle + window.setTimeout(() => this.replaceWith('pieces'), 0); + } + }, + + render() { + return ( +
+
+
+
+ +
+
+
+
+ Existing ascribe user? +
+ + Log in + +
+
+
+ Do you need an account? +
+ + Sign up + +
+
+
+
+
+ ); + } +}); + +export default CylandLanding; diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js index b01d648d..5a17ae94 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js @@ -85,17 +85,6 @@ let CylandRegisterPiece = React.createClass({ handleRegisterSuccess(response){ - // once the user was able to register a piece successfully, we need to make sure to keep - // the piece list up to date - PieceListActions.fetchPieceList( - this.state.page, - this.state.pageSize, - this.state.searchTerm, - this.state.orderBy, - this.state.orderAsc, - this.state.filterBy - ); - // also start loading the piece for the next step if(response && response.piece) { PieceActions.updatePiece(response.piece); @@ -111,6 +100,18 @@ let CylandRegisterPiece = React.createClass({ handleLoanSuccess(response) { let notification = new GlobalNotificationModel(response.notification, 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); + + // once the user was able to register + loan a piece successfully, we need to make sure to keep + // the piece list up to date + PieceListActions.fetchPieceList( + this.state.page, + this.state.pageSize, + this.state.searchTerm, + this.state.orderBy, + this.state.orderAsc, + this.state.filterBy + ); + PieceActions.fetchOne(this.state.piece.id); this.transitionTo('piece', {pieceId: this.state.piece.id}); }, diff --git a/sass/ascribe_slides_container.scss b/sass/ascribe_slides_container.scss index 773a68bc..4b96017c 100644 --- a/sass/ascribe_slides_container.scss +++ b/sass/ascribe_slides_container.scss @@ -30,6 +30,7 @@ border: 1px solid #EEE; border-right: 1px solid rgba(0, 0, 0, 0); margin-bottom: 0.6em; + background-color: white; .active { color: #666; diff --git a/sass/ascribe_uploader.scss b/sass/ascribe_uploader.scss index c96109ed..2aab021a 100644 --- a/sass/ascribe_uploader.scss +++ b/sass/ascribe_uploader.scss @@ -41,6 +41,9 @@ position: relative; display: inline-block; + margin-left: .7em; + margin-right: .7em; + .delete-file { display: block; background-color: black;