From d035f1e41c73a90d44a0f93825e4cd897acc529d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Tue, 8 Dec 2015 16:49:06 +0100 Subject: [PATCH 01/45] Hide PieceListToolbarFilterWidget when no filters are applicable --- .../piece_list_toolbar_filter_widget.js | 112 +++++++++--------- 1 file changed, 58 insertions(+), 54 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 c463330c..b798857a 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 @@ -81,62 +81,66 @@ let PieceListToolbarFilterWidget = React.createClass({ ); - return ( - - {/* We iterate over filterParams, to receive the label and then for each - label also iterate over its items, to get all filterable options */} - {this.props.filterParams.map(({ label, items }, i) => { - return ( -
-
  • - {label}: -
  • - {items.map((param, j) => { + if(this.props.filterParams.length) { + return ( + + {/* We iterate over filterParams, to receive the label and then for each + label also iterate over its items, to get all filterable options */} + {this.props.filterParams.map(({ label, items }, i) => { + return ( +
    +
  • + {label}: +
  • + {items.map((param, j) => { - // As can be seen in the PropTypes, a param can either - // be a string or an object of the shape: - // - // { - // key: , - // label: - // } - // - // This is why we need to distinguish between both here. - if(typeof param !== 'string') { - label = param.label; - param = param.key; - } else { - param = param; - label = param.split('acl_')[1].replace(/_/g, ' '); - } + // As can be seen in the PropTypes, a param can either + // be a string or an object of the shape: + // + // { + // key: , + // label: + // } + // + // This is why we need to distinguish between both here. + if(typeof param !== 'string') { + label = param.label; + param = param.key; + } else { + param = param; + label = param.split('acl_')[1].replace(/_/g, ' '); + } - return ( -
  • -
    - - {getLangText(label)} - - -
    -
  • - ); - })} -
    - ); - })} -
    - ); + return ( +
  • +
    + + {getLangText(label)} + + +
    +
  • + ); + })} +
    + ); + })} +
    + ); + } else { + return null; + } } }); From 8278f5404f2975f3d95e018453efeb7c20e510c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 9 Dec 2015 16:17:08 +0100 Subject: [PATCH 02/45] Increase robustness of: - PieceListToolbarOrderWidget - PieceListToolbarFilterWidget --- .../piece_list_toolbar.js | 34 +++------- .../piece_list_toolbar_filter_widget.js | 12 ++-- .../piece_list_toolbar_order_widget.js | 66 ++++++++++--------- 3 files changed, 49 insertions(+), 63 deletions(-) diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js index 60370431..bcc15603 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js @@ -39,30 +39,6 @@ let PieceListToolbar = React.createClass({ ]) }, - getFilterWidget(){ - if (this.props.filterParams){ - return ( - - ); - } - return null; - }, - - getOrderWidget(){ - if (this.props.orderParams){ - return ( - - ); - } - return null; - }, - render() { const { className, children, searchFor, searchQuery } = this.props; @@ -75,8 +51,14 @@ let PieceListToolbar = React.createClass({ {children} - {this.getOrderWidget()} - {this.getFilterWidget()} + + 0) { + if (trueValuesOnly.length) { return { visibility: 'visible'}; } else { return { visibility: 'hidden' }; @@ -81,7 +81,7 @@ let PieceListToolbarFilterWidget = React.createClass({ ); - if(this.props.filterParams.length) { + if (this.props.filterParams && this.props.filterParams.length) { return ( 0) { + if (this.props.orderBy && this.props.orderBy.length) { return { visibility: 'visible'}; } else { return { visibility: 'hidden' }; @@ -51,37 +51,41 @@ let PieceListToolbarOrderWidget = React.createClass({ · ); - return ( - -
  • - {getLangText('Sort by')}: -
  • - {this.props.orderParams.map((param) => { - return ( -
    -
  • -
    - - {getLangText(param.replace('_', ' '))} - - -1} /> -
    -
  • -
    - ); - })} -
    - ); + if (this.props.orderParams && this.props.orderParams.length) { + return ( + +
  • + {getLangText('Sort by')}: +
  • + {this.props.orderParams.map((param) => { + return ( +
    +
  • +
    + + {getLangText(param.replace('_', ' '))} + + -1} /> +
    +
  • +
    + ); + })} +
    + ); + } else { + return null; + } } }); From 6cc9ce8094912e0739419422720419b1463ac7f0 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 14 Dec 2015 18:34:27 +0100 Subject: [PATCH 03/45] Don't redirect to register piece and then to contract notifications Avoids new user getting asked if they want to cancel registration of a piece before they even agree to the Ikono contract. --- js/actions/notification_actions.js | 5 ++++- js/components/piece_list.js | 7 ++++++- .../ikonotv/ikonotv_contract_notifications.js | 17 +++++++++------ .../components/ikonotv/ikonotv_piece_list.js | 21 +++++++++++++++++-- js/stores/notification_store.js | 13 ++++++++++++ 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/js/actions/notification_actions.js b/js/actions/notification_actions.js index c3a6db93..5d80e1e7 100644 --- a/js/actions/notification_actions.js +++ b/js/actions/notification_actions.js @@ -9,10 +9,13 @@ class NotificationActions { constructor() { this.generateActions( 'updatePieceListNotifications', + 'flushPieceListNotifications', 'updateEditionListNotifications', + 'flushEditionListNotifications', 'updateEditionNotifications', 'updatePieceNotifications', - 'updateContractAgreementListNotifications' + 'updateContractAgreementListNotifications', + 'flushContractAgreementListNotifications' ); } diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 9424117c..de979e65 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -37,6 +37,7 @@ let PieceList = React.createClass({ bulkModalButtonListType: React.PropTypes.func, canLoadPieceList: React.PropTypes.bool, redirectTo: React.PropTypes.string, + shouldRedirect: React.PropTypes.func, customSubmitButton: React.PropTypes.element, customThumbnailPlaceholder: React.PropTypes.func, filterParams: React.PropTypes.array, @@ -114,7 +115,11 @@ let PieceList = React.createClass({ }, componentDidUpdate() { - if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) { + const { redirectTo, shouldRedirect } = this.props; + const { unfilteredPieceListCount } = this.state; + + if (redirectTo && unfilteredPieceListCount === 0 && + (typeof shouldRedirect === 'function' && shouldRedirect(unfilteredPieceListCount))) { // FIXME: hack to redirect out of the dispatch cycle window.setTimeout(() => this.history.pushState(null, this.props.redirectTo, this.props.location.query), 0); } diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js index d3479562..7975c1f3 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js @@ -106,22 +106,27 @@ let IkonotvContractNotifications = React.createClass({ handleConfirm() { let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement; - OwnershipFetcher.confirmContractAgreement(contractAgreement).then( - () => this.handleConfirmSuccess() - ); + OwnershipFetcher + .confirmContractAgreement(contractAgreement) + .then(this.handleConfirmSuccess); }, handleConfirmSuccess() { let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 5000); GlobalNotificationActions.appendGlobalNotification(notification); + + // Flush contract notifications and refetch + NotificationActions.flushContractAgreementListNotifications(); + NotificationActions.fetchContractAgreementListNotifications(); + this.history.pushState(null, '/collection'); }, handleDeny() { let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement; - OwnershipFetcher.denyContractAgreement(contractAgreement).then( - () => this.handleDenySuccess() - ); + OwnershipFetcher + .denyContractAgreement(contractAgreement) + .then(this.handleDenySuccess); }, handleDenySuccess() { diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_piece_list.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_piece_list.js index 0b51bdbd..5b489d09 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_piece_list.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_piece_list.js @@ -1,15 +1,18 @@ 'use strict'; import React from 'react'; + import PieceList from '../../../../piece_list'; import UserActions from '../../../../../actions/user_actions'; import UserStore from '../../../../../stores/user_store'; +import NotificationStore from '../../../../../stores/notification_store'; import IkonotvAccordionListItem from './ikonotv_accordion_list/ikonotv_accordion_list_item'; -import { getLangText } from '../../../../../utils/lang_utils'; import { setDocumentTitle } from '../../../../../utils/dom_utils'; +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; let IkonotvPieceList = React.createClass({ @@ -18,20 +21,33 @@ let IkonotvPieceList = React.createClass({ }, getInitialState() { - return UserStore.getState(); + return mergeOptions( + NotificationStore.getState(), + UserStore.getState() + ); }, componentDidMount() { + NotificationStore.listen(this.onChange); UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); }, componentWillUnmount() { + NotificationStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); }, onChange(state) { this.setState(state); + + }, + + redirectIfNoContractNotifications() { + const { contractAgreementListNotifications } = this.state; + + return contractAgreementListNotifications && !contractAgreementListNotifications.length; }, render() { @@ -41,6 +57,7 @@ let IkonotvPieceList = React.createClass({
    Date: Mon, 14 Dec 2015 18:58:05 +0100 Subject: [PATCH 04/45] Remove unused `showAddWork` property from Header --- js/components/whitelabel/prize/simple_prize/prize_app.js | 2 +- js/components/whitelabel/wallet/wallet_app.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/components/whitelabel/prize/simple_prize/prize_app.js b/js/components/whitelabel/prize/simple_prize/prize_app.js index d95d7772..d5b55d5f 100644 --- a/js/components/whitelabel/prize/simple_prize/prize_app.js +++ b/js/components/whitelabel/prize/simple_prize/prize_app.js @@ -32,7 +32,7 @@ let PrizeApp = React.createClass({ if (!path || history.isActive('/login') || history.isActive('/signup')) { header = ; } else { - header =
    ; + header =
    ; } return ( diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index c2810fd0..bce7106b 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -35,7 +35,7 @@ let WalletApp = React.createClass({ && (['cyland', 'ikonotv', 'lumenus', '23vivi']).indexOf(subdomain) > -1) { header = (
    ); } else { - header =
    ; + header =
    ; } // In react-router 1.0, Routes have no 'name' property anymore. To keep functionality however, From b3b94b6f6095eba1682d5314b35a5fefdf053b3d Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 14 Dec 2015 18:58:33 +0100 Subject: [PATCH 05/45] Fix spacing of WalletRoutes --- .../whitelabel/wallet/wallet_routes.js | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 92c6d77b..96ec755d 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -54,7 +54,7 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} /> @@ -63,18 +63,18 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} /> + headerTitle='+ NEW WORK' /> + headerTitle='COLLECTION' /> @@ -88,7 +88,7 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} /> @@ -97,18 +97,18 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} /> + headerTitle='+ NEW WORK' /> + headerTitle='COLLECTION' /> @@ -123,7 +123,7 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} /> @@ -132,15 +132,15 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} /> + aclName='acl_create_contractagreement' /> + headerTitle='COLLECTION' /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} /> @@ -167,7 +167,7 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} /> @@ -176,10 +176,10 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} /> + headerTitle='COLLECTION' /> @@ -202,7 +202,7 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} /> @@ -211,19 +211,19 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} /> + aclName='acl_wallet_submit' /> + headerTitle='COLLECTION' /> From 1de06b0ac553c257f14102dd9b2305a5fa02449a Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 14 Dec 2015 18:59:10 +0100 Subject: [PATCH 06/45] Only show register work route if user can submit to wallet for IkonoTV, Lumenus --- js/components/whitelabel/wallet/wallet_routes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 96ec755d..4e5751fb 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -145,7 +145,7 @@ let ROUTES = { path='register_piece' component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvRegisterPiece)} headerTitle='+ NEW WORK' - aclName='acl_create_piece'/> + aclName='acl_wallet_submit' /> + headerTitle='+ NEW WORK' + aclName='acl_wallet_submit' /> Date: Wed, 16 Dec 2015 13:59:31 +0100 Subject: [PATCH 07/45] Cyland Admin should also not be able to register work As per (AD-1504)[https://ascribe.atlassian.net/browse/AD-1504], Cyland admin should not be able to register work, but instead use another account to get loaned work. --- js/components/whitelabel/wallet/wallet_routes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 4e5751fb..a5a0e075 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -70,7 +70,8 @@ let ROUTES = { + headerTitle='+ NEW WORK' + aclName='acl_wallet_submit' /> Date: Wed, 16 Dec 2015 15:17:47 +0100 Subject: [PATCH 08/45] Update form's email state if the email prop changes --- js/components/ascribe_forms/form_consign.js | 8 ++++++++ js/components/ascribe_forms/form_loan.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 2f0ebf05..a28d2cff 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -41,6 +41,14 @@ let ConsignForm = React.createClass({ }; }, + componentWillReceiveProps(nextProps) { + if (this.props.email !== nextProps.email) { + this.setState({ + email: nextProps.email + }); + } + }, + getFormData() { return this.props.id; }, diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 53a3bb80..861806ae 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -61,6 +61,14 @@ let LoanForm = React.createClass({ }; }, + componentWillReceiveProps(nextProps) { + if (this.props.email !== nextProps.email) { + this.setState({ + email: nextProps.email + }); + } + }, + onChange(state) { this.setState(state); }, From ad17a9d6f33f56d8de786fb95b6c579dbdd33f3e Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 16 Dec 2015 18:14:06 +0100 Subject: [PATCH 09/45] Show server error in notification if one is given for failed piece registrations --- .../pr_forms/pr_register_piece_form.js | 27 ++++++++++++------ js/utils/error_utils.js | 28 +++++++++++++++++++ js/utils/general_utils.js | 6 ++-- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js index a8d946b5..645d13a2 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js @@ -19,8 +19,9 @@ import ApiUrls from '../../../../../../constants/api_urls'; import requests from '../../../../../../utils/requests'; -import { getLangText } from '../../../../../../utils/lang_utils'; +import { getErrorNotificationMessage } from '../../../../../../utils/error_utils'; import { setCookie } from '../../../../../../utils/fetch_api_utils'; +import { getLangText } from '../../../../../../utils/lang_utils'; import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils'; @@ -35,7 +36,7 @@ const PRRegisterPieceForm = React.createClass({ mixins: [History], - getInitialState(){ + getInitialState() { return { digitalWorkKeyReady: true, thumbnailKeyReady: true, @@ -56,14 +57,14 @@ const PRRegisterPieceForm = React.createClass({ submit() { if(!this.validateForms()) { return; - } else { - // disable the submission button right after the user - // clicks on it to avoid double submission - this.setState({ - submitted: true - }); } + // disable the submission button right after the user + // clicks on it to avoid double submission + this.setState({ + submitted: true + }); + const { currentUser } = this.props; const { registerPieceForm, additionalDataForm, @@ -106,10 +107,18 @@ const PRRegisterPieceForm = React.createClass({ }) .then(() => this.history.pushState(null, `/pieces/${this.state.piece.id}`)) .catch((err) => { - const notificationMessage = new GlobalNotificationModel(getLangText("Oops! We weren't able to send your submission. Contact: support@ascribe.io"), 'danger', 5000); + const errMessage = (getErrorNotificationMessage(err) || getLangText("Oops! We weren't able to send your submission.")) + + getLangText(' Please contact support@ascribe.io'); + + const notificationMessage = new GlobalNotificationModel(errMessage, 'danger', 10000); GlobalNotificationActions.appendGlobalNotification(notificationMessage); console.logGlobal(new Error('Portfolio Review piece registration failed'), err); + + // Reset the submit button + this.setState({ + submitted: false + }); }); }, diff --git a/js/utils/error_utils.js b/js/utils/error_utils.js index 44ebedbe..eefe40d8 100644 --- a/js/utils/error_utils.js +++ b/js/utils/error_utils.js @@ -35,3 +35,31 @@ export function initLogging() { console.logGlobal = logGlobal; } + +/* + * Gets the json errors from the error as an array + * @param {Error} error A Javascript error + * @return {Array} List of json errors + */ +export function getJsonErrorsAsArray(error) { + const { json: { errors = {} } = {} } = error; + + const errorArrays = Object + .keys(errors) + .map((errorKey) => { + return errors[errorKey]; + }); + + // Collapse each errorKey's errors into a flat array + return [].concat(...errorArrays); +} + +/* + * Tries to get an error message from the error, either by using its notification + * property or first json error (if any) + * @param {Error} error A Javascript error + * @return {string} Error message string + */ +export function getErrorNotificationMessage(error) { + return (error && error.notification) || getJsonErrorsAsArray(error)[0] || ''; +} diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index e81a806d..c245cef6 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -84,8 +84,6 @@ export function formatText() { * Checks a list of objects for key duplicates and returns a boolean */ function _doesObjectListHaveDuplicates(l) { - let mergedList = []; - l = l.map((obj) => { if(!obj) { throw new Error('The object you are trying to merge is null instead of an empty object'); @@ -94,11 +92,11 @@ function _doesObjectListHaveDuplicates(l) { return Object.keys(obj); }); - // Taken from: http://stackoverflow.com/a/10865042 + // Taken from: http://stackoverflow.com/a/10865042 (but even better with rest) // How to flatten an array of arrays in javascript. // If two objects contain the same key, then these two keys // will actually be represented in the merged array - mergedList = mergedList.concat.apply(mergedList, l); + let mergedList = [].concat(...l); // Taken from: http://stackoverflow.com/a/7376645/1263876 // By casting the array to a set, and then checking if the size of the array From b6ca964adbcefe58ed7f431cf57b8fda2293fdcc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 17 Dec 2015 11:01:49 +0100 Subject: [PATCH 10/45] Fix contractAgreementList not updating correctly after initial creation --- js/actions/contract_agreement_list_actions.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/js/actions/contract_agreement_list_actions.js b/js/actions/contract_agreement_list_actions.js index 1eedf5b0..b5337d4c 100644 --- a/js/actions/contract_agreement_list_actions.js +++ b/js/actions/contract_agreement_list_actions.js @@ -83,9 +83,9 @@ class ContractAgreementListActions { contractAgreementList in the store is already set to null; */ } - }).then((publicContracAgreement) => { - if (publicContracAgreement) { - this.actions.updateContractAgreementList([publicContracAgreement]); + }).then((publicContractAgreement) => { + if (publicContractAgreement) { + this.actions.updateContractAgreementList([publicContractAgreement]); } }).catch(console.logGlobal); } @@ -93,7 +93,10 @@ class ContractAgreementListActions { createContractAgreement(issuer, contract){ return Q.Promise((resolve, reject) => { OwnershipFetcher - .createContractAgreement(issuer, contract).then(resolve) + .createContractAgreement(issuer, contract) + .then((res) => { + resolve(res && res.contractagreement) + }) .catch((err) => { console.logGlobal(err); reject(err); From 41df6fe837aacb96769b3ef4e4a24f39c1513b1a Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 17 Dec 2015 13:05:39 +0100 Subject: [PATCH 11/45] Add global notification to form validation --- js/components/ascribe_forms/form.js | 6 ++--- .../pr_forms/pr_register_piece_form.js | 9 +++---- js/utils/form_utils.js | 26 +++++++++++++++++++ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index d4002e85..91d00f65 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -178,20 +178,20 @@ let Form = React.createClass({ let formData = this.getFormData(); // sentry shouldn't post the user's password - if(formData.password) { + if (formData.password) { delete formData.password; } console.logGlobal(err, formData); - if(this.props.isInline) { + if (this.props.isInline) { let notification = new GlobalNotificationModel(getLangText('Something went wrong, please try again later'), 'danger'); GlobalNotificationActions.appendGlobalNotification(notification); } else { this.setState({errors: [getLangText('Something went wrong, please try again later')]}); } - } + this.setState({submitted: false}); }, diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js index 645d13a2..73c0d500 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js @@ -21,6 +21,7 @@ import requests from '../../../../../../utils/requests'; import { getErrorNotificationMessage } from '../../../../../../utils/error_utils'; import { setCookie } from '../../../../../../utils/fetch_api_utils'; +import { validateForms } from '../../../../../../utils/form_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils'; @@ -55,7 +56,7 @@ const PRRegisterPieceForm = React.createClass({ * second adding all the additional details */ submit() { - if(!this.validateForms()) { + if (!this.validateForms()) { return; } @@ -127,11 +128,7 @@ const PRRegisterPieceForm = React.createClass({ additionalDataForm, uploadersForm } = this.refs; - const registerPieceFormValidation = registerPieceForm.validate(); - const additionalDataFormValidation = additionalDataForm.validate(); - const uploaderFormValidation = uploadersForm.validate(); - - return registerPieceFormValidation && additionalDataFormValidation && uploaderFormValidation; + return validateForms([registerPieceForm, additionalDataForm, uploadersForm], true); }, getCreateBlobRoutine() { diff --git a/js/utils/form_utils.js b/js/utils/form_utils.js index 8d12a8c1..0581b28b 100644 --- a/js/utils/form_utils.js +++ b/js/utils/form_utils.js @@ -2,8 +2,34 @@ import { getLangText } from './lang_utils'; +import GlobalNotificationActions from '../actions/global_notification_actions'; +import GlobalNotificationModel from '../models/global_notification_model'; + import AppConstants from '../constants/application_constants'; +/** + * Validates a given list of forms + * @param {Form} forms List of forms, each of which should have a `validate` method available + * @param {boolean} showFailureNotification Show global notification if there are validation failures + * @return {boolean} True if validation did *NOT* catch any errors + */ +export function validateForms(forms, showFailureNotification) { + const validationSuccessful = forms.reduce((result, form) => { + if (form && typeof form.validate === 'function') { + return form.validate() && result; + } else { + throw new Error('Form given for validation does not have a `validate` method'); + } + }, true); + + if (!validationSuccessful && showFailureNotification) { + const notification = new GlobalNotificationModel(getLangText('Oops, there may be missing or invalid fields. Please check your inputs again.'), 'danger'); + GlobalNotificationActions.appendGlobalNotification(notification); + } + + return validationSuccessful; +} + /** * Get the data ids of the given piece or editions. * @param {boolean} isPiece Is the given entities parameter a piece? (False: array of editions) From e81341269d885e495926d15207f8c068fd3e0acf Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 17 Dec 2015 15:46:30 +0100 Subject: [PATCH 12/45] If fetching COA results in 404, created a new COA --- js/sources/coa_source.js | 9 +++++++-- js/stores/edition_store.js | 21 +++++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/js/sources/coa_source.js b/js/sources/coa_source.js index 88c72cce..bf1d8d0f 100644 --- a/js/sources/coa_source.js +++ b/js/sources/coa_source.js @@ -8,7 +8,12 @@ import EditionActions from '../actions/edition_actions'; const CoaSource = { lookupCoa: { remote(state) { - return requests.get('coa', { id: state.edition.coa }); + return requests + .get('coa', { id: state.edition.coa }) + .then((res) => { + // If no coa is found here, fake a 404 error so the error action can pick it up + return (res && res.coa) ? res : Promise.reject({ json: { status: 404 } }); + }); }, success: EditionActions.successFetchCoa, @@ -24,4 +29,4 @@ const CoaSource = { } }; -export default CoaSource; \ No newline at end of file +export default CoaSource; diff --git a/js/stores/edition_store.js b/js/stores/edition_store.js index 8fdd681b..3bf01a90 100644 --- a/js/stores/edition_store.js +++ b/js/stores/edition_store.js @@ -31,16 +31,16 @@ class EditionStore { this.getInstance().lookupEdition(); } - onSuccessFetchEdition(res) { - if(res && res.edition) { - this.edition = res.edition; + onSuccessFetchEdition({ edition }) { + if (edition) { + this.edition = edition; this.editionMeta.err = null; this.editionMeta.idToFetch = null; if (this.edition.coa && this.edition.acl.acl_coa && typeof this.edition.coa.constructor !== Object) { this.getInstance().lookupCoa(); - } else if(!this.edition.coa && this.edition.acl.acl_coa) { + } else if (!this.edition.coa && this.edition.acl.acl_coa) { this.getInstance().performCreateCoa(); } } else { @@ -48,9 +48,9 @@ class EditionStore { } } - onSuccessFetchCoa(res) { - if (res && res.coa && Object.keys(this.edition).length) { - this.edition.coa = res.coa; + onSuccessFetchCoa({ coa }) { + if (coa && Object.keys(this.edition).length) { + this.edition.coa = coa; this.coaMeta.err = null; } else { this.coaMeta.err = new Error('Problem generating/fetching the COA'); @@ -73,7 +73,12 @@ class EditionStore { } onErrorCoa(err) { - this.coaMeta.err = err; + // On 404s, create a new COA as the COA has not been made yet + if (err && err.json && err.json.status === 404) { + this.getInstance().performCreateCoa(); + } else { + this.coaMeta.err = err; + } } } From 6c5a2e08694a97ae5b2e26766c94fd8abc6f229c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 17 Dec 2015 16:09:47 +0100 Subject: [PATCH 13/45] Load babel polyfill dependency but don't import From the babel.io.github guys. --- js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index e49ee46b..dc8204cf 100644 --- a/js/app.js +++ b/js/app.js @@ -1,6 +1,6 @@ 'use strict'; -import polyfill from 'babel/polyfill'; +import 'babel/polyfill'; import React from 'react'; import { Router, Redirect } from 'react-router'; From 1580dd8f52c6863d75014f551eacf21b76cb38c4 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 17 Dec 2015 16:40:38 +0100 Subject: [PATCH 14/45] Small additional info for logging COA errors --- js/components/ascribe_detail/edition.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index 068b526c..40fd6625 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -226,7 +226,7 @@ let CoaDetails = React.createClass({ contactOnIntercom() { window.Intercom('showNewMessage', `Hi, I'm having problems generating a Certificate of Authenticity for Edition: ${this.props.editionId}`); - console.logGlobal(new Error(`Coa couldn't be created for edition: ${this.props.editionId}`)); + console.logGlobal(new Error(`Coa couldn't be created for edition: ${this.props.editionId}`), this.props.coaError); }, render() { From c7f272c3a3d58fdff39ed2e6e3e3266aa34c83af Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 17 Dec 2015 17:31:33 +0100 Subject: [PATCH 15/45] Small fixes for DRY and React warnings --- .../accordion_list_item_piece.js | 5 ++++- .../accordion_list_item_prize.js | 16 +++++++++------- .../components/prize_settings_container.js | 7 ++++--- 3 files changed, 17 insertions(+), 11 deletions(-) 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 006479c5..7c6b63a9 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_piece.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_piece.js @@ -12,7 +12,10 @@ import { getLangText } from '../../utils/lang_utils'; let AccordionListItemPiece = React.createClass({ propTypes: { className: React.PropTypes.string, - artistName: React.PropTypes.string, + artistName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.element + ]), piece: React.PropTypes.object, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), diff --git a/js/components/whitelabel/prize/simple_prize/components/ascribe_accordion_list/accordion_list_item_prize.js b/js/components/whitelabel/prize/simple_prize/components/ascribe_accordion_list/accordion_list_item_prize.js index 965b9012..3fc3f5fd 100644 --- a/js/components/whitelabel/prize/simple_prize/components/ascribe_accordion_list/accordion_list_item_prize.js +++ b/js/components/whitelabel/prize/simple_prize/components/ascribe_accordion_list/accordion_list_item_prize.js @@ -171,23 +171,25 @@ let AccordionListItemPrize = React.createClass({ }, render() { + const { children, className, content } = this.props; + const { currentUser } = this.state; + // Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted - let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) || - (this.state.currentUser.is_judge && !this.props.content.selected )) ? -
    ); diff --git a/js/components/whitelabel/prize/simple_prize/components/prize_settings_container.js b/js/components/whitelabel/prize/simple_prize/components/prize_settings_container.js index 145a9d24..b91f9789 100644 --- a/js/components/whitelabel/prize/simple_prize/components/prize_settings_container.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_settings_container.js @@ -135,14 +135,15 @@ let PrizeJurySettings = React.createClass({ handleCreateSuccess(response) { PrizeJuryActions.fetchJury(); - let notification = new GlobalNotificationModel(response.notification, 'success', 5000); - GlobalNotificationActions.appendGlobalNotification(notification); + this.displayNotification(response); this.refs.form.refs.email.refs.input.getDOMNode().value = null; }, handleActivate(event) { let email = event.target.getAttribute('data-id'); - PrizeJuryActions.activateJury(email).then((response) => { + PrizeJuryActions + .activateJury(email) + .then((response) => { PrizeJuryActions.fetchJury(); this.displayNotification(response); }); From 1808d38c7032500fe5c0c119a40483465c02e5c5 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 17 Dec 2015 17:39:30 +0100 Subject: [PATCH 16/45] Merge PrizePieceContainer from #76 --- .../ascribe_detail/prize_piece_container.js | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) 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 16849ed1..6e1ea3b0 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 @@ -107,20 +107,22 @@ let PieceContainer = React.createClass({ }, getActions() { - if (this.state.piece && - this.state.piece.notifications && - this.state.piece.notifications.length > 0) { + const { currentUser, piece } = this.state; + + if (piece && piece.notifications && piece.notifications.length > 0) { return ( ); + notifications={piece.notifications}/>); } }, render() { - if(this.state.piece && this.state.piece.id) { + const { currentUser, piece } = this.state; + + if (piece && piece.id) { /* This really needs a refactor! @@ -129,37 +131,32 @@ let PieceContainer = React.createClass({ */ // Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted - let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) || - (this.state.currentUser.is_judge && !this.state.piece.selected )) ? - null : this.state.piece.artist_name; + let artistName; + if ((currentUser.is_jury && !currentUser.is_judge) || (currentUser.is_judge && !piece.selected )) { + artistName = - - {this.props.piece.selected ? this.getLoanButton() : null} - + {this.getSelectedActionButton()}

    @@ -374,13 +356,19 @@ let PrizePieceRatings = React.createClass({
    {this.state.ratings.map((item, i) => { - let note = item.note ? + let note = item.note ? (
    note: {item.note} -
    : null; + + ) : null; + return ( -
    -
    +
    +
    Date: Mon, 21 Dec 2015 11:53:27 +0100 Subject: [PATCH 20/45] Limit thumbnail size to 5mb --- .../further_details_fileuploader.js | 2 ++ .../ascribe_forms/form_register_piece.js | 10 ++++++++-- .../ascribe_forms/input_fineuploader.js | 3 +++ .../ascribe_uploader/react_s3_fine_uploader.js | 12 ++++++++---- .../pr_forms/pr_register_piece_form.js | 17 +++++++++++++---- js/constants/application_constants.js | 4 ++++ 6 files changed, 38 insertions(+), 10 deletions(-) diff --git a/js/components/ascribe_detail/further_details_fileuploader.js b/js/components/ascribe_detail/further_details_fileuploader.js index b044bbbc..acdbe9e0 100644 --- a/js/components/ascribe_detail/further_details_fileuploader.js +++ b/js/components/ascribe_detail/further_details_fileuploader.js @@ -20,6 +20,7 @@ let FurtherDetailsFileuploader = React.createClass({ otherData: React.PropTypes.arrayOf(React.PropTypes.object), setIsUploadReady: React.PropTypes.func, submitFile: React.PropTypes.func, + onValidationFailed: React.PropTypes.func, isReadyForFormSubmission: React.PropTypes.func, editable: React.PropTypes.bool, multiple: React.PropTypes.bool @@ -60,6 +61,7 @@ let FurtherDetailsFileuploader = React.createClass({ }} validation={AppConstants.fineUploader.validation.additionalData} submitFile={this.props.submitFile} + onValidationFailed={this.props.onValidationFailed} setIsUploadReady={this.props.setIsUploadReady} isReadyForFormSubmission={this.props.isReadyForFormSubmission} session={{ diff --git a/js/components/ascribe_forms/form_register_piece.js b/js/components/ascribe_forms/form_register_piece.js index 83d38b50..596f8a56 100644 --- a/js/components/ascribe_forms/form_register_piece.js +++ b/js/components/ascribe_forms/form_register_piece.js @@ -109,6 +109,11 @@ let RegisterPieceForm = React.createClass({ ); }, + handleThumbnailValidationFailed(thumbnailFile) { + // If the validation fails, set the thumbnail as submittable since its optional + this.refs.submitButton.setReadyStateForKey('thumbnailKeyReady', true); + }, + isThumbnailDialogExpanded() { const { enableSeparateThumbnail } = this.props; const { digitalWorkFile } = this.state; @@ -194,14 +199,15 @@ let RegisterPieceForm = React.createClass({ url: ApiUrls.blob_thumbnails }} handleChangedFile={this.handleChangedThumbnail} + onValidationFailed={this.handleThumbnailValidationFailed} isReadyForFormSubmission={formSubmissionValidation.fileOptional} keyRoutine={{ url: AppConstants.serverUrl + 's3/key/', fileClass: 'thumbnail' }} validation={{ - itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit, - sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit, + itemLimit: AppConstants.fineUploader.validation.workThumbnail.itemLimit, + sizeLimit: AppConstants.fineUploader.validation.workThumbnail.sizeLimit, allowedExtensions: ['png', 'jpg', 'jpeg', 'gif'] }} setIsUploadReady={this.setIsUploadReady('thumbnailKeyReady')} diff --git a/js/components/ascribe_forms/input_fineuploader.js b/js/components/ascribe_forms/input_fineuploader.js index db5bae05..fa9c72b6 100644 --- a/js/components/ascribe_forms/input_fineuploader.js +++ b/js/components/ascribe_forms/input_fineuploader.js @@ -52,6 +52,7 @@ const InputFineUploader = React.createClass({ plural: string }), handleChangedFile: func, + onValidationFailed: func, // Provided by `Property` onChange: React.PropTypes.func @@ -107,6 +108,7 @@ const InputFineUploader = React.createClass({ isFineUploaderActive, isReadyForFormSubmission, keyRoutine, + onValidationFailed, setIsUploadReady, uploadMethod, validation, @@ -127,6 +129,7 @@ const InputFineUploader = React.createClass({ createBlobRoutine={createBlobRoutine} validation={validation} submitFile={this.submitFile} + onValidationFailed={onValidationFailed} setIsUploadReady={setIsUploadReady} isReadyForFormSubmission={isReadyForFormSubmission} areAssetsDownloadable={areAssetsDownloadable} diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js index eb211504..877146a5 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js @@ -50,6 +50,7 @@ const ReactS3FineUploader = React.createClass({ }), handleChangedFile: func, // is for when a file is dropped or selected submitFile: func, // is for when a file has been successfully uploaded, TODO: rename to handleSubmitFile + onValidationFailed: func, autoUpload: bool, debug: bool, objectProperties: shape({ @@ -523,13 +524,16 @@ const ReactS3FineUploader = React.createClass({ }, isFileValid(file) { - if(file.size > this.props.validation.sizeLimit) { + if (file.size > this.props.validation.sizeLimit) { + const fileSizeInMegaBytes = this.props.validation.sizeLimit / 1000000; - let fileSizeInMegaBytes = this.props.validation.sizeLimit / 1000000; - - let notification = new GlobalNotificationModel(getLangText('A file you submitted is bigger than ' + fileSizeInMegaBytes + 'MB.'), 'danger', 5000); + const notification = new GlobalNotificationModel(getLangText('A file you submitted is bigger than ' + fileSizeInMegaBytes + 'MB.'), 'danger', 5000); GlobalNotificationActions.appendGlobalNotification(notification); + if (typeof this.props.onValidationFailed === 'function') { + this.props.onValidationFailed(file); + } + return false; } else { return true; diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js index a8d946b5..6b67467e 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js @@ -139,7 +139,7 @@ const PRRegisterPieceForm = React.createClass({ }, /** - * This method is overloaded so that we can track the ready-state + * These two methods are overloaded so that we can track the ready-state * of each uploader in the component * @param {string} uploaderKey Name of the uploader's key to track */ @@ -151,6 +151,14 @@ const PRRegisterPieceForm = React.createClass({ }; }, + handleOptionalFileValidationFailed(uploaderKey) { + return () => { + this.setState({ + [uploaderKey]: true + }); + }; + }, + getSubmitButton() { const { digitalWorkKeyReady, thumbnailKeyReady, @@ -303,7 +311,7 @@ const PRRegisterPieceForm = React.createClass({ + label={getLangText('Featured Cover photo (max 2MB)')}> Date: Mon, 21 Dec 2015 14:46:10 +0100 Subject: [PATCH 21/45] Use the 300x300 thumbnail if available --- .../accordion_list_item_piece.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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 006479c5..086ce667 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_piece.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_piece.js @@ -13,7 +13,7 @@ let AccordionListItemPiece = React.createClass({ propTypes: { className: React.PropTypes.string, artistName: React.PropTypes.string, - piece: React.PropTypes.object, + piece: React.PropTypes.object.isRequired, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element @@ -51,17 +51,21 @@ let AccordionListItemPiece = React.createClass({ piece, subsubheading, thumbnailPlaceholder: ThumbnailPlaceholder } = this.props; - const { url, url_safe } = piece.thumbnail; + const { url: thumbnailUrl, url_safe: thumbnailSafeUrl } = piece.thumbnail; + + // Display the 300x300 thumbnail if we have it, otherwise just use the safe url + const thumbnailDisplayUrl = (piece.thumbnail.thumbnail_sizes && piece.thumbnail.thumbnail_sizes['300x300']) || thumbnailSafeUrl; + 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/)) { + if (thumbnailUrl.match(/https:\/\/.*\/media\/thumbnails\/ascribe_spiral.png/)) { thumbnail = (); } else { thumbnail = ( -
    +
    ); } @@ -79,8 +83,7 @@ let AccordionListItemPiece = React.createClass({ subsubheading={subsubheading} buttons={buttons} badge={badge} - linkData={this.getLinkData()} - > + linkData={this.getLinkData()}> {children} ); From 1cf8ca006a811d641405efd88b02e8b6214960d9 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 21 Dec 2015 16:16:41 +0100 Subject: [PATCH 22/45] Move loan request action at the end of a prize to be sluice specific --- .../whitelabel/prize/prize_routes.js | 4 +++- .../ascribe_detail/prize_piece_container.js | 8 ------- .../sluice_selected_prize_action_button.js} | 5 ++-- .../sluice_detail/sluice_piece_container.js | 23 +++++++++++++++++++ 4 files changed, 29 insertions(+), 11 deletions(-) rename js/components/whitelabel/prize/{simple_prize/components/ascribe_buttons/selected_prize_loan_request_button.js => sluice/components/sluice_buttons/sluice_selected_prize_action_button.js} (95%) create mode 100644 js/components/whitelabel/prize/sluice/components/sluice_detail/sluice_piece_container.js diff --git a/js/components/whitelabel/prize/prize_routes.js b/js/components/whitelabel/prize/prize_routes.js index 7a72e5d9..5f80b30c 100644 --- a/js/components/whitelabel/prize/prize_routes.js +++ b/js/components/whitelabel/prize/prize_routes.js @@ -12,6 +12,8 @@ import SPPieceContainer from './simple_prize/components/ascribe_detail/prize_pie import SPSettingsContainer from './simple_prize/components/prize_settings_container'; import SPApp from './simple_prize/prize_app'; +import SluicePieceContainer from './sluice/components/sluice_detail/sluice_piece_container'; + import PRApp from './portfolioreview/pr_app'; import PRLanding from './portfolioreview/components/pr_landing'; import PRRegisterPiece from './portfolioreview/components/pr_register_piece'; @@ -53,7 +55,7 @@ const ROUTES = { path='collection' component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPPieceList)} headerTitle='COLLECTION'/> - + 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 9a549625..94367817 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 @@ -6,8 +6,6 @@ import Moment from 'moment'; import StarRating from 'react-star-rating'; -import SelectedPrizeLoanRequestButton from '../ascribe_buttons/selected_prize_loan_request_button'; - import ReactError from '../../../../../../mixins/react_error'; import { ResourceNotFoundError } from '../../../../../../models/errors'; @@ -60,12 +58,6 @@ let PrizePieceContainer = React.createClass({ mixins: [ReactError], - getDefaultProps() { - return { - selectedPrizeActionButton: SelectedPrizeLoanRequestButton - }; - }, - getInitialState() { return mergeOptions( PieceStore.getState(), diff --git a/js/components/whitelabel/prize/simple_prize/components/ascribe_buttons/selected_prize_loan_request_button.js b/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js similarity index 95% rename from js/components/whitelabel/prize/simple_prize/components/ascribe_buttons/selected_prize_loan_request_button.js rename to js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js index e068692a..7778a8de 100644 --- a/js/components/whitelabel/prize/simple_prize/components/ascribe_buttons/selected_prize_loan_request_button.js +++ b/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js @@ -14,7 +14,7 @@ import ApiUrls from '../../../../../../constants/api_urls'; import { getLangText } from '../../../../../../utils/lang_utils'; -const SelectedPrizeLoanRequestButton = React.createClass({ +const SluiceSelectedPrizeActionButton = React.createClass({ propTypes: { piece: React.PropTypes.object, currentUser: React.PropTypes.object, @@ -66,4 +66,5 @@ const SelectedPrizeLoanRequestButton = React.createClass({ } }); -export default SelectedPrizeLoanRequestButton; +export default SluiceSelectedPrizeActionButton; + diff --git a/js/components/whitelabel/prize/sluice/components/sluice_detail/sluice_piece_container.js b/js/components/whitelabel/prize/sluice/components/sluice_detail/sluice_piece_container.js new file mode 100644 index 00000000..2d9debca --- /dev/null +++ b/js/components/whitelabel/prize/sluice/components/sluice_detail/sluice_piece_container.js @@ -0,0 +1,23 @@ +'use strict'; + +import React from 'react'; + +import SluiceSelectedPrizeActionButton from '../sluice_buttons/sluice_selected_prize_action_button'; + +import PrizePieceContainer from '../../../simple_prize/components/ascribe_detail/prize_piece_container'; + +const SluicePieceContainer = React.createClass({ + propTypes: { + params: React.PropTypes.object + }, + + render() { + return ( + + ); + } +}); + +export default SluicePieceContainer; From 58cd68f1c44facd31d0da30995a8edc9e85f2f07 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 22 Dec 2015 10:36:05 +0100 Subject: [PATCH 23/45] Change Portfolio Review closing date to be Dec. 27th --- .../prize/portfolioreview/components/pr_register_piece.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js b/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js index 0fbca419..d7aa1aa8 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js @@ -62,7 +62,7 @@ const PRRegisterPiece = React.createClass({

    Portfolio Review

    -

    {getLangText('Submission closing on %s', ' 22 Dec 2015')}

    +

    {getLangText('Submission closing on %s', ' 27 Dec 2015')}

    For more information, visit:  portfolio-review.de @@ -84,4 +84,4 @@ const PRRegisterPiece = React.createClass({ } }); -export default PRRegisterPiece; \ No newline at end of file +export default PRRegisterPiece; From 8a70f0acc55623566728d51e2c87dcbf7279d3da Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 22 Dec 2015 19:37:44 +0100 Subject: [PATCH 24/45] Hotfix portfolio review cover image text to show correct 5mb limit --- .../components/pr_forms/pr_register_piece_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js index 2c48fd9a..e3d5ab93 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js @@ -317,7 +317,7 @@ const PRRegisterPieceForm = React.createClass({ + label={getLangText('Featured Cover photo (max 5MB)')}> Date: Wed, 23 Dec 2015 09:32:15 +0100 Subject: [PATCH 25/45] Add initial print stylesheet overrides --- sass/ascribe_footer.scss | 1 - sass/ascribe_print.scss | 9 +++++++++ sass/main.scss | 2 ++ sass/whitelabel/prize/index.scss | 6 ++++++ sass/whitelabel/wallet/index.scss | 6 ++++++ 5 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 sass/ascribe_print.scss diff --git a/sass/ascribe_footer.scss b/sass/ascribe_footer.scss index 85f3439d..4824d1ef 100644 --- a/sass/ascribe_footer.scss +++ b/sass/ascribe_footer.scss @@ -1,4 +1,3 @@ - .ascribe-footer { text-align: center; margin-top: 5em; diff --git a/sass/ascribe_print.scss b/sass/ascribe_print.scss new file mode 100644 index 00000000..3e48b4b1 --- /dev/null +++ b/sass/ascribe_print.scss @@ -0,0 +1,9 @@ +@media print { + @page { + margin: 1.2cm; + } + + .ascribe-default-app { + padding: 0 !important; + } +} diff --git a/sass/main.scss b/sass/main.scss index 900df4a0..4d536b80 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -44,6 +44,8 @@ $BASE_URL: '<%= BASE_URL %>'; @import 'whitelabel/index'; +@import 'ascribe_print'; + html, body { diff --git a/sass/whitelabel/prize/index.scss b/sass/whitelabel/prize/index.scss index 67acaf11..dfdcaebd 100644 --- a/sass/whitelabel/prize/index.scss +++ b/sass/whitelabel/prize/index.scss @@ -7,3 +7,9 @@ padding-top: 70px; padding-bottom: 10px; } + +@media print { + .ascribe-prize-app { + padding: 0 !important; + } +} diff --git a/sass/whitelabel/wallet/index.scss b/sass/whitelabel/wallet/index.scss index 78d075dc..647bb16c 100644 --- a/sass/whitelabel/wallet/index.scss +++ b/sass/whitelabel/wallet/index.scss @@ -9,3 +9,9 @@ padding-top: 70px; padding-bottom: 10px; } + +@media print { + .ascribe-wallet-app { + padding: 0 !important; + } +} From cf6d653c684f398a60ed80c5967bdf7272ac2197 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 23 Dec 2015 09:33:25 +0100 Subject: [PATCH 26/45] Small code style fixes --- .../ascribe_detail/wallet_piece_container.js | 26 ++++++++++--------- .../ikonotv_artist_details_form.js | 7 +++-- .../ikonotv_artwork_details_form.js | 6 ++--- 3 files changed, 20 insertions(+), 19 deletions(-) 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 26a186ca..4be15b04 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 @@ -25,12 +25,16 @@ let WalletPieceContainer = React.createClass({ currentUser: React.PropTypes.object.isRequired, loadPiece: React.PropTypes.func.isRequired, handleDeleteSuccess: React.PropTypes.func.isRequired, - submitButtonType: React.PropTypes.func.isRequired + submitButtonType: React.PropTypes.func.isRequired, + children: React.PropTypes.oneOfType([ + React.PropTypes.object, + React.PropTypes.array + ]) }, - + render() { - if(this.props.piece && this.props.piece.id) { + if (this.props.piece && this.props.piece.id) { return ( } subheader={ -

    - }> +
    + + +
    +
    + }> - {this.props.children} ); - } - else { + } else { return (
    diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_forms/ikonotv_artist_details_form.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_forms/ikonotv_artist_details_form.js index 7aec7ff4..0b97c8dd 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_forms/ikonotv_artist_details_form.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_forms/ikonotv_artist_details_form.js @@ -60,9 +60,8 @@ let IkonotvArtistDetailsForm = React.createClass({ render() { let buttons, spinner, heading; let { isInline, handleSuccess } = this.props; - - if(!isInline) { + if (!isInline) { buttons = ( - - - - + const { coa = {}, coaError } = this.props; -
    + let coaDetailElement; + if (coaError) { + coaDetailElement = [ +

    {getLangText('There was an error generating your Certificate of Authenticity.')}

    , +

    + {getLangText('Try to refresh the page. If this happens repeatedly, please ')} + {getLangText('contact us')}. +

    + ]; + } else if (coa.url_safe) { + coaDetailElement = [ +
    + +
    , +
    + + + + + +
    - ); - } else if(typeof this.props.coa === 'string'){ - return ( -
    - {this.props.coa} -
    - ); - } - return ( -
    - -

    {getLangText("Just a sec, we\'re generating your COA")}

    + ]; + } else if (typeof coa === 'string') { + coaDetailElement = coa; + } else { + coaDetailElement = [ + , +

    {getLangText("Just a sec, we're generating your COA")}

    ,

    {getLangText('(you may leave the page)')}

    + ]; + } + + return ( +
    +
    + {coaDetailElement} +
    + {/* Hide the COA and just show that it's a seperate document when printing */} +
    + {getLangText('The COA is available as a seperate document')} +
    ); } diff --git a/sass/ascribe_print.scss b/sass/ascribe_print.scss index f2abeb80..6baa40af 100644 --- a/sass/ascribe_print.scss +++ b/sass/ascribe_print.scss @@ -24,4 +24,10 @@ width: 50% !important; float: right !important; } + + // Restyle COA + .ascribe-coa-print-placeholder { + padding: 0 1.5em 1em 1.5em; + margin: 0; + } } From 2dc036047f73842bbb4a1ec98282fd876daf8d62 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 23 Dec 2015 09:47:30 +0100 Subject: [PATCH 32/45] Substitute upload button for placeholder text in print --- .../file_drag_and_drop_dialog.js | 54 +++++++++++-------- .../file_drag_and_drop_preview_image.js | 4 +- sass/ascribe_print.scss | 11 ++++ 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_dialog.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_dialog.js index 25552819..db28846b 100644 --- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_dialog.js +++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_dialog.js @@ -26,7 +26,7 @@ let FileDragAndDropDialog = React.createClass({ getDragDialog(fileClass) { if (dragAndDropAvailable) { return [ -

    {getLangText('Drag %s here', fileClass)}

    , +

    {getLangText('Drag %s here', fileClass)}

    ,

    {getLangText('or')}

    ]; } else { @@ -46,6 +46,8 @@ let FileDragAndDropDialog = React.createClass({ if (hasFiles) { return null; } else { + let dialogElement; + if (enableLocalHashing && !uploadMethod) { const currentQueryParams = getCurrentQueryParams(); @@ -55,9 +57,9 @@ let FileDragAndDropDialog = React.createClass({ const queryParamsUpload = Object.assign({}, currentQueryParams); queryParamsUpload.method = 'upload'; - return ( -
    -

    {getLangText('Would you rather')}

    + dialogElement = ( +
    +

    {getLangText('Would you rather')}

    {/* The frontend in live is hosted under /app, Since `Link` is appending that base url, if its defined @@ -85,32 +87,40 @@ let FileDragAndDropDialog = React.createClass({ ); } else { if (multipleFiles) { - return ( - - {this.getDragDialog(fileClassToUpload.plural)} - - {getLangText('choose %s to upload', fileClassToUpload.plural)} - + dialogElement = [ + this.getDragDialog(fileClassToUpload.plural), + + {getLangText('choose %s to upload', fileClassToUpload.plural)} - ); + ]; } else { const dialog = uploadMethod === 'hash' ? getLangText('choose a %s to hash', fileClassToUpload.singular) : getLangText('choose a %s to upload', fileClassToUpload.singular); - return ( - - {this.getDragDialog(fileClassToUpload.singular)} - - {dialog} - + dialogElement = [ + this.getDragDialog(fileClassToUpload.singular), + + {dialog} - ); + ]; } } + + return ( +
    +
    + {dialogElement} +
    + {/* Hide the uploader and just show that there's been on files uploaded yet when printing */} +

    + {getLangText('No files uploaded')} +

    +
    + ); } } }); diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_image.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_image.js index 927a5b22..5c757121 100644 --- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_image.js +++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_image.js @@ -49,7 +49,7 @@ const FileDragAndDropPreviewImage = React.createClass({ }; let actionSymbol; - + // only if assets are actually downloadable, there should be a download icon if the process is already at // 100%. If not, no actionSymbol should be displayed if(progress === 100 && areAssetsDownloadable) { @@ -68,7 +68,7 @@ const FileDragAndDropPreviewImage = React.createClass({ return (
    diff --git a/sass/ascribe_print.scss b/sass/ascribe_print.scss index 6baa40af..c33074a4 100644 --- a/sass/ascribe_print.scss +++ b/sass/ascribe_print.scss @@ -25,6 +25,17 @@ float: right !important; } + // Restyle file uploader dialogs + .file-drag-and-drop { + padding-top: 0; + outline-width: 0; + text-align: left; + } + + .file-drag-and-drop-position { + margin: 0; + } + // Restyle COA .ascribe-coa-print-placeholder { padding: 0 1.5em 1em 1.5em; From 5b34d569f6152a6db5263c3d5cd92ac25bdd2a4c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 23 Dec 2015 09:48:45 +0100 Subject: [PATCH 33/45] Don't expand some anchor links in print --- js/components/ascribe_detail/edition.js | 30 +++++++++++++++---- .../ascribe_detail/history_iterator.js | 6 +++- sass/ascribe_print.scss | 5 ++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index 81746096..c86a6480 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -296,16 +296,34 @@ let SpoolDetails = React.createClass({ }, render() { - let bitcoinIdValue = ( - {this.props.edition.bitcoin_id} + const { edition: { + bitcoin_id: bitcoinId, + hash_as_address: hashAsAddress, + btc_owner_address_noprefix: bitcoinOwnerAddress + } } = this.props; + + const bitcoinIdValue = ( + + {bitcoinId} + ); - let hashOfArtwork = ( - {this.props.edition.hash_as_address} + const hashOfArtwork = ( + + {hashAsAddress} + ); - let ownerAddress = ( - {this.props.edition.btc_owner_address_noprefix} + const ownerAddress = ( + + {bitcoinOwnerAddress} + ); return ( diff --git a/js/components/ascribe_detail/history_iterator.js b/js/components/ascribe_detail/history_iterator.js index 413aeb21..03904863 100644 --- a/js/components/ascribe_detail/history_iterator.js +++ b/js/components/ascribe_detail/history_iterator.js @@ -22,7 +22,11 @@ let HistoryIterator = React.createClass({ return ( {historicalEventDescription} - {contractName} + + {contractName} + ); } else if(historicalEvent.length === 2) { diff --git a/sass/ascribe_print.scss b/sass/ascribe_print.scss index c33074a4..99724872 100644 --- a/sass/ascribe_print.scss +++ b/sass/ascribe_print.scss @@ -7,6 +7,11 @@ padding: 0 !important; } + // Utility class to not automatically expand an anchor href after the text + .anchor-no-expand-print:after { + content: '' !important; + } + // Replace navbar header with ascribe logo .ascribe-print-header { border-bottom: 1px solid rgba(0, 60, 105, 0.1); From 6419a4e8e0204cf880cf72e208caade15b3daf11 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 23 Dec 2015 09:49:20 +0100 Subject: [PATCH 34/45] Form and property restyling in print --- sass/ascribe_print.scss | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/sass/ascribe_print.scss b/sass/ascribe_print.scss index 99724872..973e36ed 100644 --- a/sass/ascribe_print.scss +++ b/sass/ascribe_print.scss @@ -46,4 +46,73 @@ padding: 0 1.5em 1em 1.5em; margin: 0; } + + // Force collapsible properties to be expanded + .ascribe-collapsible-content .collapse { + display: block; + } + + // Decrease property spacing + .ascribe-property-wrapper { + padding-bottom: 0.5em; + } + + .ascribe-property { + padding-top: 0.5em; + + > div, + > input, + > p, + > pre, + > select, + > span:not(.glyphicon), + > textarea { + margin: 0; + } + } + + .ascribe-collapsible-wrapper { + margin-bottom: 5px; + + > div:first-child { + margin-top: 0; + padding-bottom: 5px; + } + } + + .ascribe-form hr { + margin-bottom: 3px; + } + + // Hide placeholder text + input::-webkit-input-placeholder { + opacity: 0; + } + textarea::-webkit-input-placeholder { + opacity: 0; + } + + /* firefox 18- */ + input:-moz-placeholder { + opacity: 0; + } + textarea:-moz-placeholder { + opacity: 0; + } + + /* firefox 19+ */ + input::-moz-placeholder { + opacity: 0; + } + textarea::-moz-placeholder { + opacity: 0; + } + + /* ie */ + input:-ms-input-placeholder { + opacity: 0; + } + textarea:-ms-input-placeholder { + opacity: 0; + } } From 0f8499dfeeb143c2a2855125661173a621c28c4a Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 23 Dec 2015 09:50:38 +0100 Subject: [PATCH 35/45] Use getLangText for COA Intercom message --- js/components/ascribe_detail/edition.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index 40fd6625..141cd718 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -225,8 +225,10 @@ let CoaDetails = React.createClass({ }, contactOnIntercom() { - window.Intercom('showNewMessage', `Hi, I'm having problems generating a Certificate of Authenticity for Edition: ${this.props.editionId}`); - console.logGlobal(new Error(`Coa couldn't be created for edition: ${this.props.editionId}`), this.props.coaError); + const { coaError, editionId } = this.props; + + window.Intercom('showNewMessage', getLangText("Hi, I'm having problems generating a Certificate of Authenticity for Edition: %s", editionId)); + console.logGlobal(new Error(`Coa couldn't be created for edition: ${editionId}`), coaError); }, render() { From d817b920b4af7d8577fe0961772cd461f1b55395 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 23 Dec 2015 10:20:56 +0100 Subject: [PATCH 36/45] Use class to add overflow ellipsis instead of directly styling component --- js/components/ascribe_detail/detail_property.js | 11 ++--------- sass/main.scss | 6 ++++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/js/components/ascribe_detail/detail_property.js b/js/components/ascribe_detail/detail_property.js index 44eec06a..0191ffa9 100644 --- a/js/components/ascribe_detail/detail_property.js +++ b/js/components/ascribe_detail/detail_property.js @@ -35,27 +35,20 @@ const DetailProperty = React.createClass({ const { children, className, + ellipsis, label, labelClassName, separator, valueClassName, value } = this.props; - const styles = this.props.ellipsis ? { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis' - } : null; - return (
    {label} {separator}
    -
    +
    {children || value}
    diff --git a/sass/main.scss b/sass/main.scss index 4d536b80..9ca2a07a 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -108,6 +108,12 @@ hr { color: $ascribe-dark-blue; } +.add-overflow-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .ascribe-subheader { padding-bottom: 10px; margin-top: -10px; From 13937c99515087624562be2d6fb516f97d7dd60a Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 23 Dec 2015 20:43:46 +0100 Subject: [PATCH 37/45] Fix stale prize ratings showing up on other pieces when they do not have ratings --- .../actions/prize_rating_actions.js | 3 +- .../ascribe_detail/prize_piece_container.js | 48 +++++++++---------- .../simple_prize/stores/prize_rating_store.js | 24 ++++++++-- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js b/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js index 68b5334b..01637b7a 100644 --- a/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js +++ b/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js @@ -10,7 +10,8 @@ class PrizeRatingActions { this.generateActions( 'updatePrizeRatings', 'updatePrizeRatingAverage', - 'updatePrizeRating' + 'updatePrizeRating', + 'resetPrizeRatings' ); } 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 94367817..68a66cc0 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 @@ -59,31 +59,32 @@ let PrizePieceContainer = React.createClass({ mixins: [ReactError], getInitialState() { - return mergeOptions( - PieceStore.getState(), - UserStore.getState() - ); + //FIXME: this component uses the PieceStore, but we avoid using the getState() here since it may contain stale data + // It should instead use something like getInitialState() where that call also resets the state. + return UserStore.getState(); + }, + + componentWillMount() { + // Every time we enter the piece detail page, just reset the piece + // store as it will otherwise display wrong/old data once the user loads + // the piece detail a second time + PieceActions.updatePiece({}); }, componentDidMount() { PieceStore.listen(this.onChange); UserStore.listen(this.onChange); - // Every time we enter the piece detail page, just reset the piece - // store as it will otherwise display wrong/old data once the user loads - // the piece detail a second time - PieceActions.updatePiece({}); - - this.loadPiece(); UserActions.fetchCurrentUser(); + this.loadPiece(); }, // This is done to update the container when the user clicks on the prev or next // button to update the URL parameter (and therefore to switch pieces) componentWillReceiveProps(nextProps) { - if(this.props.params.pieceId !== nextProps.params.pieceId) { + if (this.props.params.pieceId !== nextProps.params.pieceId) { PieceActions.updatePiece({}); - PieceActions.fetchOne(nextProps.params.pieceId); + this.loadPiece(nextProps.params.pieceId); } }, @@ -100,7 +101,6 @@ let PrizePieceContainer = React.createClass({ UserStore.unlisten(this.onChange); }, - onChange(state) { this.setState(state); }, @@ -118,8 +118,8 @@ let PrizePieceContainer = React.createClass({ } }, - loadPiece() { - PieceActions.fetchOne(this.props.params.pieceId); + loadPiece(pieceId = this.props.params.pieceId) { + PieceActions.fetchOne(pieceId); }, render() { @@ -234,23 +234,19 @@ let PrizePieceRatings = React.createClass({ getInitialState() { return mergeOptions( PieceListStore.getState(), - PrizeRatingStore.getState() + PrizeRatingStore.getInitialState() ); }, componentDidMount() { PrizeRatingStore.listen(this.onChange); + PieceListStore.listen(this.onChange); + PrizeRatingActions.fetchOne(this.props.piece.id); PrizeRatingActions.fetchAverage(this.props.piece.id); - PieceListStore.listen(this.onChange); }, componentWillUnmount() { - // Every time we're leaving the piece detail page, - // just reset the piece that is saved in the piece store - // as it will otherwise display wrong/old data once the user loads - // the piece detail a second time - PrizeRatingActions.updateRating({}); PrizeRatingStore.unlisten(this.onChange); PieceListStore.unlisten(this.onChange); }, @@ -311,7 +307,7 @@ let PrizePieceRatings = React.createClass({ }); }, - render(){ + render() { if (this.props.piece && this.props.currentUser && this.props.currentUser.is_judge && this.state.average) { // Judge sees shortlisting, average and per-jury notes return ( @@ -380,8 +376,7 @@ let PrizePieceRatings = React.createClass({
    ); - } - else if (this.props.currentUser && this.props.currentUser.is_jury) { + } else if (this.props.currentUser && this.props.currentUser.is_jury) { // Jury can set rating and note return ( ); + } else { + return null; } - return null; } }); diff --git a/js/components/whitelabel/prize/simple_prize/stores/prize_rating_store.js b/js/components/whitelabel/prize/simple_prize/stores/prize_rating_store.js index 9f1552bb..6c478f3c 100644 --- a/js/components/whitelabel/prize/simple_prize/stores/prize_rating_store.js +++ b/js/components/whitelabel/prize/simple_prize/stores/prize_rating_store.js @@ -6,10 +6,12 @@ import PrizeRatingActions from '../actions/prize_rating_actions'; class PrizeRatingStore { constructor() { - this.ratings = []; - this.currentRating = null; - this.average = null; + this.getInitialState(); + this.bindActions(PrizeRatingActions); + this.exportPublicMethods({ + getInitialState: this.getInitialState.bind(this) + }); } onUpdatePrizeRatings(ratings) { @@ -24,6 +26,22 @@ class PrizeRatingStore { this.average = data.average; this.ratings = data.ratings; } + + onResetPrizeRatings() { + this.getInitialState(); + } + + getInitialState() { + this.ratings = []; + this.currentRating = null; + this.average = null; + + return { + ratings: this.ratings, + currentRating: this.currentRating, + average: this.average + }; + } } export default alt.createStore(PrizeRatingStore, 'PrizeRatingStore'); \ No newline at end of file From 15a2bfae9a1dfd376ef6fab6e334782b0d97e9d8 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 4 Jan 2016 18:06:26 +0100 Subject: [PATCH 38/45] Fix check for coa --- js/components/ascribe_detail/edition.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index 191cfe87..0879dea0 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -233,9 +233,9 @@ let CoaDetails = React.createClass({ }, render() { - const { coa = {}, coaError } = this.props; - + const { coa, coaError } = this.props; let coaDetailElement; + if (coaError) { coaDetailElement = [

    {getLangText('There was an error generating your Certificate of Authenticity.')}

    , @@ -244,7 +244,7 @@ let CoaDetails = React.createClass({ {getLangText('contact us')}.

    ]; - } else if (coa.url_safe) { + } else if (coa && coa.url_safe) { coaDetailElement = [
    Date: Mon, 4 Jan 2016 19:40:54 +0100 Subject: [PATCH 39/45] Add `round` parameter to prize ratings --- .../actions/prize_rating_actions.js | 18 ++++----- .../ascribe_detail/prize_piece_container.js | 36 ++++++++++++------ .../fetchers/prize_rating_fetcher.js | 37 ++++++++++++++++--- .../prize/simple_prize/stores/prize_store.js | 4 +- 4 files changed, 65 insertions(+), 30 deletions(-) diff --git a/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js b/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js index 01637b7a..91852704 100644 --- a/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js +++ b/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js @@ -15,10 +15,10 @@ class PrizeRatingActions { ); } - fetchAverage(pieceId) { + fetchAverage(pieceId, round) { return Q.Promise((resolve, reject) => { PrizeRatingFetcher - .fetchAverage(pieceId) + .fetchAverage(pieceId, round) .then((res) => { this.actions.updatePrizeRatingAverage(res.data); resolve(res); @@ -30,10 +30,10 @@ class PrizeRatingActions { }); } - fetchOne(pieceId) { + fetchOne(pieceId, round) { return Q.Promise((resolve, reject) => { PrizeRatingFetcher - .fetchOne(pieceId) + .fetchOne(pieceId, round) .then((res) => { this.actions.updatePrizeRating(res.rating.rating); resolve(res); @@ -44,10 +44,10 @@ class PrizeRatingActions { }); } - createRating(pieceId, rating) { + createRating(pieceId, rating, round) { return Q.Promise((resolve, reject) => { PrizeRatingFetcher - .rate(pieceId, rating) + .rate(pieceId, rating, round) .then((res) => { this.actions.updatePrizeRating(res.rating.rating); resolve(res); @@ -71,10 +71,6 @@ class PrizeRatingActions { }); }); } - - updateRating(rating) { - this.actions.updatePrizeRating(rating); - } } -export default alt.createActions(PrizeRatingActions); \ No newline at end of file +export default alt.createActions(PrizeRatingActions); 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 68a66cc0..c5ab2c81 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 @@ -9,15 +9,16 @@ import StarRating from 'react-star-rating'; import ReactError from '../../../../../../mixins/react_error'; import { ResourceNotFoundError } from '../../../../../../models/errors'; -import PieceActions from '../../../../../../actions/piece_actions'; -import PieceStore from '../../../../../../stores/piece_store'; - -import PieceListStore from '../../../../../../stores/piece_list_store'; -import PieceListActions from '../../../../../../actions/piece_list_actions'; - +import PrizeActions from '../../actions/prize_actions'; +import PrizeStore from '../../stores/prize_store'; import PrizeRatingActions from '../../actions/prize_rating_actions'; import PrizeRatingStore from '../../stores/prize_rating_store'; +import PieceActions from '../../../../../../actions/piece_actions'; +import PieceStore from '../../../../../../stores/piece_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'; @@ -234,21 +235,24 @@ let PrizePieceRatings = React.createClass({ getInitialState() { return mergeOptions( PieceListStore.getState(), + PrizeStore.getState(), PrizeRatingStore.getInitialState() ); }, componentDidMount() { - PrizeRatingStore.listen(this.onChange); PieceListStore.listen(this.onChange); + PrizeStore.listen(this.onChange); + PrizeRatingStore.listen(this.onChange); - PrizeRatingActions.fetchOne(this.props.piece.id); - PrizeRatingActions.fetchAverage(this.props.piece.id); + PrizeActions.fetchPrize(); + this.fetchPrizeRatings(); }, componentWillUnmount() { - PrizeRatingStore.unlisten(this.onChange); PieceListStore.unlisten(this.onChange); + PrizeStore.unlisten(this.onChange); + PrizeRatingStore.unlisten(this.onChange); }, // The StarRating component does not have a property that lets us set @@ -256,7 +260,12 @@ let PrizePieceRatings = React.createClass({ // every mouseover be overridden, we need to set it ourselves initially to deal // with the problem. onChange(state) { + if (state.prize && state.prize.active_round != this.state.prize.active_round) { + this.fetchPrizeRatings(state); + } + this.setState(state); + if (this.refs.rating) { this.refs.rating.state.ratingCache = { pos: this.refs.rating.state.pos, @@ -267,10 +276,15 @@ let PrizePieceRatings = React.createClass({ } }, + fetchPrizeRatings(state = this.state) { + PrizeRatingActions.fetchOne(this.props.piece.id, state.prize.active_round); + PrizeRatingActions.fetchAverage(this.props.piece.id, state.prize.active_round); + }, + onRatingClick(event, args) { event.preventDefault(); PrizeRatingActions - .createRating(this.props.piece.id, args.rating) + .createRating(this.props.piece.id, args.rating, this.state.prize.active_round) .then(this.refreshPieceData); }, diff --git a/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js b/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js index 38d0576e..9ec82c30 100644 --- a/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js +++ b/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js @@ -4,16 +4,41 @@ import requests from '../../../../../utils/requests'; let PrizeRatingFetcher = { - fetchAverage(pieceId) { - return requests.get('rating_average', {'piece_id': pieceId}); + fetchAverage(pieceId, round) { + const params = { + 'piece_id': pieceId + }; + + if (typeof round === 'number') { + params.round = round; + } + + return requests.get('rating_average', params); }, - fetchOne(pieceId) { - return requests.get('rating', {'piece_id': pieceId}); + fetchOne(pieceId, round) { + const params = { + 'piece_id': pieceId + }; + + if (typeof round === 'number') { + params.round = round; + } + + return requests.get('rating', params); }, - rate(pieceId, rating) { - return requests.post('ratings', {body: {'piece_id': pieceId, 'note': rating}}); + rate(pieceId, rating, round) { + const body = { + 'piece_id': pieceId, + 'note': rating + }; + + if (typeof round === 'number') { + body.round = round; + } + + return requests.post('ratings', { body }); }, select(pieceId) { diff --git a/js/components/whitelabel/prize/simple_prize/stores/prize_store.js b/js/components/whitelabel/prize/simple_prize/stores/prize_store.js index 8d9c4bbe..d54ab549 100644 --- a/js/components/whitelabel/prize/simple_prize/stores/prize_store.js +++ b/js/components/whitelabel/prize/simple_prize/stores/prize_store.js @@ -6,7 +6,7 @@ import PrizeActions from '../actions/prize_actions'; class PrizeStore { constructor() { - this.prize = []; + this.prize = {}; this.bindActions(PrizeActions); } @@ -15,4 +15,4 @@ class PrizeStore { } } -export default alt.createStore(PrizeStore, 'PrizeStore'); \ No newline at end of file +export default alt.createStore(PrizeStore, 'PrizeStore'); From c3f05861cc80e41c774e6b212779e6e1c9b663e3 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 5 Jan 2016 13:36:58 +0100 Subject: [PATCH 40/45] Upgrade react-router and history to latest non-breaking release --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index be5c1202..ec4d505e 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "gulp-uglify": "^1.2.0", "gulp-util": "^3.0.4", "harmonize": "^1.4.2", - "history": "^1.13.1", + "history": "1.17.0", "invariant": "^2.1.1", "isomorphic-fetch": "^2.0.2", "jest-cli": "^0.4.0", @@ -80,7 +80,7 @@ "react": "0.13.2", "react-bootstrap": "0.25.1", "react-datepicker": "^0.12.0", - "react-router": "1.0.0", + "react-router": "1.0.3", "react-router-bootstrap": "^0.19.0", "react-star-rating": "~1.3.2", "react-textarea-autosize": "^2.5.2", From af9d9132d2b49408d380b5ee87d49f6343dcd9db Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 5 Jan 2016 14:13:46 +0100 Subject: [PATCH 41/45] Use query-string instead of qs Saves bytes, as done by rackt/history: https://github.com/rackt/history/issues/121 --- js/utils/url_utils.js | 11 +++++------ package.json | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/js/utils/url_utils.js b/js/utils/url_utils.js index 86c8dfc5..94584026 100644 --- a/js/utils/url_utils.js +++ b/js/utils/url_utils.js @@ -2,7 +2,7 @@ import camelCase from 'camelcase'; import decamelize from 'decamelize'; -import qs from 'qs'; +import queryString from 'query-string'; import { sanitize } from './general_utils'; @@ -36,8 +36,7 @@ export function argsToQueryParams(obj) { queryParamObj[decamelize(key)] = sanitizedObj[key]; }); - // Use bracket arrayFormat as history.js and react-router use it - return '?' + qs.stringify(queryParamObj, { arrayFormat: 'brackets' }); + return '?' + queryString.stringify(queryParamObj); } /** @@ -56,13 +55,13 @@ export function getCurrentQueryParams() { * @return {object} Query params dictionary */ export function queryParamsToArgs(queryParamString) { - const qsQueryParamObj = qs.parse(queryParamString); + const queryParamObj = queryString.parse(queryParamString); const camelCaseParamObj = {}; Object - .keys(qsQueryParamObj) + .keys(queryParamObj) .forEach((key) => { - camelCaseParamObj[camelCase(key)] = qsQueryParamObj[key]; + camelCaseParamObj[camelCase(key)] = queryParamObj[key]; }); return camelCaseParamObj; diff --git a/package.json b/package.json index ec4d505e..a874ea2c 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "object-assign": "^2.0.0", "opn": "^3.0.2", "q": "^1.4.1", - "qs": "^4.0.0", + "query-string": "^3.0.0", "raven-js": "^1.1.19", "react": "0.13.2", "react-bootstrap": "0.25.1", From 4624e936b3fd6482bf5eda83ea8f3401ccf30296 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 5 Jan 2016 17:27:25 +0100 Subject: [PATCH 42/45] Use the new history api and its LocationDescriptors --- .../ascribe_detail/edition_action_panel.js | 8 +++---- .../ascribe_detail/piece_container.js | 2 +- .../form_send_contract_agreement.js | 2 +- js/components/ascribe_routes/proxy_handler.js | 4 ++-- .../slides_container.js | 14 ++++++------- js/components/password_reset_container.js | 5 +++-- js/components/piece_list.js | 21 ++++++++++--------- js/components/register_piece.js | 4 ++-- .../pr_forms/pr_register_piece_form.js | 2 +- .../portfolioreview/components/pr_landing.js | 17 ++++++++------- .../components/pr_register_piece.js | 2 +- .../components/pr_routes/pr_proxy_handler.js | 2 +- .../simple_prize/components/prize_landing.js | 2 +- .../cyland_detail/cyland_piece_container.js | 2 +- .../components/cyland/cyland_landing.js | 2 +- .../cyland/cyland_register_piece.js | 2 +- .../ikonotv/ikonotv_contract_notifications.js | 4 ++-- .../ikonotv_detail/ikonotv_piece_container.js | 2 +- .../ikonotv/ikonotv_register_piece.js | 4 ++-- .../market/market_register_piece.js | 2 +- js/third_party/notifications.js | 4 ++-- 21 files changed, 56 insertions(+), 51 deletions(-) diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index 36a79e7c..92c51c32 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -72,10 +72,10 @@ let EditionActionPanel = React.createClass({ EditionListActions.closeAllEditionLists(); EditionListActions.clearAllEditionSelections(); - let notification = new GlobalNotificationModel(response.notification, 'success'); + const notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState(null, '/collection'); + this.history.push('/collection'); }, refreshCollection() { @@ -84,7 +84,7 @@ let EditionActionPanel = React.createClass({ EditionListActions.refreshEditionList({pieceId: this.props.edition.parent}); }, - handleSuccess(response){ + handleSuccess(response) { this.refreshCollection(); this.props.handleSuccess(); if (response){ @@ -93,7 +93,7 @@ let EditionActionPanel = React.createClass({ } }, - render(){ + render() { const { actionPanelButtonListType: ActionPanelButtonListType, edition, diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 1aebff75..9615c455 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -159,7 +159,7 @@ let PieceContainer = React.createClass({ let notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState(null, '/collection'); + this.history.push('/collection'); }, getCreateEditionsDialog() { diff --git a/js/components/ascribe_forms/form_send_contract_agreement.js b/js/components/ascribe_forms/form_send_contract_agreement.js index 6f5f74d7..043c0361 100644 --- a/js/components/ascribe_forms/form_send_contract_agreement.js +++ b/js/components/ascribe_forms/form_send_contract_agreement.js @@ -58,7 +58,7 @@ let SendContractAgreementForm = React.createClass({ notification = new GlobalNotificationModel(notification, 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState(null, '/collection'); + this.history.push('/collection'); }, getFormData() { diff --git a/js/components/ascribe_routes/proxy_handler.js b/js/components/ascribe_routes/proxy_handler.js index 228f0f62..7752912a 100644 --- a/js/components/ascribe_routes/proxy_handler.js +++ b/js/components/ascribe_routes/proxy_handler.js @@ -40,7 +40,7 @@ export function AuthRedirect({to, when}) { // and redirect if `true`. if(exprToValidate) { - window.setTimeout(() => history.replaceState(null, to, query)); + window.setTimeout(() => history.replace({ query, pathname: to })); return true; // Otherwise there can also be the case that the backend @@ -48,7 +48,7 @@ export function AuthRedirect({to, when}) { } else if(!exprToValidate && when === 'loggedIn' && redirect) { delete query.redirect; - window.setTimeout(() => history.replaceState(null, '/' + redirect, query)); + window.setTimeout(() => history.replace({ query, pathname: '/' + redirect })); return true; } else if(!exprToValidate && when === 'loggedOut' && redirectAuthenticated) { diff --git a/js/components/ascribe_slides_container/slides_container.js b/js/components/ascribe_slides_container/slides_container.js index 39d515a3..109bbae7 100644 --- a/js/components/ascribe_slides_container/slides_container.js +++ b/js/components/ascribe_slides_container/slides_container.js @@ -57,21 +57,21 @@ const SlidesContainer = React.createClass({ // When the start_from parameter is used, this.setSlideNum can not simply be used anymore. nextSlide(additionalQueryParams) { const slideNum = parseInt(this.props.location.query.slide_num, 10) || 0; - let nextSlide = slideNum + 1; - this.setSlideNum(nextSlide, additionalQueryParams); + this.setSlideNum(slideNum + 1, additionalQueryParams); }, setSlideNum(nextSlideNum, additionalQueryParams = {}) { - let queryParams = Object.assign(this.props.location.query, additionalQueryParams); - queryParams.slide_num = nextSlideNum; - this.history.pushState(null, this.props.location.pathname, queryParams); + const { location: { pathname } } = this.props; + const query = Object.assign({}, this.props.location.query, additionalQueryParams, { slide_num: nextSlideNum }); + + this.history.push({ pathname, query }); }, // breadcrumbs are defined as attributes of the slides. // To extract them we have to read the DOM element's attributes extractBreadcrumbs() { const startFrom = parseInt(this.props.location.query.start_from, 10) || -1; - let breadcrumbs = []; + const breadcrumbs = []; React.Children.map(this.props.children, (child, i) => { if(child && i >= startFrom && child.props['data-slide-title']) { @@ -179,4 +179,4 @@ const SlidesContainer = React.createClass({ } }); -export default SlidesContainer; \ No newline at end of file +export default SlidesContainer; diff --git a/js/components/password_reset_container.js b/js/components/password_reset_container.js index 31275a08..6d2d089e 100644 --- a/js/components/password_reset_container.js +++ b/js/components/password_reset_container.js @@ -130,8 +130,9 @@ let PasswordResetForm = React.createClass({ }, handleSuccess() { - this.history.pushState(null, '/collection'); - let notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000); + this.history.push('/collection'); + + const notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); }, diff --git a/js/components/piece_list.js b/js/components/piece_list.js index de979e65..68ba5c33 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -115,13 +115,13 @@ let PieceList = React.createClass({ }, componentDidUpdate() { - const { redirectTo, shouldRedirect } = this.props; + const { location: { query }, redirectTo, shouldRedirect } = this.props; const { unfilteredPieceListCount } = this.state; if (redirectTo && unfilteredPieceListCount === 0 && (typeof shouldRedirect === 'function' && shouldRedirect(unfilteredPieceListCount))) { // FIXME: hack to redirect out of the dispatch cycle - window.setTimeout(() => this.history.pushState(null, this.props.redirectTo, this.props.location.query), 0); + window.setTimeout(() => this.history.push({ query, pathname: redirectTo }), 0); } }, @@ -174,15 +174,16 @@ let PieceList = React.createClass({ } }, - searchFor(searchTerm) { - this.loadPieceList({ - page: 1, - search: searchTerm - }); - this.history.pushState(null, this.props.location.pathname, {page: 1}); + searchFor(search) { + const { location: { pathname } } = this.props; + + this.loadPieceList({ search, page: 1 }); + this.history.push({ pathname, query: { page: 1 } }); }, - applyFilterBy(filterBy){ + applyFilterBy(filterBy) { + const { location: { pathname } } = this.props; + this.setState({ isFilterDirty: true }); @@ -207,7 +208,7 @@ let PieceList = React.createClass({ // we have to redirect the user always to page one as it could be that there is no page two // for filtered pieces - this.history.pushState(null, this.props.location.pathname, {page: 1}); + this.history.push({ pathname, query: { page: 1 } }); }, applyOrderBy(orderBy) { diff --git a/js/components/register_piece.js b/js/components/register_piece.js index 8211e91e..b69ed7c2 100644 --- a/js/components/register_piece.js +++ b/js/components/register_piece.js @@ -66,7 +66,7 @@ let RegisterPiece = React.createClass( { }, handleSuccess(response){ - let notification = new GlobalNotificationModel(response.notification, 'success', 10000); + const notification = new GlobalNotificationModel(response.notification, 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); // once the user was able to register a piece successfully, we need to make sure to keep @@ -80,7 +80,7 @@ let RegisterPiece = React.createClass( { this.state.filterBy ); - this.history.pushState(null, `/pieces/${response.piece.id}`); + this.history.push(`/pieces/${response.piece.id}`); }, getSpecifyEditions() { diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js index e3d5ab93..271901bb 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js @@ -106,7 +106,7 @@ const PRRegisterPieceForm = React.createClass({ GlobalNotificationActions.appendGlobalNotification(notificationMessage); }); }) - .then(() => this.history.pushState(null, `/pieces/${this.state.piece.id}`)) + .then(() => this.history.push(`/pieces/${this.state.piece.id}`)) .catch((err) => { const errMessage = (getErrorNotificationMessage(err) || getLangText("Oops! We weren't able to send your submission.")) + getLangText(' Please contact support@ascribe.io'); diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_landing.js b/js/components/whitelabel/prize/portfolioreview/components/pr_landing.js index cdada68b..9e86ecf9 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_landing.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_landing.js @@ -14,7 +14,7 @@ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; import UserStore from '../../../../../stores/user_store'; import UserActions from '../../../../../actions/user_actions'; -import { mergeOptions } from '../../../../../utils/general_utils'; +import { mergeOptions, omitFromObject } from '../../../../../utils/general_utils'; import { getLangText } from '../../../../../utils/lang_utils'; @@ -34,15 +34,18 @@ const PRLanding = React.createClass({ componentDidMount() { const { location } = this.props; + UserStore.listen(this.onChange); - UserActions.fetchCurrentUser(); PrizeStore.listen(this.onChange); + + UserActions.fetchCurrentUser(); PrizeActions.fetchPrize(); - if(location && location.query && location.query.redirect) { - let queryCopy = JSON.parse(JSON.stringify(location.query)); - delete queryCopy.redirect; - window.setTimeout(() => this.history.replaceState(null, `/${location.query.redirect}`, queryCopy)); + if (location && location.query && location.query.redirect) { + window.setTimeout(() => this.history.replace({ + pathname: `/${location.query.redirect}`, + query: omitFromObject(location.query, ['redirect']) + })); } }, @@ -125,4 +128,4 @@ const PRLanding = React.createClass({ } }); -export default PRLanding; \ No newline at end of file +export default PRLanding; diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js b/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js index d7aa1aa8..99c9a401 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js @@ -39,7 +39,7 @@ const PRRegisterPiece = React.createClass({ if(currentUser && currentUser.email) { const submittedPieceId = getCookie(currentUser.email); if(submittedPieceId) { - this.history.pushState(null, `/pieces/${submittedPieceId}`); + this.history.push(`/pieces/${submittedPieceId}`); } } }, diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_routes/pr_proxy_handler.js b/js/components/whitelabel/prize/portfolioreview/components/pr_routes/pr_proxy_handler.js index 04ff8ce6..f81c4539 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_routes/pr_proxy_handler.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_routes/pr_proxy_handler.js @@ -17,7 +17,7 @@ export function AuthPrizeRoleRedirect({ to, when }) { .reduce((a, b) => a || b); if (exprToValidate) { - window.setTimeout(() => history.replaceState(null, to, query)); + window.setTimeout(() => history.replace({ query, pathname: to })); return true; } else { return false; diff --git a/js/components/whitelabel/prize/simple_prize/components/prize_landing.js b/js/components/whitelabel/prize/simple_prize/components/prize_landing.js index e26a05b5..82a21eab 100644 --- a/js/components/whitelabel/prize/simple_prize/components/prize_landing.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_landing.js @@ -46,7 +46,7 @@ let Landing = React.createClass({ // if user is already logged in, redirect him to piece list if(this.state.currentUser && this.state.currentUser.email) { // FIXME: hack to redirect out of the dispatch cycle - window.setTimeout(() => this.history.replaceState(null, '/collection'), 0); + window.setTimeout(() => this.history.replace('/collection'), 0); } }, diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js b/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js index d211d3e8..4a857e92 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js @@ -85,7 +85,7 @@ let CylandPieceContainer = React.createClass({ let notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState(null, '/collection'); + this.history.push('/collection'); }, render() { diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js index 21f8835a..0a8dadd1 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js @@ -49,7 +49,7 @@ let CylandLanding = React.createClass({ // if user is already logged in, redirect him to piece list if(this.state.currentUser && this.state.currentUser.email) { // FIXME: hack to redirect out of the dispatch cycle - window.setTimeout(() => this.history.replaceState(null, '/collection'), 0); + window.setTimeout(() => this.history.replace('/collection'), 0); } }, 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 fabed011..3f5f0116 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js @@ -129,7 +129,7 @@ let CylandRegisterPiece = React.createClass({ PieceActions.fetchOne(this.state.piece.id); - this.history.pushState(null, `/pieces/${this.state.piece.id}`); + this.history.push(`/pieces/${this.state.piece.id}`); }, // We need to increase the step to lock the forms that are already filled out diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js index 7975c1f3..4f6a88a1 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_contract_notifications.js @@ -119,7 +119,7 @@ let IkonotvContractNotifications = React.createClass({ NotificationActions.flushContractAgreementListNotifications(); NotificationActions.fetchContractAgreementListNotifications(); - this.history.pushState(null, '/collection'); + this.history.push('/collection'); }, handleDeny() { @@ -132,7 +132,7 @@ let IkonotvContractNotifications = React.createClass({ handleDenySuccess() { let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 5000); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState(null, '/collection'); + this.history.push('/collection'); }, getCopyrightAssociationForm(){ diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_detail/ikonotv_piece_container.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_detail/ikonotv_piece_container.js index df58b7c7..c9b5ca83 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_detail/ikonotv_piece_container.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_detail/ikonotv_piece_container.js @@ -94,7 +94,7 @@ let IkonotvPieceContainer = React.createClass({ let notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState(null, '/collection'); + this.history.push('/collection'); }, render() { 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 c40d779d..508f1d68 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js @@ -103,7 +103,7 @@ let IkonotvRegisterPiece = React.createClass({ PieceActions.updatePiece(response.piece); } if (!this.canSubmit()) { - this.history.pushState(null, '/collection'); + this.history.push('/collection'); } else { this.incrementStep(); @@ -134,7 +134,7 @@ let IkonotvRegisterPiece = React.createClass({ this.refreshPieceList(); PieceActions.fetchOne(this.state.piece.id); - this.history.pushState(null, `/pieces/${this.state.piece.id}`); + this.history.push(`/pieces/${this.state.piece.id}`); }, // We need to increase the step to lock the forms that are already filled out 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 387934f9..f879ede3 100644 --- a/js/components/whitelabel/wallet/components/market/market_register_piece.js +++ b/js/components/whitelabel/wallet/components/market/market_register_piece.js @@ -82,7 +82,7 @@ let MarketRegisterPiece = React.createClass({ handleAdditionalDataSuccess() { this.refreshPieceList(); - this.history.pushState(null, '/collection'); + this.history.push('/collection'); }, // We need to increase the step to lock the forms that are already filled out diff --git a/js/third_party/notifications.js b/js/third_party/notifications.js index 85379479..6820617e 100644 --- a/js/third_party/notifications.js +++ b/js/third_party/notifications.js @@ -22,14 +22,14 @@ class NotificationsHandler { return; } - let subdomain = getSubdomain(); + const subdomain = getSubdomain(); if (subdomain === 'ikonotv') { NotificationActions.fetchContractAgreementListNotifications().then( (res) => { if (res.notifications && res.notifications.length > 0) { this.loaded = true; console.log('Contractagreement notifications loaded'); - history.pushState(null, '/contract_notifications'); + history.push('/contract_notifications'); } } ); From 13e61fe35f501a978d8446fe908bd4bd52a2f8e2 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 5 Jan 2016 17:27:35 +0100 Subject: [PATCH 43/45] Add query support for history --- js/history.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/history.js b/js/history.js index 903f2b73..4e8c03c7 100644 --- a/js/history.js +++ b/js/history.js @@ -1,6 +1,7 @@ 'use strict'; import useBasename from 'history/lib/useBasename'; +import useQueries from 'history/lib/useQueries'; import createBrowserHistory from 'history/lib/createBrowserHistory'; import AppConstants from './constants/application_constants'; @@ -8,6 +9,6 @@ import AppConstants from './constants/application_constants'; // Remove the trailing slash if present let baseUrl = AppConstants.baseUrl.replace(/\/$/, ''); -export default useBasename(createBrowserHistory)({ +export default useBasename(useQueries(createBrowserHistory))({ basename: baseUrl }); From e76a36621e534eff6fd09c96c11cf94a5ec6fa92 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 5 Jan 2016 19:45:49 +0100 Subject: [PATCH 44/45] Use correct `prize_round` parameter --- .../prize/simple_prize/fetchers/prize_rating_fetcher.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js b/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js index 9ec82c30..e2f20d93 100644 --- a/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js +++ b/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js @@ -10,7 +10,7 @@ let PrizeRatingFetcher = { }; if (typeof round === 'number') { - params.round = round; + params['prize_round'] = round; } return requests.get('rating_average', params); @@ -22,7 +22,7 @@ let PrizeRatingFetcher = { }; if (typeof round === 'number') { - params.round = round; + params['prize_round'] = round; } return requests.get('rating', params); @@ -35,7 +35,7 @@ let PrizeRatingFetcher = { }; if (typeof round === 'number') { - body.round = round; + body['prize_round'] = round; } return requests.post('ratings', { body }); From 2acf3f4056a347ffa22147e384c693c63e66cb29 Mon Sep 17 00:00:00 2001 From: diminator Date: Wed, 6 Jan 2016 14:24:58 +0100 Subject: [PATCH 45/45] Added round to prizepiece upon new round: - reset is_selected - increment round for is_selected=True bug fix filter on is_selected --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index be5c1202..13bdfd46 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "devDependencies": { "babel-eslint": "^3.1.11", "babel-jest": "^5.2.0", + "gulp-sass": "^2.1.1", "jest-cli": "^0.4.0" }, "dependencies": {