From 7116133337a987ccb2faf696052b97a4aac11514 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 21 Oct 2015 14:10:21 +0200 Subject: [PATCH 01/83] AD-1194 Create front-end host configs Default route components to be replaced with Lumenus specific ones. --- .../wallet/constants/wallet_api_urls.js | 9 ++++- .../whitelabel/wallet/wallet_routes.js | 35 +++++++++++++++++++ js/constants/application_constants.js | 7 ++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 2cdc0054..8f4543c7 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -19,7 +19,14 @@ function getWalletApiUrls(subdomain) { 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; } + else if (subdomain === 'lumenus'){ + return { + 'editions': walletConstants.walletApiEndpoint + subdomain + 'editions/', + 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', + 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' + }; + } return {}; } -export default getWalletApiUrls; \ No newline at end of file +export default getWalletApiUrls; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 8e4d5197..bcd0a60b 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -15,6 +15,7 @@ import EditionContainer from '../../../components/ascribe_detail/edition_contain import SettingsContainer from '../../../components/ascribe_settings/settings_container'; import ContractSettings from '../../../components/ascribe_settings/contract_settings'; import ErrorNotFoundPage from '../../../components/error_not_found_page'; +import RegisterPiece from '../../../components/register_piece'; //TODO: Remove once finished with LumenusRegisterPiece import CylandLanding from './components/cyland/cyland_landing'; import CylandPieceContainer from './components/cyland/cyland_detail/cyland_piece_container'; @@ -148,6 +149,40 @@ let ROUTES = { + ), + 'lumenus': ( + + + + + + + + + + + + + + ) }; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 0fe5e210..a961f453 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -46,6 +46,13 @@ let constants = { 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono-logo-black.png', 'permissions': ['register', 'edit', 'share', 'del_from_collection'], 'type': 'wallet' + }, + { + 'subdomain': 'lumenus', + 'name': 'Lumenus', + 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/lumenus/lumenus-logo.png', + 'permissions': ['register', 'edit', 'share', 'del_from_collection'], + 'type': 'wallet' } ], 'defaultDomain': { From 0c51eb374e76f27bc46a89c4e7417bf8fe385695 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 21 Oct 2015 17:37:29 +0200 Subject: [PATCH 02/83] Add Lumenus specific piece list --- .../piece_list_bulk_modal.js | 13 ++-- js/components/piece_list.js | 7 ++- .../components/lumenus/lumenus_piece_list.js | 61 +++++++++++++++++++ .../whitelabel/wallet/wallet_routes.js | 4 +- sass/ascribe_piece_list_toolbar.scss | 4 ++ 5 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index 3e5b6495..f5d97842 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -22,9 +22,16 @@ import { getLangText } from '../../utils/lang_utils.js'; let PieceListBulkModal = React.createClass({ propTypes: { + aclFilterBy: React.PropTypes.func, className: React.PropTypes.string }, + getDefaultProps() { + return { + aclFilterBy: (aclName) => aclName !== 'acl_view' + }; + }, + getInitialState() { return mergeOptions( EditionListStore.getState(), @@ -33,8 +40,6 @@ let PieceListBulkModal = React.createClass({ ); }, - - componentDidMount() { EditionListStore.listen(this.onChange); UserStore.listen(this.onChange); @@ -95,7 +100,7 @@ let PieceListBulkModal = React.createClass({ render() { let selectedEditions = this.fetchSelectedEditionList(); - let availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); + let availableAcls = getAvailableAcls(selectedEditions, this.props.aclFilterBy); if(Object.keys(availableAcls).length > 0) { return ( @@ -136,4 +141,4 @@ let PieceListBulkModal = React.createClass({ } }); -export default PieceListBulkModal; \ No newline at end of file +export default PieceListBulkModal; diff --git a/js/components/piece_list.js b/js/components/piece_list.js index de40be22..a5b9ff55 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -30,6 +30,7 @@ import { setDocumentTitle } from '../utils/dom_utils'; let PieceList = React.createClass({ propTypes: { accordionListItemType: React.PropTypes.func, + aclFilterBy: React.PropTypes.func, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, filterParams: React.PropTypes.array, @@ -63,7 +64,7 @@ let PieceList = React.createClass({ componentDidMount() { let page = this.props.location.query.page || 1; - + PieceListStore.listen(this.onChange); EditionListStore.listen(this.onChange); @@ -170,7 +171,9 @@ let PieceList = React.createClass({ applyOrderBy={this.applyOrderBy}> {this.props.customSubmitButton} - + diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js new file mode 100644 index 00000000..0a6b3b27 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -0,0 +1,61 @@ +'use strict'; + +import React from 'react'; +import PieceList from '../../../../piece_list'; + +import UserActions from '../../../../../actions/user_actions'; +import UserStore from '../../../../../stores/user_store'; + +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; + + +let LumenusPieceList = React.createClass({ + propTypes: { + location: React.PropTypes.object + }, + + getInitialState() { + return UserStore.getState(); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + showOnlyConsignAcl(aclName) { + return aclName === 'acl_consign' || + aclName === 'acl_unconsign'; + }, + + render() { + setDocumentTitle(getLangText('Collection')); + + return ( +
+ +
+ ); + } +}); + +export default LumenusPieceList; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index bcd0a60b..e258dfa7 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -29,6 +29,8 @@ import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; +import LumenusPieceList from './components/lumenus/lumenus_piece_list'; + import CCRegisterPiece from './components/cc/cc_register_piece'; import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler'; @@ -176,7 +178,7 @@ let ROUTES = { headerTitle='+ NEW WORK'/> diff --git a/sass/ascribe_piece_list_toolbar.scss b/sass/ascribe_piece_list_toolbar.scss index dde7f2f0..166ba382 100644 --- a/sass/ascribe_piece_list_toolbar.scss +++ b/sass/ascribe_piece_list_toolbar.scss @@ -86,4 +86,8 @@ top: 2px; } } + + .dropdown-menu { + min-width: 170px; + } } From de3c5bca14863b79c9cb5d66ad24f28b56629412 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 22 Oct 2015 11:12:41 +0200 Subject: [PATCH 03/83] Correct misleading comments in utils --- js/utils/general_utils.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index 2cba98b9..cc1d22dd 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -131,7 +131,8 @@ export function mergeOptions(...l) { } /** - * Merges a number of objects even if there're having duplicates. + * Merges a number of objects even if there're having duplicates, + * taking the last given value for the key. * * DOES NOT RETURN AN ERROR! * @@ -141,8 +142,6 @@ export function mergeOptions(...l) { * @return {[type]} [description] */ export function mergeOptionsWithDuplicates(...l) { - // If the objects submitted in the list have duplicates,in their key names, - // abort the merge and tell the function's user to check his objects. let newObj = {}; for(let i = 1; i < l.length; i++) { @@ -174,7 +173,7 @@ export function update(a, ...l) { */ function _mergeOptions(obj1, obj2) { let obj3 = {}; - + for (let attrname in obj1) { obj3[attrname] = obj1[attrname]; } @@ -232,4 +231,4 @@ export function getSubdomain() { let { host } = window.location; let tokens = host.split('.'); return tokens.length > 2 ? tokens[0] : 'www'; -} \ No newline at end of file +} From ba777781b7a81a5f7a67132118a476ab6485b507 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 22 Oct 2015 11:13:44 +0200 Subject: [PATCH 04/83] Add sliding piece registration with dummy additional form --- .../lumenus_additional_data_form.js | 129 ++++++++++++ .../lumenus/lumenus_register_piece.js | 195 ++++++++++++++++++ .../whitelabel/wallet/wallet_routes.js | 3 +- 3 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js new file mode 100644 index 00000000..c0493ddd --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -0,0 +1,129 @@ +'use strict'; + +import React from 'react'; + +import Form from '../../../../../ascribe_forms/form'; +import Property from '../../../../../ascribe_forms/property'; + +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; + +import ApiUrls from '../../../../../../constants/api_urls'; +import AppConstants from '../../../../../../constants/application_constants'; + +import requests from '../../../../../../utils/requests'; + +import { getLangText } from '../../../../../../utils/lang_utils'; + + +let LumenusAdditionalDataForm = React.createClass({ + propTypes: { + handleSuccess: React.PropTypes.func, + piece: React.PropTypes.object.isRequired, + isInline: React.PropTypes.bool, + location: React.PropTypes.object + }, + + getDefaultProps() { + return { + isInline: false + }; + }, + + getInitialState() { + return { + isUploadReady: true + }; + }, + + handleSuccess() { + let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + 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 + }; + + }, + + render() { + let { piece, isInline, handleSuccess } = this.props; + let buttons, spinner, heading; + + if(!isInline) { + buttons = ( + + ); + + spinner = ( +
+ +
+ ); + + heading = ( +
+

+ {getLangText('Provide additional details')} +

+
+ ); + } + + if(piece && piece.id) { + return ( +
+ {heading} + + + + + + +
+ ); + } else { + return ( +
+ +
+ ); + } + } +}); + +export default LumenusAdditionalDataForm; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js new file mode 100644 index 00000000..9d671ee0 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js @@ -0,0 +1,195 @@ +'use strict'; + +import React from 'react'; +import { History } from 'react-router'; + +import Col from 'react-bootstrap/lib/Col'; +import Row from 'react-bootstrap/lib/Row'; + +import Property from '../../../../ascribe_forms/property'; +import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece'; + +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + +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 LumenusAdditionalDataForm from './lumenus_forms/lumenus_additional_data_form'; + +import SlidesContainer from '../../../../ascribe_slides_container/slides_container'; + +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; +import { mergeOptions } from '../../../../../utils/general_utils'; + + +let LumenusRegisterPiece = React.createClass({ + propTypes: { + location: React.PropTypes.object + }, + + mixins: [History], + + getInitialState(){ + return mergeOptions( + UserStore.getState(), + PieceListStore.getState(), + PieceStore.getState(), + WhitelabelStore.getState(), + { + selectedLicense: 0, + isFineUploaderActive: false, + step: 0 + }); + }, + + componentDidMount() { + PieceListStore.listen(this.onChange); + UserStore.listen(this.onChange); + PieceStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + WhitelabelActions.fetchWhitelabel(); + + let queryParams = this.props.location.query; + + // 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); + WhitelabelStore.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({}); + PieceActions.updatePiece(response.piece); + } + + this.incrementStep(); + + this.refs.slidesContainer.nextSlide({ piece_id: response.piece.id }); + }, + + 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.history.pushState(null, `/collection`); + }, + + // 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 + ); + }, + + // basically redirects to the second slide (index: 1), when the user is not logged in + onLoggedOut() { + this.history.pushState(null, '/login'); + }, + + render() { + setDocumentTitle(getLangText('Register a new piece')); + + return ( + +
+ + + 0} + enableLocalHashing={false} + headerMessage={getLangText('Consign to Lumenus')} + submitMessage={getLangText('Proceed to additional details')} + isFineUploaderActive={this.state.isFineUploaderActive} + handleSuccess={this.handleRegisterSuccess} + onLoggedOut={this.onLoggedOut} + location={this.props.location}> + + + + + + +
+
+ + + + + +
+
+ ); + } +}); + +export default LumenusRegisterPiece; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index e258dfa7..e170d671 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -30,6 +30,7 @@ import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_p import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; import LumenusPieceList from './components/lumenus/lumenus_piece_list'; +import LumenusRegisterPiece from './components/lumenus/lumenus_register_piece'; import CCRegisterPiece from './components/cc/cc_register_piece'; @@ -174,7 +175,7 @@ let ROUTES = { component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/> Date: Thu, 22 Oct 2015 14:14:06 +0200 Subject: [PATCH 05/83] Fix typo in edition api url --- .../ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js | 1 - js/components/whitelabel/wallet/constants/wallet_api_urls.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index f5d97842..2a4d4301 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -137,7 +137,6 @@ let PieceListBulkModal = React.createClass({ } else { return null; } - } }); diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 8f4543c7..87ee6b14 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -21,7 +21,7 @@ function getWalletApiUrls(subdomain) { } else if (subdomain === 'lumenus'){ return { - 'editions': walletConstants.walletApiEndpoint + subdomain + 'editions/', + 'editions': walletConstants.walletApiEndpoint + subdomain + '/editions/', 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; From 3dedc93d2e979fc33c3d821343256691a03122b8 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 23 Oct 2015 14:19:44 +0200 Subject: [PATCH 06/83] Force consignee to be submissions@lumenus.co Special white label form settings only defined for consign form for now, but could be added to others as needed. --- js/components/ascribe_buttons/acl_button.js | 70 ++++++++----------- js/components/ascribe_forms/form_consign.js | 10 ++- .../cyland/cyland_register_piece.js | 7 +- js/constants/application_constants.js | 8 ++- js/utils/form_utils.js | 57 ++++++++++++--- 5 files changed, 97 insertions(+), 55 deletions(-) diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index e3c7fa1c..ee337149 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -34,15 +34,19 @@ let AclButton = React.createClass({ className: React.PropTypes.string }, - isPiece(){ + isPiece() { return this.props.pieceOrEditions.constructor !== Array; }, - actionProperties(){ + actionProperties() { + let message = getAclFormMessage({ + aclName: this.props.action, + entities: this.props.pieceOrEditions, + isPiece: this.isPiece(), + senderName: this.props.currentUser.username + }); - let message = getAclFormMessage(this.props.action, this.getTitlesString(), this.props.currentUser.username); - - if (this.props.action === 'acl_consign'){ + if (this.props.action === 'acl_consign') { return { title: getLangText('Consign artwork'), tooltip: getLangText('Have someone else sell the artwork'), @@ -51,11 +55,10 @@ let AclButton = React.createClass({ message={message} id={this.getFormDataId()} url={ApiUrls.ownership_consigns}/> - ), + ), handleSuccess: this.showNotification }; - } - if (this.props.action === 'acl_unconsign'){ + } else if (this.props.action === 'acl_unconsign') { return { title: getLangText('Unconsign artwork'), tooltip: getLangText('Have the owner manage his sales again'), @@ -64,10 +67,10 @@ let AclButton = React.createClass({ message={message} id={this.getFormDataId()} url={ApiUrls.ownership_unconsigns}/> - ), + ), handleSuccess: this.showNotification }; - }else if (this.props.action === 'acl_transfer') { + } else if (this.props.action === 'acl_transfer') { return { title: getLangText('Transfer artwork'), tooltip: getLangText('Transfer the ownership of the artwork'), @@ -79,32 +82,32 @@ let AclButton = React.createClass({ ), handleSuccess: this.showNotification }; - } - else if (this.props.action === 'acl_loan'){ + } else if (this.props.action === 'acl_loan') { return { title: getLangText('Loan artwork'), tooltip: getLangText('Loan your artwork for a limited period of time'), - form: ( + url={this.isPiece() ? ApiUrls.ownership_loans_pieces + : ApiUrls.ownership_loans_editions}/> ), handleSuccess: this.showNotification }; - } - else if (this.props.action === 'acl_loan_request'){ + } else if (this.props.action === 'acl_loan_request') { return { title: getLangText('Loan artwork'), tooltip: getLangText('Someone requested you to loan your artwork for a limited period of time'), - form: ( ), handleSuccess: this.showNotification }; - } - else if (this.props.action === 'acl_share'){ + } else if (this.props.action === 'acl_share') { return { title: getLangText('Share artwork'), tooltip: getLangText('Share the artwork'), @@ -112,8 +115,9 @@ let AclButton = React.createClass({ - ), + url={this.isPiece() ? ApiUrls.ownership_shares_pieces + : ApiUrls.ownership_shares_editions}/> + ), handleSuccess: this.showNotification }; } else { @@ -121,32 +125,18 @@ let AclButton = React.createClass({ } }, - showNotification(response){ + showNotification(response) { this.props.handleSuccess(); - if(response.notification) { + if (response.notification) { let notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); } }, - // plz move to share form - getTitlesString(){ - if (this.isPiece()){ - return '\"' + this.props.pieceOrEditions.title + '\"'; - } - else { - return this.props.pieceOrEditions.map(function(edition) { - return '- \"' + edition.title + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n'; - }).join(''); - } - - }, - getFormDataId(){ if (this.isPiece()) { return {piece_id: this.props.pieceOrEditions.id}; - } - else { + } else { return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){ return edition.bitcoin_id; }).join()}; @@ -162,7 +152,7 @@ let AclButton = React.createClass({ }, render() { - if (this.props.availableAcls){ + if (this.props.availableAcls) { let shouldDisplay = this.props.availableAcls[this.props.action]; let aclProps = this.actionProperties(); let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : ''; @@ -183,4 +173,4 @@ let AclButton = React.createClass({ } }); -export default AclButton; \ No newline at end of file +export default AclButton; diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index f57e0045..7e3233e8 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -11,6 +11,7 @@ import InputTextAreaToggable from './input_textarea_toggable'; import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils.js'; +import { getSubdomainFormSettings } from '../../utils/form_utils'; let ConsignForm = React.createClass({ propTypes: { @@ -25,6 +26,8 @@ let ConsignForm = React.createClass({ }, render() { + let envSettings = getSubdomainFormSettings('consign'); + return (
}> + label={getLangText('Email')} + editable={!envSettings.consigneeDisabled} + overrideForm={true}> Date: Mon, 26 Oct 2015 11:09:04 +0100 Subject: [PATCH 07/83] Revert changes to allow piece list to filter ACLs We can do this with the backend instead. --- .../piece_list_bulk_modal.js | 9 +-------- js/components/piece_list.js | 4 +--- .../wallet/components/lumenus/lumenus_piece_list.js | 6 ------ 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index 2a4d4301..3f2217e6 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -22,16 +22,9 @@ import { getLangText } from '../../utils/lang_utils.js'; let PieceListBulkModal = React.createClass({ propTypes: { - aclFilterBy: React.PropTypes.func, className: React.PropTypes.string }, - getDefaultProps() { - return { - aclFilterBy: (aclName) => aclName !== 'acl_view' - }; - }, - getInitialState() { return mergeOptions( EditionListStore.getState(), @@ -100,7 +93,7 @@ let PieceListBulkModal = React.createClass({ render() { let selectedEditions = this.fetchSelectedEditionList(); - let availableAcls = getAvailableAcls(selectedEditions, this.props.aclFilterBy); + let availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); if(Object.keys(availableAcls).length > 0) { return ( diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 07a9d47e..c427a911 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -32,7 +32,6 @@ import { setDocumentTitle } from '../utils/dom_utils'; let PieceList = React.createClass({ propTypes: { accordionListItemType: React.PropTypes.func, - aclFilterBy: React.PropTypes.func, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, filterParams: React.PropTypes.array, @@ -179,8 +178,7 @@ let PieceList = React.createClass({ } + className="ascribe-piece-list-bulk-modal"/> diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index 0a6b3b27..c3f01374 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -32,11 +32,6 @@ let LumenusPieceList = React.createClass({ this.setState(state); }, - showOnlyConsignAcl(aclName) { - return aclName === 'acl_consign' || - aclName === 'acl_unconsign'; - }, - render() { setDocumentTitle(getLangText('Collection')); @@ -44,7 +39,6 @@ let LumenusPieceList = React.createClass({
Date: Mon, 26 Oct 2015 19:17:06 +0100 Subject: [PATCH 08/83] Set the consignee email by using the server's white label settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consignee email is available through the white label settings we get from the server, so we don’t need to hardcode them into the constants. Separated out AclButtonList from PieceListbulkModal to make the modal more flexible and separated in concerns. --- js/components/ascribe_buttons/acl_button.js | 10 +- js/components/ascribe_forms/form_consign.js | 11 +-- .../piece_list_bulk_modal.js | 68 +++----------- js/components/piece_list.js | 61 ++++++++++++- .../lumenus_acl_button_list.js | 39 ++++++++ .../lumenus_buttons/lumenus_submit_button.js | 91 +++++++++++++++++++ .../components/lumenus/lumenus_piece_list.js | 4 + .../lumenus/lumenus_register_piece.js | 7 -- js/constants/application_constants.js | 6 -- js/utils/acl_utils.js | 13 ++- js/utils/form_utils.js | 21 +++-- 11 files changed, 230 insertions(+), 101 deletions(-) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index ee337149..7eec36f1 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -16,7 +16,7 @@ import GlobalNotificationActions from '../../actions/global_notification_actions import ApiUrls from '../../constants/api_urls'; -import { getAclFormMessage } from '../../utils/form_utils'; +import { getAclFormMessage, getAclFormDataId } from '../../utils/form_utils'; import { getLangText } from '../../utils/lang_utils'; let AclButton = React.createClass({ @@ -134,13 +134,7 @@ let AclButton = React.createClass({ }, getFormDataId(){ - if (this.isPiece()) { - return {piece_id: this.props.pieceOrEditions.id}; - } else { - return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){ - return edition.bitcoin_id; - }).join()}; - } + return getAclFormDataId(this.isPiece(), this.props.pieceOrEditions); }, // Removes the acl_ prefix and converts to upper case diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 7e3233e8..9617acd4 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -11,12 +11,11 @@ import InputTextAreaToggable from './input_textarea_toggable'; import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils.js'; -import { getSubdomainFormSettings } from '../../utils/form_utils'; - let ConsignForm = React.createClass({ propTypes: { url: React.PropTypes.string, id: React.PropTypes.object, + email: React.PropTypes.string, message: React.PropTypes.string, handleSuccess: React.PropTypes.func }, @@ -26,8 +25,6 @@ let ConsignForm = React.createClass({ }, render() { - let envSettings = getSubdomainFormSettings('consign'); - return ( + editable={!this.props.email} + overrideForm={!!this.props.email}> { - return this.state.editionList[pieceId] - .filter((edition) => edition.selected).length > 0; - }); - return filteredPieceIdList; - }, - - fetchSelectedEditionList() { - let selectedEditionList = []; - - Object - .keys(this.state.editionList) - .forEach((pieceId) => { - let filteredEditionsForPiece = this.state.editionList[pieceId].filter((edition) => edition.selected); - selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece); - }); - - return selectedEditionList; - }, - clearAllSelections() { EditionListActions.clearAllEditionSelections(); EditionListActions.closeAllEditionLists(); }, - handleSuccess() { - PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, this.state.filterBy); - - this.fetchSelectedPieceEditionList() - .forEach((pieceId) => { - EditionListActions.refreshEditionList({pieceId, filterBy: {}}); - }); - EditionListActions.clearAllEditionSelections(); - }, - render() { - let selectedEditions = this.fetchSelectedEditionList(); - let availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); - - if(Object.keys(availableAcls).length > 0) { + if (Object.keys(this.props.availableAcls).length > 0) { return (
@@ -104,7 +70,7 @@ let PieceListBulkModal = React.createClass({
+ numberOfSelectedEditions={this.props.selectedEditions.length} />          

- - - + {this.props.children}
diff --git a/js/components/piece_list.js b/js/components/piece_list.js index c427a911..528ee409 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -13,6 +13,9 @@ import AccordionList from './ascribe_accordion_list/accordion_list'; import AccordionListItemWallet from './ascribe_accordion_list/accordion_list_item_wallet'; import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_list_item_table_editions'; +import AclButtonList from './ascribe_buttons/acl_button_list.js'; +import DeleteButton from './ascribe_buttons/delete_button'; + import Pagination from './ascribe_pagination/pagination'; import PieceListFilterDisplay from './piece_list_filter_display'; @@ -24,6 +27,7 @@ import AscribeSpinner from './ascribe_spinner'; import AppConstants from '../constants/application_constants'; +import { getAvailableAcls } from '../utils/acl_utils'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; import { setDocumentTitle } from '../utils/dom_utils'; @@ -32,6 +36,7 @@ import { setDocumentTitle } from '../utils/dom_utils'; let PieceList = React.createClass({ propTypes: { accordionListItemType: React.PropTypes.func, + bulkModalButtonListType: React.PropTypes.func, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, filterParams: React.PropTypes.array, @@ -45,6 +50,7 @@ let PieceList = React.createClass({ getDefaultProps() { return { accordionListItemType: AccordionListItemWallet, + bulkModalButtonListType: AclButtonList, orderParams: ['artist_name', 'title'], filterParams: [{ label: getLangText('Show works I can'), @@ -152,9 +158,46 @@ let PieceList = React.createClass({ orderBy, this.state.orderAsc, this.state.filterBy); }, + fetchSelectedPieceEditionList() { + let filteredPieceIdList = Object.keys(this.state.editionList) + .filter((pieceId) => { + return this.state.editionList[pieceId] + .filter((edition) => edition.selected).length > 0; + }); + return filteredPieceIdList; + }, + + fetchSelectedEditionList() { + let selectedEditionList = []; + + Object + .keys(this.state.editionList) + .forEach((pieceId) => { + let filteredEditionsForPiece = this.state.editionList[pieceId].filter((edition) => edition.selected); + selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece); + }); + + return selectedEditionList; + }, + + handleAclSuccess() { + PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, + this.state.orderBy, this.state.orderAsc, this.state.filterBy); + + this.fetchSelectedPieceEditionList() + .forEach((pieceId) => { + EditionListActions.refreshEditionList({pieceId, filterBy: {}}); + }); + EditionListActions.clearAllEditionSelections(); + }, + render() { - let loadingElement = ; - let AccordionListItemType = this.props.accordionListItemType; + const loadingElement = ; + const AccordionListItemType = this.props.accordionListItemType; + const BulkModalButtonListType = this.props.bulkModalButtonListType; + + const selectedEditions = this.fetchSelectedEditionList(); + const availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); setDocumentTitle(getLangText('Collection')); @@ -178,7 +221,19 @@ let PieceList = React.createClass({ } + availableAcls={availableAcls} + selectedEditions={selectedEditions} + className="ascribe-piece-list-bulk-modal"> + + + + diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js new file mode 100644 index 00000000..6c1aa0ae --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js @@ -0,0 +1,39 @@ +'use strict'; + +import React from 'react'; + +import LumenusSubmitButton from './lumenus_submit_button'; + +import AclProxy from '../../../../../acl_proxy'; + +import DeleteButton from '../../../../../ascribe_buttons/delete_button'; + +let LumenusAclButtonList = React.createClass({ + propTypes: { + availableAcls: React.PropTypes.object.isRequired, + className: React.PropTypes.string, + editions: React.PropTypes.array, + handleSuccess: React.PropTypes.func, + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]) + }, + + render() { + return ( +
+ + + + {this.props.children} +
+ ); + } +}); + +export default LumenusAclButtonList; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js new file mode 100644 index 00000000..13483502 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -0,0 +1,91 @@ +'use strict'; + +import React from 'react'; +import classNames from 'classnames'; + + +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; +import UserActions from '../../../../../../actions/user_actions'; +import UserStore from '../../../../../../stores/user_store'; +import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../../stores/whitelabel_store'; + +import ConsignForm from '../../../../../ascribe_forms/form_consign'; + +import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; + +import ApiUrls from '../../../../../../constants/api_urls'; + +import { getAclFormMessage, getAclFormDataId } from '../../../../../../utils/form_utils'; +import { mergeOptions } from '../../../../../../utils/general_utils'; +import { getLangText } from '../../../../../../utils/lang_utils'; + +let LumenusSubmitButton = React.createClass({ + propTypes: { + className: React.PropTypes.string, + editions: React.PropTypes.array, + handleSuccess: React.PropTypes.func, + }, + + getInitialState() { + return mergeOptions( + UserStore.getState(), + WhitelabelStore.getState() + ); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + showNotification(response) { + this.props.handleSuccess(); + if (response.notification) { + let notification = new GlobalNotificationModel(response.notification, 'success'); + GlobalNotificationActions.appendGlobalNotification(notification); + } + }, + + render() { + const { className, editions, handleSuccess } = this.props; + const title = getLangText('Consign to Lumenus'); + const message = getAclFormMessage({ + aclName: 'acl_consign', + entities: editions, + isPiece: false, + senderName: this.state.currentUser.username + }); + + return ( + + {title} + + } + handleSuccess={this.showNotification} + title={title}> + + + ); + } +}); + +export default LumenusSubmitButton; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index c3f01374..403e226b 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -1,6 +1,9 @@ 'use strict'; import React from 'react'; + +import LumenusAclButtonList from './lumenus_buttons/lumenus_acl_button_list'; + import PieceList from '../../../../piece_list'; import UserActions from '../../../../../actions/user_actions'; @@ -39,6 +42,7 @@ let LumenusPieceList = React.createClass({
= 1) { + if (editionsCopy.length >= 1) { availableAcls = editionsCopy[0].acl; - } - if(editionsCopy.length >= 2) { - for(let i = 1; i < editionsCopy.length; i++) { + } else if (editionsCopy.length >= 2) { + for (let i = 1; i < editionsCopy.length; i++) { availableAcls = intersectAcls(availableAcls, editionsCopy[i].acl); } } // convert acls back to key-value object let availableAclsObj = {}; - for(let i = 0; i < availableAcls.length; i++) { + for (let i = 0; i < availableAcls.length; i++) { availableAclsObj[availableAcls[i]] = true; } return availableAclsObj; -} \ No newline at end of file +} diff --git a/js/utils/form_utils.js b/js/utils/form_utils.js index a98d956f..d2d2cd29 100644 --- a/js/utils/form_utils.js +++ b/js/utils/form_utils.js @@ -5,15 +5,19 @@ import { getLangText } from './lang_utils'; import AppConstants from '../constants/application_constants'; /** - * Gets a dictionary of settings for a form based on the environment - * (ie. if on a whitelabel) - * @param {string} formName Name of the form - * @return {object} Settings key-val dictionary + * Get the data ids of the given piece or editions. + * @param {boolean} isPiece Is the given entities parameter a piece? (False: array of editions) + * @param {(object|object[])} pieceOrEditions Piece or array of editions + * @return {(object|object[])} Data IDs of the pieceOrEditions for the form */ -export function getSubdomainFormSettings(formName) { - let subdomainFormSettings = AppConstants.whitelabel.formSettings || {}; - - return subdomainFormSettings[formName] || {}; +export function getAclFormDataId(isPiece, pieceOrEditions) { + if (isPiece) { + return {piece_id: pieceOrEditions.id}; + } else { + return {bitcoin_id: pieceOrEditions.map(function(edition){ + return edition.bitcoin_id; + }).join()}; + } } /** @@ -21,6 +25,7 @@ export function getSubdomainFormSettings(formName) { * @param {object} options Options object for creating the message: * @param {string} options.aclName Enum name of an acl * @param {(object|object[])} options.entities Piece or array of Editions + * @param {boolean} options.isPiece Is the given entities parameter a piece? (False: array of editions) * @param {string} [options.senderName] Name of the sender * @return {string} Completed message */ From 633939b7fa926404bd0f24b0e93386c22cdb8cf4 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 27 Oct 2015 17:01:50 +0100 Subject: [PATCH 09/83] Use AclButton in LumenusSubmitButton Instead of copying functionality from AclButton to add a default email, LumenusSubmitButton just returns one with preset settings. --- js/components/ascribe_buttons/acl_button.js | 3 + .../lumenus_acl_button_list.js | 44 ++++++++++--- .../lumenus_buttons/lumenus_submit_button.js | 65 +++++-------------- 3 files changed, 52 insertions(+), 60 deletions(-) diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index 7eec36f1..e3a1b8c4 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -30,6 +30,7 @@ let AclButton = React.createClass({ currentUser: React.PropTypes.object, buttonAcceptName: React.PropTypes.string, buttonAcceptClassName: React.PropTypes.string, + email: React.PropTypes.string, handleSuccess: React.PropTypes.func.isRequired, className: React.PropTypes.string }, @@ -52,6 +53,7 @@ let AclButton = React.createClass({ tooltip: getLangText('Have someone else sell the artwork'), form: ( @@ -88,6 +90,7 @@ let AclButton = React.createClass({ tooltip: getLangText('Loan your artwork for a limited period of time'), form: ( - - - +
+ + {this.props.children}
); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js index 13483502..365a5a34 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -3,47 +3,32 @@ import React from 'react'; import classNames from 'classnames'; +import AclButton from '../../../../../ascribe_buttons/acl_button'; -import GlobalNotificationModel from '../../../../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; -import UserActions from '../../../../../../actions/user_actions'; -import UserStore from '../../../../../../stores/user_store'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../../stores/whitelabel_store'; -import ConsignForm from '../../../../../ascribe_forms/form_consign'; - -import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; - -import ApiUrls from '../../../../../../constants/api_urls'; - -import { getAclFormMessage, getAclFormDataId } from '../../../../../../utils/form_utils'; -import { mergeOptions } from '../../../../../../utils/general_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; let LumenusSubmitButton = React.createClass({ propTypes: { - className: React.PropTypes.string, + availableAcls: React.PropTypes.object.isRequired, + currentUser: React.PropTypes.object, editions: React.PropTypes.array, handleSuccess: React.PropTypes.func, + className: React.PropTypes.string, }, getInitialState() { - return mergeOptions( - UserStore.getState(), - WhitelabelStore.getState() - ); + return WhitelabelStore.getState(); }, componentDidMount() { - UserStore.listen(this.onChange); - UserActions.fetchCurrentUser(); WhitelabelStore.listen(this.onChange); WhitelabelActions.fetchWhitelabel(); }, componentWillUnmount() { - UserStore.unlisten(this.onChange); WhitelabelStore.unlisten(this.onChange); }, @@ -51,39 +36,19 @@ let LumenusSubmitButton = React.createClass({ this.setState(state); }, - showNotification(response) { - this.props.handleSuccess(); - if (response.notification) { - let notification = new GlobalNotificationModel(response.notification, 'success'); - GlobalNotificationActions.appendGlobalNotification(notification); - } - }, - render() { - const { className, editions, handleSuccess } = this.props; - const title = getLangText('Consign to Lumenus'); - const message = getAclFormMessage({ - aclName: 'acl_consign', - entities: editions, - isPiece: false, - senderName: this.state.currentUser.username - }); + const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; return ( - - {title} - - } - handleSuccess={this.showNotification} - title={title}> - - + ); } }); From 82b49fc653b5b90b698364e717f3c7284f1943ba Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 27 Oct 2015 17:54:35 +0100 Subject: [PATCH 10/83] Add additional required properties for Lumenus Currently uses the FurtherDetailsFileuploader, just relabeled, to handle the marketplace image. For now, this allows us to avoid creating another property in the backend to handle the image, but could be replaced or modified in the future if we do want to create another property. --- .../further_details_fileuploader.js | 6 +- .../cyland_additional_data_form.js | 4 +- .../lumenus_additional_data_form.js | 73 +++++++++++++++---- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/js/components/ascribe_detail/further_details_fileuploader.js b/js/components/ascribe_detail/further_details_fileuploader.js index c5ef8a1c..6f9abca4 100644 --- a/js/components/ascribe_detail/further_details_fileuploader.js +++ b/js/components/ascribe_detail/further_details_fileuploader.js @@ -13,6 +13,7 @@ import { getCookie } from '../../utils/fetch_api_utils'; let FurtherDetailsFileuploader = React.createClass({ propTypes: { + label: React.PropTypes.string, uploadStarted: React.PropTypes.func, pieceId: React.PropTypes.number, otherData: React.PropTypes.arrayOf(React.PropTypes.object), @@ -26,6 +27,7 @@ let FurtherDetailsFileuploader = React.createClass({ getDefaultProps() { return { + label: "Additional files", multiple: false }; }, @@ -44,7 +46,7 @@ let FurtherDetailsFileuploader = React.createClass({ return ( + label={this.props.label}> + className="btn btn-default btn-wide" + disabled={!this.state.isUploadReady}> {getLangText('Register work')} ); @@ -98,20 +114,49 @@ let LumenusAdditionalDataForm = React.createClass({ buttons={buttons} spinner={spinner}> {heading} + - + - + + + + + + + From c72a5aa308ab4f0b163f1e19d60456a3c8d4b422 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 28 Oct 2015 11:26:54 +0100 Subject: [PATCH 11/83] Add Lumenus specific data forms to edition and piece details --- js/components/ascribe_detail/edition.js | 19 ++++++-- .../ascribe_detail/edition_action_panel.js | 26 +++++++---- .../ascribe_detail/edition_container.js | 4 ++ .../ascribe_detail/piece_container.js | 12 ++++- .../lumenus_edition_container.js | 30 ++++++++++++ .../lumenus_detail/lumenus_further_details.js | 46 +++++++++++++++++++ .../lumenus_detail/lumenus_piece_container.js | 27 +++++++++++ .../lumenus_additional_data_form.js | 18 +++++--- .../components/lumenus/lumenus_piece_list.js | 21 --------- .../lumenus/lumenus_register_piece.js | 11 ++--- .../whitelabel/wallet/wallet_routes.js | 6 ++- 11 files changed, 170 insertions(+), 50 deletions(-) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index bcd2902c..bcd6743c 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -40,6 +40,8 @@ import { getLangText } from '../../utils/lang_utils'; */ let Edition = React.createClass({ propTypes: { + actionPanelButtonListType: React.PropTypes.func, + furtherDetailsType: React.PropTypes.func, edition: React.PropTypes.object, loadEdition: React.PropTypes.func, location: React.PropTypes.object @@ -47,6 +49,12 @@ let Edition = React.createClass({ mixins: [History], + getDefaultProps() { + return { + furtherDetailsType: FurtherDetails + }; + }, + getInitialState() { return UserStore.getState(); }, @@ -74,6 +82,8 @@ let Edition = React.createClass({ }, render() { + let FurtherDetailsType = this.props.furtherDetailsType; + return ( @@ -89,6 +99,7 @@ let Edition = React.createClass({
@@ -151,13 +162,13 @@ let Edition = React.createClass({ show={this.props.edition.acl.acl_edit || Object.keys(this.props.edition.extra_data).length > 0 || this.props.edition.other_data.length > 0}> - + location={this.props.location} /> {this.getStatus()} diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index bd423e4c..3d6d3043 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -34,6 +34,7 @@ import { getLangText } from '../../utils/lang_utils'; */ let EditionActionPanel = React.createClass({ propTypes: { + actionPanelButtonListType: React.PropTypes.func, edition: React.PropTypes.object, currentUser: React.PropTypes.object, handleSuccess: React.PropTypes.func @@ -41,6 +42,12 @@ let EditionActionPanel = React.createClass({ mixins: [History], + getDefaultProps() { + return { + actionPanelButtonListType: AclButtonList + }; + }, + getInitialState() { return PieceListStore.getState(); }, @@ -85,7 +92,8 @@ let EditionActionPanel = React.createClass({ }, render(){ - let {edition, currentUser} = this.props; + let { edition, currentUser } = this.props; + let ActionPanelButtonListType = this.props.actionPanelButtonListType; if (edition && edition.notifications && @@ -102,7 +110,7 @@ let EditionActionPanel = React.createClass({ return ( -
+ ); + } +}); + +export default LumenusEditionContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js new file mode 100644 index 00000000..17dbad20 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js @@ -0,0 +1,46 @@ +'use strict'; + +import React from 'react'; + +import LumenusAdditionalDataForm from '../lumenus_forms/lumenus_additional_data_form' + +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; + +let FurtherDetails = React.createClass({ + propTypes: { + pieceId: React.PropTypes.number, + extraData: React.PropTypes.object, + otherData: React.PropTypes.arrayOf(React.PropTypes.object), + handleSuccess: React.PropTypes.func, + location: React.PropTypes.object + }, + + showNotification() { + this.props.handleSuccess(); + let notification = new GlobalNotificationModel('Further details updated', 'success'); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + render() { + const { pieceId, extraData, otherData, handleSuccess, location } = this.props; + + // Instead of grabbing the entire piece from the PieceStore and making this component + // stateful, we can put together a piece for the additional form solely based on the props + const piece = { + id: pieceId, + extra_data: extraData, + other_data: otherData + }; + + return ( + + ); + } +}); + +export default FurtherDetails; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js new file mode 100644 index 00000000..03fd9d9b --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js @@ -0,0 +1,27 @@ +'use strict'; + +import React from 'react'; + +import LumenusFurtherDetails from './lumenus_further_details'; + +import PieceContainer from '../../../../../ascribe_detail/piece_container'; + +let LumenusPieceContainer = React.createClass({ + propTypes: { + params: React.PropTypes.object, + location: React.PropTypes.object + }, + + render() { + return ( +
+ +
+ ); + } +}); + +export default LumenusPieceContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index d75bb946..3df03fd8 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -23,7 +23,11 @@ import { getLangText } from '../../../../../../utils/lang_utils'; let LumenusAdditionalDataForm = React.createClass({ propTypes: { handleSuccess: React.PropTypes.func, - piece: React.PropTypes.object.isRequired, + piece: React.PropTypes.shape({ + id: React.PropTypes.number, + extra_data: React.PropTypes.object, + other_data: React.PropTypes.arrayOf(React.PropTypes.object) + }).isRequired, isInline: React.PropTypes.bool, location: React.PropTypes.object }, @@ -78,7 +82,7 @@ let LumenusAdditionalDataForm = React.createClass({ let { piece, isInline, handleSuccess } = this.props; let buttons, spinner, heading; - if(!isInline) { + if (!isInline) { buttons = (
} - badge={this.getGlyphicon()}> + badge={this.getGlyphicon()}> {this.getCreateEditionsDialog()} {/* this.props.children is AccordionListItemTableEditions */} {this.props.children} diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index e3a1b8c4..5197a744 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -156,7 +156,8 @@ let AclButton = React.createClass({ return ( + } diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js index b9139979..e87a6407 100644 --- a/js/components/ascribe_buttons/acl_button_list.js +++ b/js/components/ascribe_buttons/acl_button_list.js @@ -1,12 +1,15 @@ 'use strict'; -import React from 'react'; +import React from 'react/addons'; import UserActions from '../../actions/user_actions'; import UserStore from '../../stores/user_store'; import AclButton from '../ascribe_buttons/acl_button'; +import { mergeOptions } from '../../utils/general_utils'; + + let AclButtonList = React.createClass({ propTypes: { className: React.PropTypes.string, @@ -15,6 +18,7 @@ let AclButtonList = React.createClass({ React.PropTypes.array ]), availableAcls: React.PropTypes.object, + buttonsStyle: React.PropTypes.object, handleSuccess: React.PropTypes.func, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), @@ -23,56 +27,97 @@ let AclButtonList = React.createClass({ }, getInitialState() { - return UserStore.getState(); + return mergeOptions( + UserStore.getState(), + { + buttonListSize: 0 + } + ); }, componentDidMount() { UserStore.listen(this.onChange); UserActions.fetchCurrentUser(); + + window.addEventListener('resize', this.handleResize); + window.dispatchEvent(new Event('resize')); + }, + + componentDidUpdate(prevProps) { + if(prevProps.availableAcls && prevProps.availableAcls !== this.props.availableAcls) { + window.dispatchEvent(new Event('resize')); + } }, componentWillUnmount() { UserStore.unlisten(this.onChange); + + window.removeEventListener('resize', this.handleResize); + }, + + handleResize() { + this.setState({ + buttonListSize: this.refs.buttonList.getDOMNode().offsetWidth + }); }, onChange(state) { this.setState(state); }, + renderChildren() { + const { children } = this.props; + const { buttonListSize } = this.state; + + return React.Children.map(children, (child) => { + return React.addons.cloneWithProps(child, { buttonListSize }); + }); + }, + render() { + const { className, + buttonsStyle, + availableAcls, + editions, + handleSuccess } = this.props; + + const { currentUser } = this.state; + return ( -
- - - - - - {this.props.children} +
+ + + + + + + {this.renderChildren()} +
); } diff --git a/js/components/ascribe_buttons/acl_information.js b/js/components/ascribe_buttons/acl_information.js new file mode 100644 index 00000000..8d412e02 --- /dev/null +++ b/js/components/ascribe_buttons/acl_information.js @@ -0,0 +1,133 @@ +'use strict'; + +import React from 'react'; +import classnames from 'classnames'; + +import { InformationTexts } from '../../constants/information_text'; +import { replaceSubstringAtIndex, sanitize, intersectLists } from '../../utils/general_utils'; +import { getLangText } from '../../utils/lang_utils'; + + +let AclInformation = React.createClass({ + propTypes: { + verbs: React.PropTypes.arrayOf(React.PropTypes.string), + aim: React.PropTypes.string.isRequired, + aclObject: React.PropTypes.object, + + // Must be inserted from the outside + buttonListSize: React.PropTypes.number.isRequired + }, + + getDefaultProps() { + return { + buttonListSize: 400 + }; + }, + + getInitialState() { + return { isVisible: false }; + }, + + onOff() { + if(!this.state.isVisible) { + this.setState({ isVisible: true }); + } + else { + this.setState({ isVisible: false }); + } + }, + + getInfoText(title, info, example){ + let aim = this.props.aim; + + if(aim) { + if(aim === 'form') { + return ( +

+ + {replaceSubstringAtIndex(info.slice(2), 's ', ' ')} + + + {' ' + example} + +

+ ); + } + else if(aim === 'button') { + return ( +

+ + {title} + + + {info + ' '} + + + {example} + +

+ ); + } + } + else { + console.log('Aim is required when you want to place information text'); + } + }, + + produceInformationBlock() { + const { titles, informationSentences, exampleSentences } = InformationTexts; + const { verbs, aim } = this.props; + + const availableInformations = intersectLists(verbs, Object.keys(titles)); + + // sorting is not needed, as `this.props.verbs` takes care of sorting already + // So we assume a user of `AclInformationButton` puts an ordered version of + // `verbs` into `propTypes` + let verbsToDisplay = []; + + + if(aim === 'form' && availableInformations.length > 0) { + verbsToDisplay = verbsToDisplay.concat(verbs); + } else if(aim === 'button' && this.props.aclObject) { + const { aclObject } = this.props; + const sanitizedAclObject = sanitize(aclObject, (val) => !val); + verbsToDisplay = verbsToDisplay.concat(intersectLists(verbs, Object.keys(sanitizedAclObject))); + } + + return verbsToDisplay.map((verb) => { + return this.getInfoText(getLangText(titles[verb]), getLangText(informationSentences[verb]), getLangText(exampleSentences[verb])); + }); + }, + + getButton() { + return this.props.aim === 'button' ? + ; + btnDelete = ; } else if(availableAcls.acl_unshare){ @@ -57,7 +57,7 @@ let DeleteButton = React.createClass({ title = getLangText('Remove Piece from Collection'); } - btnDelete = ; + btnDelete = ; } else { return null; diff --git a/js/components/ascribe_detail/detail_property.js b/js/components/ascribe_detail/detail_property.js index 12054996..8b0f50b5 100644 --- a/js/components/ascribe_detail/detail_property.js +++ b/js/components/ascribe_detail/detail_property.js @@ -2,6 +2,7 @@ import React from 'react'; + let DetailProperty = React.createClass({ propTypes: { label: React.PropTypes.string, @@ -23,14 +24,19 @@ let DetailProperty = React.createClass({ getDefaultProps() { return { separator: '', - labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-2 col-xs-height col-bottom ascribe-detail-property-label', + labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-2 col-xs-height ascribe-detail-property-label', valueClassName: 'col-xs-9 col-sm-9 col-md-10 col-lg-10 col-xs-height col-bottom ascribe-detail-property-value' }; }, render() { - let value = this.props.value; let styles = {}; + const { labelClassName, + label, + separator, + valueClassName, + children, + value } = this.props; if(this.props.ellipsis) { styles = { @@ -40,30 +46,16 @@ let DetailProperty = React.createClass({ }; } - - if (this.props.children){ - value = ( -
-
- { this.props.value } -
-
- { this.props.children } -
-
); - } return (
-
- { this.props.label } { this.props.separator} +
+ {label} {separator}
- {value} + {children || value}
diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index bcd6743c..80bf81d5 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -25,11 +25,11 @@ import LicenseDetail from './license_detail'; import FurtherDetails from './further_details'; import EditionActionPanel from './edition_action_panel'; +import AclProxy from '../acl_proxy'; import Note from './note'; import ApiUrls from '../../constants/api_urls'; -import AppConstants from '../../constants/application_constants'; import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils'; @@ -95,7 +95,7 @@ let Edition = React.createClass({

{this.props.edition.title}

- +
- - {this.getStatus()} - + + + + +
); diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index 3d6d3043..d0f16103 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -22,6 +22,8 @@ import DeleteButton from '../ascribe_buttons/delete_button'; import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationActions from '../../actions/global_notification_actions'; +import AclInformation from '../ascribe_buttons/acl_information'; + import AclProxy from '../acl_proxy'; import ApiUrls from '../../constants/api_urls'; @@ -73,7 +75,7 @@ let EditionActionPanel = React.createClass({ let notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState('/collection'); + this.history.pushState(null, '/collection'); }, refreshCollection() { @@ -111,7 +113,7 @@ let EditionActionPanel = React.createClass({ @@ -131,7 +133,7 @@ let EditionActionPanel = React.createClass({ value={edition.bitcoin_id} readOnly />
- @@ -168,6 +170,10 @@ let EditionActionPanel = React.createClass({ + diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 879f7b32..554903c3 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -27,6 +27,9 @@ import CreateEditionsForm from '../ascribe_forms/create_editions_form'; import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; import DeleteButton from '../ascribe_buttons/delete_button'; +import AclInformation from '../ascribe_buttons/acl_information'; +import AclProxy from '../acl_proxy'; + import ListRequestActions from '../ascribe_forms/list_form_request_actions'; import GlobalNotificationModel from '../../models/global_notification_model'; @@ -187,33 +190,41 @@ let PieceContainer = React.createClass({ }, getActions() { - if (this.state.piece && - this.state.piece.notifications && - this.state.piece.notifications.length > 0) { + const { piece, currentUser } = this.state; + + if (piece && piece.notifications && piece.notifications.length > 0) { return ( ); - } - else { + notifications={piece.notifications}/>); + } else { return ( - - - - + + + + + + + + + ); } }, @@ -232,7 +243,7 @@ let PieceContainer = React.createClass({

{this.state.piece.title}

- + {this.state.piece.num_editions > 0 ? : null}
diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index c5f60b76..fe15f537 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -288,10 +288,8 @@ let Form = React.createClass({ {this.renderChildren()} {this.getButtons()} - ); } }); - export default Form; diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 9617acd4..db18ba4f 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -9,6 +9,9 @@ import Property from './property'; import InputTextAreaToggable from './input_textarea_toggable'; import AscribeSpinner from '../ascribe_spinner'; + +import AclInformation from '../ascribe_buttons/acl_information'; + import { getLangText } from '../../utils/lang_utils.js'; let ConsignForm = React.createClass({ @@ -47,6 +50,7 @@ let ConsignForm = React.createClass({

}> + }> +

{getLangText('Are you sure you would like to permanently delete this edition')}?

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

diff --git a/js/components/ascribe_forms/form_delete_piece.js b/js/components/ascribe_forms/form_delete_piece.js index 4b0c9e39..ee066d3f 100644 --- a/js/components/ascribe_forms/form_delete_piece.js +++ b/js/components/ascribe_forms/form_delete_piece.js @@ -4,6 +4,8 @@ import React from 'react'; import Form from '../ascribe_forms/form'; +import AclInformation from '../ascribe_buttons/acl_information'; + import ApiUrls from '../../constants/api_urls'; import AscribeSpinner from '../ascribe_spinner'; @@ -51,6 +53,7 @@ let PieceDeleteForm = React.createClass({

}> +

{getLangText('Are you sure you would like to permanently delete this piece')}?

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

diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 919b6118..d6102f14 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -19,7 +19,7 @@ import AscribeSpinner from '../ascribe_spinner'; import { mergeOptions } from '../../utils/general_utils'; import { getLangText } from '../../utils/lang_utils'; - +import AclInformation from '../ascribe_buttons/acl_information'; let LoanForm = React.createClass({ propTypes: { @@ -232,6 +232,7 @@ let LoanForm = React.createClass({

{this.props.loanHeading}

+

}> + diff --git a/js/components/ascribe_forms/form_transfer.js b/js/components/ascribe_forms/form_transfer.js index 010c4829..3fb95ff6 100644 --- a/js/components/ascribe_forms/form_transfer.js +++ b/js/components/ascribe_forms/form_transfer.js @@ -9,6 +9,8 @@ import Form from './form'; import Property from './property'; import InputTextAreaToggable from './input_textarea_toggable'; +import AclInformation from '../ascribe_buttons/acl_information'; + import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils.js'; @@ -52,6 +54,7 @@ let TransferForm = React.createClass({

}> + diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js index f00eee9e..5c3ce742 100644 --- a/js/components/ascribe_modal/modal_wrapper.js +++ b/js/components/ascribe_modal/modal_wrapper.js @@ -65,7 +65,7 @@ let ModalWrapper = React.createClass({ {this.props.title} -
+
{this.renderChildren()}
diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index 16886def..38de2af6 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -7,7 +7,7 @@ import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import { getLangText } from '../../utils/lang_utils.js'; -let PieceListToolbarFilterWidgetFilter = React.createClass({ +let PieceListToolbarFilterWidget = React.createClass({ propTypes: { filterParams: React.PropTypes.arrayOf( React.PropTypes.shape({ @@ -83,6 +83,7 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({ return ( {/* We iterate over filterParams, to receive the label and then for each @@ -139,4 +140,4 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({ } }); -export default PieceListToolbarFilterWidgetFilter; \ No newline at end of file +export default PieceListToolbarFilterWidget; \ No newline at end of file diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js index a3615aec..c38144b0 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js @@ -54,6 +54,7 @@ let PieceListToolbarOrderWidget = React.createClass({ return (
  • @@ -72,7 +73,7 @@ let PieceListToolbarOrderWidget = React.createClass({ -1} />
  • diff --git a/js/components/ascribe_spinner.js b/js/components/ascribe_spinner.js index ecdf641b..e1daf5b2 100644 --- a/js/components/ascribe_spinner.js +++ b/js/components/ascribe_spinner.js @@ -20,12 +20,13 @@ let AscribeSpinner = React.createClass({ render() { return ( -
    -
    -
    A
    +
    +
    +
    A
    ); } diff --git a/js/components/header.js b/js/components/header.js index 042d2b1c..51f91318 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -2,6 +2,8 @@ import React from 'react'; +import { Link } from 'react-router'; + import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav'; @@ -29,7 +31,7 @@ import NavRoutesLinks from './nav_routes_links'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; -import {constructHead} from '../utils/head_setter'; +import { constructHead } from '../utils/dom_utils'; let Header = React.createClass({ @@ -71,12 +73,16 @@ let Header = React.createClass({ } if (whitelabel.subdomain && whitelabel.subdomain !== 'www' && whitelabel.logo){ - return (); + return ( + + Whitelabel brand + + ); } return ( - + ); }, @@ -201,7 +207,7 @@ let Header = React.createClass({ {this.getPoweredBy()} diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 528ee409..425247da 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -25,8 +25,6 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar'; import AscribeSpinner from './ascribe_spinner'; -import AppConstants from '../constants/application_constants'; - import { getAvailableAcls } from '../utils/acl_utils'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; diff --git a/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js b/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js index abda8a1b..caef504b 100644 --- a/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js +++ b/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js @@ -182,7 +182,7 @@ let AccordionListItemPrize = React.createClass({ artistName={artistName} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getPrizeButtons()} badge={this.getPrizeBadge()}> diff --git a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js b/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js index a5f9838f..07e84b0e 100644 --- a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js +++ b/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js @@ -141,7 +141,7 @@ let PieceContainer = React.createClass({

    {this.state.piece.title}

    - + {artistEmail} {this.getActions()}
    diff --git a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js index 8a38f02e..5644b5b0 100644 --- a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js +++ b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js @@ -39,7 +39,7 @@ let WalletPieceContainer = React.createClass({

    {this.props.piece.title}

    - +
    } diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js index ae96db98..755e550b 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js @@ -100,7 +100,7 @@ let CylandAccordionListItem = React.createClass({ piece={this.props.content} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getSubmitButtons()}> {this.props.children} diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js index 8a6c38b5..63085fc9 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js @@ -3,7 +3,6 @@ import React from 'react'; import { History } from 'react-router'; - import WhitelabelActions from '../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../stores/whitelabel_store'; @@ -14,10 +13,13 @@ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; import UserStore from '../../../../../stores/user_store'; import UserActions from '../../../../../actions/user_actions'; +import AscribeSpinner from '../../../../ascribe_spinner'; + import { mergeOptions } from '../../../../../utils/general_utils'; import { getLangText } from '../../../../../utils/lang_utils'; import { setDocumentTitle } from '../../../../../utils/dom_utils'; + let CylandLanding = React.createClass({ mixins: [History], @@ -61,10 +63,9 @@ let CylandLanding = React.createClass({
    - {getLangText('Submissions to Cyland Archive are powered by')} + {getLangText('Submissions to Cyland Archive are powered by') + ' '} - ascribe - +
    diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js index 00e7f318..7445eb36 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js @@ -106,7 +106,7 @@ let IkonotvAccordionListItem = React.createClass({ piece={this.props.content} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getSubmitButtons()}> {this.props.children} diff --git a/js/constants/information_text.js b/js/constants/information_text.js new file mode 100644 index 00000000..442e481e --- /dev/null +++ b/js/constants/information_text.js @@ -0,0 +1,33 @@ +'use strict'; + +export const InformationTexts = { + 'titles': { + 'acl_consign': 'CONSIGN', + 'acl_loan': 'LOAN', + 'acl_share': 'SHARE', + 'acl_delete': 'DELETE', + 'acl_create_editions': 'CREATE EDITIONS', + 'acl_unconsign': 'UNCONSIGN', + 'acl_request_unconsign': 'REQUEST UNCONSIGN' + }, + 'informationSentences': { + 'acl_consign': ' - Lets someone represent you in dealing with the work, under the terms you agree to.', + 'acl_loan': ' - Lets someone use or put the Work on display for a limited amount of time.', + 'acl_share': ' - Lets someone view the Work or Edition, but does not give rights to publish or display it.', + 'acl_delete': ' - Removes the Work from your Wallet. Note that the previous registration and transfer ' + + 'history will still exist on the blockchain and cannot be deleted.', + 'acl_create_editions': ' Lets the artist set a fixed number of editions of a work which can then be transferred, guaranteeing each edition is authentic and from the artist.', + 'acl_unconsign': 'Ends the consignment agreement between the owner and a consignee.', + 'acl_request_unconsign': 'Lets the owner ask the consignee to confirm that they will no longer manage the work.' + }, + 'exampleSentences': { + 'acl_consign': '(e.g. an artist Consigns 10 Editions of her new Work to a gallery ' + + 'so the gallery can sell them on her behalf, under the terms the artist and the gallery have agreed to)', + 'acl_loan': '(e.g. a collector Loans a Work to a gallery for one month for display in the gallery\'s show)', + 'acl_share': '(e.g. a photographer Shares proofs of a graduation photo with the graduate\'s grandparents)', + 'acl_delete': '(e.g. an artist uploaded the wrong file and doesn\'t want it cluttering his Wallet, so he Deletes it)', + 'acl_create_editions': '(e.g. A company commissions a visual artists to create three limited edition prints for a giveaway)', + 'acl_unconsign': '(e.g. An artist regains full control over their work and releases the consignee of any rights or responsibilities)', + 'acl_request_unconsign': '(e.g. An artist submits an unconsign request to a gallery after his exhibition ends, as per their agreement)' + } +}; \ No newline at end of file diff --git a/js/utils/acl_utils.js b/js/utils/acl_utils.js index 170d7aec..dd39a380 100644 --- a/js/utils/acl_utils.js +++ b/js/utils/acl_utils.js @@ -1,10 +1,6 @@ 'use strict'; -import { sanitize } from './general_utils'; - -function intersectAcls(a, b) { - return a.filter((val) => b.indexOf(val) > -1); -} +import { sanitize, intersectLists } from './general_utils'; export function getAvailableAcls(editions, filterFn) { let availableAcls = []; @@ -37,13 +33,15 @@ export function getAvailableAcls(editions, filterFn) { }); // If no edition has been selected, availableActions is empty - // If only one edition has been selected, their actions are available - // If more than one editions have been selected, their acl properties are intersected + // If only one edition has been selected, its actions are available + // If more than one editions have been selected, intersect all their acl properties if (editionsCopy.length >= 1) { availableAcls = editionsCopy[0].acl; - } else if (editionsCopy.length >= 2) { - for (let i = 1; i < editionsCopy.length; i++) { - availableAcls = intersectAcls(availableAcls, editionsCopy[i].acl); + + if (editionsCopy.length >= 2) { + for (let i = 1; i < editionsCopy.length; i++) { + availableAcls = intersectLists(availableAcls, editionsCopy[i].acl); + } } } @@ -53,6 +51,5 @@ export function getAvailableAcls(editions, filterFn) { availableAclsObj[availableAcls[i]] = true; } - return availableAclsObj; } diff --git a/js/utils/dom_utils.js b/js/utils/dom_utils.js index c20e1009..d009f90f 100644 --- a/js/utils/dom_utils.js +++ b/js/utils/dom_utils.js @@ -1,9 +1,45 @@ 'use strict'; - /** * Set the title in the browser window. */ export function setDocumentTitle(title) { document.title = title; } + +/** + * @param {string} elementType: string, is the type of the element, such as link, meta, etc. + * @param {string} elementId id of the element + * @param {object} elementAttributes: hash table containing the attributes of the relevant element + */ +function constructHeadElement(elementType, elementId, elementAttributes) { + let head = (document.head || document.getElementsByTagName('head')[0]); + let element = document.createElement(elementType); + let oldElement = document.getElementById(elementId); + element.setAttribute('id', elementId); + for (let k in elementAttributes){ + try { + element.setAttribute(k, elementAttributes[k]); + } + catch(e){ + console.warn(e.message); + } + } + if (oldElement) { + head.removeChild(oldElement); + } + head.appendChild(element); +} + +/** + * Accepts a dictionary of dictionaries which comprises a part or all of html head part + * @param {object} headObject {link : {id1: {rel: ... }}} + */ +export function constructHead(headObject){ + for (let k in headObject){ + let favicons = headObject[k]; + for (let f in favicons){ + constructHeadElement(k, f, favicons[f]); + } + } +} \ No newline at end of file diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index cc1d22dd..1aea3cd9 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -222,6 +222,16 @@ export function truncateTextAtCharIndex(text, charIndex, replacement = '...') { return truncatedText; } +/** + * @param index, int, the starting index of the substring to be replaced + * @param character, substring to be replaced + * @returns {string} + */ +export function replaceSubstringAtIndex(baseString, substrToReplace, stringToBePut) { + let index = baseString.indexOf(substrToReplace); + return baseString.substr(0, index) + stringToBePut + baseString.substr(index + substrToReplace.length); +} + /** * Extracts the user's subdomain from the browser's window. * If no subdomain is found (for example on a naked domain), the default "www" is just assumed. @@ -232,3 +242,13 @@ export function getSubdomain() { let tokens = host.split('.'); return tokens.length > 2 ? tokens[0] : 'www'; } + +/** + * Takes two lists and returns their intersection as a list + * @param {Array} a + * @param {Array} b + * @return {[Array]} Intersected list of a and b + */ +export function intersectLists(a, b) { + return a.filter((val) => b.indexOf(val) > -1); +} diff --git a/js/utils/head_setter.js b/js/utils/head_setter.js deleted file mode 100644 index 6ca2c9b0..00000000 --- a/js/utils/head_setter.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -// elementType: string, is the type of the element, such as link, meta, etc. -// elementId id of the element -// elementAttributes: hash table containing the attributes of the relevant element - -function constructHeadElement(elementType, elementId, elementAttributes) { - let head = (document.head || document.getElementsByTagName('head')[0]); - let element = document.createElement(elementType); - let oldElement = document.getElementById(elementId); - element.setAttribute('id', elementId); - for (let k in elementAttributes){ - try { - element.setAttribute(k, elementAttributes[k]); - } - catch(e){ - console.warn(e.message); - continue; - } - } - if (oldElement) { - head.removeChild(oldElement); - } - head.appendChild(element); -} - -// Accepts a dictionary of dictionaries which comprises a part or all of html head part -// {link : {id1: {rel: ... }}} -// traverses a tree of depth 3 (no backtracking) -export function constructHead(headObject){ - for (let k in headObject){ - let favicons = headObject[k]; - for (let f in favicons){ - constructHeadElement(k, f, favicons[f]); - } - } -} diff --git a/sass/ascribe_acl_information.scss b/sass/ascribe_acl_information.scss new file mode 100644 index 00000000..063c8ae6 --- /dev/null +++ b/sass/ascribe_acl_information.scss @@ -0,0 +1,25 @@ +.acl-information-dropdown-list { + text-align: justify; + padding: .5em .5em .5em 0; + + p { + margin: 0 .5em 1em 0; + line-height: 1.2; + } + + span { + font-size: 13px; + } + + .title { + color: $ascribe-dark-blue; + } + + .info { + color: #212121; + } + + .example { + color: #616161; + } +} \ No newline at end of file diff --git a/sass/ascribe_custom_style.scss b/sass/ascribe_custom_style.scss index 51281d91..05d39027 100644 --- a/sass/ascribe_custom_style.scss +++ b/sass/ascribe_custom_style.scss @@ -91,12 +91,27 @@ hr { } } - .navbar-brand, - .navbar-brand:hover { + .navbar-brand { font-size: 23px; padding: 12px 15px; - color: $ascribe--nav-fg-prim-color; + + .icon-ascribe-logo { + color: $ascribe--nav-fg-prim-color; + + &:hover { + color: $ascribe--nav-fg-sec-color; + text-decoration: none; + } + &:focus { + text-decoration: none; + } + } + + .img-brand { + height: 100%; + } } + .img-brand .navbar-brand { width: 0; height: 0; @@ -327,6 +342,29 @@ fieldset[disabled] .btn-secondary.active { } } +.btn-tertiary { + background-color: transparent; + border-color: transparent; + color: $ascribe-dark-blue; + + &:focus, + &:active:focus, + &:active.focus { + background-color: transparent; + border-color: transparent; + color: $ascribe-dark-blue; + } + + &:hover, + &:active, + &:active:hover, + &.active:hover{ + background-color: $ascribe-pink; + border-color: $ascribe-pink; + color: $ascribe-white; + } +} + .ascribe-piece-list-toolbar-filter-widget button { background-color: transparent; border: 1px solid transparent; diff --git a/sass/ascribe_edition.scss b/sass/ascribe_edition.scss index 9fa30387..195e79e0 100644 --- a/sass/ascribe_edition.scss +++ b/sass/ascribe_edition.scss @@ -17,8 +17,4 @@ border: 1px solid #CCC; display: table-cell; vertical-align: middle; -} - -.ascribe-button-list { - margin-top: 1em; -} +} \ No newline at end of file diff --git a/sass/lib/buttons.scss b/sass/lib/buttons.scss index e69de29b..68a124f9 100644 --- a/sass/lib/buttons.scss +++ b/sass/lib/buttons.scss @@ -0,0 +1,9 @@ +.btn-transparent { + color: black; + background-color: transparent; + + &:hover, &:active, &:focus { + color:#424242; + outline: none; + } +} \ No newline at end of file diff --git a/sass/lib/modals.scss b/sass/lib/modals.scss new file mode 100644 index 00000000..20a720c1 --- /dev/null +++ b/sass/lib/modals.scss @@ -0,0 +1,8 @@ +.modal-body { + padding-top:0; +} + +.modal-header { + padding: 15px 15px 0 15px; + border-bottom: none; +} \ No newline at end of file diff --git a/sass/main.scss b/sass/main.scss index 4b9e0f71..337dd32e 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -35,6 +35,9 @@ $BASE_URL: '<%= BASE_URL %>'; @import 'ascribe_form'; @import 'ascribe_panel'; @import 'ascribe_collapsible'; +@import 'ascribe_acl_information'; +@import 'lib/buttons'; +@import 'lib/modals'; @import 'ascribe_custom_style'; @import 'ascribe_spinner'; @@ -155,6 +158,7 @@ hr { } .ascribe-detail-property-label { + vertical-align: top; font-size: .8em; } diff --git a/sass/variables.scss b/sass/variables.scss index ccd48864..ac93a2fb 100644 --- a/sass/variables.scss +++ b/sass/variables.scss @@ -613,7 +613,7 @@ $modal-header-border-color: #e5e5e5 !default; $modal-footer-border-color: $modal-header-border-color !default; $modal-lg: 900px !default; -$modal-md: 600px !default; +$modal-md: 500px !default; $modal-sm: 300px !default; diff --git a/sass/whitelabel/prize/sluice/sluice_custom_style.scss b/sass/whitelabel/prize/sluice/sluice_custom_style.scss index e2ceeeb3..4cfb7c82 100644 --- a/sass/whitelabel/prize/sluice/sluice_custom_style.scss +++ b/sass/whitelabel/prize/sluice/sluice_custom_style.scss @@ -251,3 +251,7 @@ $sluice--button-color: $sluice--nav-fg-prim-color; .client--sluice .ascribe-progress-bar > .progress-bar { background-color: $sluice--button-color; } + +.client--sluice .acl-information-dropdown-list .title { + color: $sluice--button-color; +} \ No newline at end of file diff --git a/sass/whitelabel/wallet/cc/cc_custom_style.scss b/sass/whitelabel/wallet/cc/cc_custom_style.scss index 238937e7..44cb0dd1 100644 --- a/sass/whitelabel/wallet/cc/cc_custom_style.scss +++ b/sass/whitelabel/wallet/cc/cc_custom_style.scss @@ -204,3 +204,7 @@ $cc--button-color: $cc--nav-fg-prim-color; .client--cc .ascribe-progress-bar > .progress-bar { background-color: $cc--button-color; } + +.client--cc .acl-information-dropdown-list .title { + color: $cc--button-color; +} \ No newline at end of file diff --git a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss index c33e247b..eaf45621 100644 --- a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss +++ b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss @@ -179,3 +179,7 @@ $cyland--button-color: $cyland--nav-fg-prim-color; .client--cyland .ascribe-progress-bar > .progress-bar { background-color: $cyland--button-color; } + +.client--cyland .acl-information-dropdown-list .title { + color: $cyland--button-color; +} \ No newline at end of file diff --git a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss index 5fc9220b..52affdaf 100644 --- a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss +++ b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss @@ -521,3 +521,7 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important; .client--ikonotv .ascribe-progress-bar > .progress-bar { background-color: $ikono--button-color; } + +.client--ikonotv .acl-information-dropdown-list .title { + color: $ikono--button-color; +} \ No newline at end of file From 3a8f91e29ef16e390b6f326357ef05a345a7b74a Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 3 Nov 2015 10:49:06 +0100 Subject: [PATCH 14/83] Change required prop for AD1251 changes --- .../lumenus/lumenus_forms/lumenus_additional_data_form.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index 3df03fd8..e08a8bc7 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -134,7 +134,7 @@ let LumenusAdditionalDataForm = React.createClass({ rows={1} defaultValue={piece.extra_data.artist_bio} placeholder={getLangText('Enter a biography of the artist...')} - required="required"/> + required />
    + required /> + required /> + required /> ); From f7259a8ab3784cb74ac2d71e710cd6ef90ee917c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 3 Nov 2015 11:13:32 +0100 Subject: [PATCH 15/83] Merge with AD-1255 --- js/components/ascribe_buttons/acl_button.js | 174 ------------------ .../ascribe_buttons/acl_button_list.js | 45 +++-- .../ascribe_buttons/acls/acl_button.js | 83 +++++++++ .../ascribe_buttons/acls/consign_button.js | 27 +++ .../ascribe_buttons/acls/loan_button.js | 27 +++ .../acls/loan_request_button.js | 24 +++ .../ascribe_buttons/acls/share_button.js | 24 +++ .../ascribe_buttons/acls/transfer_button.js | 24 +++ .../ascribe_buttons/acls/unconsign_button.js | 24 +++ .../ascribe_detail/edition_action_panel.js | 2 +- .../ascribe_detail/piece_container.js | 2 +- .../ascribe_forms/acl_form_factory.js | 128 +++++++++++++ .../ascribe_forms/form_request_action.js | 70 ++++--- .../ascribe_detail/wallet_action_panel.js | 4 +- js/utils/form_utils.js | 16 ++ js/utils/general_utils.js | 39 +++- js/utils/lang_utils.js | 6 +- js/utils/requests.js | 8 +- 18 files changed, 474 insertions(+), 253 deletions(-) delete mode 100644 js/components/ascribe_buttons/acl_button.js create mode 100644 js/components/ascribe_buttons/acls/acl_button.js create mode 100644 js/components/ascribe_buttons/acls/consign_button.js create mode 100644 js/components/ascribe_buttons/acls/loan_button.js create mode 100644 js/components/ascribe_buttons/acls/loan_request_button.js create mode 100644 js/components/ascribe_buttons/acls/share_button.js create mode 100644 js/components/ascribe_buttons/acls/transfer_button.js create mode 100644 js/components/ascribe_buttons/acls/unconsign_button.js create mode 100644 js/components/ascribe_forms/acl_form_factory.js diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js deleted file mode 100644 index 5197a744..00000000 --- a/js/components/ascribe_buttons/acl_button.js +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - -import React from 'react'; - -import ConsignForm from '../ascribe_forms/form_consign'; -import UnConsignForm from '../ascribe_forms/form_unconsign'; -import TransferForm from '../ascribe_forms/form_transfer'; -import LoanForm from '../ascribe_forms/form_loan'; -import LoanRequestAnswerForm from '../ascribe_forms/form_loan_request_answer'; -import ShareForm from '../ascribe_forms/form_share_email'; -import ModalWrapper from '../ascribe_modal/modal_wrapper'; -import AppConstants from '../../constants/application_constants'; - -import GlobalNotificationModel from '../../models/global_notification_model'; -import GlobalNotificationActions from '../../actions/global_notification_actions'; - -import ApiUrls from '../../constants/api_urls'; - -import { getAclFormMessage, getAclFormDataId } from '../../utils/form_utils'; -import { getLangText } from '../../utils/lang_utils'; - -let AclButton = React.createClass({ - propTypes: { - action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, - availableAcls: React.PropTypes.object.isRequired, - pieceOrEditions: React.PropTypes.oneOfType([ - React.PropTypes.object, - React.PropTypes.array - ]).isRequired, - currentUser: React.PropTypes.object, - buttonAcceptName: React.PropTypes.string, - buttonAcceptClassName: React.PropTypes.string, - email: React.PropTypes.string, - handleSuccess: React.PropTypes.func.isRequired, - className: React.PropTypes.string - }, - - isPiece() { - return this.props.pieceOrEditions.constructor !== Array; - }, - - actionProperties() { - let message = getAclFormMessage({ - aclName: this.props.action, - entities: this.props.pieceOrEditions, - isPiece: this.isPiece(), - senderName: this.props.currentUser.username - }); - - if (this.props.action === 'acl_consign') { - return { - title: getLangText('Consign artwork'), - tooltip: getLangText('Have someone else sell the artwork'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_unconsign') { - return { - title: getLangText('Unconsign artwork'), - tooltip: getLangText('Have the owner manage his sales again'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_transfer') { - return { - title: getLangText('Transfer artwork'), - tooltip: getLangText('Transfer the ownership of the artwork'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_loan') { - return { - title: getLangText('Loan artwork'), - tooltip: getLangText('Loan your artwork for a limited period of time'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_loan_request') { - return { - title: getLangText('Loan artwork'), - tooltip: getLangText('Someone requested you to loan your artwork for a limited period of time'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_share') { - return { - title: getLangText('Share artwork'), - tooltip: getLangText('Share the artwork'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else { - throw new Error('Your specified action did not match a form.'); - } - }, - - showNotification(response) { - this.props.handleSuccess(); - if (response.notification) { - let notification = new GlobalNotificationModel(response.notification, 'success'); - GlobalNotificationActions.appendGlobalNotification(notification); - } - }, - - getFormDataId(){ - return getAclFormDataId(this.isPiece(), this.props.pieceOrEditions); - }, - - // Removes the acl_ prefix and converts to upper case - sanitizeAction() { - if (this.props.buttonAcceptName) { - return this.props.buttonAcceptName; - } - return this.props.action.split('acl_')[1].toUpperCase(); - }, - - render() { - 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; - } -}); - -export default AclButton; diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js index e87a6407..83495363 100644 --- a/js/components/ascribe_buttons/acl_button_list.js +++ b/js/components/ascribe_buttons/acl_button_list.js @@ -5,21 +5,25 @@ import React from 'react/addons'; import UserActions from '../../actions/user_actions'; import UserStore from '../../stores/user_store'; -import AclButton from '../ascribe_buttons/acl_button'; +import ConsignButton from './acls/consign_button'; +import LoanButton from './acls/loan_button'; +import LoanRequestButton from './acls/loan_request_button'; +import ShareButton from './acls/share_button'; +import TransferButton from './acls/transfer_button'; +import UnconsignButton from './acls/unconsign_button'; import { mergeOptions } from '../../utils/general_utils'; - let AclButtonList = React.createClass({ propTypes: { className: React.PropTypes.string, - editions: React.PropTypes.oneOfType([ + pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array - ]), - availableAcls: React.PropTypes.object, + ]).isRequired, + availableAcls: React.PropTypes.object.isRequired, buttonsStyle: React.PropTypes.object, - handleSuccess: React.PropTypes.func, + handleSuccess: React.PropTypes.func.isRequired, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element @@ -78,7 +82,7 @@ let AclButtonList = React.createClass({ const { className, buttonsStyle, availableAcls, - editions, + pieceOrEditions, handleSuccess } = this.props; const { currentUser } = this.state; @@ -86,34 +90,29 @@ let AclButtonList = React.createClass({ return (
    - - - - - {this.renderChildren()} @@ -123,4 +122,4 @@ let AclButtonList = React.createClass({ } }); -export default AclButtonList; \ No newline at end of file +export default AclButtonList; diff --git a/js/components/ascribe_buttons/acls/acl_button.js b/js/components/ascribe_buttons/acls/acl_button.js new file mode 100644 index 00000000..d81a19f4 --- /dev/null +++ b/js/components/ascribe_buttons/acls/acl_button.js @@ -0,0 +1,83 @@ +'use strict'; + +import React from 'react'; +import classNames from 'classnames'; + +import AclProxy from '../../acl_proxy'; + +import AclFormFactory from '../../ascribe_forms/acl_form_factory'; + +import ModalWrapper from '../../ascribe_modal/modal_wrapper'; + +import AppConstants from '../../../constants/application_constants'; + +import GlobalNotificationModel from '../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../actions/global_notification_actions'; + +import ApiUrls from '../../../constants/api_urls'; + +import { getAclFormMessage, getAclFormDataId } from '../../../utils/form_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let AclButton = React.createClass({ + propTypes: { + action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, + availableAcls: React.PropTypes.object.isRequired, + buttonAcceptName: React.PropTypes.string, + buttonAcceptClassName: React.PropTypes.string, + currentUser: React.PropTypes.object.isRequired, + email: React.PropTypes.string, + pieceOrEditions: React.PropTypes.oneOfType([ + React.PropTypes.object, + React.PropTypes.array + ]).isRequired, + title: React.PropTypes.string, + handleSuccess: React.PropTypes.func.isRequired, + className: React.PropTypes.string + }, + + // Removes the acl_ prefix and converts to upper case + sanitizeAction() { + if (this.props.buttonAcceptName) { + return this.props.buttonAcceptName; + } + return this.props.action.split('acl_')[1].toUpperCase(); + }, + + render() { + const { + action, + availableAcls, + buttonAcceptClassName, + currentUser, + email, + pieceOrEditions, + handleSuccess, + title } = this.props; + + return ( + + + {this.sanitizeAction()} + + } + handleSuccess={handleSuccess} + title={title}> + + + + ); + } +}); + +export default AclButton; diff --git a/js/components/ascribe_buttons/acls/consign_button.js b/js/components/ascribe_buttons/acls/consign_button.js new file mode 100644 index 00000000..6a3759db --- /dev/null +++ b/js/components/ascribe_buttons/acls/consign_button.js @@ -0,0 +1,27 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let ConsignButton = React.createClass({ + propTypes: { + ...omitFromObject(AclButton.propTypes, ['action']), + email: React.PropTypes.string + }, + + render() { + return ( + + ); + } +}); + +export default ConsignButton; diff --git a/js/components/ascribe_buttons/acls/loan_button.js b/js/components/ascribe_buttons/acls/loan_button.js new file mode 100644 index 00000000..283b43eb --- /dev/null +++ b/js/components/ascribe_buttons/acls/loan_button.js @@ -0,0 +1,27 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let LoanButton = React.createClass({ + propTypes: { + ...omitFromObject(AclButton.propTypes, ['action']), + email: React.PropTypes.string + }, + + render() { + return ( + + ); + } +}); + +export default LoanButton; diff --git a/js/components/ascribe_buttons/acls/loan_request_button.js b/js/components/ascribe_buttons/acls/loan_request_button.js new file mode 100644 index 00000000..f4ffe9a4 --- /dev/null +++ b/js/components/ascribe_buttons/acls/loan_request_button.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let LoanButton = React.createClass({ + propTypes: omitFromObject(AclButton.propTypes, ['action']), + + render() { + return ( + + ); + } +}); + +export default LoanButton; diff --git a/js/components/ascribe_buttons/acls/share_button.js b/js/components/ascribe_buttons/acls/share_button.js new file mode 100644 index 00000000..4fb914a0 --- /dev/null +++ b/js/components/ascribe_buttons/acls/share_button.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let ShareButton = React.createClass({ + propTypes: omitFromObject(AclButton.propTypes, ['action']), + + render() { + return ( + + ); + } +}); + +export default ShareButton; diff --git a/js/components/ascribe_buttons/acls/transfer_button.js b/js/components/ascribe_buttons/acls/transfer_button.js new file mode 100644 index 00000000..e85a81d1 --- /dev/null +++ b/js/components/ascribe_buttons/acls/transfer_button.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let TransferButton = React.createClass({ + propTypes: omitFromObject(AclButton.propTypes, ['action']), + + render() { + return ( + + ); + } +}); + +export default TransferButton; diff --git a/js/components/ascribe_buttons/acls/unconsign_button.js b/js/components/ascribe_buttons/acls/unconsign_button.js new file mode 100644 index 00000000..39029c18 --- /dev/null +++ b/js/components/ascribe_buttons/acls/unconsign_button.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let UnconsignButton = React.createClass({ + propTypes: omitFromObject(AclButton.propTypes, ['action']), + + render() { + return ( + + ); + } +}); + +export default UnconsignButton; diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index d0f16103..6bd8108d 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -115,7 +115,7 @@ let EditionActionPanel = React.createClass({ + ); + } else if (action === 'acl_unconsign') { + return ( + + ); + } else if (action === 'acl_transfer') { + return ( + + ); + } else if (action === 'acl_loan') { + return ( + + ); + } else if (action === 'acl_loan_request') { + return ( + + ); + } else if (action === 'acl_share') { + return ( + + ); + } else { + throw new Error('Your specified action did not match a form.'); + } + } +}); + +export default AclFormFactory; diff --git a/js/components/ascribe_forms/form_request_action.js b/js/components/ascribe_forms/form_request_action.js index b0f3b6c6..d3d1bc71 100644 --- a/js/components/ascribe_forms/form_request_action.js +++ b/js/components/ascribe_forms/form_request_action.js @@ -2,10 +2,13 @@ import React from 'react'; -import AclButton from './../ascribe_buttons/acl_button'; -import ActionPanel from '../ascribe_panel/action_panel'; import Form from './form'; +import LoanRequestButton from '../ascribe_buttons/acls/loan_request_button'; +import UnconsignButton from '../ascribe_buttons/acls/unconsign_button'; + +import ActionPanel from '../ascribe_panel/action_panel'; + import NotificationActions from '../../actions/notification_actions'; import GlobalNotificationModel from '../../models/global_notification_model'; @@ -13,9 +16,9 @@ import GlobalNotificationActions from '../../actions/global_notification_actions import ApiUrls from '../../constants/api_urls'; +import { getAclFormDataId } from '../../utils/form_utils'; import { getLangText } from '../../utils/lang_utils.js'; - let RequestActionForm = React.createClass({ propTypes: { pieceOrEditions: React.PropTypes.oneOfType([ @@ -27,26 +30,26 @@ let RequestActionForm = React.createClass({ handleSuccess: React.PropTypes.func }, - isPiece(){ + isPiece() { return this.props.pieceOrEditions.constructor !== Array; }, getUrls() { let urls = {}; - if (this.props.notifications.action === 'consign'){ + if (this.props.notifications.action === 'consign') { urls.accept = ApiUrls.ownership_consigns_confirm; urls.deny = ApiUrls.ownership_consigns_deny; - } else if (this.props.notifications.action === 'unconsign'){ + } else if (this.props.notifications.action === 'unconsign') { urls.accept = ApiUrls.ownership_unconsigns; urls.deny = ApiUrls.ownership_unconsigns_deny; - } else if (this.props.notifications.action === 'loan' && !this.isPiece()){ + } else if (this.props.notifications.action === 'loan' && !this.isPiece()) { urls.accept = ApiUrls.ownership_loans_confirm; urls.deny = ApiUrls.ownership_loans_deny; - } else if (this.props.notifications.action === 'loan' && this.isPiece()){ + } else if (this.props.notifications.action === 'loan' && this.isPiece()) { urls.accept = ApiUrls.ownership_loans_pieces_confirm; urls.deny = ApiUrls.ownership_loans_pieces_deny; - } else if (this.props.notifications.action === 'loan_request' && this.isPiece()){ + } else if (this.props.notifications.action === 'loan_request' && this.isPiece()) { urls.accept = ApiUrls.ownership_loans_pieces_request_confirm; urls.deny = ApiUrls.ownership_loans_pieces_request_deny; } @@ -54,37 +57,28 @@ let RequestActionForm = React.createClass({ return urls; }, - getFormData(){ - if (this.isPiece()) { - return {piece_id: this.props.pieceOrEditions.id}; - } - else { - return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){ - return edition.bitcoin_id; - }).join()}; - } + getFormData() { + return getAclFormDataId(this.isPiece(), this.props.pieceOrEditions); }, showNotification(option, action, owner) { return () => { - let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner; - - let notifications = new GlobalNotificationModel(message, 'success'); + const message = getLangText('You have successfully %s the %s request from %s', getLangText(option), getLangText(action), owner); + const notifications = new GlobalNotificationModel(message, 'success'); GlobalNotificationActions.appendGlobalNotification(notifications); this.handleSuccess(); - }; }, handleSuccess() { - if (this.isPiece()){ + if (this.isPiece()) { NotificationActions.fetchPieceListNotifications(); - } - else { + } else { NotificationActions.fetchEditionListNotifications(); } - if(this.props.handleSuccess) { + + if (typeof this.props.handleSuccess === 'function') { this.props.handleSuccess(); } }, @@ -98,21 +92,19 @@ let RequestActionForm = React.createClass({ }, getAcceptButtonForm(urls) { - if(this.props.notifications.action === 'unconsign') { + if (this.props.notifications.action === 'unconsign') { return ( - ); - } else if(this.props.notifications.action === 'loan_request') { + } else if (this.props.notifications.action === 'loan_request') { return ( - @@ -140,8 +132,8 @@ let RequestActionForm = React.createClass({ }, getButtonForm() { - let urls = this.getUrls(); - let acceptButtonForm = this.getAcceptButtonForm(urls); + const urls = this.getUrls(); + const acceptButtonForm = this.getAcceptButtonForm(urls); return (
    @@ -150,13 +142,13 @@ let RequestActionForm = React.createClass({ isInline={true} getFormData={this.getFormData} handleSuccess={ - this.showNotification(getLangText('denied'), this.props.notifications.action, this.props.notifications.by) + this.showNotification('denied', this.props.notifications.action, this.props.notifications.by) } className='inline pull-right'> {acceptButtonForm} @@ -168,10 +160,10 @@ let RequestActionForm = React.createClass({ return ( + buttons={this.getButtonForm()} /> ); } }); -export default RequestActionForm; \ No newline at end of file +export default RequestActionForm; diff --git a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js index 178be7da..bd240126 100644 --- a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js +++ b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js @@ -47,7 +47,7 @@ let WalletActionPanel = React.createClass({ { + return filter.indexOf(key) >= 0; + }); + } else if (filter && typeof filter === 'function') { + return filterObjOnFn(obj, filter); + } else { + throw new Error('The given filter is not an array or function. Exclude aborted'); + } } /** diff --git a/js/utils/lang_utils.js b/js/utils/lang_utils.js index f2e2fa14..ee2d292c 100644 --- a/js/utils/lang_utils.js +++ b/js/utils/lang_utils.js @@ -22,15 +22,15 @@ export function getLangText(s, ...args) { let lang = getLang(); try { if(lang in languages) { - return formatText(languages[lang][s], args); + return formatText(languages[lang][s], ...args); } else { // just use the english language - return formatText(languages['en-US'][s], args); + return formatText(languages['en-US'][s], ...args); } } catch(err) { //if(!(s in languages[lang])) { //console.warn('Language-string is not in constants file. Add: "' + s + '" to the "' + lang + '" language file. Defaulting to keyname'); - return formatText(s, args); + return formatText(s, ...args); //} else { // console.error(err); //} diff --git a/js/utils/requests.js b/js/utils/requests.js index 7e9c9a58..43c09458 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -6,7 +6,7 @@ import { argsToQueryParams, getCookie } from '../utils/fetch_api_utils'; import AppConstants from '../constants/application_constants'; -import {excludePropFromObject} from '../utils/general_utils'; +import { omitFromObject } from '../utils/general_utils'; class Requests { _merge(defaults, options) { @@ -138,9 +138,9 @@ class Requests { return this.request('delete', newUrl); } - _putOrPost(url, paramsAndBody, method){ - let paramsCopy = this._merge(paramsAndBody); - let params = excludePropFromObject(paramsAndBody, ['body']); + _putOrPost(url, paramsAndBody, method) { + let paramsCopy = Object.assign({}, paramsAndBody); + let params = omitFromObject(paramsAndBody, ['body']); let newUrl = this.prepareUrl(url, params); let body = null; if (paramsCopy && paramsCopy.body) { From 54212627bbab35b1b7ab3b2bda841f8b77c095d3 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 3 Nov 2015 12:10:21 +0100 Subject: [PATCH 16/83] Change lumens buttons for AD-1264 changes --- js/components/piece_list.js | 2 +- .../lumenus_buttons/lumenus_acl_button_list.js | 13 ++++++------- .../lumenus_buttons/lumenus_submit_button.js | 11 +++++------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 425247da..3099d31a 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -224,7 +224,7 @@ let PieceList = React.createClass({ className="ascribe-piece-list-bulk-modal"> - {this.props.children}
    diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js index 365a5a34..941b3ec0 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -3,7 +3,7 @@ import React from 'react'; import classNames from 'classnames'; -import AclButton from '../../../../../ascribe_buttons/acl_button'; +import ConsignButton from '../../../../../ascribe_buttons/acls/consign_button'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../../stores/whitelabel_store'; @@ -14,7 +14,7 @@ let LumenusSubmitButton = React.createClass({ propTypes: { availableAcls: React.PropTypes.object.isRequired, currentUser: React.PropTypes.object, - editions: React.PropTypes.array, + pieceOrEditions: React.PropTypes.array, handleSuccess: React.PropTypes.func, className: React.PropTypes.string, }, @@ -37,17 +37,16 @@ let LumenusSubmitButton = React.createClass({ }, render() { - const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; + const { availableAcls, currentUser, className, pieceOrEditions, handleSuccess } = this.props; return ( - ); } From 3929712e9ddbe9fc2efa019739bddd3a6096df2a Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 3 Nov 2015 12:10:43 +0100 Subject: [PATCH 17/83] Prune unnecessary divs from some components --- .../lumenus_edition_container.js | 12 ++++------ .../lumenus_detail/lumenus_piece_container.js | 10 ++++---- .../components/lumenus/lumenus_piece_list.js | 24 +++++++++---------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js index 756735eb..77cca99c 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js @@ -16,13 +16,11 @@ let LumenusEditionContainer = React.createClass({ render() { return ( -
    - -
    + ); } }); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js index 03fd9d9b..5b7cf8ee 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js @@ -14,12 +14,10 @@ let LumenusPieceContainer = React.createClass({ render() { return ( -
    - -
    + ); } }); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index 4bc2485f..14dac214 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -18,19 +18,17 @@ let LumenusPieceList = React.createClass({ setDocumentTitle(getLangText('Collection')); return ( -
    - -
    + ); } }); From 85eb45b5cd8cb443441e1da40bde06dc1caf604c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 4 Nov 2015 00:41:12 +0100 Subject: [PATCH 18/83] Add small changes for previous merges that were missed --- js/components/piece_list.js | 2 +- .../lumenus/lumenus_buttons/lumenus_acl_button_list.js | 8 +++++++- .../lumenus_forms/lumenus_additional_data_form.js | 6 ++---- .../wallet/components/lumenus/lumenus_piece_list.js | 6 +++--- js/stores/edition_list_store.js | 2 +- js/utils/requests.js | 10 +++------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 3099d31a..69f5b23f 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -184,7 +184,7 @@ let PieceList = React.createClass({ this.fetchSelectedPieceEditionList() .forEach((pieceId) => { - EditionListActions.refreshEditionList({pieceId, filterBy: {}}); + EditionListActions.refreshEditionList({pieceId}); }); EditionListActions.clearAllEditionSelections(); }, diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js index b902b93b..76608032 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js @@ -6,6 +6,7 @@ import LumenusSubmitButton from './lumenus_submit_button'; import DeleteButton from '../../../../../ascribe_buttons/delete_button'; import ShareButton from '../../../../../ascribe_buttons/acls/share_button'; +import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button'; import UserActions from '../../../../../../actions/user_actions'; import UserStore from '../../../../../../stores/user_store'; @@ -46,13 +47,18 @@ let LumenusAclButtonList = React.createClass({ + {this.props.children}
    ); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index e08a8bc7..3e67c21a 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -28,8 +28,7 @@ let LumenusAdditionalDataForm = React.createClass({ extra_data: React.PropTypes.object, other_data: React.PropTypes.arrayOf(React.PropTypes.object) }).isRequired, - isInline: React.PropTypes.bool, - location: React.PropTypes.object + isInline: React.PropTypes.bool }, getDefaultProps() { @@ -125,8 +124,7 @@ let LumenusAdditionalDataForm = React.createClass({ setIsUploadReady={this.setIsUploadReady} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} pieceId={piece.id} - otherData={piece.other_data} - location={this.props.location}/> + otherData={piece.other_data} /> diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index 14dac214..ccfb7e1c 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -22,10 +22,10 @@ let LumenusPieceList = React.createClass({ redirectTo="/register_piece?slide_num=0" bulkModalButtonListType={LumenusAclButtonList} filterParams={[{ - label: getLangText('Show works I have'), + label: getLangText('Show works I can'), items: [{ - key: 'acl_consigned', - label: getLangText('consigned to Lumenus') + key: 'acl_consign', + label: getLangText('consign to Lumenus') }] }]} location={this.props.location}/> diff --git a/js/stores/edition_list_store.js b/js/stores/edition_list_store.js index 4ccada4e..107f9af4 100644 --- a/js/stores/edition_list_store.js +++ b/js/stores/edition_list_store.js @@ -60,7 +60,7 @@ class EditionListStore { * We often just have to refresh the edition list for a certain pieceId, * this method provides exactly that functionality without any side effects */ - onRefreshEditionList({pieceId, filterBy}) { + onRefreshEditionList({pieceId, filterBy = {}}) { // It may happen that the user enters the site logged in already // through /editions // If he then tries to delete a piece/edition and this method is called, diff --git a/js/utils/requests.js b/js/utils/requests.js index 8f015a11..112588e0 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -99,8 +99,7 @@ class Requests { return newUrl; } - request(verb, url, options) { - options = options || {}; + request(verb, url, options = {}) { let merged = Object.assign({}, this.httpOptions, options); let csrftoken = getCookie(AppConstants.csrftoken); if (csrftoken) { @@ -128,13 +127,10 @@ class Requests { } _putOrPost(url, paramsAndBody, method) { - let paramsCopy = Object.assign({}, paramsAndBody); let params = omitFromObject(paramsAndBody, ['body']); let newUrl = this.prepareUrl(url, params); - let body = null; - if (paramsCopy && paramsCopy.body) { - body = JSON.stringify(paramsCopy.body); - } + let body = paramsAndBody && paramsAndBody.body ? JSON.stringify(paramsAndBody.body) + : null; return this.request(method, newUrl, { body }); } From adf0d411d67a06331f1b002f663fb50cf4f9426e Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 5 Nov 2015 11:56:17 +0100 Subject: [PATCH 19/83] Fetch piece data in AdditionalDetailForm Although this moves state further down the hierarchy, it allows the AdditionalDataForm to be used more easily. Parent components would otherwise have to have the piece prop carried down through multiple levels or create a makeshift object. --- js/components/ascribe_forms/property.js | 5 +- .../lumenus_edition_container.js | 10 +- .../lumenus_detail/lumenus_further_details.js | 31 +----- .../lumenus_detail/lumenus_piece_container.js | 10 +- .../lumenus_additional_data_form.js | 99 ++++++++++++++----- .../lumenus/lumenus_register_piece.js | 58 +++++------ 6 files changed, 111 insertions(+), 102 deletions(-) diff --git a/js/components/ascribe_forms/property.js b/js/components/ascribe_forms/property.js index 793be538..b4baf9df 100644 --- a/js/components/ascribe_forms/property.js +++ b/js/components/ascribe_forms/property.js @@ -31,7 +31,10 @@ let Property = React.createClass({ footer: React.PropTypes.element, handleChange: React.PropTypes.func, ignoreFocus: React.PropTypes.bool, - name: React.PropTypes.string.isRequired, + name: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number + ]).isRequired, className: React.PropTypes.string, onClick: React.PropTypes.func, diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js index 77cca99c..c81fb0bb 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js @@ -9,18 +9,14 @@ import LumenusAclButtonList from '../lumenus_buttons/lumenus_acl_button_list'; import EditionContainer from '../../../../../ascribe_detail/edition_container'; let LumenusEditionContainer = React.createClass({ - propTypes: { - params: React.PropTypes.object, - location: React.PropTypes.object - }, + propTypes: EditionContainer.propTypes, render() { return ( + furtherDetailsType={LumenusFurtherDetails} /> ); } }); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js index 17dbad20..79199b68 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js @@ -4,43 +4,20 @@ import React from 'react'; import LumenusAdditionalDataForm from '../lumenus_forms/lumenus_additional_data_form' -import GlobalNotificationModel from '../../../../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; - -let FurtherDetails = React.createClass({ +let LumenusFurtherDetails = React.createClass({ propTypes: { pieceId: React.PropTypes.number, - extraData: React.PropTypes.object, - otherData: React.PropTypes.arrayOf(React.PropTypes.object), handleSuccess: React.PropTypes.func, - location: React.PropTypes.object - }, - - showNotification() { - this.props.handleSuccess(); - let notification = new GlobalNotificationModel('Further details updated', 'success'); - GlobalNotificationActions.appendGlobalNotification(notification); }, render() { - const { pieceId, extraData, otherData, handleSuccess, location } = this.props; - - // Instead of grabbing the entire piece from the PieceStore and making this component - // stateful, we can put together a piece for the additional form solely based on the props - const piece = { - id: pieceId, - extra_data: extraData, - other_data: otherData - }; - return ( + showNotification /> ); } }); -export default FurtherDetails; +export default LumenusFurtherDetails; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js index 5b7cf8ee..391a7cb5 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js @@ -7,17 +7,13 @@ import LumenusFurtherDetails from './lumenus_further_details'; import PieceContainer from '../../../../../ascribe_detail/piece_container'; let LumenusPieceContainer = React.createClass({ - propTypes: { - params: React.PropTypes.object, - location: React.PropTypes.object - }, + propTypes: PieceContainer.propTypes, render() { return ( + {...this.props} + furtherDetailsType={LumenusFurtherDetails} /> ); } }); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index 3e67c21a..3b69ce81 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -14,38 +14,70 @@ import GlobalNotificationActions from '../../../../../../actions/global_notifica import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils'; +import PieceActions from '../../../../../../actions/piece_actions'; +import PieceStore from '../../../../../../stores/piece_store'; + import ApiUrls from '../../../../../../constants/api_urls'; import AppConstants from '../../../../../../constants/application_constants'; import requests from '../../../../../../utils/requests'; +import { mergeOptions } from '../../../../../../utils/general_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; let LumenusAdditionalDataForm = React.createClass({ propTypes: { - handleSuccess: React.PropTypes.func, - piece: React.PropTypes.shape({ - id: React.PropTypes.number, - extra_data: React.PropTypes.object, - other_data: React.PropTypes.arrayOf(React.PropTypes.object) - }).isRequired, - isInline: React.PropTypes.bool - }, - - getDefaultProps() { - return { - isInline: false - }; + pieceId: React.PropTypes.oneOfType([ + React.PropTypes.number, + React.PropTypes.string + ]), + isInline: React.PropTypes.bool, + showHeading: React.PropTypes.bool, + showNotification: React.PropTypes.bool, + handleSuccess: React.PropTypes.func }, getInitialState() { - return { - isUploadReady: false - }; + const pieceStore = PieceStore.getState(); + + return mergeOptions( + pieceStore, + { + // Allow the form to be submitted if there's already an additional image uploaded + isUploadReady: this.isUploadReadyOnChange(pieceStore.piece), + forceUpdateKey: 0, + }); }, - handleSuccess() { - let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000); - GlobalNotificationActions.appendGlobalNotification(notification); + componentDidMount() { + PieceStore.listen(this.onChange); + + // If the Piece store doesn't already have the piece we want loaded, load it + const { pieceId } = this.props; + if (pieceId && this.state.piece.id !== pieceId) { + PieceActions.fetchOne(pieceId); + } + }, + + componentWillUnmount() { + PieceStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + + this.setState({ + // Allow the form to be submitted if the updated piece already has an additional image uploaded + isUploadReady: this.isUploadReadyOnChange(state.piece), + + /** + * Increment the forceUpdateKey to force the form to rerender on each change + * + * THIS IS A HACK TO MAKE SURE THE FORM ALWAYS DISPLAYS THE MOST RECENT STATE + * BECAUSE SOME OF OUR FORM ELEMENTS DON'T UPDATE FROM PROP CHANGES (ie. + * InputTextAreaToggable). + */ + forceUpdateKey: this.state.forceUpdateKey + 1 + }); }, getFormData() { @@ -61,10 +93,23 @@ let LumenusAdditionalDataForm = React.createClass({ return { extradata: extradata, - piece_id: this.props.piece.id + piece_id: this.state.piece.id }; }, + isUploadReadyOnChange(piece) { + return piece && piece.other_data && piece.other_data.length > 0 ? true : false; + }, + + handleSuccessWithNotification() { + if (typeof this.props.handleSuccess === 'function') { + this.props.handleSuccess(); + } + + let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + uploadStarted() { this.setState({ isUploadReady: false @@ -78,7 +123,8 @@ let LumenusAdditionalDataForm = React.createClass({ }, render() { - let { piece, isInline, handleSuccess } = this.props; + const { isInline, handleSuccess, showHeading, showNotification } = this.props; + const { piece } = this.state; let buttons, spinner, heading; if (!isInline) { @@ -97,13 +143,13 @@ let LumenusAdditionalDataForm = React.createClass({ ); - heading = ( + heading = showHeading ? (

    {getLangText('Provide additional details')}

    - ); + ) : null; } if (piece && piece.id) { @@ -111,8 +157,9 @@ let LumenusAdditionalDataForm = React.createClass({
    @@ -144,11 +191,11 @@ let LumenusAdditionalDataForm = React.createClass({ required /> diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js index 342c4a32..3d43418d 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js @@ -13,7 +13,6 @@ import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece'; 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 PieceListStore from '../../../../../stores/piece_list_store'; import PieceListActions from '../../../../../actions/piece_list_actions'; @@ -35,7 +34,6 @@ let LumenusRegisterPiece = React.createClass({ return mergeOptions( UserStore.getState(), PieceListStore.getState(), - PieceStore.getState(), { selectedLicense: 0, isFineUploaderActive: false, @@ -46,33 +44,22 @@ let LumenusRegisterPiece = React.createClass({ componentDidMount() { PieceListStore.listen(this.onChange); UserStore.listen(this.onChange); - PieceStore.listen(this.onChange); UserActions.fetchCurrentUser(); - let queryParams = this.props.location.query; - - // 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); - } + // Reset the piece store to make sure that we don't display old data + // if the user repeatedly registers works + PieceActions.updatePiece({}); }, 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) { + if (this.state.currentUser && this.state.currentUser.email) { // we should also make the fineuploader component editable again this.setState({ isFineUploaderActive: true @@ -80,26 +67,21 @@ let LumenusRegisterPiece = React.createClass({ } }, - handleRegisterSuccess(response){ + handleRegisterSuccess(response) { this.refreshPieceList(); - // also start loading the piece for the next step - if(response && response.piece) { - PieceActions.updatePiece({}); + // Use the response's piece for the next step if available + let pieceId = null; + if (response && response.piece) { + pieceId = response.piece.id; PieceActions.updatePiece(response.piece); } this.incrementStep(); - - this.refs.slidesContainer.nextSlide({ piece_id: response.piece.id }); + this.refs.slidesContainer.nextSlide({ piece_id: pieceId }); }, 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.history.pushState(null, `/collection`); @@ -107,13 +89,21 @@ let LumenusRegisterPiece = React.createClass({ // 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 + step: this.state.step + 1 }); }, + getPieceFromQueryParam() { + const queryParams = this.props.location.query; + + // 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. + return queryParams && queryParams.piece_id; + }, + refreshPieceList() { PieceListActions.fetchPieceList( this.state.page, @@ -161,7 +151,7 @@ let LumenusRegisterPiece = React.createClass({ type="number" placeholder="(e.g. 32)" min={0} - required/> + required /> @@ -172,8 +162,8 @@ let LumenusRegisterPiece = React.createClass({ + pieceId={this.getPieceFromQueryParam()} + showHeading /> From 4ca8ca8feb8a992e89fba0c93380536181497d1c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 5 Nov 2015 11:58:32 +0100 Subject: [PATCH 20/83] Show AdditionalDetailsModal from SubmitButton when details need to be filled in --- .../lumenus_buttons/lumenus_submit_button.js | 102 +++++++++++++++--- 1 file changed, 87 insertions(+), 15 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js index 941b3ec0..6722beb7 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -3,19 +3,30 @@ import React from 'react'; import classNames from 'classnames'; +import LumenusAdditionalDataForm from '../lumenus_forms/lumenus_additional_data_form'; + import ConsignButton from '../../../../../ascribe_buttons/acls/consign_button'; +import AclFormFactory from '../../../../../ascribe_forms/acl_form_factory'; +import ConsignForm from '../../../../../ascribe_forms/form_consign'; + +import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; + +import PieceActions from '../../../../../../actions/piece_actions'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../../stores/whitelabel_store'; +import ApiUrls from '../../../../../../constants/api_urls'; + +import { getAclFormDataId } from '../../../../../../utils/form_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; let LumenusSubmitButton = React.createClass({ propTypes: { availableAcls: React.PropTypes.object.isRequired, - currentUser: React.PropTypes.object, - pieceOrEditions: React.PropTypes.array, - handleSuccess: React.PropTypes.func, + currentUser: React.PropTypes.object.isRequired, + editions: React.PropTypes.array.isRequired, + handleSuccess: React.PropTypes.func.isRequired, className: React.PropTypes.string, }, @@ -25,6 +36,7 @@ let LumenusSubmitButton = React.createClass({ componentDidMount() { WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); }, @@ -36,19 +48,79 @@ let LumenusSubmitButton = React.createClass({ this.setState(state); }, - render() { - const { availableAcls, currentUser, className, pieceOrEditions, handleSuccess } = this.props; + getFormDataId() { + return getAclFormDataId(false, this.props.editions); + }, - return ( - - ); + getAggregateEditionDetails() { + const { editions } = this.props; + + // Currently, we only care if all the given editions are from the same parent piece + // and if they can be submitted + return editions.reduce((details, curEdition) => { + return { + solePieceId: details.solePieceId === curEdition.parent ? details.solePieceId : null, + canSubmit: details.canSubmit && curEdition.acl.acl_wallet_submit + }; + }, { + solePieceId: editions.length > 0 ? editions[0].parent : null, + canSubmit: editions.length > 0 ? editions[0].acl.acl_wallet_submit : false + }); + }, + + handleAdditionalDataSuccess(pieceId) { + // Fetch newly updated piece to update the views + PieceActions.fetchOne(pieceId); + + this.refs.consignModal.show(); + }, + + render() { + const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; + const buttonTitle = getLangText('CONSIGN TO LUMENUS'); + + const { solePieceId, canSubmit } = this.getAggregateEditionDetails(); + + if (solePieceId && !canSubmit) { + return ( + + + {buttonTitle} + + } + handleSuccess={this.handleAdditionalDataSuccess.bind(this, solePieceId)} + title={getLangText('Add additional information')}> + + + + + + + + ); + } else { + return ( + + ); + } } }); From 6223248ea03e58c2af3a460455c112a44ee3ab3d Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 5 Nov 2015 13:51:40 +0100 Subject: [PATCH 21/83] Fix lumenus whitelabel api endpoints --- .../whitelabel/wallet/constants/wallet_api_urls.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 87ee6b14..11e29a39 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -4,25 +4,26 @@ import walletConstants from './wallet_application_constants'; // gets subdomain as a parameter function getWalletApiUrls(subdomain) { - if (subdomain === 'cyland'){ + if (subdomain === 'cyland') { return { 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; - } - else if (subdomain === 'ikonotv'){ + } else if (subdomain === 'ikonotv') { return { 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; - } - else if (subdomain === 'lumenus'){ + } else if (subdomain === 'lumenus') { return { - 'editions': walletConstants.walletApiEndpoint + subdomain + '/editions/', + 'editions_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/editions/', + 'edition': walletConstants.walletApiEndpoint + subdomain + '/editions/${edition_id}/', 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', + 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; } From 04e453b28cf6041b4c4e92c2f662259ed00f77d8 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 5 Nov 2015 16:05:20 +0100 Subject: [PATCH 22/83] Fix lumenus edition endpoint --- js/components/whitelabel/wallet/constants/wallet_api_urls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 11e29a39..27be363d 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -20,7 +20,7 @@ function getWalletApiUrls(subdomain) { } else if (subdomain === 'lumenus') { return { 'editions_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/editions/', - 'edition': walletConstants.walletApiEndpoint + subdomain + '/editions/${edition_id}/', + 'edition': walletConstants.walletApiEndpoint + subdomain + '/editions/${bitcoin_id}/', 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', From fb62d2d2e0461b2511eb38d55f333e11d9332916 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 6 Nov 2015 14:00:58 +0100 Subject: [PATCH 23/83] Check for completion of additional data on front end In the end, it made more sense to check if all the additional details have been filled in on the front end than receiving an acl or flag from the backend. --- .../lumenus_buttons/lumenus_submit_button.js | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js index 6722beb7..7046c069 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -12,6 +12,8 @@ import ConsignForm from '../../../../../ascribe_forms/form_consign'; import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; +import AclProxy from '../../../../../acl_proxy'; + import PieceActions from '../../../../../../actions/piece_actions'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../../stores/whitelabel_store'; @@ -48,6 +50,20 @@ let LumenusSubmitButton = React.createClass({ this.setState(state); }, + canEditionBeSubmitted(edition) { + if (edition && edition.extra_data && edition.other_data) { + const { extra_data, other_data } = edition; + + if (extra_data.artist_bio && extra_data.work_description && + extra_data.technology_details && extra_data.display_instructions && + other_data.length > 0) { + return true; + } + } + + return false; + }, + getFormDataId() { return getAclFormDataId(false, this.props.editions); }, @@ -60,11 +76,11 @@ let LumenusSubmitButton = React.createClass({ return editions.reduce((details, curEdition) => { return { solePieceId: details.solePieceId === curEdition.parent ? details.solePieceId : null, - canSubmit: details.canSubmit && curEdition.acl.acl_wallet_submit + canSubmit: details.canSubmit && this.canEditionBeSubmitted(curEdition) }; }, { solePieceId: editions.length > 0 ? editions[0].parent : null, - canSubmit: editions.length > 0 ? editions[0].acl.acl_wallet_submit : false + canSubmit: this.canEditionBeSubmitted(editions[0]) }); }, @@ -83,7 +99,9 @@ let LumenusSubmitButton = React.createClass({ if (solePieceId && !canSubmit) { return ( - + @@ -107,12 +125,12 @@ let LumenusSubmitButton = React.createClass({ pieceOrEditions={editions} showNotification /> - + ); } else { return ( Date: Fri, 6 Nov 2015 14:01:26 +0100 Subject: [PATCH 24/83] Show unconsign button --- .../lumenus/lumenus_buttons/lumenus_acl_button_list.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js index 76608032..77657aca 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js @@ -7,6 +7,7 @@ import LumenusSubmitButton from './lumenus_submit_button'; import DeleteButton from '../../../../../ascribe_buttons/delete_button'; import ShareButton from '../../../../../ascribe_buttons/acls/share_button'; import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button'; +import UnconsignButton from '../../../../../ascribe_buttons/acls/transfer_button'; import UserActions from '../../../../../../actions/user_actions'; import UserStore from '../../../../../../stores/user_store'; @@ -59,6 +60,11 @@ let LumenusAclButtonList = React.createClass({ currentUser={this.state.currentUser} pieceOrEditions={pieceOrEditions} handleSuccess={handleSuccess} /> + {this.props.children} ); From 318a0bf4b2c0f2e8b383342de5a54d4ad117b158 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 6 Nov 2015 14:10:54 +0100 Subject: [PATCH 25/83] Filter the collection to only show the consignable items by default --- .../piece_list_bulk_modal.js | 31 ------------------- .../piece_list_toolbar_filter_widget.js | 10 +++--- js/components/piece_list.js | 23 +++++++++++++- .../components/lumenus/lumenus_piece_list.js | 3 +- 4 files changed, 29 insertions(+), 38 deletions(-) diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index 1380f21d..bb8d4ccc 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -6,12 +6,6 @@ import { mergeOptions } from '../../utils/general_utils'; import EditionListActions from '../../actions/edition_list_actions'; -import UserStore from '../../stores/user_store'; -import UserActions from '../../actions/user_actions'; - -import PieceListStore from '../../stores/piece_list_store'; -import PieceListActions from '../../actions/piece_list_actions'; - import PieceListBulkModalSelectedEditionsWidget from './piece_list_bulk_modal_selected_editions_widget'; import { getLangText } from '../../utils/lang_utils.js'; @@ -30,31 +24,6 @@ let PieceListBulkModal = React.createClass({ ]) }, - getInitialState() { - return mergeOptions( - UserStore.getState(), - PieceListStore.getState() - ); - }, - - componentDidMount() { - UserStore.listen(this.onChange); - PieceListStore.listen(this.onChange); - - UserActions.fetchCurrentUser(); - PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, this.state.filterBy); - }, - - componentWillUnmount() { - PieceListStore.unlisten(this.onChange); - UserStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - clearAllSelections() { EditionListActions.clearAllEditionSelections(); EditionListActions.closeAllEditionLists(); diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index 38de2af6..cea41e3b 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -28,7 +28,7 @@ let PieceListToolbarFilterWidget = React.createClass({ }, generateFilterByStatement(param) { - let filterBy = this.props.filterBy; + const { filterBy } = this.props; if(filterBy) { // we need hasOwnProperty since the values are all booleans @@ -56,13 +56,13 @@ let PieceListToolbarFilterWidget = React.createClass({ */ filterBy(param) { return () => { - let filterBy = this.generateFilterByStatement(param); + const filterBy = this.generateFilterByStatement(param); this.props.applyFilterBy(filterBy); }; }, isFilterActive() { - let trueValuesOnly = Object.keys(this.props.filterBy).filter((acl) => acl); + const trueValuesOnly = Object.keys(this.props.filterBy).filter((acl) => acl); // We're hiding the star in that complicated matter so that, // the surrounding button is not resized up on appearance @@ -74,7 +74,7 @@ let PieceListToolbarFilterWidget = React.createClass({ }, render() { - let filterIcon = ( + const filterIcon = ( * @@ -140,4 +140,4 @@ let PieceListToolbarFilterWidget = React.createClass({ } }); -export default PieceListToolbarFilterWidget; \ No newline at end of file +export default PieceListToolbarFilterWidget; diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 69f5b23f..1ea64ff6 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -60,11 +60,17 @@ let PieceList = React.createClass({ }] }; }, + getInitialState() { - return mergeOptions( + const stores = mergeOptions( PieceListStore.getState(), EditionListStore.getState() ); + + // Use the default filters but use the stores' settings if they're available + stores.filterBy = Object.assign(this.getDefaultFilterBy(), stores.filterBy); + + return stores; }, componentDidMount() { @@ -96,6 +102,21 @@ let PieceList = React.createClass({ this.setState(state); }, + getDefaultFilterBy() { + const { filterParams } = this.props; + const defaultFilterBy = {}; + + filterParams.forEach(({ label, items }) => { + items.forEach((item) => { + if (typeof item === 'object' && item.defaultValue) { + defaultFilterBy[item.key] = true; + } + }); + }); + + return defaultFilterBy; + }, + paginationGoToPage(page) { return () => { // if the users clicks a pager of the pagination, diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index ccfb7e1c..58ad7813 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -25,7 +25,8 @@ let LumenusPieceList = React.createClass({ label: getLangText('Show works I can'), items: [{ key: 'acl_consign', - label: getLangText('consign to Lumenus') + label: getLangText('consign to Lumenus'), + defaultValue: true }] }]} location={this.props.location}/> From b100fdd80a35ae138afd2a33c9c9391e50d6f0bc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 6 Nov 2015 16:13:47 +0100 Subject: [PATCH 26/83] Change default collection filter for lumens admin --- .../lumenus_acl_button_list.js | 2 +- .../components/lumenus/lumenus_piece_list.js | 39 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js index 77657aca..8398dbda 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js @@ -7,7 +7,7 @@ import LumenusSubmitButton from './lumenus_submit_button'; import DeleteButton from '../../../../../ascribe_buttons/delete_button'; import ShareButton from '../../../../../ascribe_buttons/acls/share_button'; import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button'; -import UnconsignButton from '../../../../../ascribe_buttons/acls/transfer_button'; +import UnconsignButton from '../../../../../ascribe_buttons/acls/unconsign_button'; import UserActions from '../../../../../../actions/user_actions'; import UserStore from '../../../../../../stores/user_store'; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index 58ad7813..ebf90b3c 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -6,15 +6,48 @@ import LumenusAclButtonList from './lumenus_buttons/lumenus_acl_button_list'; import PieceList from '../../../../piece_list'; -import { getLangText } from '../../../../../utils/lang_utils'; +import UserActions from '../../../../../actions/user_actions'; +import UserStore from '../../../../../stores/user_store'; +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + import { setDocumentTitle } from '../../../../../utils/dom_utils'; +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; let LumenusPieceList = React.createClass({ propTypes: { location: React.PropTypes.object }, + getInitialState() { + return mergeOptions( + UserStore.getState(), + WhitelabelStore.getState() + ); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); + + UserActions.fetchCurrentUser(); + WhitelabelActions.fetchWhitelabel(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + render() { + const { currentUser, whitelabel } = this.state; + const isUserAdmin = currentUser.email === whitelabel.user; + setDocumentTitle(getLangText('Collection')); return ( @@ -24,8 +57,8 @@ let LumenusPieceList = React.createClass({ filterParams={[{ label: getLangText('Show works I can'), items: [{ - key: 'acl_consign', - label: getLangText('consign to Lumenus'), + key: isUserAdmin ? 'acl_transfer' : 'acl_consign', + label: getLangText(isUserAdmin ? 'transfer' : 'consign to Lumenus'), defaultValue: true }] }]} From 84e8e4612f23809c4ff19c719a1bc0df9450f731 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 10 Nov 2015 18:24:46 +0100 Subject: [PATCH 27/83] Autofocus message field in consignment form and use custom labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For now, we’ll let the artist specify their suggested price in the consignment form’s message field. --- .../ascribe_buttons/acls/acl_button.js | 2 +- .../ascribe_forms/acl_form_factory.js | 10 ++- js/components/ascribe_forms/form_consign.js | 24 ++++--- .../ascribe_forms/input_textarea_toggable.js | 6 ++ .../list_form_request_actions.js | 4 +- js/components/ascribe_forms/property.js | 7 ++ .../lumenus_buttons/lumenus_submit_button.js | 64 +++++++++++-------- .../lumenus_additional_data_form.js | 6 +- js/utils/form_utils.js | 4 ++ 9 files changed, 84 insertions(+), 43 deletions(-) diff --git a/js/components/ascribe_buttons/acls/acl_button.js b/js/components/ascribe_buttons/acls/acl_button.js index 82650bb6..8b21f92a 100644 --- a/js/components/ascribe_buttons/acls/acl_button.js +++ b/js/components/ascribe_buttons/acls/acl_button.js @@ -31,7 +31,7 @@ export default function ({ action, displayName, title, tooltip }) { availableAcls: React.PropTypes.object.isRequired, buttonAcceptName: React.PropTypes.string, buttonAcceptClassName: React.PropTypes.string, - currentUser: React.PropTypes.object.isRequired, + currentUser: React.PropTypes.object, email: React.PropTypes.string, pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, diff --git a/js/components/ascribe_forms/acl_form_factory.js b/js/components/ascribe_forms/acl_form_factory.js index d5494c2d..9422a351 100644 --- a/js/components/ascribe_forms/acl_form_factory.js +++ b/js/components/ascribe_forms/acl_form_factory.js @@ -20,9 +20,11 @@ import { getAclFormMessage, getAclFormDataId } from '../../utils/form_utils'; let AclFormFactory = React.createClass({ propTypes: { action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, - currentUser: React.PropTypes.object.isRequired, + autoFocusProperty: React.PropTypes.string, + currentUser: React.PropTypes.object, email: React.PropTypes.string, message: React.PropTypes.string, + labels: React.PropTypes.object, pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array @@ -53,10 +55,12 @@ let AclFormFactory = React.createClass({ render() { const { action, + autoFocusProperty, pieceOrEditions, currentUser, email, message, + labels, handleSuccess, showNotification } = this.props; @@ -64,14 +68,16 @@ let AclFormFactory = React.createClass({ aclName: action, entities: pieceOrEditions, isPiece: this.isPiece(), - senderName: currentUser.username + senderName: currentUser && currentUser.username }); if (action === 'acl_consign') { return ( diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index b8417961..c659610d 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -17,8 +17,10 @@ let ConsignForm = React.createClass({ propTypes: { url: React.PropTypes.string, id: React.PropTypes.object, + autoFocusProperty: React.PropTypes.string, email: React.PropTypes.string, message: React.PropTypes.string, + labels: React.PropTypes.object, handleSuccess: React.PropTypes.func }, @@ -27,10 +29,12 @@ let ConsignForm = React.createClass({ }, render() { + const { autoFocusProperty, email, id, handleSuccess, message, labels, url } = this.props; + return ( }> + label={labels.email || getLangText('Email')} + editable={!email} + overrideForm={!!email}> + label={labels.message || getLangText('Personal Message')} + editable + overrideForm> diff --git a/js/components/ascribe_forms/input_textarea_toggable.js b/js/components/ascribe_forms/input_textarea_toggable.js index c17a0e5a..0be8b87a 100644 --- a/js/components/ascribe_forms/input_textarea_toggable.js +++ b/js/components/ascribe_forms/input_textarea_toggable.js @@ -7,6 +7,7 @@ import TextareaAutosize from 'react-textarea-autosize'; let InputTextAreaToggable = React.createClass({ propTypes: { + autoFocus: React.PropTypes.bool, disabled: React.PropTypes.bool, rows: React.PropTypes.number.isRequired, required: React.PropTypes.bool, @@ -23,6 +24,10 @@ let InputTextAreaToggable = React.createClass({ }, componentDidMount() { + if (this.props.autoFocus) { + this.refs.textarea.focus(); + } + this.setState({ value: this.props.defaultValue }); @@ -51,6 +56,7 @@ let InputTextAreaToggable = React.createClass({ className = className + ' ascribe-textarea-editable'; textarea = ( + {getLangText('CONSIGN TO LUMENUS')} + + ); + const consignForm = ( + + ); if (solePieceId && !canSubmit) { return ( @@ -103,11 +124,7 @@ let LumenusSubmitButton = React.createClass({ aclObject={availableAcls} aclName='acl_consign'> - {buttonTitle} - - } + trigger={triggerButton} handleSuccess={this.handleAdditionalDataSuccess.bind(this, solePieceId)} title={getLangText('Add additional information')}> - + {consignForm} ); } else { return ( - + + + {consignForm} + + ); } } diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index 3b69ce81..2fbf5679 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -51,10 +51,8 @@ let LumenusAdditionalDataForm = React.createClass({ componentDidMount() { PieceStore.listen(this.onChange); - // If the Piece store doesn't already have the piece we want loaded, load it - const { pieceId } = this.props; - if (pieceId && this.state.piece.id !== pieceId) { - PieceActions.fetchOne(pieceId); + if (this.props.pieceId) { + PieceActions.fetchOne(this.props.pieceId); } }, diff --git a/js/utils/form_utils.js b/js/utils/form_utils.js index d2d2cd29..8d12a8c1 100644 --- a/js/utils/form_utils.js +++ b/js/utils/form_utils.js @@ -72,6 +72,10 @@ export function getAclFormMessage(options) { throw new Error('Your specified aclName did not match a an acl class.'); } + if (options.additionalMessage) { + message += '\n\n' + options.additionalMessage; + } + if (options.senderName) { message += '\n\n'; message += getLangText('Truly yours,'); From e85747144aacb402df4f1795c5240b23472522aa Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 10 Nov 2015 18:49:08 +0100 Subject: [PATCH 28/83] Add Lumenus landing page --- .../components/lumenus/lumenus_landing.js | 82 +++++++++++++++++++ .../whitelabel/wallet/wallet_routes.js | 2 + 2 files changed, 84 insertions(+) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js new file mode 100644 index 00000000..279327d5 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js @@ -0,0 +1,82 @@ +'use strict'; + +import React from 'react'; + +import Button from 'react-bootstrap/lib/Button'; +import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; + +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; + + +let LumenusLanding = React.createClass({ + + getInitialState() { + return mergeOptions( + WhitelabelStore.getState() + ); + }, + + componentDidMount() { + WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); + }, + + componentWillUnmount() { + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + setDocumentTitle('Lumenus Marketplace'); + + return ( +
    +
    +
    +
    + +
    + {getLangText('Artwork from the Lumenus Marketplace is powered by') + ' '} + + + +
    +
    +
    +
    +

    + {getLangText('Existing ascribe user?')} +

    + + + +
    +
    +

    + {getLangText('Do you need an account?')} +

    + + + +
    +
    +
    +
    +
    + ); + } +}); + +export default LumenusLanding; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 9b0e9465..86b3474a 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -29,6 +29,7 @@ import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; +import LumenusLanding from './components/lumenus/lumenus_landing'; import LumenusPieceList from './components/lumenus/lumenus_piece_list'; import LumenusRegisterPiece from './components/lumenus/lumenus_register_piece'; import LumenusPieceContainer from './components/lumenus/lumenus_detail/lumenus_piece_container'; @@ -157,6 +158,7 @@ let ROUTES = { ), 'lumenus': ( + From f7b55e56a9f0e53be1cd231d668e436448fff2db Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 12:04:08 +0100 Subject: [PATCH 29/83] Split Lumenus into a generic market wallet for 23vivi --- .../lumenus_edition_container.js | 24 ------------------- .../market_buttons/market_acl_button_list.js} | 12 +++++----- .../market_buttons/market_submit_button.js} | 12 +++++----- .../market_detail/market_edition_container.js | 24 +++++++++++++++++++ .../market_detail/market_further_details.js} | 8 +++---- .../market_detail/market_piece_container.js} | 8 +++---- .../market_additional_data_form.js} | 8 +++---- .../market_piece_list.js} | 10 ++++---- .../market_register_piece.js} | 10 ++++---- .../whitelabel/wallet/wallet_routes.js | 22 ++++++++--------- 10 files changed, 69 insertions(+), 69 deletions(-) delete mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js rename js/components/whitelabel/wallet/components/{lumenus/lumenus_buttons/lumenus_acl_button_list.js => market/market_buttons/market_acl_button_list.js} (88%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_buttons/lumenus_submit_button.js => market/market_buttons/market_submit_button.js} (93%) create mode 100644 js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js rename js/components/whitelabel/wallet/components/{lumenus/lumenus_detail/lumenus_further_details.js => market/market_detail/market_further_details.js} (58%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_detail/lumenus_piece_container.js => market/market_detail/market_piece_container.js} (56%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_forms/lumenus_additional_data_form.js => market/market_forms/market_additional_data_form.js} (97%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_piece_list.js => market/market_piece_list.js} (88%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_register_piece.js => market/market_register_piece.js} (96%) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js deleted file mode 100644 index c81fb0bb..00000000 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -import React from 'react'; - -import LumenusFurtherDetails from './lumenus_further_details'; - -import LumenusAclButtonList from '../lumenus_buttons/lumenus_acl_button_list'; - -import EditionContainer from '../../../../../ascribe_detail/edition_container'; - -let LumenusEditionContainer = React.createClass({ - propTypes: EditionContainer.propTypes, - - render() { - return ( - - ); - } -}); - -export default LumenusEditionContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js similarity index 88% rename from js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js rename to js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js index 8398dbda..1dcdd4e5 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js +++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js @@ -2,17 +2,17 @@ import React from 'react'; -import LumenusSubmitButton from './lumenus_submit_button'; +import MarketSubmitButton from './market_submit_button'; import DeleteButton from '../../../../../ascribe_buttons/delete_button'; -import ShareButton from '../../../../../ascribe_buttons/acls/share_button'; +import EmailButton from '../../../../../ascribe_buttons/acls/email_button'; import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button'; import UnconsignButton from '../../../../../ascribe_buttons/acls/unconsign_button'; import UserActions from '../../../../../../actions/user_actions'; import UserStore from '../../../../../../stores/user_store'; -let LumenusAclButtonList = React.createClass({ +let MarketAclButtonList = React.createClass({ propTypes: { availableAcls: React.PropTypes.object.isRequired, className: React.PropTypes.string, @@ -45,12 +45,12 @@ let LumenusAclButtonList = React.createClass({ let { availableAcls, className, pieceOrEditions, handleSuccess } = this.props; return (
    - - - {getLangText('CONSIGN TO LUMENUS')} + {getLangText('CONSIGN TO TODO')} ); const consignForm = ( @@ -127,7 +127,7 @@ let LumenusSubmitButton = React.createClass({ trigger={triggerButton} handleSuccess={this.handleAdditionalDataSuccess.bind(this, solePieceId)} title={getLangText('Add additional information')}> - @@ -147,7 +147,7 @@ let LumenusSubmitButton = React.createClass({ + title={getLangText('Consign artwork to TODO')}> {consignForm} @@ -156,4 +156,4 @@ let LumenusSubmitButton = React.createClass({ } }); -export default LumenusSubmitButton; +export default MarketSubmitButton; diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js b/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js new file mode 100644 index 00000000..97284dbc --- /dev/null +++ b/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import MarketFurtherDetails from './market_further_details'; + +import MarketAclButtonList from '../market_buttons/market_acl_button_list'; + +import EditionContainer from '../../../../../ascribe_detail/edition_container'; + +let MarketEditionContainer = React.createClass({ + propTypes: EditionContainer.propTypes, + + render() { + return ( + + ); + } +}); + +export default MarketEditionContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js similarity index 58% rename from js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js rename to js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js index 79199b68..4e1e3ee8 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js +++ b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js @@ -2,9 +2,9 @@ import React from 'react'; -import LumenusAdditionalDataForm from '../lumenus_forms/lumenus_additional_data_form' +import MarketAdditionalDataForm from '../market_forms/market_additional_data_form' -let LumenusFurtherDetails = React.createClass({ +let MarketFurtherDetails = React.createClass({ propTypes: { pieceId: React.PropTypes.number, handleSuccess: React.PropTypes.func, @@ -12,7 +12,7 @@ let LumenusFurtherDetails = React.createClass({ render() { return ( - @@ -20,4 +20,4 @@ let LumenusFurtherDetails = React.createClass({ } }); -export default LumenusFurtherDetails; +export default MarketFurtherDetails; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js b/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js similarity index 56% rename from js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js rename to js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js index 391a7cb5..d41ade56 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js +++ b/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js @@ -2,20 +2,20 @@ import React from 'react'; -import LumenusFurtherDetails from './lumenus_further_details'; +import MarketFurtherDetails from './market_further_details'; import PieceContainer from '../../../../../ascribe_detail/piece_container'; -let LumenusPieceContainer = React.createClass({ +let MarketPieceContainer = React.createClass({ propTypes: PieceContainer.propTypes, render() { return ( + furtherDetailsType={MarketFurtherDetails} /> ); } }); -export default LumenusPieceContainer; +export default MarketPieceContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js similarity index 97% rename from js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js rename to js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js index 2fbf5679..a2318b99 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js @@ -24,7 +24,7 @@ import requests from '../../../../../../utils/requests'; import { mergeOptions } from '../../../../../../utils/general_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; -let LumenusAdditionalDataForm = React.createClass({ +let MarketAdditionalDataForm = React.createClass({ propTypes: { pieceId: React.PropTypes.oneOfType([ React.PropTypes.number, @@ -163,7 +163,7 @@ let LumenusAdditionalDataForm = React.createClass({ spinner={spinner}> {heading} 0} enableLocalHashing={false} - headerMessage={getLangText('Consign to Lumenus')} + headerMessage={getLangText('Consign to Market')} submitMessage={getLangText('Proceed to additional details')} isFineUploaderActive={this.state.isFineUploaderActive} handleSuccess={this.handleRegisterSuccess} @@ -160,7 +160,7 @@ let LumenusRegisterPiece = React.createClass({
    - @@ -172,4 +172,4 @@ let LumenusRegisterPiece = React.createClass({ } }); -export default LumenusRegisterPiece; +export default MarketRegisterPiece; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 86b3474a..85e79924 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -15,7 +15,8 @@ import EditionContainer from '../../../components/ascribe_detail/edition_contain import SettingsContainer from '../../../components/ascribe_settings/settings_container'; import ContractSettings from '../../../components/ascribe_settings/contract_settings'; import ErrorNotFoundPage from '../../../components/error_not_found_page'; -import RegisterPiece from '../../../components/register_piece'; //TODO: Remove once finished with LumenusRegisterPiece + +import CCRegisterPiece from './components/cc/cc_register_piece'; import CylandLanding from './components/cyland/cyland_landing'; import CylandPieceContainer from './components/cyland/cyland_detail/cyland_piece_container'; @@ -29,13 +30,12 @@ import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; -import LumenusLanding from './components/lumenus/lumenus_landing'; -import LumenusPieceList from './components/lumenus/lumenus_piece_list'; -import LumenusRegisterPiece from './components/lumenus/lumenus_register_piece'; -import LumenusPieceContainer from './components/lumenus/lumenus_detail/lumenus_piece_container'; -import LumenusEditionContainer from './components/lumenus/lumenus_detail/lumenus_edition_container'; +import MarketPieceList from './components/market/market_piece_list'; +import MarketRegisterPiece from './components/market/market_register_piece'; +import MarketPieceContainer from './components/market/market_detail/market_piece_container'; +import MarketEditionContainer from './components/market/market_detail/market_edition_container'; -import CCRegisterPiece from './components/cc/cc_register_piece'; +import LumenusLanding from './components/lumenus/lumenus_landing'; import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler'; @@ -179,14 +179,14 @@ let ROUTES = { component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/> - - + + From ee61369d46aff112c6aaec6d583218f13be4ea58 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 12:04:18 +0100 Subject: [PATCH 30/83] Update market api endpoints --- .../whitelabel/wallet/constants/wallet_api_urls.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 27be363d..56088b03 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -19,12 +19,12 @@ function getWalletApiUrls(subdomain) { }; } else if (subdomain === 'lumenus') { return { - 'editions_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/editions/', - 'edition': walletConstants.walletApiEndpoint + subdomain + '/editions/${bitcoin_id}/', - 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', - 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', - 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', - 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' + 'editions_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/editions/', + 'edition': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/editions/${bitcoin_id}/', + 'pieces_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/', + 'piece_extradata': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/extradata/', + 'user': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/users/' }; } return {}; From a72fea5db4f919f2672c997781904b6e7eb79e90 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 12:08:37 +0100 Subject: [PATCH 31/83] Add 23vivi routes --- .../wallet/constants/wallet_api_urls.js | 2 +- .../whitelabel/wallet/wallet_routes.js | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 56088b03..cebe4aa6 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -17,7 +17,7 @@ function getWalletApiUrls(subdomain) { 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; - } else if (subdomain === 'lumenus') { + } else if (subdomain === 'lumenus' || subdomain === '23vivi') { return { 'editions_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/editions/', 'edition': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/editions/${bitcoin_id}/', diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 85e79924..08adec44 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -190,10 +190,54 @@ let ROUTES = { + ), + '23vivi': ( + + + + + + + + + + + + + + + ) }; +function getRoutes(commonRoutes, subdomain) { + if(subdomain in ROUTES) { + return ROUTES[subdomain]; + } else { + throw new Error('Subdomain wasn\'t specified in the wallet app.'); + } +}; + + function getRoutes(commonRoutes, subdomain) { if(subdomain in ROUTES) { return ROUTES[subdomain]; From 2e03ab584a874918a51df8428cf25f2a301e9b97 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 18:17:38 +0100 Subject: [PATCH 32/83] Fix api endpoints --- .../whitelabel/wallet/constants/wallet_api_urls.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index cebe4aa6..8ad2eb81 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -19,12 +19,12 @@ function getWalletApiUrls(subdomain) { }; } else if (subdomain === 'lumenus' || subdomain === '23vivi') { return { - 'editions_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/editions/', - 'edition': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/editions/${bitcoin_id}/', - 'pieces_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/', - 'piece': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/', - 'piece_extradata': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/extradata/', - 'user': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/users/' + 'editions_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/editions/', + 'edition': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/editions/${bitcoin_id}/', + 'pieces_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/', + 'piece_extradata': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/extradata/', + 'user': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/users/' }; } return {}; From 47f56b5505bcd7e9ad06c50c07be61010165c06f Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 18:17:51 +0100 Subject: [PATCH 33/83] Add 23vivi to app costs --- js/constants/application_constants.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 70a9be89..1f2129da 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -58,6 +58,13 @@ const constants = { 'permissions': ['register', 'edit', 'share', 'del_from_collection'], 'type': 'wallet' }, + { + 'subdomain': '23vivi', + 'name': '23vivi', + 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/23vivi/23vivi-logo.png', + 'permissions': ['register', 'edit', 'share', 'del_from_collection'], + 'type': 'wallet' + }, { 'subdomain': 'portfolioreview', 'name': 'Portfolio Review', From e228a4bf153cb9dec93612153438005cae184198 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 18:18:49 +0100 Subject: [PATCH 34/83] Dynamically get marketplace name for submit button --- .../market/market_buttons/market_submit_button.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js index 5ad9f995..6a617bfb 100644 --- a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js +++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js @@ -91,6 +91,7 @@ let MarketSubmitButton = React.createClass({ render() { const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; + const { whitelabel } = this.state; const { solePieceId, canSubmit } = this.getAggregateEditionDetails(); const message = getAclFormMessage({ aclName: 'acl_consign', @@ -102,14 +103,14 @@ let MarketSubmitButton = React.createClass({ const triggerButton = ( ); const consignForm = ( + title={getLangText('Consign artwork to %s', whitelabel.name)}> {consignForm} From c270977eb92f3cd87ccb5ca2b20bcbbe38e930cc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 19:26:45 +0100 Subject: [PATCH 35/83] Fix default filter on piece list --- .../piece_list_toolbar_filter_widget.js | 2 +- js/components/piece_list.js | 121 ++++++++++++------ .../components/lumenus/lumenus_landing.js | 6 +- .../components/market/market_piece_list.js | 33 +++-- js/utils/general_utils.js | 6 + package.json | 1 + 6 files changed, 114 insertions(+), 55 deletions(-) diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index cea41e3b..c463330c 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -28,7 +28,7 @@ let PieceListToolbarFilterWidget = React.createClass({ }, generateFilterByStatement(param) { - const { filterBy } = this.props; + const filterBy = Object.assign({}, this.props.filterBy); if(filterBy) { // we need hasOwnProperty since the values are all booleans diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 1ea64ff6..758edf7c 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -26,7 +26,7 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar'; import AscribeSpinner from './ascribe_spinner'; import { getAvailableAcls } from '../utils/acl_utils'; -import { mergeOptions } from '../utils/general_utils'; +import { mergeOptions, isShallowEqual } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; import { setDocumentTitle } from '../utils/dom_utils'; @@ -35,6 +35,7 @@ let PieceList = React.createClass({ propTypes: { accordionListItemType: React.PropTypes.func, bulkModalButtonListType: React.PropTypes.func, + canLoadPieceList: React.PropTypes.bool, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, filterParams: React.PropTypes.array, @@ -49,6 +50,7 @@ let PieceList = React.createClass({ return { accordionListItemType: AccordionListItemWallet, bulkModalButtonListType: AclButtonList, + canLoadPieceList: true, orderParams: ['artist_name', 'title'], filterParams: [{ label: getLangText('Show works I can'), @@ -62,27 +64,51 @@ let PieceList = React.createClass({ }, getInitialState() { + const pieceListStore = PieceListStore.getState(); const stores = mergeOptions( - PieceListStore.getState(), - EditionListStore.getState() + pieceListStore, + EditionListStore.getState(), + { + isFilterDirty: false + } ); - // Use the default filters but use the stores' settings if they're available - stores.filterBy = Object.assign(this.getDefaultFilterBy(), stores.filterBy); + // Use the default filters but use the pieceListStore's settings if they're available + stores.filterBy = Object.assign(this.getDefaultFilterBy(), pieceListStore.filterBy); return stores; }, componentDidMount() { - let page = this.props.location.query.page || 1; - PieceListStore.listen(this.onChange); EditionListStore.listen(this.onChange); - let orderBy = this.props.orderBy ? this.props.orderBy : this.state.orderBy; - if (this.state.pieceList.length === 0 || this.state.page !== page){ - PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, - orderBy, this.state.orderAsc, this.state.filterBy); + let page = this.props.location.query.page || 1; + if (this.props.canLoadPieceList && (this.state.pieceList.length === 0 || this.state.page !== page)) { + this.loadPieceList({ page }); + } + }, + + componentWillReceiveProps(nextProps) { + let filterBy; + let page = this.props.location.query.page || 1; + + // If the user hasn't changed the filter and the new default filter is different + // than the current filter, apply the new default filter + if (!this.state.isFilterDirty) { + const newDefaultFilterBy = this.getDefaultFilterBy(nextProps); + + // Only need to check shallowly since the filterBy shouldn't be nested + if (!isShallowEqual(this.state.filterBy, newDefaultFilterBy)) { + filterBy = newDefaultFilterBy; + page = 1; + } + } + + // Only load if we are applying a new filter or if it's the first time we can + // load the piece list + if (nextProps.canLoadPieceList && (filterBy || !this.props.canLoadPieceList)) { + this.loadPieceList({ page, filterBy }); } }, @@ -102,17 +128,19 @@ let PieceList = React.createClass({ this.setState(state); }, - getDefaultFilterBy() { - const { filterParams } = this.props; + getDefaultFilterBy(props = this.props) { + const { filterParams } = props; const defaultFilterBy = {}; - filterParams.forEach(({ label, items }) => { - items.forEach((item) => { - if (typeof item === 'object' && item.defaultValue) { - defaultFilterBy[item.key] = true; - } + if (filterParams && typeof filterParams.forEach === 'function') { + filterParams.forEach(({ label, items }) => { + items.forEach((item) => { + if (typeof item === 'object' && item.defaultValue) { + defaultFilterBy[item.key] = true; + } + }); }); - }); + } return defaultFilterBy; }, @@ -122,9 +150,7 @@ let PieceList = React.createClass({ // if the users clicks a pager of the pagination, // the site should go to the top document.body.scrollTop = document.documentElement.scrollTop = 0; - PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, - this.state.filterBy); + this.loadPieceList({ page }); }; }, @@ -143,29 +169,35 @@ let PieceList = React.createClass({ }, searchFor(searchTerm) { - PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy, - this.state.orderAsc, this.state.filterBy); - this.history.pushState(null, this.props.location.pathname, {page: 1}); + this.loadPieceList({ + page: 1, + search: searchTerm + }); + this.history.pushState(null, this.props.location.pathname, {page: 1}); }, applyFilterBy(filterBy){ - // first we need to apply the filter on the piece list - PieceListActions.fetchPieceList(1, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, filterBy) - .then(() => { - // but also, we need to filter all the open edition lists - this.state.pieceList - .forEach((piece) => { - // but only if they're actually open - if(this.state.isEditionListOpenForPieceId[piece.id].show) { - EditionListActions.refreshEditionList({ - pieceId: piece.id, - filterBy - }); - } + this.setState({ + isFilterDirty: true + }); - }); - }); + // first we need to apply the filter on the piece list + this + .loadPieceList({ page: 1, filterBy }) + .then(() => { + // but also, we need to filter all the open edition lists + this.state.pieceList + .forEach((piece) => { + // but only if they're actually open + if(this.state.isEditionListOpenForPieceId[piece.id].show) { + EditionListActions.refreshEditionList({ + pieceId: piece.id, + filterBy + }); + } + + }); + }); // we have to redirect the user always to page one as it could be that there is no page two // for filtered pieces @@ -177,6 +209,13 @@ let PieceList = React.createClass({ orderBy, this.state.orderAsc, this.state.filterBy); }, + loadPieceList({ page, filterBy = this.state.filterBy, search = this.state.search }) { + let orderBy = this.state.orderBy ? this.state.orderBy : this.props.orderBy; + + return PieceListActions.fetchPieceList(page, this.state.pageSize, search, + orderBy, this.state.orderAsc, filterBy); + }, + fetchSelectedPieceEditionList() { let filteredPieceIdList = Object.keys(this.state.editionList) .filter((pieceId) => { diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js index 279327d5..e68b1781 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js @@ -21,6 +21,10 @@ let LumenusLanding = React.createClass({ ); }, + componentWillMount() { + setDocumentTitle('Lumenus Marketplace'); + }, + componentDidMount() { WhitelabelStore.listen(this.onChange); WhitelabelActions.fetchWhitelabel(); @@ -35,8 +39,6 @@ let LumenusLanding = React.createClass({ }, render() { - setDocumentTitle('Lumenus Marketplace'); - return (
    diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js index 90564f2b..64198bb3 100644 --- a/js/components/whitelabel/wallet/components/market/market_piece_list.js +++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js @@ -27,6 +27,10 @@ let MarketPieceList = React.createClass({ ); }, + componentWillMount() { + setDocumentTitle(getLangText('Collection')); + }, + componentDidMount() { UserStore.listen(this.onChange); WhitelabelStore.listen(this.onChange); @@ -46,23 +50,30 @@ let MarketPieceList = React.createClass({ render() { const { currentUser, whitelabel } = this.state; - const isUserAdmin = currentUser.email === whitelabel.user; + let filterParams = undefined; + let canLoadPieceList = false; - setDocumentTitle(getLangText('Collection')); + if (currentUser.email && whitelabel.user) { + canLoadPieceList = true; + const isUserAdmin = currentUser.email === whitelabel.user; + + filterParams = [{ + label: getLangText('Show works I can'), + items: [{ + key: isUserAdmin ? 'acl_transfer' : 'acl_consign', + label: getLangText(isUserAdmin ? 'transfer' : 'consign to %s', whitelabel.name), + defaultValue: true + }] + }]; + } return ( + filterParams={filterParams} + location={this.props.location} /> ); } }); diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index 8283f8cf..e81a806d 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -1,5 +1,11 @@ 'use strict'; +/** + * Checks shallow equality + * Re-export of shallow from shallow-equals + */ +export { default as isShallowEqual } from 'shallow-equals'; + /** * Takes an object and returns a shallow copy without any keys * that fail the passed in filter function. diff --git a/package.json b/package.json index 63c6d1e0..fbae7243 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react-star-rating": "~1.3.2", "react-textarea-autosize": "^2.5.2", "reactify": "^1.1.0", + "shallow-equals": "0.0.0", "shmui": "^0.1.0", "spark-md5": "~1.0.0", "uglifyjs": "^2.4.10", From 0c02c033e57c9d12a057e24010e82a9efcaf392c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 27 Nov 2015 23:53:54 +0100 Subject: [PATCH 36/83] Fix consign form when labels aren't give as props --- js/components/ascribe_forms/form_consign.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index c659610d..158fd046 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -24,7 +24,13 @@ let ConsignForm = React.createClass({ handleSuccess: React.PropTypes.func }, - getFormData(){ + getInitialProps() { + return { + labels: {} + }; + }, + + getFormData() { return this.props.id; }, From 539c34db48f2d71f64891f61cf6905847866da34 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 30 Nov 2015 11:19:59 +0100 Subject: [PATCH 37/83] Fix typo for getDefaultProps() in consign form --- js/components/ascribe_forms/form_consign.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 158fd046..d88add94 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -24,7 +24,7 @@ let ConsignForm = React.createClass({ handleSuccess: React.PropTypes.func }, - getInitialProps() { + getDefaultProps() { return { labels: {} }; From ea52c54e3a38ef37e0670878c5bf3d31e953e26f Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 30 Nov 2015 11:27:15 +0100 Subject: [PATCH 38/83] Fix check for determining if a piece's edition list is open --- js/components/piece_list.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 3d4309f8..4a269aa8 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -130,7 +130,9 @@ let PieceList = React.createClass({ this.state.pieceList .forEach((piece) => { // but only if they're actually open - if(this.state.isEditionListOpenForPieceId[piece.id].show) { + const isEditionListOpenForPiece = this.state.isEditionListOpenForPieceId[piece.id]; + + if (isEditionListOpenForPiece && isEditionListOpenForPiece.show) { EditionListActions.refreshEditionList({ pieceId: piece.id, filterBy From 3d017392c10ad28fc3192a4b0a53ec8d9f241aa1 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 30 Nov 2015 11:32:48 +0100 Subject: [PATCH 39/83] Only use previous filterBy on refreshing edition lists if no filterBy argument was given MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also fix the behaviour of refresh using the list’s current length rather than the previous page size. --- js/stores/edition_list_store.js | 43 ++++++++++----------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/js/stores/edition_list_store.js b/js/stores/edition_list_store.js index 4ccada4e..6b4d64f9 100644 --- a/js/stores/edition_list_store.js +++ b/js/stores/edition_list_store.js @@ -32,7 +32,7 @@ class EditionListStore { // page let storeEditionIndex = (page - 1) * pageSize + i; let editionsForPieces = this.editionList[pieceId]; - + // if edition already exists, just merge if(editionsForPieces[storeEditionIndex]) { editionsForPieces[storeEditionIndex] = React.addons.update(editionsForPieces[storeEditionIndex], {$merge: editionListOfPiece[i]}); @@ -60,46 +60,29 @@ class EditionListStore { * We often just have to refresh the edition list for a certain pieceId, * this method provides exactly that functionality without any side effects */ - onRefreshEditionList({pieceId, filterBy}) { + onRefreshEditionList({pieceId, filterBy = this.editionList[pieceId].filterBy}) { + const pieceEditionList = this.editionList[pieceId]; + // It may happen that the user enters the site logged in already // through /editions // If he then tries to delete a piece/edition and this method is called, // we'll not be able to refresh his edition list since its not yet there. // Therefore we can just return, since there is no data to be refreshed - if(!this.editionList[pieceId]) { + if (!pieceEditionList) { return; } - let prevEditionListLength = this.editionList[pieceId].length; - let prevEditionListPage = this.editionList[pieceId].page; - let prevEditionListPageSize = this.editionList[pieceId].pageSize; - - // we can also refresh the edition list using filterBy, - // if we decide not to do that then the old filter will just be applied. - if(filterBy && Object.keys(filterBy).length <= 0) { - filterBy = this.editionList[pieceId].filterBy; - prevEditionListLength = 10; - prevEditionListPage = 1; - prevEditionListPageSize = 10; - } - // to clear an array, david walsh recommends to just set it's length to zero // http://davidwalsh.name/empty-array - this.editionList[pieceId].length = 0; + pieceEditionList.length = 0; - // refetch editions with adjusted page size - EditionsListActions.fetchEditionList(pieceId, 1, prevEditionListLength, - this.editionList[pieceId].orderBy, - this.editionList[pieceId].orderAsc, - filterBy) - .then(() => { - // reset back to the normal pageSize and page - this.editionList[pieceId].page = prevEditionListPage; - this.editionList[pieceId].pageSize = prevEditionListPageSize; - }) - .catch((err) => { - console.logGlobal(err); - }); + // refetch editions from the beginning with the previous settings + EditionsListActions + .fetchEditionList(pieceId, 1, pieceEditionList.pageSize, + pieceEditionList.orderBy, + pieceEditionList.orderAsc, + filterBy) + .catch(console.logGlobal); } onSelectEdition({pieceId, editionId, toValue}) { From 85340a91269672813c215c28d366318992a0e0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 2 Dec 2015 12:23:38 +0100 Subject: [PATCH 40/83] Fix bug that threw an error when passing nullish prop to PieceListFilterDisplay --- js/components/piece_list.js | 5 ++--- js/components/piece_list_filter_display.js | 2 +- .../whitelabel/wallet/components/market/market_piece_list.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 383d870f..9208df92 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -133,7 +133,7 @@ let PieceList = React.createClass({ const defaultFilterBy = {}; if (filterParams && typeof filterParams.forEach === 'function') { - filterParams.forEach(({ label, items }) => { + filterParams.forEach(({ items }) => { items.forEach((item) => { if (typeof item === 'object' && item.defaultValue) { defaultFilterBy[item.key] = true; @@ -211,7 +211,7 @@ let PieceList = React.createClass({ }, loadPieceList({ page, filterBy = this.state.filterBy, search = this.state.search }) { - let orderBy = this.state.orderBy ? this.state.orderBy : this.props.orderBy; + const orderBy = this.state.orderBy || this.props.orderBy; return PieceListActions.fetchPieceList(page, this.state.pageSize, search, orderBy, this.state.orderAsc, filterBy); @@ -259,7 +259,6 @@ let PieceList = React.createClass({ const availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); setDocumentTitle(getLangText('Collection')); - return (
    diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js index 64198bb3..8ffab5a5 100644 --- a/js/components/whitelabel/wallet/components/market/market_piece_list.js +++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js @@ -50,7 +50,7 @@ let MarketPieceList = React.createClass({ render() { const { currentUser, whitelabel } = this.state; - let filterParams = undefined; + let filterParams = null; let canLoadPieceList = false; if (currentUser.email && whitelabel.user) { From 8b3060d6133e72765b1c07c3b1c9a0cc3b88e10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 2 Dec 2015 12:25:11 +0100 Subject: [PATCH 41/83] Fix simple comments in pr --- .../ascribe_detail/further_details_fileuploader.js | 4 +++- .../market/market_buttons/market_submit_button.js | 4 ++-- .../market_forms/market_additional_data_form.js | 6 ++++-- .../wallet/components/market/market_register_piece.js | 11 +---------- js/components/whitelabel/wallet/wallet_routes.js | 11 ----------- 5 files changed, 10 insertions(+), 26 deletions(-) diff --git a/js/components/ascribe_detail/further_details_fileuploader.js b/js/components/ascribe_detail/further_details_fileuploader.js index c8e17036..dc638816 100644 --- a/js/components/ascribe_detail/further_details_fileuploader.js +++ b/js/components/ascribe_detail/further_details_fileuploader.js @@ -10,6 +10,8 @@ import ApiUrls from '../../constants/api_urls'; import AppConstants from '../../constants/application_constants'; import { getCookie } from '../../utils/fetch_api_utils'; +import { getLangText } from '../../utils/lang_utils'; + let FurtherDetailsFileuploader = React.createClass({ propTypes: { @@ -26,7 +28,7 @@ let FurtherDetailsFileuploader = React.createClass({ getDefaultProps() { return { - label: 'Additional files', + label: getLangText('Additional files'), multiple: false }; }, diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js index 6a617bfb..d75282af 100644 --- a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js +++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js @@ -53,8 +53,8 @@ let MarketSubmitButton = React.createClass({ const { extra_data, other_data } = edition; if (extra_data.artist_bio && extra_data.work_description && - extra_data.technology_details && extra_data.display_instructions && - other_data.length > 0) { + extra_data.technology_details && extra_data.display_instructions && + other_data.length > 0) { return true; } } diff --git a/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js index a2318b99..90baebb0 100644 --- a/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js @@ -8,6 +8,7 @@ import Property from '../../../../../ascribe_forms/property'; import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable'; import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader'; +import AscribeSpinner from '../../../../../ascribe_spinner'; import GlobalNotificationModel from '../../../../../../models/global_notification_model'; import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; @@ -24,6 +25,7 @@ import requests from '../../../../../../utils/requests'; import { mergeOptions } from '../../../../../../utils/general_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; + let MarketAdditionalDataForm = React.createClass({ propTypes: { pieceId: React.PropTypes.oneOfType([ @@ -96,7 +98,7 @@ let MarketAdditionalDataForm = React.createClass({ }, isUploadReadyOnChange(piece) { - return piece && piece.other_data && piece.other_data.length > 0 ? true : false; + return piece && piece.other_data && piece.other_data.length > 0; }, handleSuccessWithNotification() { @@ -211,7 +213,7 @@ let MarketAdditionalDataForm = React.createClass({ } else { return (
    - +
    ); } diff --git a/js/components/whitelabel/wallet/components/market/market_register_piece.js b/js/components/whitelabel/wallet/components/market/market_register_piece.js index a59ce5fa..9a919561 100644 --- a/js/components/whitelabel/wallet/components/market/market_register_piece.js +++ b/js/components/whitelabel/wallet/components/market/market_register_piece.js @@ -35,8 +35,6 @@ let MarketRegisterPiece = React.createClass({ UserStore.getState(), PieceListStore.getState(), { - selectedLicense: 0, - isFineUploaderActive: false, step: 0 }); }, @@ -58,13 +56,6 @@ let MarketRegisterPiece = React.createClass({ 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) { @@ -140,7 +131,7 @@ let MarketRegisterPiece = React.createClass({ enableLocalHashing={false} headerMessage={getLangText('Consign to Market')} submitMessage={getLangText('Proceed to additional details')} - isFineUploaderActive={this.state.isFineUploaderActive} + isFineUploaderActive={true} handleSuccess={this.handleRegisterSuccess} onLoggedOut={this.onLoggedOut} location={this.props.location}> diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 08adec44..92b53dc6 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -228,16 +228,6 @@ let ROUTES = { ) }; - -function getRoutes(commonRoutes, subdomain) { - if(subdomain in ROUTES) { - return ROUTES[subdomain]; - } else { - throw new Error('Subdomain wasn\'t specified in the wallet app.'); - } -}; - - function getRoutes(commonRoutes, subdomain) { if(subdomain in ROUTES) { return ROUTES[subdomain]; @@ -246,5 +236,4 @@ function getRoutes(commonRoutes, subdomain) { } } - export default getRoutes; From fe24a5e15a47c67271d1ef8d6d751ea142482038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 2 Dec 2015 12:37:21 +0100 Subject: [PATCH 42/83] Remove all occurrences of inactivity mgmt of ReactS3Fineuploader --- .../ascribe_forms/form_register_piece.js | 2 -- .../ascribe_forms/input_fineuploader.js | 21 +++++++++-------- .../file_drag_and_drop.js | 9 -------- .../react_s3_fine_uploader.js | 3 --- js/components/register_piece.js | 14 ++--------- .../cyland/cyland_register_piece.js | 17 +------------- .../ikonotv/ikonotv_register_piece.js | 23 +------------------ .../market/market_register_piece.js | 6 ----- 8 files changed, 15 insertions(+), 80 deletions(-) diff --git a/js/components/ascribe_forms/form_register_piece.js b/js/components/ascribe_forms/form_register_piece.js index b09d29ac..5b5f97fe 100644 --- a/js/components/ascribe_forms/form_register_piece.js +++ b/js/components/ascribe_forms/form_register_piece.js @@ -26,7 +26,6 @@ let RegisterPieceForm = React.createClass({ isFineUploaderActive: React.PropTypes.bool, isFineUploaderEditable: React.PropTypes.bool, enableLocalHashing: React.PropTypes.bool, - onLoggedOut: React.PropTypes.func, // For this form to work with SlideContainer, we sometimes have to disable it disabled: React.PropTypes.bool, @@ -116,7 +115,6 @@ let RegisterPieceForm = React.createClass({ setIsUploadReady={this.setIsUploadReady} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} isFineUploaderActive={this.props.isFineUploaderActive} - onLoggedOut={this.props.onLoggedOut} disabled={!this.props.isFineUploaderEditable} enableLocalHashing={enableLocalHashing} uploadMethod={this.props.location.query.method} /> diff --git a/js/components/ascribe_forms/input_fineuploader.js b/js/components/ascribe_forms/input_fineuploader.js index 948521c0..1a9889db 100644 --- a/js/components/ascribe_forms/input_fineuploader.js +++ b/js/components/ascribe_forms/input_fineuploader.js @@ -38,7 +38,6 @@ const InputFineUploader = React.createClass({ // a user is actually not logged in already to prevent him from droping files // before login in isFineUploaderActive: bool, - onLoggedOut: func, enableLocalHashing: bool, uploadMethod: string, @@ -51,7 +50,10 @@ const InputFineUploader = React.createClass({ fileClassToUpload: shape({ singular: string, plural: string - }) + }), + + // Provided by `Property` + onChange: React.PropTypes.func }, getDefaultProps() { @@ -101,16 +103,16 @@ const InputFineUploader = React.createClass({ setIsUploadReady, isReadyForFormSubmission, areAssetsDownloadable, - onLoggedOut, enableLocalHashing, + uploadMethod, fileClassToUpload, - location } = this.props; + disabled } = this.props; let editable = this.props.isFineUploaderActive; // if disabled is actually set by property, we want to override // isFineUploaderActive - if(typeof this.props.disabled !== 'undefined') { - editable = !this.props.disabled; + if(typeof disabled !== 'undefined') { + editable = !disabled; } return ( @@ -139,10 +141,9 @@ const InputFineUploader = React.createClass({ 'X-CSRFToken': getCookie(AppConstants.csrftoken) } }} - onInactive={this.props.onLoggedOut} - enableLocalHashing={this.props.enableLocalHashing} - uploadMethod={this.props.uploadMethod} - fileClassToUpload={this.props.fileClassToUpload} /> + enableLocalHashing={enableLocalHashing} + uploadMethod={uploadMethod} + fileClassToUpload={fileClassToUpload} /> ); } }); diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js index 0cc7ff5e..2f3d6af5 100644 --- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js +++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js @@ -15,7 +15,6 @@ let FileDragAndDrop = React.createClass({ className: React.PropTypes.string, onDrop: React.PropTypes.func.isRequired, onDragOver: React.PropTypes.func, - onInactive: React.PropTypes.func, filesToUpload: React.PropTypes.array, handleDeleteFile: React.PropTypes.func, handleCancelFile: React.PropTypes.func, @@ -63,11 +62,6 @@ let FileDragAndDrop = React.createClass({ let files; if(this.props.dropzoneInactive) { - // if there is a handle function for doing stuff - // when the dropzone is inactive, then call it - if(this.props.onInactive) { - this.props.onInactive(); - } return; } @@ -119,9 +113,6 @@ let FileDragAndDrop = React.createClass({ if(this.props.dropzoneInactive) { // if there is a handle function for doing stuff // when the dropzone is inactive, then call it - if(this.props.onInactive) { - this.props.onInactive(); - } return; } diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js index bf4250c5..911f887e 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js @@ -99,7 +99,6 @@ let ReactS3FineUploader = React.createClass({ areAssetsDownloadable: React.PropTypes.bool, areAssetsEditable: React.PropTypes.bool, defaultErrorMessage: React.PropTypes.string, - onInactive: React.PropTypes.func, // We encountered some cases where people had difficulties to upload their // works to ascribe due to a slow internet connection. @@ -891,7 +890,6 @@ let ReactS3FineUploader = React.createClass({ multiple, areAssetsDownloadable, areAssetsEditable, - onInactive, enableLocalHashing, fileClassToUpload, fileInputElement: FileInputElement, @@ -901,7 +899,6 @@ let ReactS3FineUploader = React.createClass({ multiple, areAssetsDownloadable, areAssetsEditable, - onInactive, enableLocalHashing, uploadMethod, fileClassToUpload, diff --git a/js/components/register_piece.js b/js/components/register_piece.js index 43ac7bb7..bc0639e3 100644 --- a/js/components/register_piece.js +++ b/js/components/register_piece.js @@ -45,10 +45,7 @@ let RegisterPiece = React.createClass( { UserStore.getState(), WhitelabelStore.getState(), PieceListStore.getState(), - { - selectedLicense: 0, - isFineUploaderActive: false - }); + ); }, componentDidMount() { @@ -66,13 +63,6 @@ let RegisterPiece = React.createClass( { 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 - }); - } }, handleSuccess(response){ @@ -117,7 +107,7 @@ let RegisterPiece = React.createClass( { {this.props.children} 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 470da761..85cd01bd 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js @@ -53,8 +53,6 @@ let CylandRegisterPiece = React.createClass({ PieceStore.getState(), WhitelabelStore.getState(), { - selectedLicense: 0, - isFineUploaderActive: false, step: 0 }); }, @@ -90,13 +88,6 @@ let CylandRegisterPiece = React.createClass({ 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){ @@ -167,11 +158,6 @@ let CylandRegisterPiece = React.createClass({ } }, - // basically redirects to the second slide (index: 1), when the user is not logged in - onLoggedOut() { - this.history.pushState(null, '/login'); - }, - render() { let today = new Moment(); @@ -197,9 +183,8 @@ let CylandRegisterPiece = React.createClass({ enableLocalHashing={false} headerMessage={getLangText('Submit to Cyland Archive')} submitMessage={getLangText('Submit')} - isFineUploaderActive={this.state.isFineUploaderActive} + isFineUploaderActive={true} handleSuccess={this.handleRegisterSuccess} - onLoggedOut={this.onLoggedOut} location={this.props.location}/> diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js index d3f93ddf..16d893b7 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js @@ -85,13 +85,6 @@ let IkonotvRegisterPiece = React.createClass({ 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 - }); - } }, @@ -157,19 +150,6 @@ let IkonotvRegisterPiece = React.createClass({ ); }, - 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.history.pushState(null, '/login'); - }, - canSubmit() { let currentUser = this.state.currentUser; return currentUser && currentUser.acl && currentUser.acl.acl_wallet_submit; @@ -260,9 +240,8 @@ let IkonotvRegisterPiece = React.createClass({ enableLocalHashing={false} headerMessage={getLangText('Register work')} submitMessage={getLangText('Register')} - isFineUploaderActive={this.state.isFineUploaderActive} + isFineUploaderActive={true} handleSuccess={this.handleRegisterSuccess} - onLoggedOut={this.onLoggedOut} location={this.props.location}/> diff --git a/js/components/whitelabel/wallet/components/market/market_register_piece.js b/js/components/whitelabel/wallet/components/market/market_register_piece.js index 9a919561..3572fd6d 100644 --- a/js/components/whitelabel/wallet/components/market/market_register_piece.js +++ b/js/components/whitelabel/wallet/components/market/market_register_piece.js @@ -106,11 +106,6 @@ let MarketRegisterPiece = React.createClass({ ); }, - // basically redirects to the second slide (index: 1), when the user is not logged in - onLoggedOut() { - this.history.pushState(null, '/login'); - }, - render() { setDocumentTitle(getLangText('Register a new piece')); @@ -133,7 +128,6 @@ let MarketRegisterPiece = React.createClass({ submitMessage={getLangText('Proceed to additional details')} isFineUploaderActive={true} handleSuccess={this.handleRegisterSuccess} - onLoggedOut={this.onLoggedOut} location={this.props.location}> Date: Wed, 2 Dec 2015 13:52:17 +0100 Subject: [PATCH 43/83] Rearrange handleDrop() to flow more naturally --- .../file_drag_and_drop.js | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js index 2f3d6af5..1157a540 100644 --- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js +++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js @@ -59,23 +59,21 @@ let FileDragAndDrop = React.createClass({ handleDrop(event) { event.preventDefault(); event.stopPropagation(); - let files; - if(this.props.dropzoneInactive) { - return; + if (!this.props.dropzoneInactive) { + let files; + + // handle Drag and Drop + if(event.dataTransfer && event.dataTransfer.files.length > 0) { + files = event.dataTransfer.files; + } else if(event.target.files) { // handle input type file + files = event.target.files; + } + + if(typeof this.props.onDrop === 'function' && files) { + this.props.onDrop(files); + } } - - // handle Drag and Drop - if(event.dataTransfer && event.dataTransfer.files.length > 0) { - files = event.dataTransfer.files; - } else if(event.target.files) { // handle input type file - files = event.target.files; - } - - if(typeof this.props.onDrop === 'function' && files) { - this.props.onDrop(files); - } - }, handleDeleteFile(fileId) { @@ -107,28 +105,25 @@ let FileDragAndDrop = React.createClass({ }, handleOnClick() { - let evt; - // when multiple is set to false and the user already uploaded a piece, - // do not propagate event - if(this.props.dropzoneInactive) { - // if there is a handle function for doing stuff - // when the dropzone is inactive, then call it - return; - } + // do not propagate event if the drop zone's inactive, + // for example when multiple is set to false and the user already uploaded a piece + if (!this.props.dropzoneInactive) { + let evt; - try { - evt = new MouseEvent('click', { - view: window, - bubbles: true, - cancelable: true - }); - } catch(e) { - // For browsers that do not support the new MouseEvent syntax - evt = document.createEvent('MouseEvents'); - evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); - } + try { + evt = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true + }); + } catch(e) { + // For browsers that do not support the new MouseEvent syntax + evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); + } - this.refs.fileSelector.getDOMNode().dispatchEvent(evt); + this.refs.fileSelector.getDOMNode().dispatchEvent(evt); + } }, render: function () { From 07834414c75ac2ff0b451b0031d241eb0b5c4bc9 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 14:26:15 +0100 Subject: [PATCH 44/83] Set default name for marketplace wallets to be 'Market' until the white label name loads --- .../market_buttons/market_submit_button.js | 8 +++---- .../components/market/market_piece_list.js | 12 ++++++---- .../market/market_register_piece.js | 23 +++++++++++++++---- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js index d75282af..a4b1b7c3 100644 --- a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js +++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js @@ -91,7 +91,7 @@ let MarketSubmitButton = React.createClass({ render() { const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; - const { whitelabel } = this.state; + const { whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } } = this.state; const { solePieceId, canSubmit } = this.getAggregateEditionDetails(); const message = getAclFormMessage({ aclName: 'acl_consign', @@ -103,14 +103,14 @@ let MarketSubmitButton = React.createClass({ const triggerButton = ( ); const consignForm = ( + title={getLangText('Consign artwork to %s', whitelabelName)}> {consignForm} diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js index 8ffab5a5..e45da164 100644 --- a/js/components/whitelabel/wallet/components/market/market_piece_list.js +++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js @@ -49,19 +49,23 @@ let MarketPieceList = React.createClass({ }, render() { - const { currentUser, whitelabel } = this.state; + const { + currentUser: { email: userEmail }, + whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } + } = this.state; + let filterParams = null; let canLoadPieceList = false; - if (currentUser.email && whitelabel.user) { + if (userEmail && whitelabelAdminEmail) { canLoadPieceList = true; - const isUserAdmin = currentUser.email === whitelabel.user; + const isUserAdmin = userEmail === whitelabelAdminEmail; filterParams = [{ label: getLangText('Show works I can'), items: [{ key: isUserAdmin ? 'acl_transfer' : 'acl_consign', - label: getLangText(isUserAdmin ? 'transfer' : 'consign to %s', whitelabel.name), + label: getLangText(isUserAdmin ? 'transfer' : 'consign to %s', whitelabelName), defaultValue: true }] }]; diff --git a/js/components/whitelabel/wallet/components/market/market_register_piece.js b/js/components/whitelabel/wallet/components/market/market_register_piece.js index 3572fd6d..d68be85d 100644 --- a/js/components/whitelabel/wallet/components/market/market_register_piece.js +++ b/js/components/whitelabel/wallet/components/market/market_register_piece.js @@ -11,11 +11,13 @@ import MarketAdditionalDataForm from './market_forms/market_additional_data_form import Property from '../../../../ascribe_forms/property'; import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece'; -import UserStore from '../../../../../stores/user_store'; -import UserActions from '../../../../../actions/user_actions'; import PieceActions from '../../../../../actions/piece_actions'; 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 WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; import SlidesContainer from '../../../../ascribe_slides_container/slides_container'; @@ -32,8 +34,9 @@ let MarketRegisterPiece = React.createClass({ getInitialState(){ return mergeOptions( - UserStore.getState(), PieceListStore.getState(), + UserStore.getState(), + WhitelabelStore.getState(), { step: 0 }); @@ -42,7 +45,10 @@ let MarketRegisterPiece = React.createClass({ componentDidMount() { PieceListStore.listen(this.onChange); UserStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + WhitelabelActions.fetchWhitelabel(); // Reset the piece store to make sure that we don't display old data // if the user repeatedly registers works @@ -52,6 +58,7 @@ let MarketRegisterPiece = React.createClass({ componentWillUnmount() { PieceListStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); + WhitelabelStore.unlisten(this.onChange); }, onChange(state) { @@ -107,6 +114,12 @@ let MarketRegisterPiece = React.createClass({ }, render() { + const { + isFineUploaderActive, + step, + whitelabel: { name: whitelabelName = 'Market' } + } = this.state; + setDocumentTitle(getLangText('Register a new piece')); return ( @@ -122,9 +135,9 @@ let MarketRegisterPiece = React.createClass({ 0} + disabled={step > 0} enableLocalHashing={false} - headerMessage={getLangText('Consign to Market')} + headerMessage={getLangText('Consign to %s', whitelabelName)} submitMessage={getLangText('Proceed to additional details')} isFineUploaderActive={true} handleSuccess={this.handleRegisterSuccess} From ad736ad12f4151a57299fa26f6884d983f1064cc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 14:32:34 +0100 Subject: [PATCH 45/83] Remove unnecessary hover styling on the edition list widget --- sass/ascribe_accordion_list.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sass/ascribe_accordion_list.scss b/sass/ascribe_accordion_list.scss index c0b81096..675bfa80 100644 --- a/sass/ascribe_accordion_list.scss +++ b/sass/ascribe_accordion_list.scss @@ -211,10 +211,6 @@ $ascribe-accordion-list-item-height: 100px; -ms-user-select: none; -webkit-user-select: none; - &:hover { - color: $ascribe-dark-blue; - } - .glyphicon { font-size: .8em; top: 1px !important; From 8e837f8149039f6694367a95164c5c54c900decc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 14:47:03 +0100 Subject: [PATCH 46/83] Make dropdown items have padding inside them as opposed to outside margin Avoids weird highlighting when the items are selected. --- sass/ascribe_custom_style.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sass/ascribe_custom_style.scss b/sass/ascribe_custom_style.scss index 98cce937..1b6bb02f 100644 --- a/sass/ascribe_custom_style.scss +++ b/sass/ascribe_custom_style.scss @@ -68,10 +68,15 @@ hr { .dropdown-menu { background-color: $ascribe--nav-bg-color; } + .navbar-nav > li > .dropdown-menu { + padding: 0; + } .dropdown-menu > li > a { color: $ascribe--nav-fg-prim-color; font-weight: $ascribe--font-weight-light; + padding-bottom: 9px; + padding-top: 9px; } .dropdown-menu > li > a:hover, @@ -79,6 +84,10 @@ hr { background-color: rgba($ascribe--button-default-color, .05); } + .dropdown-menu > .divider { + margin: 0; + } + .notification-menu { .dropdown-menu { background-color: white; From 384d597405bda4d28b3d1c3b61edff8bb722413e Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 14:47:19 +0100 Subject: [PATCH 47/83] First cut of 23vivi styling --- .../wallet/23vivi/23vivi_custom_style.scss | 325 ++++++++++++++++++ sass/whitelabel/wallet/index.scss | 1 + 2 files changed, 326 insertions(+) create mode 100644 sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss diff --git a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss new file mode 100644 index 00000000..79be4a23 --- /dev/null +++ b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss @@ -0,0 +1,325 @@ +/** Sass cannot use a number as the first character of a variable, so we'll have to settle with vivi23 **/ +$vivi23--fg-color: black; +$vivi23--bg-color: white; +$vivi23--nav-fg-prim-color: $vivi23--fg-color; +$vivi23--nav-fg-sec-color: #3a3a3a; +$vivi23--nav-bg-color: $vivi23--bg-color; +$vivi23--nav-highlight-color: #f8f8f8; +$vivi23--button-default-color: $vivi23--fg-color; +$vivi23--highlight-color: #de2600; + + +.client--23vivi { + /** Navbar **/ + .navbar-default { + background-color: $vivi23--nav-fg-prim-color; + + .navbar-brand .icon-ascribe-logo { + color: $vivi23--bg-color; + &:hover { + color: darken($vivi23--bg-color, 20%); + } + } + + } + + .navbar-nav > li > a, + .navbar-nav > li > a:focus, + .navbar-nav > li > .active a, + .navbar-nav > li > .active a:focus { + color: $vivi23--nav-bg-color; + } + + .navbar-nav > li > a:hover { + color: darken($vivi23--nav-bg-color, 20%); + } + + .navbar-nav > .active a, + .navbar-nav > .active a:hover, + .navbar-nav > .active a:focus { + background-color: $vivi23--nav-fg-prim-color; + border-bottom-color: $vivi23--nav-bg-color; + color: $vivi23--nav-bg-color; + } + + .navbar-nav > .open > a, + .dropdown-menu > .active > a, + .dropdown-menu > li > a { + color: $vivi23--nav-fg-prim-color; + background-color: $vivi23--nav-bg-color; + } + + .navbar-nav > .open > a:hover, + .navbar-nav > .open > a:focus, + .dropdown-menu > .active > a:hover, + .dropdown-menu > .active > a:focus, + .dropdown-menu > li > a:hover, + .dropdown-menu > li > a:focus { + color: lighten($vivi23--nav-fg-prim-color, 20%); + background-color: $vivi23--nav-highlight-color; + } + + .navbar-collapse.collapsing, + .navbar-collapse.collapse.in { + background-color: $vivi23--nav-bg-color; + + .navbar-nav > .open > a, + .navbar-nav > .active > a { + background-color: $vivi23--nav-highlight-color; + } + } + + .navbar-collapse.collapsing li a, + .navbar-collapse.collapse.in li a { + color: $vivi23--nav-fg-prim-color; + } + .navbar-collapse.collapse.in li a:not(.ascribe-powered-by):hover { + color: lighten($vivi23--nav-fg-prim-color, 20%); + background-color: $vivi23--nav-highlight-color; + } + + .navbar-toggle { + border-color: $vivi23--highlight-color; + + .icon-bar { + background-color: $vivi23--highlight-color; + } + } + + .navbar-toggle:hover, + .navbar-toggle:focus { + border-color: lighten($vivi23--highlight-color, 10%); + background-color: $vivi23--nav-fg-prim-color; + + .icon-bar { + background-color: lighten($vivi23--highlight-color, 10%); + } + } + + .notification-menu { + .dropdown-menu { + background-color: $vivi23--nav-bg-color; + } + + .notification-header { + background-color: $vivi23--nav-fg-sec-color; + border-top-width: 0; + color: $vivi23--nav-bg-color; + } + + .notification-action { + color: $vivi23--highlight-color; + } + } + + /** Buttons **/ + // reset disabled button styling for btn-default + .btn-default.disabled, + .btn-default.disabled:hover, + .btn-default.disabled:focus, + .btn-default.disabled.focus, + .btn-default.disabled:active, + .btn-default.disabled.active, + .btn-default[disabled], + .btn-default[disabled]:hover, + .btn-default[disabled]:focus, + .btn-default[disabled].focus, + .btn-default[disabled]:active, + .btn-default[disabled].active, + fieldset[disabled] .btn-default, + fieldset[disabled] .btn-default:hover, + fieldset[disabled] .btn-default:focus, + fieldset[disabled] .btn-default.focus, + fieldset[disabled] .btn-default:active, + fieldset[disabled] .btn-default.active { + background-color: lighten($vivi23--button-default-color, 30%); + border-color: lighten($vivi23--button-default-color, 30%); + } + + .btn-default { + background-color: $vivi23--button-default-color; + border-color: $vivi23--button-default-color; + + &:hover, + &:active, + &:focus, + &:active:hover, + &:active:focus, + &:active.focus, + &.active:hover, + &.active:focus, + &.active.focus { + background-color: lighten($vivi23--button-default-color, 30%); + border-color: lighten($vivi23--button-default-color, 30%); + } + } + + // disabled buttons + .btn-secondary.disabled, + .btn-secondary.disabled:hover, + .btn-secondary.disabled:focus, + .btn-secondary.disabled.focus, + .btn-secondary.disabled:active, + .btn-secondary.disabled.active, + .btn-secondary[disabled], + .btn-secondary[disabled]:hover, + .btn-secondary[disabled]:focus, + .btn-secondary[disabled].focus, + .btn-secondary[disabled]:active, + .btn-secondary[disabled].active, + fieldset[disabled] .btn-secondary, + fieldset[disabled] .btn-secondary:hover, + fieldset[disabled] .btn-secondary:focus, + fieldset[disabled] .btn-secondary.focus, + fieldset[disabled] .btn-secondary:active, + fieldset[disabled] .btn-secondary.active { + background-color: darken($vivi23--bg-color, 20%); + border-color: $vivi23--button-default-color; + } + + .btn-secondary { + border-color: $vivi23--button-default-color; + + &:hover, + &:active, + &:focus, + &:active:hover, + &:active:focus, + &:active.focus, + &.active:hover, + &.active:focus, + &.active.focus { + background-color: $vivi23--button-default-color; + border-color: $vivi23--button-default-color; + } + } + + .btn-tertiary { + &:hover, + &:active, + &ctive:hover, + &.active:hover{ + background-color: $vivi23--highlight-color; + border-color: $vivi23--highlight-color; + color: $vivi23--highlight-color; + } + } + + /** Other components **/ + .ascribe-piece-list-toolbar .btn-ascribe-add { + display: none; + } + + .ascribe-footer { + display: none; + } + + .ascribe-accordion-list-table-toggle:hover { + color: $vivi23--fg-color; + } + + .request-action-badge { + color: $vivi23--fg-color; + } + + .acl-information-dropdown-list .title { + color: $vivi23--fg-color; + } + + // filter widget + .ascribe-piece-list-toolbar-filter-widget button { + background-color: transparent !important; + border-color: transparent !important; + color: $vivi23--button-default-color !important; + + &:hover, + &:active { + background-color: $vivi23--button-default-color !important; + border-color: $vivi23--button-default-color !important; + color: $vivi23--bg-color !important; + } + } + + .icon-ascribe-search { + color: $vivi23--fg-color; + } + + // forms + .ascribe-property-wrapper:hover { + border-left-color: rgba($vivi23--fg-color, 0.5); + } + + .ascribe-property textarea, + .ascribe-property input, + .search-bar > .form-group > .input-group input { + &::-webkit-input-placeholder { + color: rgba($vivi23--fg-color, 0.5); + } + &::-moz-placeholder { + color: rgba($vivi23--fg-color, 0.5); + } + &:-ms-input-placeholder { + color: rgba($vivi23--fg-color, 0.5); + } + &:-moz-placeholder { + color: rgba($vivi23--fg-color, 0.5); + } + } + + .ascribe-property { + > div, + > input, + > pre, + > select, + > span:not(.glyphicon), + > p, + > p > span, + > textarea { + color: $vivi23--fg-color; + } + } + + // global notification + .ascribe-global-notification-success { + background-color: lighten($vivi23--fg-color, 20%); + } + + // uploader progress + .ascribe-progress-bar > .progress-bar { + background-color: lighten($vivi23--fg-color, 20%); + } + + // spinner + .spinner-circle { + border-color: $vivi23--fg-color; + } + .spinner-inner { + display: none; + } + + // video player + .ascribe-media-player .vjs-default-skin { + .vjs-play-progress, + .vjs-volume-level { + background-color: $vivi23--highlight-color; + } + } + + // pager + .pager li > a, + .pager li > span { + background-color: $vivi23--fg-color; + border-color: $vivi23--fg-color; + } + .pager .disabled > a, + .pager .disabled > span { + background-color: $vivi23--fg-color !important; + border-color: $vivi23--fg-color; + } + + // intercom + #intercom-container .intercom-launcher-button { + background-color: $vivi23--button-default-color !important; + border-color: $vivi23--button-default-color !important; + } +} diff --git a/sass/whitelabel/wallet/index.scss b/sass/whitelabel/wallet/index.scss index 024fb3cc..01c374d9 100644 --- a/sass/whitelabel/wallet/index.scss +++ b/sass/whitelabel/wallet/index.scss @@ -1,6 +1,7 @@ @import 'cc/cc_custom_style'; @import 'cyland/cyland_custom_style'; @import 'ikonotv/ikonotv_custom_style'; +@import '23vivi/23vivi_custom_style'; .ascribe-wallet-app { border-radius: 0; From 2381bd22ac1cc1181fed2356b6599664f60479de Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 16:05:21 +0100 Subject: [PATCH 48/83] Change nested destructuring formatting --- .../wallet/components/market/market_piece_list.js | 6 ++++-- .../wallet/components/market/market_register_piece.js | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js index e45da164..647a84be 100644 --- a/js/components/whitelabel/wallet/components/market/market_piece_list.js +++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js @@ -51,8 +51,10 @@ let MarketPieceList = React.createClass({ render() { const { currentUser: { email: userEmail }, - whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } - } = this.state; + whitelabel: { + name: whitelabelName = 'Market', + user: whitelabelAdminEmail + } } = this.state; let filterParams = null; let canLoadPieceList = false; diff --git a/js/components/whitelabel/wallet/components/market/market_register_piece.js b/js/components/whitelabel/wallet/components/market/market_register_piece.js index d68be85d..e02df80c 100644 --- a/js/components/whitelabel/wallet/components/market/market_register_piece.js +++ b/js/components/whitelabel/wallet/components/market/market_register_piece.js @@ -117,8 +117,9 @@ let MarketRegisterPiece = React.createClass({ const { isFineUploaderActive, step, - whitelabel: { name: whitelabelName = 'Market' } - } = this.state; + whitelabel: { + name: whitelabelName = 'Market' + } } = this.state; setDocumentTitle(getLangText('Register a new piece')); From ed6b80df891c229dede268b55a066916e27c8dd7 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 17:54:31 +0100 Subject: [PATCH 49/83] Fix weird word breaks on edition action panel --- sass/ascribe_panel.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sass/ascribe_panel.scss b/sass/ascribe_panel.scss index 09d773ad..af1366cd 100644 --- a/sass/ascribe_panel.scss +++ b/sass/ascribe_panel.scss @@ -26,7 +26,7 @@ vertical-align: middle; &:first-child { - word-break: break-all; + word-break: break-word; font-size: .9em; } } From 4664a488988eb50b315a290211be859ded2157a1 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 18:18:20 +0100 Subject: [PATCH 50/83] Change additional form's button text when not in registration flow --- .../market/market_buttons/market_submit_button.js | 3 ++- .../market_forms/market_additional_data_form.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js index d75282af..60a071d8 100644 --- a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js +++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js @@ -129,7 +129,8 @@ let MarketSubmitButton = React.createClass({ handleSuccess={this.handleAdditionalDataSuccess.bind(this, solePieceId)} title={getLangText('Add additional information')}> + pieceId={solePieceId} + submitLabel={getLangText('Continue to consignment')} /> - {getLangText('Register work')} + {submitLabel} ); From 594853535b7e592cbdf99b3d5ae6b114672be4fb Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 18:28:30 +0100 Subject: [PATCH 51/83] Fix trailing comma from previous PR merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out this is actually illegal in ES6 but on some version of babel, they don’t care. Might be an ES7 thing though: http://www.2ality.com/2015/11/trailing-comma-parameters.html --- js/components/register_piece.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/register_piece.js b/js/components/register_piece.js index bc0639e3..9cd1779c 100644 --- a/js/components/register_piece.js +++ b/js/components/register_piece.js @@ -44,7 +44,7 @@ let RegisterPiece = React.createClass( { return mergeOptions( UserStore.getState(), WhitelabelStore.getState(), - PieceListStore.getState(), + PieceListStore.getState() ); }, From 743c11df5b16a47f8fcad7a39c024c1fe1443a2f Mon Sep 17 00:00:00 2001 From: diminator Date: Wed, 2 Dec 2015 18:56:48 +0100 Subject: [PATCH 52/83] fixed cyland/ikono serializers --- js/components/register_piece.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/register_piece.js b/js/components/register_piece.js index bc0639e3..9cd1779c 100644 --- a/js/components/register_piece.js +++ b/js/components/register_piece.js @@ -44,7 +44,7 @@ let RegisterPiece = React.createClass( { return mergeOptions( UserStore.getState(), WhitelabelStore.getState(), - PieceListStore.getState(), + PieceListStore.getState() ); }, From 3be033cdd5893eefe79e2dd9187b6ee73e0a8e2e Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 2 Dec 2015 19:31:50 +0100 Subject: [PATCH 53/83] Add thumbnail placeholder for 23vivi --- .../accordion_list_item_piece.js | 28 +++++++++++++------ ...cordion_list_item_thumbnail_placeholder.js | 15 ++++++++++ .../accordion_list_item_wallet.js | 23 +++++++++------ js/components/piece_list.js | 22 ++++++++++----- ...cordion_list_item_thumbnail_placeholder.js | 15 ++++++++++ .../components/23vivi/23vivi_piece_list.js | 24 ++++++++++++++++ .../components/market/market_piece_list.js | 5 +++- .../whitelabel/wallet/wallet_routes.js | 7 +++-- sass/ascribe-fonts/ascribe-fonts.scss | 22 --------------- sass/ascribe_accordion_list.scss | 28 +++++++++++++++++++ 10 files changed, 140 insertions(+), 49 deletions(-) create mode 100644 js/components/ascribe_accordion_list/accordion_list_item_thumbnail_placeholder.js create mode 100644 js/components/whitelabel/wallet/components/23vivi/23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder.js create mode 100644 js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js diff --git a/js/components/ascribe_accordion_list/accordion_list_item_piece.js b/js/components/ascribe_accordion_list/accordion_list_item_piece.js index 4547ce3b..74322e51 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_piece.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_piece.js @@ -4,6 +4,7 @@ import React from 'react'; import { Link } from 'react-router'; import AccordionListItem from './accordion_list_item'; +import AccordionListItemThumbnailPlacholder from './accordion_list_item_thumbnail_placeholder'; import { getLangText } from '../../utils/lang_utils'; @@ -19,7 +20,14 @@ let AccordionListItemPiece = React.createClass({ ]), subsubheading: React.PropTypes.object, buttons: React.PropTypes.object, - badge: React.PropTypes.object + badge: React.PropTypes.object, + thumbnailPlaceholder: React.PropTypes.element + }, + + getDefaultProps() { + return { + thumbnailPlaceholder: AccordionListItemThumbnailPlacholder + }; }, getLinkData() { @@ -34,19 +42,23 @@ let AccordionListItemPiece = React.createClass({ }, render() { - const { className, piece, artistName, buttons, badge, children, subsubheading } = this.props; + const { + artistName, + badge, + buttons, + children, + className, + piece, + subsubheading, + thumbnailPlaceholder: ThumbnailPlaceholder } = this.props; const { url, url_safe } = piece.thumbnail; let thumbnail; // Since we're going to refactor the thumbnail generation anyway at one point, // for not use the annoying ascribe_spiral.png, we're matching the url against // this name and replace it with a CSS version of the new logo. - if(url.match(/https:\/\/.*\/media\/thumbnails\/ascribe_spiral.png/)) { - thumbnail = ( - - A - - ); + if (url.match(/https:\/\/.*\/media\/thumbnails\/ascribe_spiral.png/)) { + thumbnail = (); } else { thumbnail = (
    diff --git a/js/components/ascribe_accordion_list/accordion_list_item_thumbnail_placeholder.js b/js/components/ascribe_accordion_list/accordion_list_item_thumbnail_placeholder.js new file mode 100644 index 00000000..6ba4d6ab --- /dev/null +++ b/js/components/ascribe_accordion_list/accordion_list_item_thumbnail_placeholder.js @@ -0,0 +1,15 @@ +'use strict' + +import React from 'react'; + +let accordionListItemThumbnailPlaceholder = React.createClass({ + render() { + return ( + + A + + ); + } +}); + +export default accordionListItemThumbnailPlaceholder; diff --git a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js index da45d1e8..6fb3abeb 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js @@ -31,6 +31,7 @@ let AccordionListItemWallet = React.createClass({ propTypes: { className: React.PropTypes.string, content: React.PropTypes.object, + thumbnailPlaceholder: React.PropTypes.element, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element @@ -123,32 +124,36 @@ let AccordionListItemWallet = React.createClass({ }, render() { + const { children, className, content, thumbnailPlaceholder } = this.props; return ( - {Moment(this.props.content.date_created, 'YYYY-MM-DD').year()} + {Moment(content.date_created, 'YYYY-MM-DD').year()} {this.getLicences()} -
    } +
    + } buttons={
    -
    } - badge={this.getGlyphicon()}> +
    + } + badge={this.getGlyphicon()} + thumbnailPlaceholder={thumbnailPlaceholder}> {this.getCreateEditionsDialog()} {/* this.props.children is AccordionListItemTableEditions */} - {this.props.children} + {children} ); } diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 9208df92..229fb078 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -38,6 +38,7 @@ let PieceList = React.createClass({ canLoadPieceList: React.PropTypes.bool, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, + customThumbnailPlaceholder: React.PropTypes.element, filterParams: React.PropTypes.array, orderParams: React.PropTypes.array, orderBy: React.PropTypes.string, @@ -251,9 +252,15 @@ let PieceList = React.createClass({ }, render() { + const { + accordionListItemType: AccordionListItemType, + bulkModalButtonListType: BulkModalButtonListType, + customSubmitButton, + customThumbnailPlaceholder, + filterParams, + orderParams } = this.props; + const loadingElement = ; - const AccordionListItemType = this.props.accordionListItemType; - const BulkModalButtonListType = this.props.bulkModalButtonListType; const selectedEditions = this.fetchSelectedEditionList(); const availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); @@ -265,14 +272,14 @@ let PieceList = React.createClass({ className="ascribe-piece-list-toolbar" searchFor={this.searchFor} searchQuery={this.state.search} - filterParams={this.props.filterParams} - orderParams={this.props.orderParams} + filterParams={filterParams} + orderParams={orderParams} filterBy={this.state.filterBy} orderBy={this.state.orderBy} applyFilterBy={this.applyFilterBy} applyOrderBy={this.applyOrderBy}> - {this.props.customSubmitButton ? - this.props.customSubmitButton : + {customSubmitButton ? + customSubmitButton : @@ -294,7 +301,7 @@ let PieceList = React.createClass({ + filterParams={filterParams}/> + 23 + + ); + } +}); + +export default Vivi23AccordionListItemThumbnailPlaceholder; diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js new file mode 100644 index 00000000..d889a9a0 --- /dev/null +++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js @@ -0,0 +1,24 @@ +'use strict' + +import React from 'react'; + +import Vivi23AccordionListItemThumbnailPlaceholder from './23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder'; + +import MarketPieceList from '../market/market_piece_list'; + +let vivi23PieceList = React.createClass({ + propTypes: { + location: React.PropTypes.object + }, + + render() { + return ( + + ); + } + +}); + +export default vivi23PieceList; diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js index 8ffab5a5..a6198b4a 100644 --- a/js/components/whitelabel/wallet/components/market/market_piece_list.js +++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js @@ -17,6 +17,7 @@ import { getLangText } from '../../../../../utils/lang_utils'; let MarketPieceList = React.createClass({ propTypes: { + customThumbnailPlaceholder: React.PropTypes.element, location: React.PropTypes.object }, @@ -49,6 +50,7 @@ let MarketPieceList = React.createClass({ }, render() { + const { customThumbnailPlaceholder, location } = this.props; const { currentUser, whitelabel } = this.state; let filterParams = null; let canLoadPieceList = false; @@ -72,8 +74,9 @@ let MarketPieceList = React.createClass({ canLoadPieceList={canLoadPieceList} redirectTo="/register_piece?slide_num=0" bulkModalButtonListType={MarketAclButtonList} + customThumbnailPlaceholder={customThumbnailPlaceholder} filterParams={filterParams} - location={this.props.location} /> + location={location} /> ); } }); diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 92b53dc6..fbb124bf 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -37,6 +37,9 @@ import MarketEditionContainer from './components/market/market_detail/market_edi import LumenusLanding from './components/lumenus/lumenus_landing'; +import Vivi23Landing from './components/23vivi/23vivi_landing'; +import Vivi23PieceList from './components/23vivi/23vivi_piece_list'; + import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler'; import WalletApp from './wallet_app'; @@ -193,7 +196,7 @@ let ROUTES = { ), '23vivi': ( - + @@ -218,7 +221,7 @@ let ROUTES = { headerTitle='+ NEW WORK'/> diff --git a/sass/ascribe-fonts/ascribe-fonts.scss b/sass/ascribe-fonts/ascribe-fonts.scss index 8b66627d..a770d556 100644 --- a/sass/ascribe-fonts/ascribe-fonts.scss +++ b/sass/ascribe-fonts/ascribe-fonts.scss @@ -248,25 +248,3 @@ font-size: 18px; padding: 4px 12px 0 10px } - -.ascribe-logo-circle { - border: 6px solid #F6F6F6; - border-radius: 10em; - position: relative; - top: 10%; - left: 10%; - - display: block; - width: 80%; - height: 80%; - - > span { - color: #F6F6F6; - position: absolute; - top: -.29em; - left: .16em; - - font-size: 5em; - font-weight: normal; - } -} \ No newline at end of file diff --git a/sass/ascribe_accordion_list.scss b/sass/ascribe_accordion_list.scss index 675bfa80..791743fc 100644 --- a/sass/ascribe_accordion_list.scss +++ b/sass/ascribe_accordion_list.scss @@ -60,6 +60,34 @@ $ascribe-accordion-list-item-height: 100px; background-size: cover; } + .ascribe-logo-circle { + border: 6px solid #F6F6F6; + border-radius: 10em; + position: relative; + top: 10%; + left: 10%; + + display: block; + width: 80%; + height: 80%; + + > span { + color: #F6F6F6; + position: absolute; + top: -.29em; + left: .16em; + + font-size: 5em; + font-weight: normal; + } + } + + .ascribe-thumbnail-placeholder { + color: #F6F6F6; + font-size: 5em; + font-weight: normal; + } + //&::before { // content: ' '; // display: inline-block; From 565bf308aedee0fc845a8fb1c1b8de93961f0232 Mon Sep 17 00:00:00 2001 From: diminator Date: Wed, 2 Dec 2015 21:42:35 +0100 Subject: [PATCH 54/83] tested and fixed ikonotv --- .../components/ikonotv/ikonotv_register_piece.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js index 16d893b7..c6f59a53 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js @@ -16,6 +16,9 @@ import UserActions from '../../../../../actions/user_actions'; import PieceStore from '../../../../../stores/piece_store'; import PieceActions from '../../../../../actions/piece_actions'; +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + import GlobalNotificationModel from '../../../../../models/global_notification_model'; import GlobalNotificationActions from '../../../../../actions/global_notification_actions'; @@ -47,6 +50,7 @@ let IkonotvRegisterPiece = React.createClass({ UserStore.getState(), PieceListStore.getState(), PieceStore.getState(), + WhitelabelStore.getState(), { step: 0, pageExitWarning: getLangText("If you leave this form now, your work will not be loaned to Ikono TV.") @@ -57,7 +61,9 @@ let IkonotvRegisterPiece = React.createClass({ PieceListStore.listen(this.onChange); UserStore.listen(this.onChange); PieceStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); UserActions.fetchCurrentUser(); + WhitelabelActions.fetchWhitelabel(); // Before we load the new piece, we reset the piece store to delete old data that we do // not want to display to the user. @@ -81,6 +87,7 @@ let IkonotvRegisterPiece = React.createClass({ PieceListStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); PieceStore.unlisten(this.onChange); + WhitelabelStore.listen(this.onChange); }, onChange(state) { @@ -152,7 +159,8 @@ let IkonotvRegisterPiece = React.createClass({ canSubmit() { let currentUser = this.state.currentUser; - return currentUser && currentUser.acl && currentUser.acl.acl_wallet_submit; + let whitelabel = this.state.whitelabel; + return currentUser && currentUser.acl && currentUser.acl.acl_wallet_submit && whitelabel && whitelabel.user; }, getSlideArtistDetails() { @@ -194,15 +202,16 @@ let IkonotvRegisterPiece = React.createClass({ let today = new Moment(); let enddate = new Moment(); enddate.add(2, 'years'); + const {piece, whitelabel} = this.state; return (
    Date: Thu, 3 Dec 2015 00:48:21 +0100 Subject: [PATCH 55/83] Fix prop type for MarketAdditionalDataForm's submitLabel --- .../market/market_forms/market_additional_data_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js index f43f906f..1c9b8a54 100644 --- a/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js @@ -35,7 +35,7 @@ let MarketAdditionalDataForm = React.createClass({ isInline: React.PropTypes.bool, showHeading: React.PropTypes.bool, showNotification: React.PropTypes.bool, - submitLabel: React.PropTypes.bool, + submitLabel: React.PropTypes.string, handleSuccess: React.PropTypes.func }, From 55bdd4e03f72f58348c19e0c985d5fd5a3b7b8e0 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 00:49:26 +0100 Subject: [PATCH 56/83] Only show the label or errors part of a Property if they exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix weird spacing for properties that don’t have labels (ie. checkboxes for terms of service, etc) --- js/components/ascribe_forms/property.js | 54 ++++++++++++------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/js/components/ascribe_forms/property.js b/js/components/ascribe_forms/property.js index 8039e636..dce6b49b 100644 --- a/js/components/ascribe_forms/property.js +++ b/js/components/ascribe_forms/property.js @@ -2,15 +2,13 @@ import React from 'react'; import ReactAddons from 'react/addons'; +import classNames from 'classnames'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import Tooltip from 'react-bootstrap/lib/Tooltip'; import AppConstants from '../../constants/application_constants'; -import { mergeOptions } from '../../utils/general_utils'; - - let Property = React.createClass({ propTypes: { hidden: React.PropTypes.bool, @@ -231,44 +229,44 @@ let Property = React.createClass({ }, render() { - let footer = null; - let tooltip = ; - let style = this.props.style ? mergeOptions({}, this.props.style) : {}; + const { className, editable, footer, label, tooltip } = this.props; + const style = Object.assign({}, this.props.style, { cursor: !editable ? 'not-allowed' : null }); - if(this.props.tooltip){ - tooltip = ( - - {this.props.tooltip} - ); + let tooltipEl = tooltip ? {tooltip} + : ; + + let labelEl; + if (label || this.state.errors) { + labelEl = ( +

    + {label} + {this.state.errors} +

    + ); } - - if(this.props.footer){ - footer = ( + + let footerEl; + if (footer) { + footerEl = (
    - {this.props.footer} -
    ); - } - - if(!this.props.editable) { - style.cursor = 'not-allowed'; + {footer} +
    + ); } return (
    -
    -

    - {this.props.label} - {this.state.errors} -

    + overlay={tooltipEl}> +
    + {labelEl} {this.renderChildren(style)} - {footer} + {footerEl}
    From 0084dbb8e4c23d3dc2a04a3486068f166a366d48 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 11:54:23 +0100 Subject: [PATCH 57/83] Change piece filter text to be normal font-weight --- sass/main.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sass/main.scss b/sass/main.scss index 8a732e96..5cc91e9a 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -350,7 +350,7 @@ hr { > span { font-size: 1.1em; - font-weight: 600; + font-weight: normal; color: #616161; padding-left: .3em; From cec652551eaf5ee33acb4f56fbe91649ea6c5820 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 12:41:27 +0100 Subject: [PATCH 58/83] Add 23vivi landing page --- .../components/23vivi/23vivi_landing.js | 78 +++++++++++++++++++ .../wallet/23vivi/23vivi_custom_style.scss | 35 +++++++++ 2 files changed, 113 insertions(+) create mode 100644 js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js new file mode 100644 index 00000000..ebf2108d --- /dev/null +++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js @@ -0,0 +1,78 @@ +'use strict'; + +import React from 'react'; + +import Button from 'react-bootstrap/lib/Button'; +import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; + +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; + +let Vivi23Landing = React.createClass({ + getInitialState() { + return WhitelabelStore.getState(); + }, + + componentWillMount() { + setDocumentTitle('23vivi Marketplace'); + }, + + componentDidMount() { + WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); + }, + + componentWillUnmount() { + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + return ( +
    +
    +
    +
    + +
    + {getLangText('Artwork from the 23vivi Marketplace is powered by') + ' '} + +
    +
    +
    +
    +

    + {getLangText('Existing ascribe user?')} +

    + + + +
    +
    +

    + {getLangText('Do you need an account?')} +

    + + + +
    +
    +
    +
    +
    + ); + } +}); + +export default Vivi23Landing; diff --git a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss index 79be4a23..37269bd6 100644 --- a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss +++ b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss @@ -10,6 +10,41 @@ $vivi23--highlight-color: #de2600; .client--23vivi { + /** Landing page **/ + .route--landing { + display: table; + + > .container { + display: table-cell; + padding-bottom: 100px; + vertical-align: middle; + } + + .vivi23-landing { + font-weight: normal; + text-align: center; + } + + .vivi23-landing--header { + background-color: $vivi23--fg-color; + border: 1px solid $vivi23--fg-color; + color: $vivi23--bg-color; + padding: 2em; + + .vivi23-landing--header-logo { + margin-top: 1em; + margin-bottom: 2em; + height: 75px; + } + } + + .vivi23-landing--content { + border: 1px solid darken($vivi23--bg-color, 20%); + border-top: none; + padding: 2em; + } + } + /** Navbar **/ .navbar-default { background-color: $vivi23--nav-fg-prim-color; From 43916625bd6e83830c93b56ca56496b46c1ce408 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 12:41:59 +0100 Subject: [PATCH 59/83] Remove header from white labels that have landing pages --- js/components/whitelabel/wallet/wallet_app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index 5056716a..c2810fd0 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -32,7 +32,7 @@ let WalletApp = React.createClass({ // if the path of the current activeRoute is not defined, then this is the IndexRoute if ((!path || history.isActive('/login') || history.isActive('/signup') || history.isActive('/contract_notifications')) - && (['ikonotv']).indexOf(subdomain) > -1) { + && (['cyland', 'ikonotv', 'lumenus', '23vivi']).indexOf(subdomain) > -1) { header = (
    ); } else { header =
    ; From 78b8887fbf65b5e550ce3b49fddcbc2ab48f8616 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 12:44:12 +0100 Subject: [PATCH 60/83] Fix Cyland landing page Fix font family to be Gibson, center vertically. --- .../components/cyland/cyland_landing.js | 4 ++-- .../wallet/cyland/cyland_custom_style.scss | 21 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js index 63085fc9..dc6420f4 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js @@ -57,9 +57,9 @@ let CylandLanding = React.createClass({ setDocumentTitle('CYLAND MediaArtLab'); return ( -
    +
    -
    +
    diff --git a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss index eaf45621..c91239da 100644 --- a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss +++ b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss @@ -59,7 +59,6 @@ $cyland--button-color: $cyland--nav-fg-prim-color; display: none; } - .client--cyland .icon-ascribe-search{ color: $cyland--button-color; } @@ -148,6 +147,24 @@ $cyland--button-color: $cyland--nav-fg-prim-color; } } +// landing page +.client--cyland { + .route--landing { + display: table; + + > .container { + display: table-cell; + padding-bottom: 100px; + vertical-align: middle; + } + + .cyland-landing { + font-weight: normal; + text-align: center; + } + } +} + // spinner! .client--cyland { .btn-spinner { @@ -182,4 +199,4 @@ $cyland--button-color: $cyland--nav-fg-prim-color; .client--cyland .acl-information-dropdown-list .title { color: $cyland--button-color; -} \ No newline at end of file +} From bde1e454ed690e53140f3e258175c788774fad08 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 13:34:00 +0100 Subject: [PATCH 61/83] Change loan form's startdate and enddate props to be camel case --- js/components/ascribe_forms/form_loan.js | 75 ++++++++++++------- .../ascribe_forms/form_loan_request_answer.js | 6 +- .../ascribe_detail/prize_piece_container.js | 4 +- .../cyland/cyland_register_piece.js | 4 +- .../ikonotv/ikonotv_register_piece.js | 8 +- 5 files changed, 58 insertions(+), 39 deletions(-) diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 7a0af7fd..84d4d58d 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -26,8 +26,8 @@ let LoanForm = React.createClass({ loanHeading: React.PropTypes.string, email: React.PropTypes.string, gallery: React.PropTypes.string, - startdate: React.PropTypes.object, - enddate: React.PropTypes.object, + startDate: React.PropTypes.object, + endDate: React.PropTypes.object, showPersonalMessage: React.PropTypes.bool, showEndDate: React.PropTypes.bool, showStartDate: React.PropTypes.bool, @@ -36,7 +36,11 @@ let LoanForm = React.createClass({ id: React.PropTypes.object, message: React.PropTypes.string, createPublicContractAgreement: React.PropTypes.bool, - handleSuccess: React.PropTypes.func + handleSuccess: React.PropTypes.func, + children: React.PropTypes.oneOfType([ + React.PropTypes.object, + React.PropTypes.array + ]) }, getDefaultProps() { @@ -214,14 +218,29 @@ let LoanForm = React.createClass({ }, render() { + const { + children, + email, + handleSuccess, + gallery, + loanHeading, + message, + showPersonalMessage, + endDate, + startDate, + showEndDate, + showStartDate, + showPassword, + url } = this.props; + return ( @@ -229,18 +248,18 @@ let LoanForm = React.createClass({

    }> -
    -

    {this.props.loanHeading}

    +
    +

    {loanHeading}

    + overrideForm={!!email}> @@ -248,31 +267,31 @@ let LoanForm = React.createClass({ + editable={!gallery} + overrideForm={!!gallery}> {this.getContractCheckbox()} {this.getAppendix()} - {this.props.children} + {children} ); } diff --git a/js/components/ascribe_forms/form_loan_request_answer.js b/js/components/ascribe_forms/form_loan_request_answer.js index 1bfe90db..349b4efc 100644 --- a/js/components/ascribe_forms/form_loan_request_answer.js +++ b/js/components/ascribe_forms/form_loan_request_answer.js @@ -65,8 +65,8 @@ let LoanRequestAnswerForm = React.createClass({ url={this.props.url} email={this.state.loanRequest ? this.state.loanRequest.new_owner : null} gallery={this.state.loanRequest ? this.state.loanRequest.gallery : null} - startdate={startDate} - enddate={endDate} + startDate={startDate} + endDate={endDate} showPassword={true} showPersonalMessage={false} handleSuccess={this.props.handleSuccess}/> @@ -76,4 +76,4 @@ let LoanRequestAnswerForm = React.createClass({ } }); -export default LoanRequestAnswerForm; \ No newline at end of file +export default LoanRequestAnswerForm; diff --git a/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js b/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js index 93ca50f3..7becb847 100644 --- a/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js +++ b/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js @@ -292,8 +292,8 @@ let PrizePieceRatings = React.createClass({ url={ApiUrls.ownership_loans_pieces_request} email={this.props.currentUser.email} gallery={this.props.piece.prize.name} - startdate={today} - enddate={endDate} + startDate={today} + endDate={endDate} showPersonalMessage={true} showPassword={false} handleSuccess={this.handleLoanSuccess} /> 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 85cd01bd..42b7c1ad 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js @@ -214,8 +214,8 @@ let CylandRegisterPiece = React.createClass({ url={ApiUrls.ownership_loans_pieces} email={this.state.whitelabel.user} gallery="Cyland Archive" - startdate={today} - enddate={datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain} + startDate={today} + endDate={datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain} showStartDate={false} showEndDate={false} showPersonalMessage={false} diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js index 16d893b7..c5a8b8ec 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js @@ -192,8 +192,8 @@ let IkonotvRegisterPiece = React.createClass({ getSlideLoan() { if (this.canSubmit()) { let today = new Moment(); - let enddate = new Moment(); - enddate.add(2, 'years'); + let endDate = new Moment(); + endDate.add(2, 'years'); return (
    @@ -203,8 +203,8 @@ let IkonotvRegisterPiece = React.createClass({ id={{piece_id: this.state.piece.id}} url={ApiUrls.ownership_loans_pieces} email="submissions@ikono.org" - startdate={today} - enddate={enddate} + startDate={today} + endDate={endDate} showStartDate={false} showEndDate={false} gallery="IkonoTV archive" From e1477be3936b67d8b06e21eadd50f150cc163c07 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 13:34:24 +0100 Subject: [PATCH 62/83] Fix typo in PropertyCollapsible declaration --- js/components/ascribe_forms/property_collapsible.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/components/ascribe_forms/property_collapsible.js b/js/components/ascribe_forms/property_collapsible.js index 92970583..db74868b 100644 --- a/js/components/ascribe_forms/property_collapsible.js +++ b/js/components/ascribe_forms/property_collapsible.js @@ -8,7 +8,7 @@ import Tooltip from 'react-bootstrap/lib/Tooltip'; import Panel from 'react-bootstrap/lib/Panel'; -let PropertyCollapsile = React.createClass({ +let PropertyCollapsible = React.createClass({ propTypes: { children: React.PropTypes.arrayOf(React.PropTypes.element), checkboxLabel: React.PropTypes.string, @@ -93,4 +93,4 @@ let PropertyCollapsile = React.createClass({ } }); -export default PropertyCollapsile; \ No newline at end of file +export default PropertyCollapsible; From e442e6bcec919918869669cbee384df85ab914c0 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 15:21:14 +0100 Subject: [PATCH 63/83] Make email input be react controlled in loan and consign forms --- js/components/ascribe_forms/form_consign.js | 33 +++++++++++++++++---- js/components/ascribe_forms/form_loan.js | 21 +++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index d88add94..0d4fa899 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -4,11 +4,12 @@ import React from 'react'; import Button from 'react-bootstrap/lib/Button'; +import InputTextAreaToggable from './input_textarea_toggable'; import Form from './form'; import Property from './property'; -import InputTextAreaToggable from './input_textarea_toggable'; import AscribeSpinner from '../ascribe_spinner'; + import AclInformation from '../ascribe_buttons/acl_information'; import { getLangText } from '../../utils/lang_utils.js'; @@ -30,12 +31,33 @@ let ConsignForm = React.createClass({ }; }, + getInitialState() { + return { + email: this.props.email + }; + }, + getFormData() { return this.props.id; }, + handleEmailOnChange(event) { + // event.target.value is the submitted email of the consignee + this.setState({ + email: event && event.target && event.target.value || '' + }); + }, + render() { - const { autoFocusProperty, email, id, handleSuccess, message, labels, url } = this.props; + const { email } = this.state; + const { + autoFocusProperty, + email: defaultEmail, + handleSuccess, + id, + message, + labels, + url } = this.props; return (
    + editable={!defaultEmail} + onChange={this.handleEmailOnChange} + overrideForm={!!defaultEmail}> + editable={!defaultEmail} + onChange={this.handleEmailOnChange} + overrideForm={!!defaultEmail}> Date: Thu, 3 Dec 2015 15:22:17 +0100 Subject: [PATCH 64/83] Refactor contract agreement to be a self contained component that can be used in forms --- js/actions/contract_agreement_list_actions.js | 40 ++--- js/components/ascribe_forms/form_consign.js | 9 + js/components/ascribe_forms/form_loan.js | 149 ++-------------- .../property_contract_agreement.js | 168 ++++++++++++++++++ js/utils/regex_utils.js | 7 + 5 files changed, 217 insertions(+), 156 deletions(-) create mode 100644 js/components/ascribe_forms/property_contract_agreement.js create mode 100644 js/utils/regex_utils.js diff --git a/js/actions/contract_agreement_list_actions.js b/js/actions/contract_agreement_list_actions.js index 4993b129..1eedf5b0 100644 --- a/js/actions/contract_agreement_list_actions.js +++ b/js/actions/contract_agreement_list_actions.js @@ -22,8 +22,7 @@ class ContractAgreementListActions { if (contractAgreementList.count > 0) { this.actions.updateContractAgreementList(contractAgreementList.results); resolve(contractAgreementList.results); - } - else{ + } else { resolve(null); } }) @@ -35,13 +34,13 @@ class ContractAgreementListActions { ); } - fetchAvailableContractAgreementList(issuer, createContractAgreement) { + fetchAvailableContractAgreementList(issuer, createPublicContractAgreement) { return Q.Promise((resolve, reject) => { OwnershipFetcher.fetchContractAgreementList(issuer, true, null) .then((acceptedContractAgreementList) => { // if there is at least an accepted contract agreement, we're going to // use it - if(acceptedContractAgreementList.count > 0) { + if (acceptedContractAgreementList.count > 0) { this.actions.updateContractAgreementList(acceptedContractAgreementList.results); } else { // otherwise, we're looking for contract agreements that are still pending @@ -50,15 +49,13 @@ class ContractAgreementListActions { // overcomplicate the method OwnershipFetcher.fetchContractAgreementList(issuer, null, true) .then((pendingContractAgreementList) => { - if(pendingContractAgreementList.count > 0) { + if (pendingContractAgreementList.count > 0) { this.actions.updateContractAgreementList(pendingContractAgreementList.results); - } else { + } else if (createPublicContractAgreement) { // if there was neither a pending nor an active contractAgreement - // found and createContractAgreement is set to true, we create a - // new contract agreement - if(createContractAgreement) { - this.actions.createContractAgreementFromPublicContract(issuer); - } + // found and createPublicContractAgreement is set to true, we create a + // new public contract agreement + this.actions.createContractAgreementFromPublicContract(issuer); } }) .catch((err) => { @@ -81,8 +78,7 @@ class ContractAgreementListActions { // create an agreement with the public contract if there is one if (publicContract && publicContract.length > 0) { return this.actions.createContractAgreement(null, publicContract[0]); - } - else { + } else { /* contractAgreementList in the store is already set to null; */ @@ -91,21 +87,17 @@ class ContractAgreementListActions { if (publicContracAgreement) { this.actions.updateContractAgreementList([publicContracAgreement]); } - }).catch((err) => { - console.logGlobal(err); - }); + }).catch(console.logGlobal); } createContractAgreement(issuer, contract){ return Q.Promise((resolve, reject) => { - OwnershipFetcher.createContractAgreement(issuer, contract).then( - (contractAgreement) => { - resolve(contractAgreement); - } - ).catch((err) => { - console.logGlobal(err); - reject(err); - }); + OwnershipFetcher + .createContractAgreement(issuer, contract).then(resolve) + .catch((err) => { + console.logGlobal(err); + reject(err); + }); }); } } diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 0d4fa899..77f81a69 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -4,6 +4,7 @@ import React from 'react'; import Button from 'react-bootstrap/lib/Button'; +import ContractAgreementProperty from './property_contract_agreement'; import InputTextAreaToggable from './input_textarea_toggable'; import Form from './form'; import Property from './property'; @@ -22,6 +23,7 @@ let ConsignForm = React.createClass({ email: React.PropTypes.string, message: React.PropTypes.string, labels: React.PropTypes.object, + createPublicContractAgreement: React.PropTypes.bool, handleSuccess: React.PropTypes.func }, @@ -52,6 +54,7 @@ let ConsignForm = React.createClass({ const { email } = this.state; const { autoFocusProperty, + createPublicContractAgreement, email: defaultEmail, handleSuccess, id, @@ -107,6 +110,12 @@ let ConsignForm = React.createClass({ placeholder={getLangText('Enter a message...')} required /> + diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 022d81d9..a0b1240f 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -6,20 +6,18 @@ import classnames from 'classnames'; import Button from 'react-bootstrap/lib/Button'; +import ContractAgreementProperty from './property_contract_agreement'; import Form from './form'; -import Property from './property'; import InputTextAreaToggable from './input_textarea_toggable'; import InputDate from './input_date'; -import InputCheckbox from './input_checkbox'; - -import ContractAgreementListStore from '../../stores/contract_agreement_list_store'; -import ContractAgreementListActions from '../../actions/contract_agreement_list_actions'; +import Property from './property'; import AscribeSpinner from '../ascribe_spinner'; +import AclInformation from '../ascribe_buttons/acl_information'; + import { mergeOptions } from '../../utils/general_utils'; import { getLangText } from '../../utils/lang_utils'; -import AclInformation from '../ascribe_buttons/acl_information'; let LoanForm = React.createClass({ propTypes: { @@ -49,55 +47,20 @@ let LoanForm = React.createClass({ showPersonalMessage: true, showEndDate: true, showStartDate: true, - showPassword: true, - createPublicContractAgreement: true + showPassword: true }; }, getInitialState() { - return ContractAgreementListStore.getState(); - }, - - componentDidMount() { - ContractAgreementListStore.listen(this.onChange); - this.getContractAgreementsOrCreatePublic(this.props.email); - }, - - /** - * This method needs to be in form_loan as some whitelabel pages (Cyland) load - * the loanee's email async! - * - * SO LEAVE IT IN! - */ - componentWillReceiveProps(nextProps) { - if(nextProps && nextProps.email && this.props.email !== nextProps.email) { - this.getContractAgreementsOrCreatePublic(nextProps.email); - } - }, - - componentWillUnmount() { - ContractAgreementListStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - - getContractAgreementsOrCreatePublic(email){ - ContractAgreementListActions.flushContractAgreementList.defer(); - if (email) { - // fetch the available contractagreements (pending/accepted) - ContractAgreementListActions.fetchAvailableContractAgreementList(email, true); - } return { email: this.props.email || '' }; }, - getFormData(){ + getFormData() { return mergeOptions( this.props.id, - this.getContractAgreementId() + this.refs.contractAgreement.getFormDataForProperty() ); }, @@ -108,90 +71,8 @@ let LoanForm = React.createClass({ }); }, - getContractAgreementId() { - if (this.state.contractAgreementList && this.state.contractAgreementList.length > 0) { - return {'contract_agreement_id': this.state.contractAgreementList[0].id}; - } - return {}; - }, - - getContractCheckbox() { - if(this.state.contractAgreementList && this.state.contractAgreementList.length > 0) { - // we need to define a key on the InputCheckboxes as otherwise - // react is not rerendering them on a store switch and is keeping - // the default value of the component (which is in that case true) - let contractAgreement = this.state.contractAgreementList[0]; - let contract = contractAgreement.contract; - - if(contractAgreement.datetime_accepted) { - return ( - - ); - } else { - return ( - - - - {getLangText('I agree to the')}  - - {getLangText('terms of ')} {contract.issuer} - - - - - ); - } - } else { - return ( - - ); - } - }, - - getAppendix() { - if(this.state.contractAgreementList && this.state.contractAgreementList.length > 0) { - let appendix = this.state.contractAgreementList[0].appendix; - if (appendix && appendix.default) { - return ( - -
    {appendix.default}
    -
    - ); - } - } - return null; + handleReset(event) { + this.handleEmailOnChange(); }, getButtons() { @@ -222,7 +103,7 @@ let LoanForm = React.createClass({ const { email } = this.state; const { children, - email, + createPublicContractAgreement, email: defaultEmail, handleSuccess, gallery, @@ -242,7 +123,7 @@ let LoanForm = React.createClass({ ref='form' url={url} getFormData={this.getFormData} - onReset={this.handleOnChange} + onReset={this.handleReset} handleSuccess={handleSuccess} buttons={this.getButtons()} spinner={ @@ -309,8 +190,12 @@ let LoanForm = React.createClass({ placeholder={getLangText('Enter a message...')} required={showPersonalMessage}/>
    - {this.getContractCheckbox()} - {this.getAppendix()} + 0) { + ContractAgreementListActions.flushContractAgreementList(); + } + }, + + componentWillUnmount() { + ContractAgreementListStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + getFormDataForProperty() { + return this.getContractAgreementId(); + }, + + getContractAgreementId() { + if (this.state.contractAgreementList && this.state.contractAgreementList.length > 0) { + return { 'contract_agreement_id': this.state.contractAgreementList[0].id }; + } else { + return {}; + } + }, + + getContractAgreementsOrCreatePublic(email) { + ContractAgreementListActions.flushContractAgreementList.defer(); + + if (email) { + // fetch the available contractagreements (pending/accepted) + ContractAgreementListActions.fetchAvailableContractAgreementList(email, this.props.createPublicContractAgreement); + } + }, + + getAppendix() { + const { contractAgreementList } = this.state; + + if (contractAgreementList && contractAgreementList.length > 0) { + const appendix = contractAgreementList[0].appendix; + if (appendix && appendix.default) { + return ( + +
    {appendix.default}
    +
    + ); + } + } + }, + + getContractCheckbox() { + const { contractAgreementList } = this.state; + + if (contractAgreementList && contractAgreementList.length > 0) { + // we need to define a key on the InputCheckboxes as otherwise + // react is not rerendering them on a store switch and is keeping + // the default value of the component (which is in that case true) + const contractAgreement = contractAgreementList[0]; + const { issuer: contractIssuer, blob: { url_safe: contractUrl } } = contractAgreement.contract; + + if (contractAgreement.datetime_accepted) { + return ( + + ); + } else { + return ( + + + + {getLangText('I agree to the')}  + + {getLangText('terms of ')} {contractIssuer} + + + + + ); + } + } else { + return ( + + ); + } + }, + + render() { + return ( +
    + {this.getContractCheckbox()} + {this.getAppendix()} +
    + ); + } +}); + +export default ContractAgreementProperty; diff --git a/js/utils/regex_utils.js b/js/utils/regex_utils.js new file mode 100644 index 00000000..af948b2b --- /dev/null +++ b/js/utils/regex_utils.js @@ -0,0 +1,7 @@ +'use strict' + +export function isEmail(string) { + // This is a bit of a weak test for an email, but you really can't win them all + // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address + return !!string && string.match(/.*@.*\..*/); +} From 328d60b279d51328656f22f1087f862769e6e426 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 15:22:37 +0100 Subject: [PATCH 65/83] Rename ContractAgreementForm to avoid confusion with ContractAgreementProperty --- ...ontract_agreement.js => form_send_contract_agreement.js} | 6 +++--- js/components/whitelabel/wallet/wallet_routes.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename js/components/ascribe_forms/{form_contract_agreement.js => form_send_contract_agreement.js} (97%) diff --git a/js/components/ascribe_forms/form_contract_agreement.js b/js/components/ascribe_forms/form_send_contract_agreement.js similarity index 97% rename from js/components/ascribe_forms/form_contract_agreement.js rename to js/components/ascribe_forms/form_send_contract_agreement.js index fa73fe42..8d0170fd 100644 --- a/js/components/ascribe_forms/form_contract_agreement.js +++ b/js/components/ascribe_forms/form_send_contract_agreement.js @@ -21,7 +21,7 @@ import { getLangText } from '../../utils/lang_utils'; import { mergeOptions } from '../../utils/general_utils'; -let ContractAgreementForm = React.createClass({ +let SendContractAgreementForm = React.createClass({ propTypes: { handleSuccess: React.PropTypes.func }, @@ -55,7 +55,7 @@ let ContractAgreementForm = React.createClass({ }, handleSubmitSuccess() { - let notification = 'Contract agreement send'; + let notification = 'Contract agreement sent'; notification = new GlobalNotificationModel(notification, 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); @@ -148,4 +148,4 @@ let ContractAgreementForm = React.createClass({ } }); -export default ContractAgreementForm; \ No newline at end of file +export default SendContractAgreementForm; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 92b53dc6..f65e20fb 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -25,7 +25,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 ContractAgreementForm from '../../../components/ascribe_forms/form_contract_agreement'; +import SendContractAgreementForm from '../../../components/ascribe_forms/form_send_contract_agreement'; import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; @@ -135,7 +135,7 @@ let ROUTES = { component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/> Date: Thu, 3 Dec 2015 17:17:52 +0100 Subject: [PATCH 66/83] Add hot fix from AD-1313 for using refs in properties https://github.com/ascribe/onion/blob/AD-1313-Attach-thumbnail-to-piece- in-register-form/js/components/ascribe_forms/form.js --- js/components/ascribe_forms/form.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index 0e3517a2..850de032 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -238,7 +238,15 @@ let Form = React.createClass({ renderChildren() { return ReactAddons.Children.map(this.props.children, (child, i) => { if (child) { - return ReactAddons.addons.cloneWithProps(child, { + // Since refs will be overwritten by this functions return statement, + // we still want to be able to define refs for nested `Form` or `Property` + // children, which is why we're upfront simply invoking the callback-ref- + // function before overwriting it. + if(typeof child.ref === 'function' && this.refs[child.props.name]) { + child.ref(this.refs[child.props.name]); + } + + return React.cloneElement(child, { handleChange: this.handleChangeChild, ref: child.props.name, key: i, From f5a527b251b1518f7958b60828a4bf08f58b5fbe Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 3 Dec 2015 17:19:17 +0100 Subject: [PATCH 67/83] Fix references to form due to nested property elements --- js/components/ascribe_forms/form.js | 8 ++- js/components/ascribe_forms/form_loan.js | 17 +++--- .../property_contract_agreement.js | 55 +++++++++++++------ sass/ascribe_notification_page.scss | 4 +- 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index 850de032..6be7fa2d 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -124,8 +124,12 @@ let Form = React.createClass({ getFormData() { let data = {}; - for (let ref in this.refs) { - data[this.refs[ref].props.name] = this.refs[ref].state.value; + for (let refName in this.refs) { + const ref = this.refs[refName]; + + if (ref.state && 'value' in ref.state) { + data[ref.props.name] = ref.state.value; + } } if (typeof this.props.getFormData === 'function') { diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index a0b1240f..0b4f4775 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -57,13 +57,6 @@ let LoanForm = React.createClass({ }; }, - getFormData() { - return mergeOptions( - this.props.id, - this.refs.contractAgreement.getFormDataForProperty() - ); - }, - handleEmailOnChange(event) { // event.target.value is the submitted email of the loanee this.setState({ @@ -75,6 +68,13 @@ let LoanForm = React.createClass({ this.handleEmailOnChange(); }, + getFormData() { + return mergeOptions( + this.props.id, + this.refs.contractAgreement.getFormDataForProperty() + ); + }, + getButtons() { if(this.props.loanHeading) { return ( @@ -191,10 +191,11 @@ let LoanForm = React.createClass({ required={showPersonalMessage}/>
    this.refs.contractAgreement = ref} createPublicContractAgreement={createPublicContractAgreement} email={email} embedClassName={'loan-form'} + name='contract_agreement' label={getLangText('Loan Contract')} /> 0) { - ContractAgreementListActions.flushContractAgreementList(); + if (this.props.email !== nextEmail) { + if (isEmail(nextEmail)) { + this.getContractAgreementsOrCreatePublic(nextEmail); + } else if (contractAgreementList && contractAgreementList.length > 0) { + ContractAgreementListActions.flushContractAgreementList(); + } } }, @@ -53,14 +61,21 @@ let ContractAgreementProperty = React.createClass({ }, getFormDataForProperty() { - return this.getContractAgreementId(); + const contractAgreementId = this.getContractAgreementId(); + const formData = { + 'terms': this.refs.terms.state.value + }; + + if (contractAgreementId) { + formData.contract_agreement_id = contractAgreementId; + } + + return formData; }, getContractAgreementId() { if (this.state.contractAgreementList && this.state.contractAgreementList.length > 0) { - return { 'contract_agreement_id': this.state.contractAgreementList[0].id }; - } else { - return {}; + return this.state.contractAgreementList[0].id; } }, @@ -82,7 +97,8 @@ let ContractAgreementProperty = React.createClass({ return ( + label={getLangText('Appendix')} + handleChange={this.props.handleChange}>
    {appendix.default}
    ); @@ -91,24 +107,27 @@ let ContractAgreementProperty = React.createClass({ }, getContractCheckbox() { + const { embedClassName, handleChange, label } = this.props; const { contractAgreementList } = this.state; if (contractAgreementList && contractAgreementList.length > 0) { - // we need to define a key on the InputCheckboxes as otherwise - // react is not rerendering them on a store switch and is keeping - // the default value of the component (which is in that case true) const contractAgreement = contractAgreementList[0]; const { issuer: contractIssuer, blob: { url_safe: contractUrl } } = contractAgreement.contract; + // we need to define a key on the InputCheckboxes as otherwise + // react is not rerendering them on a store switch and is keeping + // the default value of the component (which is in that case true) if (contractAgreement.datetime_accepted) { return (