diff --git a/js/actions/contract_actions.js b/js/actions/contract_actions.js
deleted file mode 100644
index d1bf1432..00000000
--- a/js/actions/contract_actions.js
+++ /dev/null
@@ -1,47 +0,0 @@
-'use strict';
-
-import alt from '../alt';
-import OwnershipFetcher from '../fetchers/ownership_fetcher';
-
-
-class ContractActions {
- constructor() {
- this.generateActions(
- 'updateContract',
- 'flushContract'
- );
- }
-
- fetchContract(email) {
- if(email.match(/.+\@.+\..+/)) {
- OwnershipFetcher.fetchContract(email)
- .then((contracts) => {
- if (contracts && contracts.length > 0) {
- this.actions.updateContract({
- contractKey: contracts[0].s3Key,
- contractUrl: contracts[0].s3Url,
- contractEmail: email
- });
- }
- else {
- this.actions.updateContract({
- contractKey: null,
- contractUrl: null,
- contractEmail: null
- });
- }
- })
- .catch((err) => {
- console.logGlobal(err);
- this.actions.updateContract({
- contractKey: null,
- contractUrl: null,
- contractEmail: null
- });
- });
- }
- }
-
-}
-
-export default alt.createActions(ContractActions);
diff --git a/js/actions/contract_agreement_list_actions.js b/js/actions/contract_agreement_list_actions.js
new file mode 100644
index 00000000..86988f47
--- /dev/null
+++ b/js/actions/contract_agreement_list_actions.js
@@ -0,0 +1,94 @@
+'use strict';
+
+import alt from '../alt';
+import Q from 'q';
+
+import OwnershipFetcher from '../fetchers/ownership_fetcher';
+import ContractListActions from './contract_list_actions';
+
+class ContractAgreementListActions {
+ constructor() {
+ this.generateActions(
+ 'updateContractAgreementList',
+ 'flushContractAgreementList'
+ );
+ }
+
+ fetchContractAgreementList(issuer, accepted, pending) {
+ return Q.Promise((resolve, reject) => {
+ this.actions.updateContractAgreementList(null);
+ OwnershipFetcher.fetchContractAgreementList(issuer, accepted, pending)
+ .then((contractAgreementList) => {
+ if (contractAgreementList.count > 0) {
+ this.actions.updateContractAgreementList(contractAgreementList.results);
+ resolve(contractAgreementList.results);
+ }
+ else{
+ resolve(null);
+ }
+ })
+ .catch((err) => {
+ console.logGlobal(err);
+ reject(err);
+ });
+ }
+ );
+ }
+
+ fetchAvailableContractAgreementList(issuer){
+ return Q.Promise((resolve, reject) => {
+ this.actions.fetchContractAgreementList(issuer, true, null)
+ .then((contractAgreementListAccepted) => {
+ if (!contractAgreementListAccepted) {
+ // fetch pending agreements if no accepted ones
+ return this.actions.fetchContractAgreementList(issuer, null, true);
+ }
+ else {
+ resolve(contractAgreementListAccepted);
+ }
+ }).then((contractAgreementListPending) => {
+ resolve(contractAgreementListPending);
+ }).catch((err) => {
+ console.logGlobal(err);
+ reject(err);
+ });
+ });
+ }
+
+ createContractAgreementFromPublicContract(issuer){
+ ContractListActions.fetchContractList(null, null, issuer)
+ .then((publicContract) => {
+ // create an agreement with the public contract if there is one
+ if (publicContract && publicContract.length > 0) {
+ return this.actions.createContractAgreement(null, publicContract[0]);
+ }
+ else {
+ /*
+ contractAgreementList in the store is already set to null;
+ */
+ }
+ }).then((publicContracAgreement) => {
+ if (publicContracAgreement) {
+ this.actions.updateContractAgreementList([publicContracAgreement]);
+ }
+ }).catch((err) => {
+ console.logGlobal(err);
+ });
+ }
+
+ createContractAgreement(issuer, contract){
+ return Q.Promise((resolve, reject) => {
+ OwnershipFetcher.createContractAgreement(issuer, contract).then(
+ (contractAgreement) => {
+ resolve(contractAgreement);
+ }
+ ).catch((err) => {
+ console.logGlobal(err);
+ reject(err);
+ });
+ });
+ }
+
+}
+
+export default alt.createActions(ContractAgreementListActions);
diff --git a/js/actions/contract_list_actions.js b/js/actions/contract_list_actions.js
index aaee33c6..a856fb2b 100644
--- a/js/actions/contract_list_actions.js
+++ b/js/actions/contract_list_actions.js
@@ -12,15 +12,19 @@ class ContractListActions {
);
}
- fetchContractList(isActive) {
- OwnershipFetcher.fetchContractList(isActive)
- .then((contracts) => {
- this.actions.updateContractList(contracts.results);
- })
- .catch((err) => {
- console.logGlobal(err);
- this.actions.updateContractList([]);
- });
+ fetchContractList(isActive, isPublic, issuer) {
+ return Q.Promise((resolve, reject) => {
+ OwnershipFetcher.fetchContractList(isActive, isPublic, issuer)
+ .then((contracts) => {
+ this.actions.updateContractList(contracts.results);
+ resolve(contracts.results);
+ })
+ .catch((err) => {
+ console.logGlobal(err);
+ this.actions.updateContractList([]);
+ reject(err);
+ });
+ });
}
diff --git a/js/components/acl_proxy.js b/js/components/acl_proxy.js
index be0d8466..4fc90a9b 100644
--- a/js/components/acl_proxy.js
+++ b/js/components/acl_proxy.js
@@ -20,21 +20,28 @@ let AclProxy = React.createClass({
show: React.PropTypes.bool
},
- render() {
- if(this.props.show) {
+ getChildren() {
+ if (React.Children.count(this.props.children) > 1){
+ /*
+ This might ruin styles for header items in the navbar etc
+ */
return (
{this.props.children}
);
+ }
+ /* can only do this when there is only 1 child, but will preserve styles */
+ return this.props.children;
+ },
+
+ render() {
+ if(this.props.show) {
+ return this.getChildren();
} else {
if(this.props.aclObject) {
if(this.props.aclObject[this.props.aclName]) {
- return (
-
- {this.props.children}
-
- );
+ return this.getChildren();
} else {
/* if(typeof this.props.aclObject[this.props.aclName] === 'undefined') {
console.warn('The aclName you\'re filtering for was not present (or undefined) in the aclObject.');
diff --git a/js/components/ascribe_forms/form_contract_agreement.js b/js/components/ascribe_forms/form_contract_agreement.js
index 0c20383c..887b99db 100644
--- a/js/components/ascribe_forms/form_contract_agreement.js
+++ b/js/components/ascribe_forms/form_contract_agreement.js
@@ -35,7 +35,7 @@ let ContractAgreementForm = React.createClass({
componentDidMount() {
ContractListStore.listen(this.onChange);
- ContractListActions.fetchContractList({is_active: 'True'});
+ ContractListActions.fetchContractList({is_active: true});
},
componentWillUnmount() {
diff --git a/js/components/ascribe_forms/form_create_contract.js b/js/components/ascribe_forms/form_create_contract.js
index 9cf97db7..b275df03 100644
--- a/js/components/ascribe_forms/form_create_contract.js
+++ b/js/components/ascribe_forms/form_create_contract.js
@@ -50,7 +50,7 @@ let CreateContractForm = React.createClass({
},
handleCreateSuccess(response) {
- ContractListActions.fetchContractList({is_active: 'True'});
+ ContractListActions.fetchContractList({is_active: true});
let notification = new GlobalNotificationModel(getLangText('Contract %s successfully created', response.name), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.refs.form.reset();
diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js
index 44a24ad2..dacbae77 100644
--- a/js/components/ascribe_forms/form_loan.js
+++ b/js/components/ascribe_forms/form_loan.js
@@ -12,11 +12,12 @@ import InputTextAreaToggable from './input_textarea_toggable';
import InputDate from './input_date';
import InputCheckbox from './input_checkbox';
-import ContractStore from '../../stores/contract_store';
-import ContractActions from '../../actions/contract_actions';
+import ContractAgreementListStore from '../../stores/contract_agreement_list_store';
+import ContractAgreementListActions from '../../actions/contract_agreement_list_actions';
import AppConstants from '../../constants/application_constants';
+import { mergeOptions } from '../../utils/general_utils';
import { getLangText } from '../../utils/lang_utils';
@@ -48,40 +49,74 @@ let LoanForm = React.createClass({
},
getInitialState() {
- return ContractStore.getState();
+ return ContractAgreementListStore.getState();
},
componentDidMount() {
- ContractStore.listen(this.onChange);
- ContractActions.flushContract.defer();
+ ContractAgreementListStore.listen(this.onChange);
+ this.getContractAgreementsOrCreatePublic(this.props.email);
+ },
+
+ componentWillReceiveProps(nextProps) {
+ // however, it can also be that at the time the component is mounting,
+ // the email is not defined (because it's asynchronously fetched from the server).
+ // Then we need to update it as soon as it is included into LoanForm's props.
+ if(nextProps && nextProps.email) {
+ this.getContractAgreementsOrCreatePublic(nextProps.email);
+ }
},
componentWillUnmount() {
- ContractStore.unlisten(this.onChange);
+ ContractAgreementListStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
+ getContractAgreementsOrCreatePublic(email){
+ ContractAgreementListActions.flushContractAgreementList();
+ if (email) {
+ ContractAgreementListActions.fetchAvailableContractAgreementList(email).then(
+ (contractAgreementList) => {
+ if (!contractAgreementList) {
+ ContractAgreementListActions.createContractAgreementFromPublicContract(email);
+ }
+ }
+ );
+ }
+ },
+
getFormData(){
- return this.props.id;
+ return mergeOptions(
+ this.props.id,
+ this.getContractAgreementId()
+ );
},
handleOnChange(event) {
// event.target.value is the submitted email of the loanee
- if(event && event.target && event.target.value && event.target.value.match(/.*@.*/)) {
- ContractActions.fetchContract(event.target.value);
+ if(event && event.target && event.target.value && event.target.value.match(/.*@.*\..*/)) {
+ this.getContractAgreementsOrCreatePublic(event.target.value);
} else {
- ContractActions.flushContract();
+ ContractAgreementListActions.flushContractAgreementList();
}
},
+ getContractAgreementId() {
+ if (this.state.contractAgreementList && this.state.contractAgreementList.length > 0) {
+ return {'contract_agreement_id': this.state.contractAgreementList[0].id};
+ }
+ return null;
+ },
+
getContractCheckbox() {
- if(this.state.contractKey && this.state.contractUrl) {
+ if(this.state.contractAgreementList && this.state.contractAgreementList.length > 0) {
// we need to define a key on the InputCheckboxes as otherwise
// react is not rerendering them on a store switch and is keeping
// the default value of the component (which is in that case true)
+ let contract = this.state.contractAgreementList[0].contract;
+
return (
{getLangText('I agree to the')}
-
- {getLangText('terms of')} {this.state.contractEmail}
+
+ {getLangText('terms of ')} {contract.issuer}
@@ -157,8 +192,8 @@ let LoanForm = React.createClass({
{
- ContractListActions.fetchContractList({is_active: 'True'});
+ ContractListActions.fetchContractList({is_active: true});
let notification = getLangText('Contract %s is now public', contract.name);
notification = new GlobalNotificationModel(notification, 'success', 4000);
GlobalNotificationActions.appendGlobalNotification(notification);
@@ -58,7 +58,7 @@ let ContractSettings = React.createClass({
return () => {
ContractListActions.removeContract(contract.id)
.then((response) => {
- ContractListActions.fetchContractList({is_active: 'True'});
+ ContractListActions.fetchContractList({is_active: true});
let notification = new GlobalNotificationModel(response.notification, 'success', 4000);
GlobalNotificationActions.appendGlobalNotification(notification);
})
diff --git a/js/components/header.js b/js/components/header.js
index 2dca74b2..0309b748 100644
--- a/js/components/header.js
+++ b/js/components/header.js
@@ -141,7 +141,7 @@ let Header = React.createClass({
{getLangText('Log out')}
);
- navRoutesLinks = ;
+ navRoutesLinks = ;
}
else {
account = {getLangText('LOGIN')};
diff --git a/js/components/nav_routes_links.js b/js/components/nav_routes_links.js
index 9a266ba8..d3342cb8 100644
--- a/js/components/nav_routes_links.js
+++ b/js/components/nav_routes_links.js
@@ -3,53 +3,80 @@
import React from 'react';
import Nav from 'react-bootstrap/lib/Nav';
-import DropdownButton from 'react-bootstrap/lib/DropdownButton';
-import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
-import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
+
+import NavRoutesLinksLink from './nav_routes_links_link';
+
+import AclProxy from './acl_proxy';
import { sanitizeList } from '../utils/general_utils';
let NavRoutesLinks = React.createClass({
propTypes: {
- routes: React.PropTypes.element
+ routes: React.PropTypes.element,
+ userAcl: React.PropTypes.object
},
- extractLinksFromRoutes(node, i) {
+ /**
+ * This method generales a bunch of react-bootstrap specific links
+ * from the routes we defined in one of the specific routes.js file
+ *
+ * We can define a headerTitle as well as a aclName and according to that the
+ * link will be created for a specific user
+ * @param {ReactElement} node Starts at the very top of a routes files root
+ * @param {object} userAcl ACL object we use throughout the whole app
+ * @param {number} i Depth of the route in comparison to the root
+ * @return {Array} Array of ReactElements that can be displayed to the user
+ */
+ extractLinksFromRoutes(node, userAcl, i) {
if(!node) {
return;
}
- node = node.props;
+ let links = node.props.children.map((child, j) => {
+ let childrenFn = null;
+ let { aclName, headerTitle, name, children } = child.props;
- let links = node.children.map((child, j) => {
+ // If the node has children that could be rendered, then we want
+ // to execute this function again with the child as the root
+ //
+ // Otherwise we'll just pass childrenFn as false
+ if(child.props.children && child.props.children.length > 0) {
+ childrenFn = this.extractLinksFromRoutes(child, userAcl, i++);
+ }
- // check if this a candidate for a link generation
- if(child.props.headerTitle && typeof child.props.headerTitle === 'string') {
-
- // also check if it is a candidate for generating a dropdown menu
- if(child.props.children && child.props.children.length > 0) {
+ // We validate if the user has set the title correctly,
+ // otherwise we're not going to render his route
+ if(headerTitle && typeof headerTitle === 'string') {
+ // if there is an aclName present on the route definition,
+ // we evaluate it against the user's acl
+ if(aclName && typeof aclName !== 'undefined') {
return (
-
- {this.extractLinksFromRoutes(child, i++)}
-
- );
- } else if(i === 1) {
- // if the node's child is actually a node of level one (a child of a node), we're
- // returning a DropdownButton matching MenuItemLink
- return (
- {child.props.headerTitle}
- );
- } else if(i === 0) {
- return (
- {child.props.headerTitle}
+
+
+
);
} else {
- return null;
+ return (
+
+ );
}
} else {
return null;
}
+
});
// remove all nulls from the list of generated links
@@ -57,9 +84,11 @@ let NavRoutesLinks = React.createClass({
},
render() {
+ let {routes, userAcl} = this.props;
+
return (
);
}
diff --git a/js/components/nav_routes_links_link.js b/js/components/nav_routes_links_link.js
new file mode 100644
index 00000000..15aff405
--- /dev/null
+++ b/js/components/nav_routes_links_link.js
@@ -0,0 +1,51 @@
+'use strict';
+
+import React from 'react';
+
+import DropdownButton from 'react-bootstrap/lib/DropdownButton';
+import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
+import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
+
+let NavRoutesLinksLink = React.createClass({
+ propTypes: {
+ headerTitle: React.PropTypes.string,
+ routeName: React.PropTypes.string,
+
+ children: React.PropTypes.oneOfType([
+ React.PropTypes.arrayOf(React.PropTypes.element),
+ React.PropTypes.element
+ ]),
+
+ depth: React.PropTypes.number
+ },
+
+ render() {
+ let { children, headerTitle, depth, routeName } = this.props;
+
+ // if the route has children, we're returning a DropdownButton that will get filled
+ // with MenuItemLinks
+ if(children) {
+ return (
+
+ {children}
+
+ );
+ } else {
+ if(depth === 1) {
+ // if the node's child is actually a node of level one (a child of a node), we're
+ // returning a DropdownButton matching MenuItemLink
+ return (
+ {headerTitle}
+ );
+ } else if(depth === 0) {
+ return (
+ {headerTitle}
+ );
+ } else {
+ return null;
+ }
+ }
+ }
+});
+
+export default NavRoutesLinksLink;
\ No newline at end of file
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 3768adab..6e8a77c3 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
@@ -222,21 +222,7 @@ let CylandRegisterPiece = React.createClass({
showStartDate={false}
showEndDate={false}
showPersonalMessage={false}
- handleSuccess={this.handleLoanSuccess}>
-
-
-
- {' ' + getLangText('I agree to the Terms of Service of Cyland Archive') + ' '}
- (
- {getLangText('read')}
- )
-
-
-
-
+ handleSuccess={this.handleLoanSuccess} />
diff --git a/js/components/whitelabel/wallet/components/ikonotv/ascribe_buttons/ikonotv_submit_button.js b/js/components/whitelabel/wallet/components/ikonotv/ascribe_buttons/ikonotv_submit_button.js
index 41b47a82..4b7f3dbc 100644
--- a/js/components/whitelabel/wallet/components/ikonotv/ascribe_buttons/ikonotv_submit_button.js
+++ b/js/components/whitelabel/wallet/components/ikonotv/ascribe_buttons/ikonotv_submit_button.js
@@ -50,21 +50,7 @@ let IkonotvSubmitButton = React.createClass({
enddate={enddate}
gallery="IkonoTV archive"
showPersonalMessage={false}
- handleSuccess={this.props.handleSuccess}>
-
-
-
- {' ' + getLangText('I agree to the Terms of Service of IkonoTV Archive') + ' '}
- (
- {getLangText('read')}
- )
-
-
-
-
+ handleSuccess={this.props.handleSuccess} />
);
diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js
index c1b101a1..e0d8a862 100644
--- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js
+++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js
@@ -14,7 +14,8 @@ function getWalletApiUrls(subdomain) {
else if (subdomain === 'ikonotv'){
return {
'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/',
- 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/'
+ 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/',
+ 'user': walletConstants.walletApiEndpoint + subdomain + '/users/'
};
}
return {};
diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js
index bc9e0e19..25fc1d80 100644
--- a/js/components/whitelabel/wallet/wallet_routes.js
+++ b/js/components/whitelabel/wallet/wallet_routes.js
@@ -72,7 +72,7 @@ let ROUTES = {
-
+
diff --git a/js/fetchers/ownership_fetcher.js b/js/fetchers/ownership_fetcher.js
index c0d32d71..fde66c7d 100644
--- a/js/fetchers/ownership_fetcher.js
+++ b/js/fetchers/ownership_fetcher.js
@@ -15,8 +15,33 @@ let OwnershipFetcher = {
/**
* Fetch the contracts of the logged-in user from the API.
*/
- fetchContractList(isActive){
- return requests.get(ApiUrls.ownership_contract_list, isActive);
+ fetchContractList(isActive, isPublic, issuer){
+ let queryParams = {
+ isActive,
+ isPublic,
+ issuer
+ };
+ return requests.get(ApiUrls.ownership_contract_list, queryParams);
+ },
+
+
+ /**
+ * Create a contractagreement between the logged-in user and the email from the API with contract.
+ */
+ createContractAgreement(signee, contractObj){
+ return requests.post(ApiUrls.ownership_contract_agreements, { body: {signee: signee, contract: contractObj.id }});
+ },
+
+ /**
+ * Fetch the contractagreement between the logged-in user and the email from the API.
+ */
+ fetchContractAgreementList(issuer, accepted, pending) {
+ let queryParams = {
+ issuer,
+ accepted,
+ pending
+ };
+ return requests.get(ApiUrls.ownership_contract_agreements, queryParams);
},
fetchLoanPieceRequestList(){
diff --git a/js/stores/contract_agreement_list_store.js b/js/stores/contract_agreement_list_store.js
new file mode 100644
index 00000000..ca1d8e6b
--- /dev/null
+++ b/js/stores/contract_agreement_list_store.js
@@ -0,0 +1,22 @@
+'use strict';
+
+import alt from '../alt';
+import ContractAgreementListActions from '../actions/contract_agreement_list_actions';
+
+
+class ContractAgreementListStore {
+ constructor() {
+ this.contractAgreementList = null;
+ this.bindActions(ContractAgreementListActions);
+ }
+
+ onUpdateContractAgreementList(contractAgreementList) {
+ this.contractAgreementList = contractAgreementList;
+ }
+
+ onFlushContractAgreementList() {
+ this.contractAgreementList = null;
+ }
+}
+
+export default alt.createStore(ContractAgreementListStore, 'ContractAgreementListStore');
diff --git a/js/stores/contract_store.js b/js/stores/contract_store.js
deleted file mode 100644
index 623bec6a..00000000
--- a/js/stores/contract_store.js
+++ /dev/null
@@ -1,28 +0,0 @@
-'use strict';
-
-import alt from '../alt';
-import ContractActions from '../actions/contract_actions';
-
-
-class ContractStore {
- constructor() {
- this.contractKey = null;
- this.contractUrl = null;
- this.contractEmail = null;
- this.bindActions(ContractActions);
- }
-
- onUpdateContract({contractKey, contractUrl, contractEmail}) {
- this.contractKey = contractKey;
- this.contractUrl = contractUrl;
- this.contractEmail = contractEmail;
- }
-
- onFlushContract() {
- this.contractKey = null;
- this.contractUrl = null;
- this.contractEmail = null;
- }
-}
-
-export default alt.createStore(ContractStore, 'ContractStore');