diff --git a/js/actions/contract_agreement_list_actions.js b/js/actions/contract_agreement_list_actions.js index 86988f47..a5059ff0 100644 --- a/js/actions/contract_agreement_list_actions.js +++ b/js/actions/contract_agreement_list_actions.js @@ -89,6 +89,10 @@ class ContractAgreementListActions { }); } + flushContractAgreementList(){ + this.actions.updateContractAgreementList(null); + } + } export default alt.createActions(ContractAgreementListActions); diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index 084de194..e3c7fa1c 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -162,21 +162,24 @@ let AclButton = React.createClass({ }, render() { - let shouldDisplay = this.props.availableAcls[this.props.action]; - let aclProps = this.actionProperties(); - let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : ''; - return ( - - {this.sanitizeAction()} - - } - handleSuccess={aclProps.handleSuccess} - title={aclProps.title}> - {aclProps.form} - - ); + if (this.props.availableAcls){ + let shouldDisplay = this.props.availableAcls[this.props.action]; + let aclProps = this.actionProperties(); + let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : ''; + return ( + + {this.sanitizeAction()} + + } + handleSuccess={aclProps.handleSuccess} + title={aclProps.title}> + {aclProps.form} + + ); + } + return null; } }); diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index aa3d81ba..f2dbf682 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -152,8 +152,9 @@ let Edition = React.createClass({ + show={!!(this.state.currentUser.username + || this.props.edition.acl.acl_edit + || this.props.edition.public_note)}> {return {'bitcoin_id': this.props.edition.bitcoin_id}; }} label={getLangText('Personal note (private)')} diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 8642ee05..f14cf743 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -237,12 +237,11 @@ let PieceContainer = React.createClass({ + show={!!(this.state.currentUser.username || this.state.piece.public_note)}> 0) { + return ( +
+ {getLangText('Send loan request')} + } + spinner={ + + + + }> +
+

{getLangText('Contract form')}

+
+ + + + {this.getContracts()} + + {getLangText('Appendix')} + + +
+ ); + } return ( -
- {getLangText('Send loan request')} - } - spinner={ - - - - }> -
-

{getLangText('Contract form')}

-
- - - - {this.getContracts()} - - {getLangText('Appendix')} - - -
+
+

+ {getLangText('No private contracts found, please go to the ')} + {getLangText('settings page')} + {getLangText(' and create them.')} +

+
); } }); diff --git a/js/components/ascribe_forms/form_copyright_association.js b/js/components/ascribe_forms/form_copyright_association.js new file mode 100644 index 00000000..53c7245f --- /dev/null +++ b/js/components/ascribe_forms/form_copyright_association.js @@ -0,0 +1,75 @@ +'use strict'; + +import React from 'react'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +import Form from './form'; +import Property from './property'; + +import ApiUrls from '../../constants/api_urls'; +import AppConstants from '../../constants/application_constants'; + +import { getLangText } from '../../utils/lang_utils'; + +let CopyrightAssociationForm = React.createClass({ + propTypes: { + currentUser: React.PropTypes.object + }, + + handleSubmitSuccess(){ + let notification = getLangText('Copyright association updated'); + notification = new GlobalNotificationModel(notification, 'success', 10000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + getProfileFormData(){ + return {email: this.props.currentUser.email}; + }, + + render() { + let selectedState = -1; + if (this.props.currentUser + && this.props.currentUser.profile + && this.props.currentUser.profile.copyright_association) { + selectedState = AppConstants.copyrightAssociations.indexOf(this.props.currentUser.profile.copyright_association); + } + if (this.props.currentUser && this.props.currentUser.email){ + return ( +
+ + + +
+
+ ); + } + return null; + } +}); + +export default CopyrightAssociationForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index dacbae77..f8946486 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -35,6 +35,7 @@ let LoanForm = React.createClass({ url: React.PropTypes.string, id: React.PropTypes.object, message: React.PropTypes.string, + createPublicContractAgreement: React.PropTypes.bool, handleSuccess: React.PropTypes.func }, @@ -44,7 +45,8 @@ let LoanForm = React.createClass({ showPersonalMessage: true, showEndDate: true, showStartDate: true, - showPassword: true + showPassword: true, + createPublicContractAgreement: true }; }, @@ -61,7 +63,7 @@ let LoanForm = React.createClass({ // however, it can also be that at the time the component is mounting, // the email is not defined (because it's asynchronously fetched from the server). // Then we need to update it as soon as it is included into LoanForm's props. - if(nextProps && nextProps.email) { + if(nextProps && nextProps.email && this.props.email !== nextProps.email) { this.getContractAgreementsOrCreatePublic(nextProps.email); } }, @@ -77,9 +79,11 @@ let LoanForm = React.createClass({ getContractAgreementsOrCreatePublic(email){ ContractAgreementListActions.flushContractAgreementList(); if (email) { + // fetch the available contractagreements (pending/accepted) ContractAgreementListActions.fetchAvailableContractAgreementList(email).then( (contractAgreementList) => { - if (!contractAgreementList) { + if (!contractAgreementList && this.props.createPublicContractAgreement) { + // for public contracts: fetch the public contract and create a contractagreement if available ContractAgreementListActions.createContractAgreementFromPublicContract(email); } } diff --git a/js/components/ascribe_forms/form_login.js b/js/components/ascribe_forms/form_login.js index 86a20119..79bced6c 100644 --- a/js/components/ascribe_forms/form_login.js +++ b/js/components/ascribe_forms/form_login.js @@ -27,7 +27,7 @@ let LoginForm = React.createClass({ onLogin: React.PropTypes.func }, - mixins: [Router.Navigation], + mixins: [Router.Navigation, Router.State], getDefaultProps() { return { @@ -95,6 +95,7 @@ let LoginForm = React.createClass({ }, render() { + let email = this.getQuery().email || null; return (
diff --git a/js/components/ascribe_settings/account_settings.js b/js/components/ascribe_settings/account_settings.js index b4d46b2d..93a419c2 100644 --- a/js/components/ascribe_settings/account_settings.js +++ b/js/components/ascribe_settings/account_settings.js @@ -13,6 +13,8 @@ import Property from '../ascribe_forms/property'; import InputCheckbox from '../ascribe_forms/input_checkbox'; import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph'; +import CopyrightAssociationForm from '../ascribe_forms/form_copyright_association'; + import ApiUrls from '../../constants/api_urls'; import AppConstants from '../../constants/application_constants'; @@ -117,6 +119,7 @@ let AccountSettings = React.createClass({ show={true} defaultExpanded={true}> {content} + {profile} {/* diff --git a/js/components/ascribe_slides_container/slides_container.js b/js/components/ascribe_slides_container/slides_container.js index 84dff61c..53092a38 100644 --- a/js/components/ascribe_slides_container/slides_container.js +++ b/js/components/ascribe_slides_container/slides_container.js @@ -178,7 +178,7 @@ let SlidesContainer = React.createClass({ let breadcrumbs = []; ReactAddons.Children.map(this.props.children, (child, i) => { - if(i >= this.state.startFrom && child.props['data-slide-title']) { + if(child && i >= this.state.startFrom && child.props['data-slide-title']) { breadcrumbs.push(child.props['data-slide-title']); } }); @@ -229,7 +229,7 @@ let SlidesContainer = React.createClass({ // since the default parameter of startFrom is -1, we do not need to check // if its actually present in the url bar, as it will just not match - if(i >= this.state.startFrom) { + if(child && i >= this.state.startFrom) { return ReactAddons.addons.cloneWithProps(child, { className: 'ascribe-slide', style: { diff --git a/js/components/signup_container.js b/js/components/signup_container.js index 46813b59..856e4af2 100644 --- a/js/components/signup_container.js +++ b/js/components/signup_container.js @@ -1,8 +1,12 @@ 'use strict'; import React from 'react'; +import Router from 'react-router'; import SignupForm from './ascribe_forms/form_signup'; +import { getLangText } from '../utils/lang_utils'; + +let Link = Router.Link; let SignupContainer = React.createClass({ getInitialState() { @@ -33,7 +37,12 @@ let SignupContainer = React.createClass({ return (
+
+ {getLangText('Already an ascribe user')}? {getLangText('Log in')}...
+ {getLangText('Forgot my password')}? {getLangText('Rescue me')}... +
+ ); } }); diff --git a/js/components/whitelabel/wallet/components/cyland/ascribe_detail/cyland_piece_container.js b/js/components/whitelabel/wallet/components/cyland/ascribe_detail/cyland_piece_container.js index 395cd86e..dab5eda4 100644 --- a/js/components/whitelabel/wallet/components/cyland/ascribe_detail/cyland_piece_container.js +++ b/js/components/whitelabel/wallet/components/cyland/ascribe_detail/cyland_piece_container.js @@ -98,12 +98,11 @@ let CylandPieceContainer = React.createClass({ + show={!!(this.state.currentUser.username || this.state.piece.public_note)}> {return {'id': this.state.piece.id}; }} label={getLangText('Personal note (private)')} - defaultValue={this.state.piece.private_note ? this.state.piece.private_note : null} + defaultValue={this.state.piece.private_note || null} placeholder={getLangText('Enter your comments ...')} editable={true} successMessage={getLangText('Private note saved')} 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 6e8a77c3..84713bd9 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js @@ -10,9 +10,6 @@ import Row from 'react-bootstrap/lib/Row'; import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece'; -import Property from '../../../../ascribe_forms/property'; -import InputCheckbox from '../../../../ascribe_forms/input_checkbox'; - import WhitelabelActions from '../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../stores/whitelabel_store'; @@ -136,7 +133,7 @@ let CylandRegisterPiece = React.createClass({ this.transitionTo('piece', {pieceId: this.state.piece.id}); }, - // We need to increase the step to lock the forms that are already filed out + // We need to increase the step to lock the forms that are already filled out incrementStep() { // also increase step let newStep = this.state.step + 1; diff --git a/js/components/whitelabel/wallet/components/ikonotv/ascribe_accordion_list/ikonotv_accordion_list_item.js b/js/components/whitelabel/wallet/components/ikonotv/ascribe_accordion_list/ikonotv_accordion_list_item.js index b716914a..2dd3cc2f 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ascribe_accordion_list/ikonotv_accordion_list_item.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ascribe_accordion_list/ikonotv_accordion_list_item.js @@ -63,12 +63,16 @@ let IkonotvAccordionListItem = React.createClass({ return (
- + + + - {getLangText('Loan to IkonoTV')} - - ); - }, - render() { + let piece = this.props.piece; + let startFrom = 1; - let today = new Moment(); - let enddate = new Moment(); - enddate.add(1, 'years'); + // In the Ikonotv loan page a user has to complete two steps. + // Since every one of those steps is atomic a user should always be able to continue + // where he left of. + // This is why we start the process form slide 1/2 if the user has already finished + // it in another session. + if(piece && piece.extra_data && Object.keys(piece.extra_data).length > 0) { + startFrom = 1; + } return ( - - - - + + {getLangText('Loan to IkonoTV')} + ); } }); diff --git a/js/components/whitelabel/wallet/components/ikonotv/ascribe_detail/ikonotv_piece_container.js b/js/components/whitelabel/wallet/components/ikonotv/ascribe_detail/ikonotv_piece_container.js index 3f84ded7..3efa2185 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ascribe_detail/ikonotv_piece_container.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ascribe_detail/ikonotv_piece_container.js @@ -5,32 +5,23 @@ import React from 'react'; import PieceActions from '../../../../../../actions/piece_actions'; import PieceStore from '../../../../../../stores/piece_store'; -import PieceListActions from '../../../../../../actions/piece_list_actions'; -import PieceListStore from '../../../../../../stores/piece_list_store'; - import UserStore from '../../../../../../stores/user_store'; import Piece from '../../../../../../components/ascribe_detail/piece'; -import ListRequestActions from '../../../../../ascribe_forms/list_form_request_actions'; -import AclButtonList from '../../../../../ascribe_buttons/acl_button_list'; -import DeleteButton from '../../../../../ascribe_buttons/delete_button'; +import AppConstants from '../../../../../../constants/application_constants'; +import Form from '../../../../../../components/ascribe_forms/form'; +import Property from '../../../../../../components/ascribe_forms/property'; +import InputTextAreaToggable from '../../../../../../components/ascribe_forms/input_textarea_toggable'; import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph'; -import IkonotvSubmitButton from '../ascribe_buttons/ikonotv_submit_button'; - import HistoryIterator from '../../../../../ascribe_detail/history_iterator'; +import Note from '../../../../../ascribe_detail/note'; import DetailProperty from '../../../../../ascribe_detail/detail_property'; - -import GlobalNotificationModel from '../../../../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; - -import AclProxy from '../../../../../acl_proxy'; - -import AppConstants from '../../../../../../constants/application_constants'; +import ApiUrls from '../../../../../../constants/api_urls'; import { getLangText } from '../../../../../../utils/lang_utils'; import { mergeOptions } from '../../../../../../utils/general_utils'; @@ -40,8 +31,7 @@ let IkonotvPieceContainer = React.createClass({ getInitialState() { return mergeOptions( PieceStore.getState(), - UserStore.getState(), - PieceListStore.getState() + UserStore.getState() ); }, @@ -49,7 +39,6 @@ let IkonotvPieceContainer = React.createClass({ PieceStore.listen(this.onChange); PieceActions.fetchOne(this.props.params.pieceId); UserStore.listen(this.onChange); - PieceListStore.listen(this.onChange); }, componentWillReceiveProps(nextProps) { @@ -67,7 +56,6 @@ let IkonotvPieceContainer = React.createClass({ PieceActions.updatePiece({}); PieceStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); - PieceListStore.unlisten(this.onChange); }, onChange(state) { @@ -78,59 +66,6 @@ let IkonotvPieceContainer = React.createClass({ PieceActions.fetchOne(this.props.params.pieceId); }, - handleSubmitSuccess(response) { - PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, this.state.filterBy); - - this.loadPiece(); - let notification = new GlobalNotificationModel(response.notification, 'success', 10000); - GlobalNotificationActions.appendGlobalNotification(notification); - }, - - getActions(){ - if (this.state.piece && - this.state.piece.notifications && - this.state.piece.notifications.length > 0) { - return ( - ); - } - else { - - //We need to disable the normal acl_loan because we're inserting a custom acl_loan button - let availableAcls; - - if(this.state.piece && this.state.piece.acl && typeof this.state.piece.acl.acl_loan !== 'undefined') { - // make a copy to not have side effects - availableAcls = mergeOptions({}, this.state.piece.acl); - availableAcls.acl_loan = false; - } - - return ( - - - - - - - ); - } - }, - render() { if(this.state.piece && this.state.piece.title) { return ( @@ -152,14 +87,28 @@ let IkonotvPieceContainer = React.createClass({
- } - buttons={this.getActions()}> + }> + 0}> + + {return {'id': this.state.piece.id}; }} + label={getLangText('Personal note (private)')} + defaultValue={this.state.piece.private_note || null} + placeholder={getLangText('Enter your comments ...')} + editable={true} + successMessage={getLangText('Private note saved')} + url={ApiUrls.note_private_piece} + currentUser={this.state.currentUser}/> + + ); } else { @@ -172,4 +121,43 @@ let IkonotvPieceContainer = React.createClass({ } }); + +let IkonotvPieceDetails = React.createClass({ + propTypes: { + piece: React.PropTypes.object + }, + + render() { + if (this.props.piece && Object.keys(this.props.piece.extra_data).length !== 0){ + return ( + + + {Object.keys(this.props.piece.extra_data).map((data, i) => { + let label = data.replace('_', ' '); + return ( + ); + } + )} +
+ +
+ ); + } + return null; + } +}); + export default IkonotvPieceContainer; diff --git a/js/components/whitelabel/wallet/components/ikonotv/ascribe_forms/ikonotv_artist_details_form.js b/js/components/whitelabel/wallet/components/ikonotv/ascribe_forms/ikonotv_artist_details_form.js new file mode 100644 index 00000000..e840173a --- /dev/null +++ b/js/components/whitelabel/wallet/components/ikonotv/ascribe_forms/ikonotv_artist_details_form.js @@ -0,0 +1,145 @@ +'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 FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader'; + +import ApiUrls from '../../../../../../constants/api_urls'; +import AppConstants from '../../../../../../constants/application_constants'; + +import requests from '../../../../../../utils/requests'; + +import { getLangText } from '../../../../../../utils/lang_utils'; + + +let IkonotvArtistDetailsForm = React.createClass({ + propTypes: { + handleSuccess: React.PropTypes.func.isRequired, + piece: React.PropTypes.object.isRequired, + + disabled: React.PropTypes.bool + }, + + getInitialState() { + return { + isUploadReady: true + }; + }, + + getFormData() { + let extradata = {}; + let formRefs = this.refs.form.refs; + + // Put additional fields in extra data object + Object + .keys(formRefs) + .forEach((fieldName) => { + extradata[fieldName] = formRefs[fieldName].state.value; + }); + + return { + extradata: extradata, + piece_id: this.props.piece.id + }; + + }, + + uploadStarted() { + this.setState({ + isUploadReady: false + }); + }, + + setIsUploadReady(isReady) { + this.setState({ + isUploadReady: isReady + }); + }, + + render() { + if(this.props.piece && this.props.piece.id && this.props.piece.extra_data) { + return ( +
+ {getLangText('Proceed to artwork details')} + + } + spinner={ +
+ +
+ }> +
+

+ {getLangText('Artist Details')} +

+
+ + + + + + + + + + + + +
+ ); + } else { + return ( +
+ +
+ ); + } + } +}); + +export default IkonotvArtistDetailsForm; \ No newline at end of file diff --git a/js/components/whitelabel/wallet/components/ikonotv/ascribe_forms/ikonotv_artwork_details_form.js b/js/components/whitelabel/wallet/components/ikonotv/ascribe_forms/ikonotv_artwork_details_form.js new file mode 100644 index 00000000..0ff3ae50 --- /dev/null +++ b/js/components/whitelabel/wallet/components/ikonotv/ascribe_forms/ikonotv_artwork_details_form.js @@ -0,0 +1,162 @@ +'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 ApiUrls from '../../../../../../constants/api_urls'; +import AppConstants from '../../../../../../constants/application_constants'; + +import requests from '../../../../../../utils/requests'; + +import { getLangText } from '../../../../../../utils/lang_utils'; + + +let IkonotvArtworkDetailsForm = React.createClass({ + propTypes: { + handleSuccess: React.PropTypes.func.isRequired, + piece: React.PropTypes.object.isRequired, + + disabled: React.PropTypes.bool + }, + + getInitialState() { + return { + isUploadReady: true + }; + }, + + getFormData() { + let extradata = {}; + let formRefs = this.refs.form.refs; + + // Put additional fields in extra data object + Object + .keys(formRefs) + .forEach((fieldName) => { + extradata[fieldName] = formRefs[fieldName].state.value; + }); + + return { + extradata: extradata, + piece_id: this.props.piece.id + }; + + }, + + uploadStarted() { + this.setState({ + isUploadReady: false + }); + }, + + setIsUploadReady(isReady) { + this.setState({ + isUploadReady: isReady + }); + }, + + render() { + if(this.props.piece && this.props.piece.id && this.props.piece.extra_data) { + return ( +
+ {getLangText('Proceed to loan')} + + } + spinner={ +
+ +
+ }> +
+

+ {getLangText('Artwork Details')} +

+
+ + + + + + + + + + + + + + + + + + +
+ ); + } else { + return ( +
+ +
+ ); + } + } +}); + +export default IkonotvArtworkDetailsForm; \ No newline at end of file diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js index 6a63657c..0f631758 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js @@ -6,20 +6,24 @@ import Router from 'react-router'; import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import Button from 'react-bootstrap/lib/Button'; -import Form from '../../../../ascribe_forms/form'; -import Property from '../../../../ascribe_forms/property'; -import InputCheckbox from '../../../../ascribe_forms/input_checkbox'; - import NotificationActions from '../../../../../actions/notification_actions'; import NotificationStore from '../../../../../stores/notification_store'; + +import UserActions from '../../../../../actions/user_actions'; +import UserStore from '../../../../../stores/user_store'; + +import OwnershipFetcher from '../../../../../fetchers/ownership_fetcher'; + import WhitelabelStore from '../../../../../stores/whitelabel_store'; +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; import GlobalNotificationModel from '../../../../../models/global_notification_model'; import GlobalNotificationActions from '../../../../../actions/global_notification_actions'; -import apiUrls from '../../../../../constants/api_urls'; +import CopyrightAssociationForm from '../../../../ascribe_forms/form_copyright_association'; + +import AppConstants from '../../../../../constants/application_constants'; -import requests from '../../../../../utils/requests'; import { getLangText } from '../../../../../utils/lang_utils'; import { mergeOptions } from '../../../../../utils/general_utils'; @@ -32,13 +36,17 @@ let IkonotvContractNotifications = React.createClass({ getInitialState() { return mergeOptions( NotificationStore.getState(), + UserStore.getState(), WhitelabelStore.getState() ); }, componentDidMount() { NotificationStore.listen(this.onChange); + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); if (this.state.contractAgreementListNotifications === null){ NotificationActions.fetchContractAgreementListNotifications(); } @@ -87,8 +95,9 @@ let IkonotvContractNotifications = React.createClass({ getAppendix() { let notifications = this.state.contractAgreementListNotifications[0]; let appendix = notifications.contract_agreement.appendix; - if (appendix) { - return (
+ if (appendix && appendix.default) { + return ( +

{getLangText('Appendix')}

                         {appendix.default}
@@ -99,30 +108,54 @@ let IkonotvContractNotifications = React.createClass({
         return null;
     },
 
+    handleConfirm() {
+        let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement;
+        OwnershipFetcher.confirmContractAgreement(contractAgreement).then(
+            () => this.handleConfirmSuccess()
+        );
+    },
+
     handleConfirmSuccess() {
-        let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 10000);
+        let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 5000);
         GlobalNotificationActions.appendGlobalNotification(notification);
         this.transitionTo('pieces');
     },
 
     handleDeny() {
         let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement;
-        requests.put(apiUrls.ownership_contract_agreements_deny, {contract_agreement_id: contractAgreement.id}).then(
+        OwnershipFetcher.denyContractAgreement(contractAgreement).then(
             () => this.handleDenySuccess()
         );
     },
 
     handleDenySuccess() {
-        let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 10000);
+        let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 5000);
         GlobalNotificationActions.appendGlobalNotification(notification);
         this.transitionTo('pieces');
     },
 
+    getCopyrightAssociationForm(){
+        let c = this.state.currentUser;
+
+        if (c && c.profile && !c.profile.copyright_association) {
+            return (
+                
+

{getLangText('Are you a member of any copyright societies?')}

+ +

+ {AppConstants.copyrightAssociations.join(', ')} +

+ +
+ ); + } + return null; + }, + render() { if (this.state.contractAgreementListNotifications && this.state.contractAgreementListNotifications.length > 0) { - let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement; return (
@@ -133,41 +166,17 @@ let IkonotvContractNotifications = React.createClass({
{this.getContract()} -
- {this.getAppendix} -

{getLangText('Are you a member of any copyright societies?')}

-

- ARS, DACS, Bildkunst, Pictoright, SODRAC, Copyright Agency/Viscopy, SAVA, Bildrecht GmbH, - SABAM, AUTVIS, CREAIMAGEN, SONECA, Copydan, EAU, Kuvasto, GCA, HUNGART, IVARO, SIAE, JASPAR-SPDA, - AKKA/LAA, LATGA-A, SOMAAP, ARTEGESTION, CARIER, BONO, APSAV, SPA, GESTOR, VISaRTA, RAO, LITA, - DALRO, VeGaP, BUS, ProLitteris, AGADU, AUTORARTE, BUBEDRA, BBDA, BCDA, BURIDA, ADAVIS, BSDA + {this.getAppendix()} + {this.getCopyrightAssociationForm()} +

+ +

-
- - -

- }> - - - - {' ' + getLangText('Yes') } - - - - -
diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_landing.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_landing.js index 1e2d3803..3fee2e50 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_landing.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_landing.js @@ -1,10 +1,10 @@ 'use strict'; import React from 'react'; +import Router from 'react-router'; import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; -import UserActions from '../../../../../actions/user_actions'; import UserStore from '../../../../../stores/user_store'; import { getLangText } from '../../../../../utils/lang_utils'; @@ -12,6 +12,8 @@ import { getLangText } from '../../../../../utils/lang_utils'; let IkonotvLanding = React.createClass({ + mixins: [Router.Navigation, Router.State], + getInitialState() { return UserStore.getState(); }, @@ -29,19 +31,19 @@ let IkonotvLanding = React.createClass({ }, getEnterButton() { + let redirect = 'login'; + if(this.state.currentUser && this.state.currentUser.email) { - return ( - - {getLangText('ENTER')} - - ); - } else { - return ( - - {getLangText('ENTER')} - - ); + redirect = 'pieces'; } + else if (this.getQuery() && this.getQuery().redirect) { + redirect = this.getQuery().redirect; + } + return ( + + {getLangText('ENTER')} + + ); }, render() { @@ -52,7 +54,7 @@ let IkonotvLanding = React.createClass({

PROTECT

-

& SHARE

+

& SHARE

Welcome to the ikonoTV

Registration Page

@@ -93,7 +95,7 @@ let IkonotvLanding = React.createClass({

Elizabeth Markevitch

-

Founder & CEO Markevitch Media GmbH

+

Founder & CEO Markevitch Media GmbH

{this.getEnterButton()}
diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js new file mode 100644 index 00000000..700b5678 --- /dev/null +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js @@ -0,0 +1,268 @@ +'use strict'; + +import React from 'react'; +import Moment from 'moment'; +import Router from 'react-router'; + +import Col from 'react-bootstrap/lib/Col'; +import Row from 'react-bootstrap/lib/Row'; + +import PieceListStore from '../../../../../stores/piece_list_store'; +import PieceListActions from '../../../../../actions/piece_list_actions'; + +import UserStore from '../../../../../stores/user_store'; +import UserActions from '../../../../../actions/user_actions'; + +import PieceStore from '../../../../../stores/piece_store'; +import PieceActions from '../../../../../actions/piece_actions'; + +import GlobalNotificationModel from '../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../actions/global_notification_actions'; + +import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece'; +import LoanForm from '../../../../ascribe_forms/form_loan'; + +import IkonotvArtistDetailsForm from './ascribe_forms/ikonotv_artist_details_form'; +import IkonotvArtworkDetailsForm from './ascribe_forms/ikonotv_artwork_details_form'; + +import SlidesContainer from '../../../../ascribe_slides_container/slides_container'; + +import ApiUrls from '../../../../../constants/api_urls'; + +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; + +let IkonotvRegisterPiece = React.createClass({ + + propTypes: { + handleSuccess: React.PropTypes.func, + piece: React.PropTypes.object.isRequired + }, + + mixins: [Router.Navigation, Router.State], + + getInitialState(){ + return mergeOptions( + UserStore.getState(), + PieceListStore.getState(), + PieceStore.getState(), + { + step: 0 + }); + }, + + componentDidMount() { + PieceListStore.listen(this.onChange); + UserStore.listen(this.onChange); + PieceStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + + let queryParams = this.getQuery(); + + // Since every step of this register process is atomic, + // we may need to enter the process at step 1 or 2. + // If this is the case, we'll need the piece number to complete submission. + // It is encoded in the URL as a queryParam and we're checking for it here. + // + // We're using 'in' here as we want to know if 'piece_id' is present in the url, + // we don't care about the value. + if (queryParams && 'piece_id' in queryParams) { + PieceActions.fetchOne(queryParams.piece_id); + } + }, + + componentWillUnmount() { + PieceListStore.unlisten(this.onChange); + UserStore.unlisten(this.onChange); + PieceStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + + if(this.state.currentUser && this.state.currentUser.email) { + // we should also make the fineuploader component editable again + this.setState({ + isFineUploaderActive: true + }); + } + }, + + + handleRegisterSuccess(response){ + + this.refreshPieceList(); + + // also start loading the piece for the next step + if(response && response.piece) { + PieceActions.updatePiece(response.piece); + } + if (!this.canSubmit()) { + this.transitionTo('pieces'); + } + else { + this.incrementStep(); + this.refs.slidesContainer.nextSlide(); + } + + }, + + handleAdditionalDataSuccess() { + + // We need to refetch the piece again after submitting the additional data + // since we want it's otherData to be displayed when the user choses to click + // on the browsers back button. + PieceActions.fetchOne(this.state.piece.id); + + this.refreshPieceList(); + + this.incrementStep(); + + this.refs.slidesContainer.nextSlide(); + }, + + handleLoanSuccess(response) { + let notification = new GlobalNotificationModel(response.notification, 'success', 10000); + GlobalNotificationActions.appendGlobalNotification(notification); + + this.refreshPieceList(); + + PieceActions.fetchOne(this.state.piece.id); + this.transitionTo('piece', {pieceId: this.state.piece.id}); + }, + + // We need to increase the step to lock the forms that are already filled out + incrementStep() { + // also increase step + let newStep = this.state.step + 1; + this.setState({ + step: newStep + }); + }, + + refreshPieceList() { + PieceListActions.fetchPieceList( + this.state.page, + this.state.pageSize, + this.state.searchTerm, + this.state.orderBy, + this.state.orderAsc, + this.state.filterBy + ); + }, + + changeSlide() { + // only transition to the login store, if user is not logged in + // ergo the currentUser object is not properly defined + if(this.state.currentUser && !this.state.currentUser.email) { + this.onLoggedOut(); + } + }, + + // basically redirects to the second slide (index: 1), when the user is not logged in + onLoggedOut() { + this.transitionTo('login'); + }, + + canSubmit() { + let c = this.state.currentUser; + return c && c.acl && c.acl.acl_submit; + }, + + getSlideArtistDetails() { + if (this.canSubmit()) { + return ( +
+ + + 1} + handleSuccess={this.handleAdditionalDataSuccess} + piece={this.state.piece}/> + + +
+ ); + } + return null; + }, + + getSlideArtworkDetails() { + if (this.canSubmit()) { + return ( +
+ + + 1} + handleSuccess={this.handleAdditionalDataSuccess} + piece={this.state.piece}/> + + +
+ ); + } + return null; + }, + + getSlideLoan() { + if (this.canSubmit()) { + + let today = new Moment(); + let enddate = new Moment(); + enddate.add(1, 'years'); + return ( +
+ + + + + +
+ ); + } + return null; + }, + + render() { + return ( + +
+ + + 0} + enableLocalHashing={false} + headerMessage={getLangText('Register work')} + submitMessage={getLangText('Register')} + isFineUploaderActive={this.state.isFineUploaderActive} + handleSuccess={this.handleRegisterSuccess} + onLoggedOut={this.onLoggedOut} /> + + +
+ {this.getSlideArtistDetails()} + {this.getSlideArtworkDetails()} + {this.getSlideLoan()} +
+ ); + } +}); + +export default IkonotvRegisterPiece; diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index 06bac15f..9a742f8b 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -19,7 +19,7 @@ let WalletApp = React.createClass({ let ROUTES = getRoutes(null, subdomain); let header = null; - if ((this.isActive('landing') || this.isActive('login') || this.isActive('signup')) + if ((this.isActive('landing') || this.isActive('login') || this.isActive('signup') || this.isActive('contract_notifications')) && (['ikonotv', 'cyland']).indexOf(subdomain) > -1) { header = (
); diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 9434615f..ab152e1b 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -22,6 +22,7 @@ import CylandPieceList from './components/cyland/cyland_piece_list'; import IkonotvLanding from './components/ikonotv/ikonotv_landing'; import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list'; import IkonotvRequestLoan from './components/ikonotv/ikonotv_request_loan'; +import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvPieceContainer from './components/ikonotv/ascribe_detail/ikonotv_piece_container'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; @@ -73,7 +74,7 @@ let ROUTES = { - + diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index a2726d73..eeabce88 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -74,7 +74,11 @@ let constants = { 'whitelabel': {}, 'raven': { 'url': 'https://0955da3388c64ab29bd32c2a429f9ef4@app.getsentry.com/48351' - } + }, + 'copyrightAssociations': ['ARS', 'DACS', 'Bildkunst', 'Pictoright', 'SODRAC', 'Copyright Agency/Viscopy', 'SAVA', + 'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART', + 'IVARO', 'SIAE', 'JASPAR-SPDA', 'AKKA/LAA', 'LATGA-A', 'SOMAAP', 'ARTEGESTION', 'CARIER', 'BONO', 'APSAV', + 'SPA', 'GESTOR', 'VISaRTA', 'RAO', 'LITA', 'DALRO', 'VeGaP', 'BUS', 'ProLitteris', 'AGADU', 'AUTORARTE', 'BUBEDRA', 'BBDA', 'BCDA', 'BURIDA', 'ADAVIS', 'BSDA'] }; export default constants; diff --git a/js/fetchers/ownership_fetcher.js b/js/fetchers/ownership_fetcher.js index f9ce5f86..b0d88927 100644 --- a/js/fetchers/ownership_fetcher.js +++ b/js/fetchers/ownership_fetcher.js @@ -44,6 +44,14 @@ let OwnershipFetcher = { return requests.get(ApiUrls.ownership_contract_agreements, queryParams); }, + confirmContractAgreement(contractAgreement){ + return requests.put(ApiUrls.ownership_contract_agreements_confirm, {contract_agreement_id: contractAgreement.id}); + }, + + denyContractAgreement(contractAgreement){ + return requests.put(ApiUrls.ownership_contract_agreements_deny, {contract_agreement_id: contractAgreement.id}); + }, + fetchLoanPieceRequestList(){ return requests.get(ApiUrls.ownership_loans_pieces_request); },