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);
diff --git a/js/actions/edition_actions.js b/js/actions/edition_actions.js
index 1727deff..1feee0dd 100644
--- a/js/actions/edition_actions.js
+++ b/js/actions/edition_actions.js
@@ -7,11 +7,11 @@ class EditionActions {
constructor() {
this.generateActions(
'fetchEdition',
- 'successFetchEdition',
'successFetchCoa',
- 'flushEdition',
+ 'successFetchEdition',
'errorCoa',
- 'errorEdition'
+ 'errorEdition',
+ 'flushEdition'
);
}
}
diff --git a/js/actions/edition_list_actions.js b/js/actions/edition_list_actions.js
index 6f9881ee..a52e32b9 100644
--- a/js/actions/edition_list_actions.js
+++ b/js/actions/edition_list_actions.js
@@ -17,23 +17,31 @@ class EditionListActions {
);
}
- fetchEditionList(pieceId, page, pageSize, orderBy, orderAsc, filterBy) {
- if((!orderBy && typeof orderAsc === 'undefined') || !orderAsc) {
+ fetchEditionList({ pieceId, page, pageSize, orderBy, orderAsc, filterBy, maxEdition }) {
+ if ((!orderBy && typeof orderAsc === 'undefined') || !orderAsc) {
orderBy = 'edition_number';
orderAsc = true;
}
// Taken from: http://stackoverflow.com/a/519157/1263876
- if((typeof page === 'undefined' || !page) && (typeof pageSize === 'undefined' || !pageSize)) {
+ if ((typeof page === 'undefined' || !page) && (typeof pageSize === 'undefined' || !pageSize)) {
page = 1;
pageSize = 10;
}
+ let itemsToFetch = pageSize;
+ // If we only want to fetch up to a specified edition, fetch all pages up to it
+ // as one page and adjust afterwards
+ if (typeof maxEdition === 'number') {
+ itemsToFetch = Math.ceil(maxEdition / pageSize) * pageSize;
+ page = 1;
+ }
+
return Q.Promise((resolve, reject) => {
EditionListFetcher
- .fetch(pieceId, page, pageSize, orderBy, orderAsc, filterBy)
+ .fetch({ pieceId, page, itemsToFetch, orderBy, orderAsc, filterBy })
.then((res) => {
- if(res && !res.editions) {
+ if (res && !res.editions) {
throw new Error('Piece has no editions to fetch.');
}
@@ -44,8 +52,9 @@ class EditionListActions {
orderBy,
orderAsc,
filterBy,
- 'editionListOfPiece': res.editions,
- 'count': res.count
+ maxEdition,
+ count: res.count,
+ editionListOfPiece: res.editions
});
resolve(res);
})
@@ -54,7 +63,6 @@ class EditionListActions {
reject(err);
});
});
-
}
}
diff --git a/js/actions/facebook_actions.js b/js/actions/facebook_actions.js
new file mode 100644
index 00000000..2e784fba
--- /dev/null
+++ b/js/actions/facebook_actions.js
@@ -0,0 +1,14 @@
+'use strict';
+
+import { altThirdParty } from '../alt';
+
+
+class FacebookActions {
+ constructor() {
+ this.generateActions(
+ 'sdkReady'
+ );
+ }
+}
+
+export default altThirdParty.createActions(FacebookActions);
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/actions/piece_actions.js b/js/actions/piece_actions.js
index 9002e8c5..7ad3ae29 100644
--- a/js/actions/piece_actions.js
+++ b/js/actions/piece_actions.js
@@ -1,28 +1,19 @@
'use strict';
import { alt } from '../alt';
-import PieceFetcher from '../fetchers/piece_fetcher';
class PieceActions {
constructor() {
this.generateActions(
+ 'fetchPiece',
+ 'successFetchPiece',
+ 'errorPiece',
+ 'flushPiece',
'updatePiece',
- 'updateProperty',
- 'pieceFailed'
+ 'updateProperty'
);
}
-
- fetchOne(pieceId) {
- PieceFetcher.fetchOne(pieceId)
- .then((res) => {
- this.actions.updatePiece(res.piece);
- })
- .catch((err) => {
- console.logGlobal(err);
- this.actions.pieceFailed(err.json);
- });
- }
}
export default alt.createActions(PieceActions);
diff --git a/js/actions/piece_list_actions.js b/js/actions/piece_list_actions.js
index 7ef9cb59..c52ef5e2 100644
--- a/js/actions/piece_list_actions.js
+++ b/js/actions/piece_list_actions.js
@@ -15,7 +15,7 @@ class PieceListActions {
);
}
- fetchPieceList(page, pageSize, search, orderBy, orderAsc, filterBy) {
+ fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy }) {
// To prevent flickering on a pagination request,
// we overwrite the piecelist with an empty list before
// pieceListCount === -1 defines the loading state
@@ -34,7 +34,7 @@ class PieceListActions {
// afterwards, we can load the list
return Q.Promise((resolve, reject) => {
PieceListFetcher
- .fetch(page, pageSize, search, orderBy, orderAsc, filterBy)
+ .fetch({ page, pageSize, search, orderBy, orderAsc, filterBy })
.then((res) => {
this.actions.updatePieceList({
page,
diff --git a/js/actions/prize_list_actions.js b/js/actions/prize_list_actions.js
deleted file mode 100644
index da2f97df..00000000
--- a/js/actions/prize_list_actions.js
+++ /dev/null
@@ -1,34 +0,0 @@
-'use strict';
-
-import { alt } from '../alt';
-import Q from 'q';
-
-import PrizeListFetcher from '../fetchers/prize_list_fetcher';
-
-class PrizeListActions {
- constructor() {
- this.generateActions(
- 'updatePrizeList'
- );
- }
-
- fetchPrizeList() {
- return Q.Promise((resolve, reject) => {
- PrizeListFetcher
- .fetch()
- .then((res) => {
- this.actions.updatePrizeList({
- prizeList: res.prizes,
- prizeListCount: res.count
- });
- resolve(res);
- })
- .catch((err) => {
- console.logGlobal(err);
- reject(err);
- });
- });
- }
-}
-
-export default alt.createActions(PrizeListActions);
\ No newline at end of file
diff --git a/js/app.js b/js/app.js
index 520bedbd..4a8eb7ca 100644
--- a/js/app.js
+++ b/js/app.js
@@ -1,14 +1,13 @@
'use strict';
-require('babel/polyfill');
+import 'babel/polyfill';
+import 'classlist-polyfill';
import React from 'react';
import { Router, Redirect } from 'react-router';
import history from './history';
-/* eslint-disable */
import fetch from 'isomorphic-fetch';
-/* eslint-enable */
import ApiUrls from './constants/api_urls';
@@ -23,15 +22,13 @@ import { getSubdomain } from './utils/general_utils';
import EventActions from './actions/event_actions';
-/* eslint-disable */
// You can comment out the modules you don't need
-// import DebugHandler from './third_party/debug';
-import GoogleAnalyticsHandler from './third_party/ga';
-import RavenHandler from './third_party/raven';
-import IntercomHandler from './third_party/intercom';
-import NotificationsHandler from './third_party/notifications';
-import FacebookHandler from './third_party/facebook';
-/* eslint-enable */
+// import DebugHandler from './third_party/debug_handler';
+import FacebookHandler from './third_party/facebook_handler';
+import GoogleAnalyticsHandler from './third_party/ga_handler';
+import IntercomHandler from './third_party/intercom_handler';
+import NotificationsHandler from './third_party/notifications_handler';
+import RavenHandler from './third_party/raven_handler';
initLogging();
@@ -105,4 +102,3 @@ class AppGateway {
let ag = new AppGateway();
ag.start();
-
diff --git a/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js b/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js
index 8033f239..2bb8b2d0 100644
--- a/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js
+++ b/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js
@@ -19,9 +19,10 @@ import { getLangText } from '../../utils/lang_utils';
let AccordionListItemEditionWidget = React.createClass({
propTypes: {
- className: React.PropTypes.string,
piece: React.PropTypes.object.isRequired,
toggleCreateEditionsDialog: React.PropTypes.func.isRequired,
+
+ className: React.PropTypes.string,
onPollingSuccess: React.PropTypes.func
},
@@ -50,14 +51,15 @@ let AccordionListItemEditionWidget = React.createClass({
* Calls the store to either show or hide the editionListTable
*/
toggleTable() {
- let pieceId = this.props.piece.id;
- let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false;
-
- if(isEditionListOpen) {
+ const { piece: { id: pieceId } } = this.props;
+ const { filterBy, isEditionListOpenForPieceId } = this.state;
+ const isEditionListOpen = isEditionListOpenForPieceId[pieceId] ? isEditionListOpenForPieceId[pieceId].show : false;
+
+ if (isEditionListOpen) {
EditionListActions.toggleEditionList(pieceId);
} else {
EditionListActions.toggleEditionList(pieceId);
- EditionListActions.fetchEditionList(pieceId, null, null, null, null, this.state.filterBy);
+ EditionListActions.fetchEditionList({ pieceId, filterBy });
}
},
@@ -68,7 +70,7 @@ let AccordionListItemEditionWidget = React.createClass({
getGlyphicon() {
let pieceId = this.props.piece.id;
let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false;
-
+
if(isEditionListOpen) {
// this is the loading feedback for the editions
// button.
@@ -118,7 +120,7 @@ let AccordionListItemEditionWidget = React.createClass({
);
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..9f876388 100644
--- a/js/components/ascribe_accordion_list/accordion_list_item_piece.js
+++ b/js/components/ascribe_accordion_list/accordion_list_item_piece.js
@@ -12,8 +12,11 @@ import { getLangText } from '../../utils/lang_utils';
let AccordionListItemPiece = React.createClass({
propTypes: {
className: React.PropTypes.string,
- artistName: React.PropTypes.string,
- piece: React.PropTypes.object,
+ artistName: React.PropTypes.oneOfType([
+ React.PropTypes.string,
+ React.PropTypes.element
+ ]),
+ piece: React.PropTypes.object.isRequired,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
@@ -51,17 +54,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 +86,7 @@ let AccordionListItemPiece = React.createClass({
subsubheading={subsubheading}
buttons={buttons}
badge={badge}
- linkData={this.getLinkData()}
- >
+ linkData={this.getLinkData()}>
{children}
);
diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js b/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js
index 23cfb239..3b0bb02e 100644
--- a/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js
+++ b/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js
@@ -66,22 +66,34 @@ let AccordionListItemTableEditions = React.createClass({
},
filterSelectedEditions() {
- let selectedEditions = this.state.editionList[this.props.parentId]
- .filter((edition) => edition.selected);
- return selectedEditions;
+ return this.state
+ .editionList[this.props.parentId]
+ .filter((edition) => edition.selected);
},
loadFurtherEditions() {
+ const { parentId: pieceId } = this.props;
+ const { page, pageSize, orderBy, orderAsc, filterBy } = this.state.editionList[pieceId];
+
// trigger loading animation
this.setState({
showMoreLoading: true
});
- let editionList = this.state.editionList[this.props.parentId];
- EditionListActions.fetchEditionList(this.props.parentId, editionList.page + 1, editionList.pageSize,
- editionList.orderBy, editionList.orderAsc, editionList.filterBy);
+ EditionListActions.fetchEditionList({
+ pieceId,
+ pageSize,
+ orderBy,
+ orderAsc,
+ filterBy,
+ page: page + 1
+ });
},
render() {
+ const { className, parentId } = this.props;
+ const { editionList, isEditionListOpenForPieceId, showMoreLoading } = this.state;
+ const editionsForPiece = editionList[parentId];
+
let selectedEditionsCount = 0;
let allEditionsCount = 0;
let orderBy;
@@ -89,95 +101,97 @@ let AccordionListItemTableEditions = React.createClass({
let show = false;
let showExpandOption = false;
- let editionsForPiece = this.state.editionList[this.props.parentId];
- let loadingSpinner = ;
-
// here we need to check if all editions of a specific
// piece are already defined. Otherwise .length will throw an error and we'll not
// be notified about it.
- if(editionsForPiece) {
+ if (editionsForPiece) {
selectedEditionsCount = this.filterSelectedEditions().length;
allEditionsCount = editionsForPiece.length;
orderBy = editionsForPiece.orderBy;
orderAsc = editionsForPiece.orderAsc;
}
- if(this.props.parentId in this.state.isEditionListOpenForPieceId) {
- show = this.state.isEditionListOpenForPieceId[this.props.parentId].show;
+ if (parentId in isEditionListOpenForPieceId) {
+ show = isEditionListOpenForPieceId[parentId].show;
}
// if the number of editions in the array is equal to the maximum number of editions,
// then the "Show me more" dialog should be hidden from the user's view
- if(editionsForPiece && editionsForPiece.count > editionsForPiece.length) {
+ if (editionsForPiece && editionsForPiece.count > editionsForPiece.length) {
showExpandOption = true;
}
- let transition = new TransitionModel('editions', 'editionId', 'bitcoin_id', (e) => e.stopPropagation() );
+ const transition = new TransitionModel({
+ to: 'editions',
+ queryKey: 'editionId',
+ valueKey: 'bitcoin_id',
+ callback: (e) => e.stopPropagation()
+ });
- let columnList = [
- new ColumnModel(
- (item) => {
+ const columnList = [
+ new ColumnModel({
+ transformFn: (item) => {
return {
'editionId': item.id,
- 'pieceId': this.props.parentId,
+ 'pieceId': parentId,
'selectItem': this.selectItem,
'selected': item.selected
- }; },
- '',
+ };
+ },
+ displayElement: (
,
- TableItemCheckbox,
- 1,
- false
- ),
- new ColumnModel(
- (item) => {
+ numOfAllEditions={allEditionsCount}/>
+ ),
+ displayType: TableItemCheckbox,
+ rowWidth: 1
+ }),
+ new ColumnModel({
+ transition,
+ transformFn: (item) => {
return {
'content': item.edition_number + ' ' + getLangText('of') + ' ' + item.num_editions
- }; },
- 'edition_number',
- getLangText('Edition'),
- TableItemText,
- 1,
- false,
- transition
- ),
- new ColumnModel(
- (item) => {
+ };
+ },
+ columnName: 'edition_number',
+ displayElement: getLangText('Edition'),
+ displayType: TableItemText,
+ rowWidth: 1
+ }),
+ new ColumnModel({
+ transition,
+ transformFn: (item) => {
return {
'content': item.bitcoin_id
- }; },
- 'bitcoin_id',
- getLangText('ID'),
- TableItemText,
- 5,
- false,
- transition,
- 'hidden-xs visible-sm visible-md visible-lg'
- ),
- new ColumnModel(
- (item) => {
- let content = item.acl;
+ };
+ },
+ columnName: 'bitcoin_id',
+ displayElement: getLangText('ID'),
+ displayType: TableItemText,
+ rowWidth: 5,
+ className: 'hidden-xs visible-sm visible-md visible-lg'
+ }),
+ new ColumnModel({
+ transition,
+ transformFn: (item) => {
return {
- 'content': content,
+ 'content': item.acl,
'notifications': item.notifications
- }; },
- 'acl',
- getLangText('Actions'),
- TableItemAclFiltered,
- 4,
- false,
- transition
- )
+ };
+ },
+ columnName: 'acl',
+ displayElement: getLangText('Actions'),
+ displayType: TableItemAclFiltered,
+ rowWidth: 4
+ })
];
- if(show && editionsForPiece && editionsForPiece.length > 0) {
+ if (show && editionsForPiece && editionsForPiece.length) {
return (
-
+
{this.state.showMoreLoading ? loadingSpinner : } Show me more : null} />
+ message={show && showExpandOption ? (
+
+ {showMoreLoading ?
+ : }
+ {getLangText('Show me more')}
+
+ ) : null
+ } />
);
} else {
diff --git a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js
index a8cab166..f6712d37 100644
--- a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js
+++ b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js
@@ -88,11 +88,12 @@ let AccordionListItemWallet = React.createClass({
},
onPollingSuccess(pieceId) {
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, this.state.filterBy);
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
+
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
EditionListActions.toggleEditionList(pieceId);
- let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000);
+ const notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
diff --git a/js/components/ascribe_buttons/create_editions_button.js b/js/components/ascribe_buttons/create_editions_button.js
index 08fb76ce..c78b0d63 100644
--- a/js/components/ascribe_buttons/create_editions_button.js
+++ b/js/components/ascribe_buttons/create_editions_button.js
@@ -28,6 +28,12 @@ let CreateEditionsButton = React.createClass({
EditionListStore.listen(this.onChange);
},
+ componentDidUpdate() {
+ if(this.props.piece.num_editions === 0 && typeof this.state.pollingIntervalIndex === 'undefined') {
+ this.startPolling();
+ }
+ },
+
componentWillUnmount() {
EditionListStore.unlisten(this.onChange);
clearInterval(this.state.pollingIntervalIndex);
@@ -37,28 +43,24 @@ let CreateEditionsButton = React.createClass({
this.setState(state);
},
- componentDidUpdate() {
- if(this.props.piece.num_editions === 0 && typeof this.state.pollingIntervalIndex === 'undefined') {
- this.startPolling();
- }
- },
-
startPolling() {
// start polling until editions are defined
let pollingIntervalIndex = setInterval(() => {
// requests, will try to merge the filterBy parameter with other parameters (mergeOptions).
// Therefore it can't but null but instead has to be an empty object
- EditionListActions.fetchEditionList(this.props.piece.id, null, null, null, null, {})
- .then((res) => {
-
- clearInterval(this.state.pollingIntervalIndex);
- this.props.onPollingSuccess(this.props.piece.id, res.editions[0].num_editions);
-
- })
- .catch((err) => {
- /* Ignore and keep going */
- });
+ EditionListActions
+ .fetchEditionList({
+ pieceId: this.props.piece.id,
+ filterBy: {}
+ })
+ .then((res) => {
+ clearInterval(this.state.pollingIntervalIndex);
+ this.props.onPollingSuccess(this.props.piece.id, res.editions[0].num_editions);
+ })
+ .catch((err) => {
+ /* Ignore and keep going */
+ });
}, 5000);
this.setState({
diff --git a/js/components/ascribe_detail/detail_property.js b/js/components/ascribe_detail/detail_property.js
index 8b0f50b5..0191ffa9 100644
--- a/js/components/ascribe_detail/detail_property.js
+++ b/js/components/ascribe_detail/detail_property.js
@@ -1,9 +1,10 @@
'use strict';
import React from 'react';
+import classNames from 'classnames';
-let DetailProperty = React.createClass({
+const DetailProperty = React.createClass({
propTypes: {
label: React.PropTypes.string,
value: React.PropTypes.oneOfType([
@@ -12,6 +13,7 @@ let DetailProperty = React.createClass({
React.PropTypes.element
]),
separator: React.PropTypes.string,
+ className: React.PropTypes.string,
labelClassName: React.PropTypes.string,
valueClassName: React.PropTypes.string,
ellipsis: React.PropTypes.bool,
@@ -30,31 +32,23 @@ let DetailProperty = React.createClass({
},
render() {
- let styles = {};
- const { labelClassName,
- label,
- separator,
- valueClassName,
- children,
- value } = this.props;
-
- if(this.props.ellipsis) {
- styles = {
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis'
- };
- }
+ const {
+ children,
+ className,
+ ellipsis,
+ label,
+ labelClassName,
+ separator,
+ valueClassName,
+ value } = this.props;
return (
-
+
{label} {separator}
-
diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js
index 068b526c..803d73bb 100644
--- a/js/components/ascribe_detail/edition.js
+++ b/js/components/ascribe_detail/edition.js
@@ -1,7 +1,7 @@
'use strict';
import React from 'react';
-import { Link, History } from 'react-router';
+import { Link } from 'react-router';
import Moment from 'moment';
import Row from 'react-bootstrap/lib/Row';
@@ -16,7 +16,7 @@ import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph
import Form from './../ascribe_forms/form';
import Property from './../ascribe_forms/property';
-import EditionDetailProperty from './detail_property';
+import DetailProperty from './detail_property';
import LicenseDetail from './license_detail';
import FurtherDetails from './further_details';
@@ -44,8 +44,6 @@ let Edition = React.createClass({
loadEdition: React.PropTypes.func
},
- mixins: [History],
-
getDefaultProps() {
return {
furtherDetailsType: FurtherDetails
@@ -53,98 +51,103 @@ let Edition = React.createClass({
},
render() {
- let FurtherDetailsType = this.props.furtherDetailsType;
+ const {
+ actionPanelButtonListType,
+ coaError,
+ currentUser,
+ edition,
+ furtherDetailsType: FurtherDetailsType,
+ loadEdition } = this.props;
return (
-
+
+ content={edition}
+ currentUser={currentUser} />
-
+
-
- {this.props.edition.title}
-
-
+
+ {edition.title}
+
+
+ actionPanelButtonListType={actionPanelButtonListType}
+ edition={edition}
+ currentUser={currentUser}
+ handleSuccess={loadEdition}/>
+ show={edition.acl.acl_coa === true}>
+ coa={edition.coa}
+ coaError={coaError}
+ editionId={edition.bitcoin_id}/>
0}>
+ show={edition.ownership_history && edition.ownership_history.length > 0}>
+ history={edition.ownership_history} />
0}>
+ show={edition.consign_history && edition.consign_history.length > 0}>
+ history={edition.consign_history} />
0}>
+ show={edition.loan_history && edition.loan_history.length > 0}>
+ history={edition.loan_history} />
+ show={!!(currentUser.username || edition.acl.acl_edit || edition.public_note)}>
{return {'bitcoin_id': this.props.edition.bitcoin_id}; }}
+ id={() => {return {'bitcoin_id': edition.bitcoin_id}; }}
label={getLangText('Personal note (private)')}
- defaultValue={this.props.edition.private_note ? this.props.edition.private_note : null}
+ defaultValue={edition.private_note ? edition.private_note : null}
placeholder={getLangText('Enter your comments ...')}
editable={true}
successMessage={getLangText('Private note saved')}
url={ApiUrls.note_private_edition}
- currentUser={this.props.currentUser}/>
+ currentUser={currentUser}/>
{return {'bitcoin_id': this.props.edition.bitcoin_id}; }}
+ id={() => {return {'bitcoin_id': edition.bitcoin_id}; }}
label={getLangText('Personal note (public)')}
- defaultValue={this.props.edition.public_note ? this.props.edition.public_note : null}
+ defaultValue={edition.public_note ? edition.public_note : null}
placeholder={getLangText('Enter your comments ...')}
- editable={!!this.props.edition.acl.acl_edit}
- show={!!this.props.edition.public_note || !!this.props.edition.acl.acl_edit}
+ editable={!!edition.acl.acl_edit}
+ show={!!edition.public_note || !!edition.acl.acl_edit}
successMessage={getLangText('Public edition note saved')}
url={ApiUrls.note_public_edition}
- currentUser={this.props.currentUser}/>
+ currentUser={currentUser}/>
0
- || this.props.edition.other_data.length > 0}>
+ show={edition.acl.acl_edit ||
+ Object.keys(edition.extra_data).length > 0 ||
+ edition.other_data.length > 0}>
+ editable={edition.acl.acl_edit}
+ pieceId={edition.parent}
+ extraData={edition.extra_data}
+ otherData={edition.other_data}
+ handleSuccess={loadEdition} />
+ edition={edition} />
@@ -169,10 +172,10 @@ let EditionSummary = React.createClass({
let status = null;
if (this.props.edition.status.length > 0){
let statusStr = this.props.edition.status.join(', ').replace(/_/g, ' ');
- status =
;
+ status =
;
if (this.props.edition.pending_new_owner && this.props.edition.acl.acl_withdraw_transfer){
status = (
-
+
);
}
}
@@ -183,14 +186,14 @@ let EditionSummary = React.createClass({
let { actionPanelButtonListType, edition, currentUser } = this.props;
return (
-
-
-
@@ -201,14 +204,15 @@ let EditionSummary = React.createClass({
`AclInformation` would show up
*/}
1}>
-
+
-
+
@@ -220,66 +224,76 @@ let EditionSummary = React.createClass({
let CoaDetails = React.createClass({
propTypes: {
editionId: React.PropTypes.string,
- coa: React.PropTypes.object,
+ coa: React.PropTypes.oneOfType([
+ React.PropTypes.number,
+ React.PropTypes.string,
+ React.PropTypes.object
+ ]),
coaError: React.PropTypes.object
},
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}`));
+ 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() {
- if(this.props.coaError) {
- return (
-
-
{getLangText('There was an error generating your Certificate of Authenticity.')}
-
- {getLangText('Try to refresh the page. If this happens repeatedly, please ')}
- {getLangText('contact us')}.
-
-
- );
- }
- if(this.props.coa && this.props.coa.url_safe) {
- return (
-
-
-
-
-
+ 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 && 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')}
+
);
}
@@ -291,16 +305,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/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js
index 36a79e7c..71bf38fe 100644
--- a/js/components/ascribe_detail/edition_action_panel.js
+++ b/js/components/ascribe_detail/edition_action_panel.js
@@ -72,19 +72,20 @@ 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() {
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, this.state.filterBy);
- EditionListActions.refreshEditionList({pieceId: this.props.edition.parent});
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
+
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
+ EditionListActions.refreshEditionList({ pieceId: this.props.edition.parent });
},
- handleSuccess(response){
+ handleSuccess(response) {
this.refreshCollection();
this.props.handleSuccess();
if (response){
@@ -93,7 +94,7 @@ let EditionActionPanel = React.createClass({
}
},
- render(){
+ render() {
const {
actionPanelButtonListType: ActionPanelButtonListType,
edition,
diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js
index ee53f0e1..d0adadf0 100644
--- a/js/components/ascribe_detail/edition_container.js
+++ b/js/components/ascribe_detail/edition_container.js
@@ -35,7 +35,7 @@ let EditionContainer = React.createClass({
getInitialState() {
return mergeOptions(
- EditionStore.getState(),
+ EditionStore.getInitialState(),
UserStore.getState()
);
},
@@ -44,27 +44,23 @@ let EditionContainer = React.createClass({
EditionStore.listen(this.onChange);
UserStore.listen(this.onChange);
- // Every time we're entering the edition detail page,
- // just reset the edition that is saved in the edition store
- // as it will otherwise display wrong/old data once the user loads
- // the edition detail a second time
- EditionActions.flushEdition();
- EditionActions.fetchEdition(this.props.params.editionId);
-
+ this.loadEdition();
UserActions.fetchCurrentUser();
},
// 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.editionId !== nextProps.params.editionId) {
- EditionActions.fetchEdition(this.props.params.editionId);
+ if (this.props.params.editionId !== nextProps.params.editionId) {
+ EditionActions.flushEdition();
+ this.loadEdition(nextProps.params.editionId);
}
},
componentDidUpdate() {
- const { editionMeta } = this.state;
- if(editionMeta.err && editionMeta.err.json && editionMeta.err.json.status === 404) {
+ const { err: editionErr } = this.state.editionMeta;
+
+ if (editionErr && editionErr.json && editionErr.json.status === 404) {
this.throws(new ResourceNotFoundError(getLangText("Oops, the edition you're looking for doesn't exist.")));
}
},
@@ -81,18 +77,22 @@ let EditionContainer = React.createClass({
if(state && state.edition && state.edition.digital_work) {
let isEncoding = state.edition.digital_work.isEncoding;
if (state.edition.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) {
- let timerId = window.setInterval(() => EditionActions.fetchOne(this.props.params.editionId), 10000);
+ let timerId = window.setInterval(() => EditionActions.fetchEdition(this.props.params.editionId), 10000);
this.setState({timerId: timerId});
}
}
},
+ loadEdition(editionId = this.props.params.editionId) {
+ EditionActions.fetchEdition(editionId);
+ },
+
render() {
const { edition, currentUser, coaMeta } = this.state;
const { actionPanelButtonListType, furtherDetailsType } = this.props;
- if (Object.keys(edition).length && edition.id) {
- setDocumentTitle([edition.artist_name, edition.title].join(', '));
+ if (edition.id) {
+ setDocumentTitle(`${edition.artist_name}, ${edition.title}`);
return (
EditionActions.fetchEdition(this.props.params.editionId)} />
+ loadEdition={this.loadEdition} />
);
} else {
return (
diff --git a/js/components/ascribe_detail/further_details.js b/js/components/ascribe_detail/further_details.js
index c178fb93..54e696c9 100644
--- a/js/components/ascribe_detail/further_details.js
+++ b/js/components/ascribe_detail/further_details.js
@@ -5,25 +5,27 @@ import React from 'react';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
-import Form from './../ascribe_forms/form';
-
-import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
-
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import FurtherDetailsFileuploader from './further_details_fileuploader';
+import Form from './../ascribe_forms/form';
+import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
+
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
+import { getLangText } from '../../utils/lang_utils';
+
let FurtherDetails = React.createClass({
propTypes: {
+ pieceId: React.PropTypes.number.isRequired,
+
editable: React.PropTypes.bool,
- pieceId: React.PropTypes.number,
extraData: React.PropTypes.object,
+ handleSuccess: React.PropTypes.func,
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
- handleSuccess: React.PropTypes.func
},
getInitialState() {
@@ -32,13 +34,18 @@ let FurtherDetails = React.createClass({
};
},
- showNotification(){
- this.props.handleSuccess();
- let notification = new GlobalNotificationModel('Details updated', 'success');
+ showNotification() {
+ const { handleSuccess } = this.props;
+
+ if (typeof handleSucess === 'function') {
+ handleSuccess();
+ }
+
+ const notification = new GlobalNotificationModel(getLangText('Details updated'), 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
},
- submitFile(file){
+ submitFile(file) {
this.setState({
otherDataKey: file.key
});
@@ -60,8 +67,7 @@ let FurtherDetails = React.createClass({
handleSuccess={this.showNotification}
editable={this.props.editable}
pieceId={this.props.pieceId}
- extraData={this.props.extraData}
- />
+ extraData={this.props.extraData} />
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/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js
index c6845a44..e4270132 100644
--- a/js/components/ascribe_detail/media_container.js
+++ b/js/components/ascribe_detail/media_container.js
@@ -14,10 +14,6 @@ import CollapsibleButton from './../ascribe_collapsible/collapsible_button';
import AclProxy from '../acl_proxy';
-import UserActions from '../../actions/user_actions';
-import UserStore from '../../stores/user_store';
-
-import { mergeOptions } from '../../utils/general_utils.js';
import { getLangText } from '../../utils/lang_utils.js';
const EMBED_IFRAME_HEIGHT = {
@@ -28,25 +24,22 @@ const EMBED_IFRAME_HEIGHT = {
let MediaContainer = React.createClass({
propTypes: {
content: React.PropTypes.object,
+ currentUser: React.PropTypes.object,
refreshObject: React.PropTypes.func
},
getInitialState() {
- return mergeOptions(
- UserStore.getState(),
- {
- timerId: null
- });
+ return {
+ timerId: null
+ };
},
componentDidMount() {
- UserStore.listen(this.onChange);
- UserActions.fetchCurrentUser();
-
if (!this.props.content.digital_work) {
return;
}
- let isEncoding = this.props.content.digital_work.isEncoding;
+
+ const isEncoding = this.props.content.digital_work.isEncoding;
if (this.props.content.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) {
let timerId = window.setInterval(this.props.refreshObject, 10000);
this.setState({timerId: timerId});
@@ -60,22 +53,16 @@ let MediaContainer = React.createClass({
},
componentWillUnmount() {
- UserStore.unlisten(this.onChange);
-
window.clearInterval(this.state.timerId);
},
- onChange(state) {
- this.setState(state);
- },
-
render() {
- const { content } = this.props;
+ const { content, currentUser } = this.props;
// Pieces and editions are joined to the user by a foreign key in the database, so
// the information in content will be updated if a user updates their username.
// We also force uniqueness of usernames, so this check is safe to dtermine if the
// content was registered by the current user.
- const didUserRegisterContent = this.state.currentUser && (this.state.currentUser.username === content.user_registered);
+ const didUserRegisterContent = currentUser && (currentUser.username === content.user_registered);
let thumbnail = content.thumbnail.thumbnail_sizes && content.thumbnail.thumbnail_sizes['600x600'] ?
content.thumbnail.thumbnail_sizes['600x600'] : content.thumbnail.url_safe;
@@ -94,8 +81,11 @@ let MediaContainer = React.createClass({
embed = (
- Embed
+
}
panel={
@@ -114,7 +104,7 @@ let MediaContainer = React.createClass({
url={content.digital_work.url}
extraData={extraData}
encodingStatus={content.digital_work.isEncoding} />
-
+
-
);
} else {
- return (
);
+ return (
);
}
},
handlePollingSuccess(pieceId, numEditions) {
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
// we need to refresh the num_editions property of the actual piece we're looking at
PieceActions.updateProperty({
@@ -189,27 +196,26 @@ let PieceContainer = React.createClass({
// btw.: It's not sufficient to just set num_editions to numEditions, since a single accordion
// list item also uses the firstEdition property which we can only get from the server in that case.
// Therefore we need to at least refetch the changed piece from the server or on our case simply all
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, this.state.filterBy);
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
- let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000);
+ const notification = new GlobalNotificationModel(getLangText('Editions successfully created'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
getId() {
- return {'id': this.state.piece.id};
+ return { 'id': this.state.piece.id };
},
getActions() {
const { piece, currentUser } = this.state;
- if (piece && piece.notifications && piece.notifications.length > 0) {
+ if (piece.notifications && piece.notifications.length > 0) {
return (
);
+ notifications={piece.notifications} />);
} else {
return (
+
+ piece={piece} />
+ aclObject={piece.acl} />
@@ -247,76 +255,76 @@ let PieceContainer = React.createClass({
},
render() {
- if (this.state.piece && this.state.piece.id) {
- let FurtherDetailsType = this.props.furtherDetailsType;
- setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
+ const { furtherDetailsType: FurtherDetailsType } = this.props;
+ const { currentUser, piece } = this.state;
+
+ if (piece.id) {
+ setDocumentTitle(`${piece.artist_name}, ${piece.title}`);
return (
-
- {this.state.piece.title}
-
-
- {this.state.piece.num_editions > 0 ? : null}
+
+ {piece.title}
+
+
+ {piece.num_editions > 0 ? : null}
}
subheader={
-
-
-
+
+
+
}
buttons={this.getActions()}>
{this.getCreateEditionsDialog()}
0}>
+ show={piece.loan_history && piece.loan_history.length > 0}>
+ history={piece.loan_history} />
+ show={!!(currentUser.username || piece.acl.acl_edit || piece.public_note)}>
+ currentUser={currentUser} />
+ currentUser={currentUser} />
0
- || this.state.piece.other_data.length > 0}
+ show={piece.acl.acl_edit
+ || Object.keys(piece.extra_data).length > 0
+ || piece.other_data.length > 0}
defaultExpanded={true}>
diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js
index d4002e85..148b2e2d 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});
},
@@ -208,7 +208,7 @@ let Form = React.createClass({
if (this.state.submitted){
return this.props.spinner;
}
- if (this.props.buttons){
+ if ('buttons' in this.props) {
return this.props.buttons;
}
let buttons = null;
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 a204fb87..861806ae 100644
--- a/js/components/ascribe_forms/form_loan.js
+++ b/js/components/ascribe_forms/form_loan.js
@@ -25,6 +25,7 @@ import { mergeOptions } from '../../utils/general_utils';
let LoanForm = React.createClass({
propTypes: {
loanHeading: React.PropTypes.string,
+ buttons: React.PropTypes.element,
email: React.PropTypes.string,
gallery: React.PropTypes.string,
startDate: React.PropTypes.object,
@@ -60,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);
},
@@ -80,7 +89,11 @@ let LoanForm = React.createClass({
},
getButtons() {
- if(this.props.loanHeading) {
+ const { buttons, loanHeading } = this.props;
+
+ if (buttons) {
+ return buttons;
+ } else if (loanHeading) {
return (
+ disabled={!editable}>
+ name={name}
+ label={title}>
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/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_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_media/media_player.js b/js/components/ascribe_media/media_player.js
index 2a23f2ed..1552b44c 100644
--- a/js/components/ascribe_media/media_player.js
+++ b/js/components/ascribe_media/media_player.js
@@ -184,7 +184,7 @@ let Video = React.createClass({
);
} else {
return (
-
+
);
}
}
diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js
index 3f3b4af4..511e7f8c 100644
--- a/js/components/ascribe_modal/modal_wrapper.js
+++ b/js/components/ascribe_modal/modal_wrapper.js
@@ -1,19 +1,20 @@
'use strict';
import React from 'react';
-import ReactAddons from 'react/addons';
import Modal from 'react-bootstrap/lib/Modal';
let ModalWrapper = React.createClass({
propTypes: {
- trigger: React.PropTypes.element,
title: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element,
React.PropTypes.string
]).isRequired,
- handleSuccess: React.PropTypes.func.isRequired,
+
+ handleCancel: React.PropTypes.func,
+ handleSuccess: React.PropTypes.func,
+ trigger: React.PropTypes.element,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
@@ -38,15 +39,32 @@ let ModalWrapper = React.createClass({
});
},
+ handleCancel() {
+ if (typeof this.props.handleCancel === 'function') {
+ this.props.handleCancel();
+ }
+
+ this.hide();
+ },
+
handleSuccess(response) {
- this.props.handleSuccess(response);
+ if (typeof this.props.handleSuccess === 'function') {
+ this.props.handleSuccess(response);
+ }
+
this.hide();
},
renderChildren() {
- return ReactAddons.Children.map(this.props.children, (child) => {
- return ReactAddons.addons.cloneWithProps(child, {
- handleSuccess: this.handleSuccess
+ return React.Children.map(this.props.children, (child) => {
+ return React.cloneElement(child, {
+ handleSuccess: (response) => {
+ if (typeof child.props.handleSuccess === 'function') {
+ child.props.handleSuccess(response);
+ }
+
+ this.handleSuccess(response);
+ }
});
});
},
@@ -54,14 +72,23 @@ let ModalWrapper = React.createClass({
render() {
const { trigger, title } = this.props;
- // If the trigger component exists, we add the ModalWrapper's show() as its onClick method.
+ // If the trigger component exists, we add the ModalWrapper's show() to its onClick method.
// The trigger component should, in most cases, be a button.
- const clonedTrigger = React.isValidElement(trigger) ? React.cloneElement(trigger, {onClick: this.show})
- : null;
+ const clonedTrigger = React.isValidElement(trigger) ?
+ React.cloneElement(trigger, {
+ onClick: (...params) => {
+ if (typeof trigger.props.onClick === 'function') {
+ trigger.props.onClick(...params);
+ }
+
+ this.show();
+ }
+ }) : null;
+
return (
{clonedTrigger}
-
+
{title}
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,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 && 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((paramItem) => {
+ let itemLabel;
+ let param;
- // 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 paramItem !== 'string') {
+ param = paramItem.key;
+ itemLabel = paramItem.label;
+ } else {
+ param = paramItem;
+ itemLabel = paramItem.split('acl_')[1].replace(/_/g, ' ');
+ }
- return (
-
-
-
- {getLangText(label)}
-
-
-
-
- );
- })}
-
- );
- })}
-
- );
+ return (
+
+
+
+ {getLangText(itemLabel)}
+
+
+
+
+ );
+ })}
+
+ );
+ })}
+
+ );
+ } else {
+ return null;
+ }
}
});
diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js
index c38144b0..5257cc07 100644
--- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js
+++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js
@@ -37,7 +37,7 @@ let PieceListToolbarOrderWidget = React.createClass({
isOrderActive() {
// We're hiding the star in that complicated matter so that,
// the surrounding button is not resized up on appearance
- if(this.props.orderBy.length > 0) {
+ if (this.props.orderBy && this.props.orderBy.length) {
return { visibility: 'visible'};
} else {
return { visibility: 'hidden' };
@@ -51,18 +51,18 @@ let PieceListToolbarOrderWidget = React.createClass({
·
);
- return (
-
-
- {getLangText('Sort by')}:
-
- {this.props.orderParams.map((param) => {
- return (
-
+ if (this.props.orderParams && this.props.orderParams.length) {
+ return (
+
+
+ {getLangText('Sort by')}:
+
+ {this.props.orderParams.map((param) => {
+ return (
-1} />
-
- );
- })}
-
- );
+ );
+ })}
+
+ );
+ } else {
+ return null;
+ }
}
});
-export default PieceListToolbarOrderWidget;
\ No newline at end of file
+export default PieceListToolbarOrderWidget;
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_settings/webhook_settings.js b/js/components/ascribe_settings/webhook_settings.js
index 9deecbcd..4928c408 100644
--- a/js/components/ascribe_settings/webhook_settings.js
+++ b/js/components/ascribe_settings/webhook_settings.js
@@ -34,7 +34,6 @@ let WebhookSettings = React.createClass({
componentDidMount() {
WebhookStore.listen(this.onChange);
WebhookActions.fetchWebhooks();
- WebhookActions.fetchWebhookEvents();
},
componentWillUnmount() {
@@ -49,7 +48,7 @@ let WebhookSettings = React.createClass({
return (event) => {
WebhookActions.removeWebhook(webhookId);
- let notification = new GlobalNotificationModel(getLangText('Webhook deleted'), 'success', 2000);
+ const notification = new GlobalNotificationModel(getLangText('Webhook deleted'), 'success', 2000);
GlobalNotificationActions.appendGlobalNotification(notification);
};
},
@@ -57,16 +56,16 @@ let WebhookSettings = React.createClass({
handleCreateSuccess() {
this.refs.webhookCreateForm.reset();
WebhookActions.fetchWebhooks(true);
- let notification = new GlobalNotificationModel(getLangText('Webhook successfully created'), 'success', 5000);
+
+ const notification = new GlobalNotificationModel(getLangText('Webhook successfully created'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
- getWebhooks(){
- let content =
;
-
+ getWebhooks() {
if (this.state.webhooks) {
- content = this.state.webhooks.map(function(webhook, i) {
+ return this.state.webhooks.map(function(webhook, i) {
const event = webhook.event.split('.')[0];
+
return (
- }/>
+ } />
);
}, this);
+ } else {
+ return (
+
+ );
}
- return content;
},
getEvents() {
@@ -110,18 +112,18 @@ let WebhookSettings = React.createClass({
);
})}
);
+ } else {
+ return null;
}
- return null;
},
-
render() {
return (
-
+
@@ -162,4 +163,4 @@ let WebhookSettings = React.createClass({
}
});
-export default WebhookSettings;
\ No newline at end of file
+export default WebhookSettings;
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/ascribe_social_share/facebook_share_button.js b/js/components/ascribe_social_share/facebook_share_button.js
index aa0b6691..d5fbf699 100644
--- a/js/components/ascribe_social_share/facebook_share_button.js
+++ b/js/components/ascribe_social_share/facebook_share_button.js
@@ -2,6 +2,8 @@
import React from 'react';
+import FacebookHandler from '../../third_party/facebook_handler';
+
import AppConstants from '../../constants/application_constants';
import { InjectInHeadUtils } from '../../utils/inject_utils';
@@ -17,24 +19,40 @@ let FacebookShareButton = React.createClass({
};
},
- componentDidMount() {
- /**
- * Ideally we would only use FB.XFBML.parse() on the component that we're
- * mounting, but doing this when we first load the FB sdk causes unpredictable behaviour.
- * The button sometimes doesn't get initialized, likely because FB hasn't properly
- * been initialized yet.
- *
- * To circumvent this, we always have the sdk parse the entire DOM on the initial load
- * (see FacebookHandler) and then use FB.XFBML.parse() on the mounting component later.
- */
-
- InjectInHeadUtils
- .inject(AppConstants.facebook.sdkUrl)
- .then(() => { FB.XFBML.parse(this.refs.fbShareButton.getDOMNode().parentElement) });
+ getInitialState() {
+ return FacebookHandler.getState();
},
- shouldComponentUpdate(nextProps) {
- return this.props.type !== nextProps.type;
+ componentDidMount() {
+ FacebookHandler.listen(this.onChange);
+
+ this.loadFacebook();
+ },
+
+ shouldComponentUpdate(nextProps, nextState) {
+ // Don't update if the props haven't changed or the FB SDK loading status is still the same
+ return this.props.type !== nextProps.type || nextState.loaded !== this.state.loaded;
+ },
+
+ componentDidUpdate() {
+ // If the component changes, we need to reparse the share button's XFBML.
+ // To prevent cases where the Facebook SDK hasn't been loaded yet at this stage,
+ // let's make sure that it's injected before trying to reparse.
+ this.loadFacebook();
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ loadFacebook() {
+ InjectInHeadUtils
+ .inject(AppConstants.facebook.sdkUrl)
+ .then(() => {
+ if (this.state.loaded) {
+ FB.XFBML.parse(this.refs.fbShareButton.getDOMNode().parent)
+ }
+ });
},
render() {
diff --git a/js/components/ascribe_table/models/table_models.js b/js/components/ascribe_table/models/table_models.js
index b675d14e..99cf7e64 100644
--- a/js/components/ascribe_table/models/table_models.js
+++ b/js/components/ascribe_table/models/table_models.js
@@ -2,15 +2,15 @@
export class ColumnModel {
// ToDo: Add validation for all passed-in parameters
- constructor(transformFn, columnName, displayName, displayType, rowWidth, canBeOrdered, transition, className) {
+ constructor({ transformFn, columnName = '', displayElement, displayType, rowWidth, canBeOrdered, transition, className = '' }) {
this.transformFn = transformFn;
this.columnName = columnName;
- this.displayName = displayName;
+ this.displayElement = displayElement;
this.displayType = displayType;
this.rowWidth = rowWidth;
this.canBeOrdered = canBeOrdered;
this.transition = transition;
- this.className = className ? className : '';
+ this.className = className;
}
}
@@ -28,7 +28,7 @@ export class ColumnModel {
* our selfes, using this TransitionModel.
*/
export class TransitionModel {
- constructor(to, queryKey, valueKey, callback) {
+ constructor({ to, queryKey, valueKey, callback }) {
this.to = to;
this.queryKey = queryKey;
this.valueKey = valueKey;
@@ -38,4 +38,4 @@ export class TransitionModel {
toReactRouterLink(queryValue) {
return '/' + this.to + '/' + queryValue;
}
-}
\ No newline at end of file
+}
diff --git a/js/components/ascribe_table/table_header.js b/js/components/ascribe_table/table_header.js
index f807627b..78a31681 100644
--- a/js/components/ascribe_table/table_header.js
+++ b/js/components/ascribe_table/table_header.js
@@ -1,5 +1,5 @@
-'use strict';
+'use strict';
import React from 'react';
import TableHeaderItem from './table_header_item';
@@ -29,7 +29,7 @@ let TableHeader = React.createClass({
- {this.props.displayName}
+ {displayElement}
);
} else {
return (
- {this.props.displayName}
+ {displayElement}
|
);
}
} else {
return (
-
+ |
- {this.props.displayName}
+ {displayElement}
|
);
diff --git a/js/components/ascribe_table/table_item_acl_filtered.js b/js/components/ascribe_table/table_item_acl_filtered.js
index 22a28130..9ca91bcf 100644
--- a/js/components/ascribe_table/table_item_acl_filtered.js
+++ b/js/components/ascribe_table/table_item_acl_filtered.js
@@ -3,15 +3,15 @@
import React from 'react';
-let TableItemAclFiltered = React.createClass({
+const TableItemAclFiltered = React.createClass({
propTypes: {
content: React.PropTypes.object,
- notifications: React.PropTypes.string
+ notifications: React.PropTypes.array
},
render() {
- var availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete'];
- if (this.props.notifications && this.props.notifications.length > 0){
+ const availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete'];
+ if (this.props.notifications && this.props.notifications.length) {
return (
{this.props.notifications[0].action_str}
@@ -19,15 +19,14 @@ let TableItemAclFiltered = React.createClass({
);
}
- let filteredAcls = Object.keys(this.props.content).filter((key) => {
- return availableAcls.indexOf(key) > -1 && this.props.content[key];
- });
-
- filteredAcls = filteredAcls.map((acl) => acl.split('acl_')[1]);
+ const filteredAcls = Object.keys(this.props.content)
+ .filter((key) => availableAcls.indexOf(key) > -1 && this.props.content[key])
+ .map((acl) => acl.split('acl_')[1])
+ .join('/');
return (
- {filteredAcls.join('/')}
+ {filteredAcls}
);
}
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/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js
index eb211504..feb0479d 100644
--- a/js/components/ascribe_uploader/react_s3_fine_uploader.js
+++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js
@@ -36,20 +36,15 @@ const ReactS3FineUploader = React.createClass({
keyRoutine: shape({
url: string,
fileClass: string,
- pieceId: oneOfType([
- string,
- number
- ])
+ pieceId: number
}),
createBlobRoutine: shape({
url: string,
- pieceId: oneOfType([
- string,
- number
- ])
+ pieceId: number
}),
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 +518,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/footer.js b/js/components/footer.js
index 31145d4b..f2e35dfc 100644
--- a/js/components/footer.js
+++ b/js/components/footer.js
@@ -7,7 +7,7 @@ import { getLangText } from '../utils/lang_utils';
let Footer = React.createClass({
render() {
return (
-
+
api |
diff --git a/js/components/global_action.js b/js/components/global_action.js
deleted file mode 100644
index 80df0c75..00000000
--- a/js/components/global_action.js
+++ /dev/null
@@ -1,43 +0,0 @@
-'use strict';
-
-import React from 'react';
-
-let GlobalAction = React.createClass({
- propTypes: {
- requestActions: React.PropTypes.object
- },
-
- render() {
- let pieceActions = null;
- if (this.props.requestActions && this.props.requestActions.pieces){
- pieceActions = this.props.requestActions.pieces.map((item) => {
- return (
-
- {item}
-
);
- });
- }
- let editionActions = null;
- if (this.props.requestActions && this.props.requestActions.editions){
- editionActions = Object.keys(this.props.requestActions.editions).map((pieceId) => {
- return this.props.requestActions.editions[pieceId].map((item) => {
- return (
-
- {item}
-
);
- });
- });
- }
-
- if (pieceActions || editionActions) {
- return (
-
- {pieceActions}
- {editionActions}
-
);
- }
- return null;
- }
-});
-
-export default GlobalAction;
\ No newline at end of file
diff --git a/js/components/header.js b/js/components/header.js
index 797684ec..c16cba86 100644
--- a/js/components/header.js
+++ b/js/components/header.js
@@ -219,10 +219,11 @@ let Header = React.createClass({
return (
+ className="hidden-print">
+
+
+
);
}
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 9424117c..666d1b54 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,9 +115,13 @@ let PieceList = React.createClass({
},
componentDidUpdate() {
- if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) {
+ 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);
}
},
@@ -169,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
});
@@ -190,31 +196,32 @@ let PieceList = React.createClass({
this.state.pieceList
.forEach((piece) => {
// but only if they're actually open
- if(this.state.isEditionListOpenForPieceId[piece.id].show) {
+ const isEditionListOpenForPiece = this.state.isEditionListOpenForPieceId[piece.id];
+
+ if (isEditionListOpenForPiece && isEditionListOpenForPiece.show) {
EditionListActions.refreshEditionList({
pieceId: piece.id,
filterBy
});
}
-
});
});
// 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) {
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- orderBy, this.state.orderAsc, this.state.filterBy);
+ const { filterBy, orderAsc, page, pageSize, search } = this.state;
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
},
loadPieceList({ page, filterBy = this.state.filterBy, search = this.state.search }) {
+ const { orderAsc, pageSize } = this.state;
const orderBy = this.state.orderBy || this.props.orderBy;
- return PieceListActions.fetchPieceList(page, this.state.pageSize, search,
- orderBy, this.state.orderAsc, filterBy);
+ return PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
},
fetchSelectedPieceEditionList() {
@@ -240,8 +247,9 @@ let PieceList = React.createClass({
},
handleAclSuccess() {
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, this.state.filterBy);
+ const { filterBy, orderBy, orderAsc, page, pageSize, search } = this.state;
+
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
this.fetchSelectedPieceEditionList()
.forEach((pieceId) => {
diff --git a/js/components/register_piece.js b/js/components/register_piece.js
index 50da9b02..3f268760 100644
--- a/js/components/register_piece.js
+++ b/js/components/register_piece.js
@@ -66,25 +66,20 @@ let RegisterPiece = React.createClass( {
},
handleSuccess(response) {
- let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
+
+ 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
// the piece list up to date
- PieceListActions.fetchPieceList(
- this.state.page,
- this.state.pageSize,
- this.state.searchTerm,
- this.state.orderBy,
- this.state.orderAsc,
- this.state.filterBy
- );
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
- this.history.pushState(null, `/pieces/${response.piece.id}`);
+ this.history.push(`/pieces/${response.piece.id}`);
},
getSpecifyEditions() {
- if(this.state.whitelabel && this.state.whitelabel.acl_create_editions || Object.keys(this.state.whitelabel).length === 0) {
+ if (this.state.whitelabel && this.state.whitelabel.acl_create_editions || Object.keys(this.state.whitelabel).length === 0) {
return (
this.history.pushState(null, `/pieces/${this.state.piece.id}`))
+ .then(() => this.history.push(`/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
+ });
});
},
@@ -118,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() {
@@ -139,7 +145,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 +157,14 @@ const PRRegisterPieceForm = React.createClass({
};
},
+ handleOptionalFileValidationFailed(uploaderKey) {
+ return () => {
+ this.setState({
+ [uploaderKey]: true
+ });
+ };
+ },
+
getSubmitButton() {
const { digitalWorkKeyReady,
thumbnailKeyReady,
@@ -183,7 +197,7 @@ const PRRegisterPieceForm = React.createClass({
return (
- }
+ }
subheader={
+ piece={piece}
+ currentUser={currentUser}
+ selectedPrizeActionButton={selectedPrizeActionButton} />
}>
-
+
);
} else {
@@ -186,16 +187,15 @@ let PieceContainer = React.createClass({
let NavigationHeader = React.createClass({
propTypes: {
- piece: React.PropTypes.object,
- currentUser: React.PropTypes.object
+ currentUser: React.PropTypes.object.isRequired,
+ piece: React.PropTypes.object.isRequired
},
render() {
const { currentUser, piece } = this.props;
- if (currentUser && currentUser.email && currentUser.is_judge && currentUser.is_jury &&
- !currentUser.is_admin && piece && piece.navigation) {
- let nav = piece.navigation;
+ if (currentUser.email && currentUser.is_judge && currentUser.is_jury && !currentUser.is_admin && piece.navigation) {
+ const nav = piece.navigation;
return (
@@ -215,41 +215,49 @@ let NavigationHeader = React.createClass({
);
+ } else {
+ return null;
}
- return null;
}
});
let PrizePieceRatings = React.createClass({
propTypes: {
- loadPiece: React.PropTypes.func,
- piece: React.PropTypes.object,
- currentUser: React.PropTypes.object
+ currentUser: React.PropTypes.object.isRequired,
+ loadPiece: React.PropTypes.func.isRequired,
+ piece: React.PropTypes.object.isRequired,
+
+ selectedPrizeActionButton: React.PropTypes.func
},
getInitialState() {
return mergeOptions(
PieceListStore.getState(),
- PrizeRatingStore.getState()
+ PrizeStore.getState(),
+ PrizeRatingStore.getInitialState()
);
},
componentDidMount() {
PrizeRatingStore.listen(this.onChange);
- PrizeRatingActions.fetchOne(this.props.piece.id);
- PrizeRatingActions.fetchAverage(this.props.piece.id);
+ PrizeStore.listen(this.onChange);
PieceListStore.listen(this.onChange);
+
+ PrizeActions.fetchPrize();
+ this.fetchRatingsIfAuthorized();
+ },
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.currentUser.email !== this.props.currentUser.email) {
+ this.fetchRatingsIfAuthorized();
+ }
},
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);
+ PrizeStore.unlisten(this.onChange);
+ PrizeRatingStore.unlisten(this.onChange);
},
// The StarRating component does not have a property that lets us set
@@ -257,7 +265,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.fetchRatingsIfAuthorized(state);
+ }
+
this.setState(state);
+
if (this.refs.rating) {
this.refs.rating.state.ratingCache = {
pos: this.refs.rating.state.pos,
@@ -268,74 +281,64 @@ let PrizePieceRatings = React.createClass({
}
},
+ fetchRatingsIfAuthorized(state = this.state) {
+ const {
+ currentUser: {
+ is_admin: isAdmin,
+ is_judge: isJudge,
+ is_jury: isJury
+ },
+ piece: { id: pieceId } } = this.props;
+
+ if (state.prize && 'active_round' in state.prize && (isAdmin || isJudge || isJury)) {
+ PrizeRatingActions.fetchOne(pieceId, state.prize.active_round);
+ PrizeRatingActions.fetchAverage(pieceId, state.prize.active_round);
+ }
+ },
+
onRatingClick(event, args) {
event.preventDefault();
- PrizeRatingActions.createRating(this.props.piece.id, args.rating).then(
- this.refreshPieceData()
- );
+ PrizeRatingActions
+ .createRating(this.props.piece.id, args.rating, this.state.prize.active_round)
+ .then(this.refreshPieceData);
},
- handleLoanRequestSuccess(message){
- let notification = new GlobalNotificationModel(message, 'success', 4000);
- GlobalNotificationActions.appendGlobalNotification(notification);
- },
+ getSelectedActionButton() {
+ const { currentUser, piece, selectedPrizeActionButton: SelectedPrizeActionButton } = this.props;
- getLoanButton(){
- let today = new Moment();
- let endDate = new Moment();
- endDate.add(6, 'months');
- return (
-
- {getLangText('SEND LOAN REQUEST')}
-
- }
- handleSuccess={this.handleLoanRequestSuccess}
- title='REQUEST LOAN'>
-
- );
- },
-
- handleShortlistSuccess(message){
- let notification = new GlobalNotificationModel(message, 'success', 2000);
- GlobalNotificationActions.appendGlobalNotification(notification);
+ if (piece.selected && SelectedPrizeActionButton) {
+ return (
+
+
+
+ );
+ }
},
refreshPieceData() {
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
+
this.props.loadPiece();
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, this.state.filterBy);
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
},
onSelectChange() {
- PrizeRatingActions.toggleShortlist(this.props.piece.id)
- .then(
- (res) => {
+ PrizeRatingActions
+ .toggleShortlist(this.props.piece.id)
+ .then((res) => {
this.refreshPieceData();
- return res;
- })
- .then(
- (res) => {
- this.handleShortlistSuccess(res.notification);
- }
- );
+
+ if (res && res.notification) {
+ const notification = new GlobalNotificationModel(res.notification, 'success', 2000);
+ GlobalNotificationActions.appendGlobalNotification(notification);
+ }
+ });
},
- render(){
- if (this.props.piece && this.props.currentUser && this.props.currentUser.is_judge && this.state.average) {
+ render() {
+ if (this.props.piece.id && this.props.currentUser.is_judge && this.state.average) {
// Judge sees shortlisting, average and per-jury notes
return (
@@ -352,9 +355,7 @@ let PrizePieceRatings = React.createClass({
-
- {this.props.piece.selected ? this.getLoanButton() : null}
-
+ {this.getSelectedActionButton()}
@@ -369,17 +370,23 @@ let PrizePieceRatings = React.createClass({
size='md'
step={0.5}
rating={this.state.average}
- ratingAmount={5}/>
+ ratingAmount={5} />
{this.state.ratings.map((item, i) => {
- let note = item.note ?
+ let note = item.note ? (
note: {item.note}
-
: null;
+
+ ) : null;
+
return (
-
-
+
+
+ ratingAmount={5} />
{item.user}
{note}
@@ -399,8 +406,7 @@ let PrizePieceRatings = React.createClass({
);
- }
- else if (this.props.currentUser && this.props.currentUser.is_jury) {
+ } else if (this.props.currentUser.is_jury) {
// Jury can set rating and note
return (
{return {'piece_id': this.props.piece.id}; }}
+ id={() => ({ 'piece_id': this.props.piece.id })}
label={getLangText('Jury note')}
- defaultValue={this.props.piece && this.props.piece.note_from_user ? this.props.piece.note_from_user.note : null}
+ defaultValue={this.props.piece.note_from_user || null}
placeholder={getLangText('Enter your comments ...')}
editable={true}
successMessage={getLangText('Jury note saved')}
url={ApiUrls.notes}
- currentUser={this.props.currentUser}/>
+ currentUser={this.props.currentUser} />
);
+ } else {
+ return null;
}
- return null;
}
});
let PrizePieceDetails = React.createClass({
propTypes: {
- piece: React.PropTypes.object
+ piece: React.PropTypes.object.isRequired
},
render() {
const { piece } = this.props;
- if (piece &&
- piece.prize &&
- piece.prize.name &&
- Object.keys(piece.extra_data).length !== 0) {
+ if (piece.prize && piece.prize.name && Object.keys(piece.extra_data).length) {
return (
-
);
+ } else {
+ return null;
}
- return null;
}
});
-export default PieceContainer;
+export default PrizePieceContainer;
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/prize/simple_prize/components/prize_piece_list.js b/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js
index 972b3fac..23cdbb23 100644
--- a/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js
+++ b/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js
@@ -48,9 +48,8 @@ let PrizePieceList = React.createClass({
},
getButtonSubmit() {
- const { currentUser } = this.state;
- if (this.state.prize && this.state.prize.active &&
- !currentUser.is_jury && !currentUser.is_admin && !currentUser.is_judge){
+ const { currentUser, prize } = this.state;
+ if (prize && prize.active && !currentUser.is_jury && !currentUser.is_admin && !currentUser.is_judge) {
return (
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);
});
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..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
@@ -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['prize_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['prize_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['prize_round'] = round;
+ }
+
+ return requests.post('ratings', { body });
},
select(pieceId) {
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/prize/simple_prize/stores/prize_rating_store.js b/js/components/whitelabel/prize/simple_prize/stores/prize_rating_store.js
index 9f1552bb..d3544f71 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,24 @@ import PrizeRatingActions from '../actions/prize_rating_actions';
class PrizeRatingStore {
constructor() {
+ this.getInitialState();
+
+ this.bindActions(PrizeRatingActions);
+ this.exportPublicMethods({
+ getInitialState: this.getInitialState.bind(this)
+ });
+ }
+
+ getInitialState() {
this.ratings = [];
this.currentRating = null;
this.average = null;
- this.bindActions(PrizeRatingActions);
+
+ return {
+ ratings: this.ratings,
+ currentRating: this.currentRating,
+ average: this.average
+ };
}
onUpdatePrizeRatings(ratings) {
@@ -24,6 +38,10 @@ class PrizeRatingStore {
this.average = data.average;
this.ratings = data.ratings;
}
+
+ onResetPrizeRatings() {
+ this.getInitialState();
+ }
}
-export default alt.createStore(PrizeRatingStore, 'PrizeRatingStore');
\ No newline at end of file
+export default alt.createStore(PrizeRatingStore, 'PrizeRatingStore');
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');
diff --git a/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js b/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js
new file mode 100644
index 00000000..7778a8de
--- /dev/null
+++ b/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js
@@ -0,0 +1,70 @@
+'use strict'
+
+import React from 'react';
+import Moment from 'moment';
+
+import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
+
+import LoanForm from '../../../../../ascribe_forms/form_loan';
+
+import GlobalNotificationModel from '../../../../../../models/global_notification_model';
+import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
+
+import ApiUrls from '../../../../../../constants/api_urls';
+
+import { getLangText } from '../../../../../../utils/lang_utils';
+
+const SluiceSelectedPrizeActionButton = React.createClass({
+ propTypes: {
+ piece: React.PropTypes.object,
+ currentUser: React.PropTypes.object,
+ startLoanDate: React.PropTypes.object,
+ endLoanDate: React.PropTypes.object,
+ className: React.PropTypes.string,
+ handleSuccess: React.PropTypes.func
+ },
+
+ handleSuccess(res) {
+ const notification = new GlobalNotificationModel(res && res.notification || getLangText('You have successfully requested the loan, pending their confirmation.'), 'success', 4000);
+ GlobalNotificationActions.appendGlobalNotification(notification);
+
+ if (typeof this.props.handleSuccess === 'function') {
+ this.props.handleSuccess(res);
+ }
+ },
+
+ render() {
+ const { currentUser, piece } = this.props;
+
+ // Can't use default props since those are only created once
+ const startLoanDate = this.props.startLoanDate || new Moment();
+ const endLoanDate = this.props.endLoanDate || (new Moment()).add(6, 'months');
+
+ return (
+
+ {getLangText('SEND LOAN REQUEST')}
+
+ }
+ handleSuccess={this.handleSuccess}
+ title={getLangText('REQUEST LOAN')}>
+
+
+ );
+ }
+});
+
+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;
diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js
index 302495a0..f6b2d50c 100644
--- a/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js
+++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js
@@ -47,7 +47,7 @@ let Vivi23Landing = React.createClass({
-
+
{getLangText('Existing ascribe user?')}
@@ -57,7 +57,7 @@ let Vivi23Landing = React.createClass({
-
+
{getLangText('Do you need an account?')}
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..ed8da83b 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,63 +25,72 @@ 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) {
+ const {
+ children,
+ currentUser,
+ handleDeleteSuccess,
+ loadPiece,
+ piece,
+ submitButtonType } = this.props;
+
+ if (piece && piece.id) {
return (
- {this.props.piece.title}
-
-
+ {piece.title}
+
+
}
subheader={
-
-
-
-
-
- }>
+
+
+
+
+
+ }>
+ piece={piece}
+ currentUser={currentUser}
+ loadPiece={loadPiece}
+ handleDeleteSuccess={handleDeleteSuccess}
+ submitButtonType={submitButtonType}/>
0}>
+ show={piece.loan_history && piece.loan_history.length > 0}>
+ history={piece.loan_history}/>
+ show={!!(currentUser.username || piece.public_note)}>
{return {'id': this.props.piece.id}; }}
+ id={() => {return {'id': piece.id}; }}
label={getLangText('Personal note (private)')}
- defaultValue={this.props.piece.private_note || null}
+ defaultValue={piece.private_note || null}
placeholder={getLangText('Enter your comments ...')}
editable={true}
successMessage={getLangText('Private note saved')}
url={ApiUrls.note_private_piece}
- currentUser={this.props.currentUser}/>
+ currentUser={currentUser}/>
-
- {this.props.children}
+ {children}
);
- }
- else {
+ } else {
return (
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js
index 9d88408f..ebdef4f4 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js
@@ -52,8 +52,9 @@ let CylandAccordionListItem = React.createClass({
},
handleSubmitSuccess(response) {
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, this.state.filterBy);
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
+
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
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..5fc3901f 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
@@ -40,7 +40,7 @@ let CylandPieceContainer = React.createClass({
getInitialState() {
return mergeOptions(
- PieceStore.getState(),
+ PieceStore.getInitialState(),
UserStore.getState(),
PieceListStore.getState()
);
@@ -51,14 +51,17 @@ let CylandPieceContainer = React.createClass({
UserStore.listen(this.onChange);
PieceListStore.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();
},
+ // We need this for when the user clicks on a notification while being in another piece view
+ componentWillReceiveProps(nextProps) {
+ if (this.props.params.pieceId !== nextProps.params.pieceId) {
+ PieceActions.flushPiece();
+ this.loadPiece();
+ }
+ },
+
componentWillUnmount() {
PieceStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange);
@@ -70,31 +73,34 @@ let CylandPieceContainer = React.createClass({
},
loadPiece() {
- PieceActions.fetchOne(this.props.params.pieceId);
+ PieceActions.fetchPiece(this.props.params.pieceId);
},
handleDeleteSuccess(response) {
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, this.state.filterBy);
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
+
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
// since we're deleting a piece, we just need to close
// all editions dialogs and not reload them
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');
},
render() {
- if(this.state.piece && this.state.piece.id) {
- setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
+ const { piece } = this.state;
+
+ if (piece.id) {
+ setDocumentTitle(`${piece.artist_name}, ${piece.title}`);
return (
);
- }
- else {
+ } else {
return (
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js b/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
index 93cc515a..6e643134 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
@@ -23,9 +23,10 @@ import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_
let CylandAdditionalDataForm = React.createClass({
propTypes: {
- handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired,
+
disabled: React.PropTypes.bool,
+ handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool
},
@@ -42,13 +43,13 @@ let CylandAdditionalDataForm = React.createClass({
},
handleSuccess() {
- let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000);
+ const notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
getFormData() {
- let extradata = {};
- let formRefs = this.refs.form.refs;
+ const extradata = {};
+ const formRefs = this.refs.form.refs;
// Put additional fields in extra data object
Object
@@ -71,10 +72,13 @@ let CylandAdditionalDataForm = React.createClass({
},
render() {
- let { piece, isInline, disabled, handleSuccess, location } = this.props;
- let buttons, spinner, heading;
+ const { disabled, handleSuccess, isInline, piece } = this.props;
- if(!isInline) {
+ let buttons;
+ let spinner;
+ let heading;
+
+ if (!isInline) {
buttons = (
-
+
);
@@ -101,13 +105,15 @@ let CylandAdditionalDataForm = React.createClass({
);
}
- if(piece && piece.id) {
+ if (piece.id) {
+ const { extra_data: extraData = {} } = piece;
+
return (
);
}
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js
index dc6420f4..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);
}
},
@@ -70,7 +70,7 @@ let CylandLanding = React.createClass({
-
+
{getLangText('Existing ascribe user?')}
@@ -80,7 +80,7 @@ let CylandLanding = React.createClass({
-
+
{getLangText('Do you need an account?')}
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 42b7c1ad..88088e9d 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
@@ -8,6 +8,8 @@ import Moment from 'moment';
import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row';
+import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
+
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
@@ -50,7 +52,7 @@ let CylandRegisterPiece = React.createClass({
return mergeOptions(
UserStore.getState(),
PieceListStore.getState(),
- PieceStore.getState(),
+ PieceStore.getInitialState(),
WhitelabelStore.getState(),
{
step: 0
@@ -65,7 +67,7 @@ let CylandRegisterPiece = React.createClass({
UserActions.fetchCurrentUser();
WhitelabelActions.fetchWhitelabel();
- let queryParams = this.props.location.query;
+ const queryParams = this.props.location.query;
// Since every step of this register process is atomic,
// we may need to enter the process at step 1 or 2.
@@ -74,8 +76,8 @@ let CylandRegisterPiece = React.createClass({
//
// We're using 'in' here as we want to know if 'piece_id' is present in the url,
// we don't care about the value.
- if(queryParams && 'piece_id' in queryParams) {
- PieceActions.fetchOne(queryParams.piece_id);
+ if ('piece_id' in queryParams) {
+ PieceActions.fetchPiece(queryParams.piece_id);
}
},
@@ -90,79 +92,78 @@ let CylandRegisterPiece = React.createClass({
this.setState(state);
},
- handleRegisterSuccess(response){
-
+ handleRegisterSuccess(response) {
this.refreshPieceList();
- // also start loading the piece for the next step
- if(response && response.piece) {
- PieceActions.updatePiece({});
+ // Also load the newly registered piece for the next step
+ if (response && response.piece) {
PieceActions.updatePiece(response.piece);
}
- this.incrementStep();
-
- this.refs.slidesContainer.nextSlide({ piece_id: response.piece.id });
+ this.nextSlide({ piece_id: response.piece.id });
},
handleAdditionalDataSuccess() {
-
// We need to refetch the piece again after submitting the additional data
- // since we want it's otherData to be displayed when the user choses to click
+ // since we want its otherData to be displayed when the user choses to click
// on the browsers back button.
- PieceActions.fetchOne(this.state.piece.id);
+ PieceActions.fetchPiece(this.state.piece.id);
this.refreshPieceList();
- this.incrementStep();
-
- this.refs.slidesContainer.nextSlide();
+ this.nextSlide();
},
handleLoanSuccess(response) {
- let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
+ const notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
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
- incrementStep() {
- // also increase step
- let newStep = this.state.step + 1;
+ nextSlide(queryParams) {
+ // We need to increase the step to lock the forms that are already filled out
this.setState({
- step: newStep
+ step: this.state.step + 1
});
+
+ this.refs.slidesContainer.nextSlide(queryParams);
},
refreshPieceList() {
- PieceListActions.fetchPieceList(
- this.state.page,
- this.state.pageSize,
- this.state.searchTerm,
- this.state.orderBy,
- this.state.orderAsc,
- this.state.filterBy
- );
- },
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
- changeSlide() {
- // only transition to the login store, if user is not logged in
- // ergo the currentUser object is not properly defined
- if(this.state.currentUser && !this.state.currentUser.email) {
- this.onLoggedOut();
- }
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
},
render() {
+ const { location } = this.props;
+ const { currentUser, piece, step, whitelabel } = this.state;
- let today = new Moment();
- let datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain = new Moment();
- datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain.add(1000, 'years');
+ const today = new Moment();
+ const datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain = new Moment().add(1000, 'years');
+
+ const loanHeading = getLangText('Loan to Cyland archive');
+ const loanButtons = (
+
+
+
+ {getLangText('Loan to archive')}
+
+
+
+
+
+ {getLangText('Loan later')}
+
+
+
+
+ );
setDocumentTitle(getLangText('Register a new piece'));
@@ -174,18 +175,18 @@ let CylandRegisterPiece = React.createClass({
pending: 'glyphicon glyphicon-chevron-right',
completed: 'glyphicon glyphicon-lock'
}}
- location={this.props.location}>
+ location={location}>
0}
+ disabled={step > 0}
enableLocalHashing={false}
headerMessage={getLangText('Submit to Cyland Archive')}
submitMessage={getLangText('Submit')}
isFineUploaderActive={true}
handleSuccess={this.handleRegisterSuccess}
- location={this.props.location}/>
+ location={location} />
@@ -193,9 +194,9 @@ let CylandRegisterPiece = React.createClass({
1}
+ disabled={step > 1}
handleSuccess={this.handleAdditionalDataSuccess}
- piece={this.state.piece} />
+ piece={piece} />
@@ -204,15 +205,16 @@ let CylandRegisterPiece = React.createClass({
this.handleConfirmSuccess()
- );
+ OwnershipFetcher
+ .confirmContractAgreement(contractAgreement)
+ .then(this.handleConfirmSuccess);
},
handleConfirmSuccess() {
let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
- this.history.pushState(null, '/collection');
+
+ // Flush contract notifications and refetch
+ NotificationActions.flushContractAgreementListNotifications();
+ NotificationActions.fetchContractAgreementListNotifications();
+
+ this.history.push('/collection');
},
handleDeny() {
let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement;
- OwnershipFetcher.denyContractAgreement(contractAgreement).then(
- () => this.handleDenySuccess()
- );
+ OwnershipFetcher
+ .denyContractAgreement(contractAgreement)
+ .then(this.handleDenySuccess);
},
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..a1280cc8 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
@@ -41,7 +41,7 @@ let IkonotvPieceContainer = React.createClass({
getInitialState() {
return mergeOptions(
- PieceStore.getState(),
+ PieceStore.getInitialState(),
UserStore.getState(),
PieceListStore.getState()
);
@@ -52,19 +52,14 @@ let IkonotvPieceContainer = React.createClass({
UserStore.listen(this.onChange);
PieceListStore.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();
},
- // We need this for when the user clicks on a notification while being in another piece view
+ // We need this for when the user clicks on a notification while being in another piece view
componentWillReceiveProps(nextProps) {
- if(this.props.params.pieceId !== nextProps.params.pieceId) {
- PieceActions.updatePiece({});
- PieceActions.fetchOne(nextProps.params.pieceId);
+ if (this.props.params.pieceId !== nextProps.params.pieceId) {
+ PieceActions.flushPiece();
+ this.loadPiece();
}
},
@@ -79,25 +74,28 @@ let IkonotvPieceContainer = React.createClass({
},
loadPiece() {
- PieceActions.fetchOne(this.props.params.pieceId);
+ PieceActions.fetchPiece(this.props.params.pieceId);
},
handleDeleteSuccess(response) {
- PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, this.state.filterBy);
+ const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
+
+ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
// since we're deleting a piece, we just need to close
// all editions dialogs and not reload them
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');
},
render() {
+ const { piece } = this.state;
+
let furtherDetails = (
);
- if(this.state.piece.extra_data && Object.keys(this.state.piece.extra_data).length > 0 && this.state.piece.acl) {
+ if (piece.extra_data && Object.keys(piece.extra_data).length && piece.acl) {
furtherDetails = (
+ disabled={!piece.acl.acl_edit} />
+ disabled={!piece.acl.acl_edit} />
);
}
- if(this.state.piece && this.state.piece.id) {
- setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
+ if (piece.id) {
+ setDocumentTitle(`${piece.artist_name}, ${piece.title}`);
+
return (
);
- }
- 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..598f5bd0 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
@@ -20,11 +20,10 @@ import { getLangText } from '../../../../../../utils/lang_utils';
let IkonotvArtistDetailsForm = React.createClass({
propTypes: {
- handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool,
-
+ handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool
},
@@ -35,8 +34,8 @@ let IkonotvArtistDetailsForm = React.createClass({
},
getFormData() {
- let extradata = {};
- let formRefs = this.refs.form.refs;
+ const extradata = {};
+ const formRefs = this.refs.form.refs;
// Put additional fields in extra data object
Object
@@ -53,21 +52,23 @@ let IkonotvArtistDetailsForm = React.createClass({
},
handleSuccess() {
- let notification = new GlobalNotificationModel('Artist details successfully updated', 'success', 10000);
+ const notification = new GlobalNotificationModel(getLangText('Artist details successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
- let buttons, spinner, heading;
- let { isInline, handleSuccess } = this.props;
-
+ const { disabled, isInline, handleSuccess, piece } = this.props;
- if(!isInline) {
+ let buttons;
+ let spinner;
+ let heading;
+
+ if (!isInline) {
buttons = (
+ disabled={disabled}>
{getLangText('Proceed to loan')}
);
@@ -75,7 +76,7 @@ let IkonotvArtistDetailsForm = React.createClass({
spinner = (
);
@@ -89,13 +90,15 @@ let IkonotvArtistDetailsForm = React.createClass({
);
}
- if(this.props.piece && this.props.piece.id && this.props.piece.extra_data) {
+ if (piece.id) {
+ const { extra_data: extraData = {} } = piece;
+
return (
);
@@ -150,4 +152,4 @@ let IkonotvArtistDetailsForm = React.createClass({
}
});
-export default IkonotvArtistDetailsForm;
\ No newline at end of file
+export default IkonotvArtistDetailsForm;
diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_forms/ikonotv_artwork_details_form.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_forms/ikonotv_artwork_details_form.js
index 97b1adc7..28c67c94 100644
--- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_forms/ikonotv_artwork_details_form.js
+++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_forms/ikonotv_artwork_details_form.js
@@ -20,11 +20,10 @@ import { getLangText } from '../../../../../../utils/lang_utils';
let IkonotvArtworkDetailsForm = React.createClass({
propTypes: {
- handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool,
-
+ handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool
},
@@ -35,8 +34,8 @@ let IkonotvArtworkDetailsForm = React.createClass({
},
getFormData() {
- let extradata = {};
- let formRefs = this.refs.form.refs;
+ const extradata = {};
+ const formRefs = this.refs.form.refs;
// Put additional fields in extra data object
Object
@@ -53,20 +52,23 @@ let IkonotvArtworkDetailsForm = React.createClass({
},
handleSuccess() {
- let notification = new GlobalNotificationModel('Artwork details successfully updated', 'success', 10000);
+ const notification = new GlobalNotificationModel(getLangText('Artwork details successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
- let buttons, spinner, heading;
- let { isInline, handleSuccess } = this.props;
+ const { disabled, isInline, handleSuccess, piece } = this.props;
- if(!isInline) {
+ let buttons;
+ let spinner;
+ let heading;
+
+ if (!isInline) {
buttons = (
+ disabled={disabled}>
{getLangText('Proceed to artist details')}
);
@@ -74,7 +76,7 @@ let IkonotvArtworkDetailsForm = React.createClass({
spinner = (
);
@@ -88,13 +90,15 @@ let IkonotvArtworkDetailsForm = React.createClass({
);
}
- if(this.props.piece && this.props.piece.id && this.props.piece.extra_data) {
+ if (piece.id && piece.extra_data) {
+ const { extra_data: extraData = {} } = piece;
+
return (
);
@@ -166,4 +170,4 @@ let IkonotvArtworkDetailsForm = React.createClass({
}
});
-export default IkonotvArtworkDetailsForm;
\ No newline at end of file
+export default IkonotvArtworkDetailsForm;
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({
+ piece={this.state.piece} />
);
+ } else {
+ return null;
}
- return null;
},
getSlideArtworkDetails() {
@@ -188,21 +175,21 @@ let IkonotvRegisterPiece = React.createClass({
+ piece={this.state.piece} />
);
+ } else {
+ return null;
}
- return null;
},
getSlideLoan() {
if (this.canSubmit()) {
const { piece, whitelabel } = this.state;
- let today = new Moment();
- let endDate = new Moment();
- endDate.add(2, 'years');
+ const today = new Moment();
+ const endDate = new Moment().add(2, 'years');
return (
@@ -225,8 +212,9 @@ let IkonotvRegisterPiece = React.createClass({
);
+ } else {
+ return null;
}
- return null;
},
render() {
@@ -252,7 +240,7 @@ let IkonotvRegisterPiece = React.createClass({
submitMessage={getLangText('Register')}
isFineUploaderActive={true}
handleSuccess={this.handleRegisterSuccess}
- location={this.props.location}/>
+ location={this.props.location} />
diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js
index e68b1781..23289276 100644
--- a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js
+++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js
@@ -53,7 +53,7 @@ let LumenusLanding = React.createClass({
-
+
{getLangText('Existing ascribe user?')}
@@ -63,7 +63,7 @@ let LumenusLanding = React.createClass({
-
+
{getLangText('Do you need an account?')}
diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js
index 1dcdd4e5..4d4f8918 100644
--- a/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js
+++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js
@@ -30,7 +30,7 @@ let MarketAclButtonList = React.createClass({
componentDidMount() {
UserStore.listen(this.onChange);
- UserActions.fetchCurrentUser();
+ UserActions.fetchCurrentUser.defer();
},
componentWillUnmount() {
diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js
index d8ef4c41..c839dea0 100644
--- a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js
+++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js
@@ -3,6 +3,11 @@
import React from 'react';
import classNames from 'classnames';
+import EditionActions from '../../../../../../actions/edition_actions';
+
+import WhitelabelActions from '../../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../../stores/whitelabel_store';
+
import MarketAdditionalDataForm from '../market_forms/market_additional_data_form';
import AclFormFactory from '../../../../../ascribe_forms/acl_form_factory';
@@ -11,10 +16,7 @@ import ConsignForm from '../../../../../ascribe_forms/form_consign';
import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
import AclProxy from '../../../../../acl_proxy';
-
-import PieceActions from '../../../../../../actions/piece_actions';
-import WhitelabelActions from '../../../../../../actions/whitelabel_actions';
-import WhitelabelStore from '../../../../../../stores/whitelabel_store';
+import AscribeSpinner from '../../../../../ascribe_spinner';
import ApiUrls from '../../../../../../constants/api_urls';
@@ -26,8 +28,9 @@ let MarketSubmitButton = React.createClass({
availableAcls: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object,
editions: React.PropTypes.array.isRequired,
- handleSuccess: React.PropTypes.func.isRequired,
+
className: React.PropTypes.string,
+ handleSuccess: React.PropTypes.func
},
getInitialState() {
@@ -50,22 +53,21 @@ let MarketSubmitButton = React.createClass({
canEditionBeSubmitted(edition) {
if (edition && edition.extra_data && edition.other_data) {
- const { extra_data, other_data } = edition;
+ const {
+ extra_data: {
+ artist_bio: artistBio,
+ display_instructions: displayInstructions,
+ technology_details: technologyDetails,
+ work_description: workDescription
+ },
+ other_data: otherData } = edition;
- if (extra_data.artist_bio && extra_data.work_description &&
- extra_data.technology_details && extra_data.display_instructions &&
- other_data.length > 0) {
- return true;
- }
+ return artistBio && displayInstructions && technologyDetails && workDescription && otherData.length;
}
return false;
},
- getFormDataId() {
- return getAclFormDataId(false, this.props.editions);
- },
-
getAggregateEditionDetails() {
const { editions } = this.props;
@@ -82,13 +84,20 @@ let MarketSubmitButton = React.createClass({
});
},
- handleAdditionalDataSuccess(pieceId) {
- // Fetch newly updated piece to update the views
- PieceActions.fetchOne(pieceId);
+ getFormDataId() {
+ return getAclFormDataId(false, this.props.editions);
+ },
+ handleAdditionalDataSuccess() {
this.refs.consignModal.show();
},
+ refreshEdition() {
+ if (this.props.editions.length === 1) {
+ EditionActions.fetchEdition(this.props.editions[0].bitcoin_id);
+ }
+ },
+
render() {
const { availableAcls, currentUser, className, editions, handleSuccess } = this.props;
const { whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } } = this.state;
@@ -101,6 +110,10 @@ let MarketSubmitButton = React.createClass({
senderName: currentUser.username
});
+ // If only a single piece is selected, all the edition's extra_data and other_data will
+ // be the same, so we just take the first edition's
+ const { extra_data: extraData, other_data: otherData } = solePieceId ? editions[0] : {};
+
const triggerButton = (
{getLangText('CONSIGN TO %s', whitelabelName.toUpperCase())}
@@ -126,16 +139,25 @@ let MarketSubmitButton = React.createClass({
aclName='acl_consign'>
{
+ if (typeof handleSuccess === 'function') {
+ handleSuccess(...params);
+ }
+
+ this.refreshEdition();
+ }}
title={getLangText('Consign artwork')}>
{consignForm}
diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js
index 4e1e3ee8..064936c9 100644
--- a/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js
+++ b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js
@@ -6,8 +6,12 @@ import MarketAdditionalDataForm from '../market_forms/market_additional_data_for
let MarketFurtherDetails = React.createClass({
propTypes: {
- pieceId: React.PropTypes.number,
+ pieceId: React.PropTypes.number.isRequired,
+
+ editable: React.PropTypes.bool,
+ extraData: React.PropTypes.object,
handleSuccess: React.PropTypes.func,
+ otherData: React.PropTypes.arrayOf(React.PropTypes.object)
},
render() {
diff --git a/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js
index d136c9cf..24a70445 100644
--- a/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js
+++ b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js
@@ -2,21 +2,18 @@
import React from 'react';
-import Form from '../../../../../ascribe_forms/form';
-import Property from '../../../../../ascribe_forms/property';
-
-import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
-
-import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
-import AscribeSpinner from '../../../../../ascribe_spinner';
-
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
+import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
+
+import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
+import Form from '../../../../../ascribe_forms/form';
+import Property from '../../../../../ascribe_forms/property';
+
import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils';
-import PieceActions from '../../../../../../actions/piece_actions';
-import PieceStore from '../../../../../../stores/piece_store';
+import AscribeSpinner from '../../../../../ascribe_spinner';
import ApiUrls from '../../../../../../constants/api_urls';
import AppConstants from '../../../../../../constants/application_constants';
@@ -28,16 +25,16 @@ import { getLangText } from '../../../../../../utils/lang_utils';
let MarketAdditionalDataForm = React.createClass({
propTypes: {
- pieceId: React.PropTypes.oneOfType([
- React.PropTypes.number,
- React.PropTypes.string
- ]),
+ pieceId: React.PropTypes.number.isRequired,
+
editable: React.PropTypes.bool,
+ extraData: React.PropTypes.object,
+ handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool,
+ otherData: React.PropTypes.arrayOf(React.PropTypes.object),
showHeading: React.PropTypes.bool,
showNotification: React.PropTypes.bool,
- submitLabel: React.PropTypes.string,
- handleSuccess: React.PropTypes.func
+ submitLabel: React.PropTypes.string
},
getDefaultProps() {
@@ -48,50 +45,34 @@ let MarketAdditionalDataForm = React.createClass({
},
getInitialState() {
- const pieceStore = PieceStore.getState();
-
- return mergeOptions(
- pieceStore,
- {
- // Allow the form to be submitted if there's already an additional image uploaded
- isUploadReady: this.isUploadReadyOnChange(pieceStore.piece),
- forceUpdateKey: 0
- });
- },
-
- componentDidMount() {
- PieceStore.listen(this.onChange);
-
- if (this.props.pieceId) {
- PieceActions.fetchOne(this.props.pieceId);
+ return {
+ // Allow the form to be submitted if there's already an additional image uploaded
+ isUploadReady: this.isUploadReadyOnChange(),
+ forceUpdateKey: 0
}
},
- componentWillUnmount() {
- PieceStore.unlisten(this.onChange);
- },
+ componentWillReceiveProps(nextProps) {
+ if (this.props.extraData !== nextProps.extraData || this.props.otherData !== nextProps.otherData) {
+ this.setState({
+ // Allow the form to be submitted if the updated piece has an additional image uploaded
+ isUploadReady: this.isUploadReadyOnChange(),
- onChange(state) {
- Object.assign({}, state, {
- // Allow the form to be submitted if the updated piece already has an additional image uploaded
- isUploadReady: this.isUploadReadyOnChange(state.piece),
-
- /**
- * Increment the forceUpdateKey to force the form to rerender on each change
- *
- * THIS IS A HACK TO MAKE SURE THE FORM ALWAYS DISPLAYS THE MOST RECENT STATE
- * BECAUSE SOME OF OUR FORM ELEMENTS DON'T UPDATE FROM PROP CHANGES (ie.
- * InputTextAreaToggable).
- */
- forceUpdateKey: this.state.forceUpdateKey + 1
- });
-
- this.setState(state);
+ /**
+ * Increment the forceUpdateKey to force the form to rerender on each change
+ *
+ * THIS IS A HACK TO MAKE SURE THE FORM ALWAYS DISPLAYS THE MOST RECENT STATE
+ * BECAUSE SOME OF OUR FORM ELEMENTS DON'T UPDATE FROM PROP CHANGES (ie.
+ * InputTextAreaToggable).
+ */
+ forceUpdateKey: this.state.forceUpdateKey + 1
+ });
+ }
},
getFormData() {
- let extradata = {};
- let formRefs = this.refs.form.refs;
+ const extradata = {};
+ const formRefs = this.refs.form.refs;
// Put additional fields in extra data object
Object
@@ -102,12 +83,12 @@ let MarketAdditionalDataForm = React.createClass({
return {
extradata: extradata,
- piece_id: this.state.piece.id
+ piece_id: this.props.pieceId
};
},
- isUploadReadyOnChange(piece) {
- return piece && piece.other_data && piece.other_data.length > 0;
+ isUploadReadyOnChange() {
+ return this.props.otherData && this.props.otherData.length;
},
handleSuccessWithNotification() {
@@ -115,7 +96,7 @@ let MarketAdditionalDataForm = React.createClass({
this.props.handleSuccess();
}
- let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000);
+ const notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
@@ -126,11 +107,20 @@ let MarketAdditionalDataForm = React.createClass({
},
render() {
- const { editable, isInline, handleSuccess, showHeading, showNotification, submitLabel } = this.props;
- const { piece } = this.state;
- let buttons, heading;
+ const {
+ editable,
+ extraData = {},
+ isInline,
+ handleSuccess,
+ otherData,
+ pieceId,
+ showHeading,
+ showNotification,
+ submitLabel } = this.props;
- let spinner = ;
+ let buttons;
+ let heading;
+ let spinner;
if (!isInline) {
buttons = (
@@ -145,7 +135,7 @@ let MarketAdditionalDataForm = React.createClass({
spinner = (
);
@@ -159,64 +149,65 @@ let MarketAdditionalDataForm = React.createClass({
) : null;
}
- if (piece && piece.id) {
+ if (pieceId) {
return (