mirror of
https://github.com/ascribe/onion.git
synced 2025-02-14 21:10:27 +01:00
Merged in AD-456-ikonotv-branded-page-for-registra (pull request #83)
Ad 456 ikonotv branded page for registra
This commit is contained in:
commit
2205f18089
224
.scss-lint.yml
Normal file
224
.scss-lint.yml
Normal file
@ -0,0 +1,224 @@
|
||||
linters:
|
||||
BangFormat:
|
||||
enabled: true
|
||||
space_before_bang: true
|
||||
space_after_bang: false
|
||||
|
||||
BemDepth:
|
||||
enabled: false
|
||||
max_elements: 1
|
||||
|
||||
BorderZero:
|
||||
enabled: true
|
||||
convention: zero # or `none`
|
||||
|
||||
ColorKeyword:
|
||||
enabled: true
|
||||
|
||||
ColorVariable:
|
||||
enabled: true
|
||||
|
||||
Comment:
|
||||
enabled: true
|
||||
|
||||
DebugStatement:
|
||||
enabled: true
|
||||
|
||||
DeclarationOrder:
|
||||
enabled: true
|
||||
|
||||
DisableLinterReason:
|
||||
enabled: false
|
||||
|
||||
DuplicateProperty:
|
||||
enabled: true
|
||||
|
||||
ElsePlacement:
|
||||
enabled: true
|
||||
style: same_line # or 'new_line'
|
||||
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: true
|
||||
ignore_single_line_blocks: true
|
||||
|
||||
EmptyRule:
|
||||
enabled: true
|
||||
|
||||
ExtendDirective:
|
||||
enabled: false
|
||||
|
||||
FinalNewline:
|
||||
enabled: false
|
||||
present: true
|
||||
|
||||
HexLength:
|
||||
enabled: true
|
||||
style: short # or 'long'
|
||||
|
||||
HexNotation:
|
||||
enabled: true
|
||||
style: lowercase # or 'uppercase'
|
||||
|
||||
HexValidation:
|
||||
enabled: true
|
||||
|
||||
IdSelector:
|
||||
enabled: true
|
||||
|
||||
ImportantRule:
|
||||
enabled: true
|
||||
|
||||
ImportPath:
|
||||
enabled: true
|
||||
leading_underscore: false
|
||||
filename_extension: false
|
||||
|
||||
Indentation:
|
||||
enabled: true
|
||||
allow_non_nested_indentation: false
|
||||
character: space # or 'tab'
|
||||
width: 4
|
||||
|
||||
LeadingZero:
|
||||
enabled: true
|
||||
style: exclude_zero # or 'include_zero'
|
||||
|
||||
MergeableSelector:
|
||||
enabled: true
|
||||
force_nesting: true
|
||||
|
||||
NameFormat:
|
||||
enabled: true
|
||||
allow_leading_underscore: true
|
||||
convention: hyphenated_lowercase # or 'camel_case', or 'snake_case', or a regex pattern
|
||||
|
||||
NestingDepth:
|
||||
enabled: true
|
||||
max_depth: 3
|
||||
ignore_parent_selectors: false
|
||||
|
||||
PlaceholderInExtend:
|
||||
enabled: true
|
||||
|
||||
PropertyCount:
|
||||
enabled: false
|
||||
include_nested: false
|
||||
max_properties: 10
|
||||
|
||||
PropertySortOrder:
|
||||
enabled: false
|
||||
ignore_unspecified: false
|
||||
min_properties: 2
|
||||
separate_groups: false
|
||||
|
||||
PropertySpelling:
|
||||
enabled: true
|
||||
extra_properties: []
|
||||
|
||||
PropertyUnits:
|
||||
enabled: true
|
||||
global: [
|
||||
'ch', 'em', 'ex', 'rem', # Font-relative lengths
|
||||
'cm', 'in', 'mm', 'pc', 'pt', 'px', 'q', # Absolute lengths
|
||||
'vh', 'vw', 'vmin', 'vmax', # Viewport-percentage lengths
|
||||
'deg', 'grad', 'rad', 'turn', # Angle
|
||||
'ms', 's', # Duration
|
||||
'Hz', 'kHz', # Frequency
|
||||
'dpi', 'dpcm', 'dppx', # Resolution
|
||||
'%'] # Other
|
||||
properties: {}
|
||||
|
||||
QualifyingElement:
|
||||
enabled: true
|
||||
allow_element_with_attribute: false
|
||||
allow_element_with_class: false
|
||||
allow_element_with_id: false
|
||||
|
||||
SelectorDepth:
|
||||
enabled: true
|
||||
max_depth: 3
|
||||
|
||||
SelectorFormat:
|
||||
enabled: true
|
||||
convention: hyphenated_lowercase # or 'strict_BEM', or 'hyphenated_BEM', or 'snake_case', or 'camel_case', or a regex pattern
|
||||
|
||||
Shorthand:
|
||||
enabled: true
|
||||
allowed_shorthands: [1, 2, 3]
|
||||
|
||||
SingleLinePerProperty:
|
||||
enabled: true
|
||||
allow_single_line_rule_sets: true
|
||||
|
||||
SingleLinePerSelector:
|
||||
enabled: true
|
||||
|
||||
SpaceAfterComma:
|
||||
enabled: true
|
||||
|
||||
SpaceAfterPropertyColon:
|
||||
enabled: true
|
||||
style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
|
||||
|
||||
SpaceAfterPropertyName:
|
||||
enabled: true
|
||||
|
||||
SpaceAfterVariableName:
|
||||
enabled: true
|
||||
|
||||
SpaceAroundOperator:
|
||||
enabled: true
|
||||
style: one_space # or 'no_space'
|
||||
|
||||
SpaceBeforeBrace:
|
||||
enabled: true
|
||||
style: space # or 'new_line'
|
||||
allow_single_line_padding: false
|
||||
|
||||
SpaceBetweenParens:
|
||||
enabled: true
|
||||
spaces: 0
|
||||
|
||||
StringQuotes:
|
||||
enabled: true
|
||||
style: single_quotes # or double_quotes
|
||||
|
||||
TrailingSemicolon:
|
||||
enabled: true
|
||||
|
||||
TrailingWhitespace:
|
||||
enabled: true
|
||||
|
||||
TrailingZero:
|
||||
enabled: false
|
||||
|
||||
TransitionAll:
|
||||
enabled: false
|
||||
|
||||
UnnecessaryMantissa:
|
||||
enabled: true
|
||||
|
||||
UnnecessaryParentReference:
|
||||
enabled: true
|
||||
|
||||
UrlFormat:
|
||||
enabled: true
|
||||
|
||||
UrlQuotes:
|
||||
enabled: true
|
||||
|
||||
VariableForProperty:
|
||||
enabled: false
|
||||
properties: []
|
||||
|
||||
VendorPrefix:
|
||||
enabled: false
|
||||
identifier_list: base
|
||||
additional_identifiers: []
|
||||
excluded_identifiers: []
|
||||
|
||||
ZeroUnit:
|
||||
enabled: true
|
||||
|
||||
Compass::*:
|
||||
enabled: false
|
15
README.md
15
README.md
@ -32,8 +32,8 @@ Additionally, to work on the white labeling functionality, you need to edit your
|
||||
```
|
||||
|
||||
|
||||
Code Conventions
|
||||
================
|
||||
JavaScript Code Conventions
|
||||
===========================
|
||||
For this project, we're using:
|
||||
|
||||
* 4 Spaces
|
||||
@ -42,6 +42,15 @@ For this project, we're using:
|
||||
* We don't use camel case for file naming but in everything Javascript related
|
||||
* We use `let` instead of `var`: [SA Post](http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword)
|
||||
|
||||
|
||||
SCSS Code Conventions
|
||||
=====================
|
||||
Install [lint-scss](https://github.com/brigade/scss-lint), check the [editor integration docs](https://github.com/brigade/scss-lint#editor-integration) to integrate the lint in your editor.
|
||||
|
||||
Some interesting links:
|
||||
* [Improving Sass code quality on theguardian.com](https://www.theguardian.com/info/developer-blog/2014/may/13/improving-sass-code-quality-on-theguardiancom)
|
||||
|
||||
|
||||
Testing
|
||||
===============
|
||||
We're using Facebook's jest to do testing as it integrates nicely with react.js as well.
|
||||
@ -127,4 +136,4 @@ Moar stuff
|
||||
- [24ways.org: JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/)
|
||||
- [Babel: Learn ES6](https://babeljs.io/docs/learn-es6/)
|
||||
- [egghead's awesome reactjs and flux tutorials](https://egghead.io/)
|
||||
- [Crockford's genious Javascript: The Good Parts (Tim has a copy)](http://www.amazon.de/JavaScript-Parts-Working-Shallow-Grain/dp/0596517742)
|
||||
- [Crockford's genious Javascript: The Good Parts (Tim has a copy)](http://www.amazon.de/JavaScript-Parts-Working-Shallow-Grain/dp/0596517742)
|
||||
|
113
js/actions/contract_agreement_list_actions.js
Normal file
113
js/actions/contract_agreement_list_actions.js
Normal file
@ -0,0 +1,113 @@
|
||||
'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) {
|
||||
this.actions.updateContractAgreementList(null);
|
||||
return Q.Promise((resolve, reject) => {
|
||||
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, createContractAgreement) {
|
||||
return Q.Promise((resolve, reject) => {
|
||||
OwnershipFetcher.fetchContractAgreementList(issuer, true, null)
|
||||
.then((acceptedContractAgreementList) => {
|
||||
// if there is at least an accepted contract agreement, we're going to
|
||||
// use it
|
||||
if(acceptedContractAgreementList.count > 0) {
|
||||
this.actions.updateContractAgreementList(acceptedContractAgreementList.results);
|
||||
} else {
|
||||
// otherwise, we're looking for contract agreements that are still pending
|
||||
//
|
||||
// Normally nesting promises, but for this conditional one, it makes sense to not
|
||||
// overcomplicate the method
|
||||
OwnershipFetcher.fetchContractAgreementList(issuer, null, true)
|
||||
.then((pendingContractAgreementList) => {
|
||||
if(pendingContractAgreementList.count > 0) {
|
||||
this.actions.updateContractAgreementList(pendingContractAgreementList.results);
|
||||
} else {
|
||||
// if there was neither a pending nor an active contractAgreement
|
||||
// found and createContractAgreement is set to true, we create a
|
||||
// new contract agreement
|
||||
if(createContractAgreement) {
|
||||
this.actions.createContractAgreementFromPublicContract(issuer);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
})
|
||||
.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);
|
58
js/actions/contract_list_actions.js
Normal file
58
js/actions/contract_list_actions.js
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
||||
import Q from 'q';
|
||||
|
||||
class ContractListActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'updateContractList',
|
||||
'flushContractList'
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
changeContract(contract){
|
||||
return Q.Promise((resolve, reject) => {
|
||||
OwnershipFetcher.changeContract(contract)
|
||||
.then((res) => {
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err)=> {
|
||||
console.logGlobal(err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeContract(contractId){
|
||||
return Q.Promise( (resolve, reject) => {
|
||||
OwnershipFetcher.deleteContract(contractId)
|
||||
.then((res) => {
|
||||
resolve(res);
|
||||
})
|
||||
.catch( (err) => {
|
||||
console.logGlobal(err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(ContractListActions);
|
@ -1,48 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
||||
|
||||
|
||||
class LoanContractActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'updateLoanContract',
|
||||
'flushLoanContract'
|
||||
);
|
||||
}
|
||||
|
||||
fetchLoanContract(email) {
|
||||
if(email.match(/.+\@.+\..+/)) {
|
||||
OwnershipFetcher.fetchLoanContract(email)
|
||||
.then((contracts) => {
|
||||
if (contracts && contracts.length > 0) {
|
||||
this.actions.updateLoanContract({
|
||||
contractKey: contracts[0].s3Key,
|
||||
contractUrl: contracts[0].s3Url,
|
||||
contractEmail: email
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.actions.updateLoanContract({
|
||||
contractKey: null,
|
||||
contractUrl: null,
|
||||
contractEmail: null
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
this.actions.updateLoanContract({
|
||||
contractKey: null,
|
||||
contractUrl: null,
|
||||
contractEmail: null
|
||||
});
|
||||
});
|
||||
} else {
|
||||
/* No email was entered - Ignore and keep going*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(LoanContractActions);
|
@ -1,27 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
||||
|
||||
|
||||
class LoanContractListActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'updateLoanContractList',
|
||||
'flushLoanContractList'
|
||||
);
|
||||
}
|
||||
|
||||
fetchLoanContractList() {
|
||||
OwnershipFetcher.fetchLoanContractList()
|
||||
.then((contracts) => {
|
||||
this.actions.updateLoanContractList(contracts);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
this.actions.updateLoanContractList([]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(LoanContractListActions);
|
68
js/actions/notification_actions.js
Normal file
68
js/actions/notification_actions.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import Q from 'q';
|
||||
|
||||
import NotificationFetcher from '../fetchers/notification_fetcher';
|
||||
|
||||
class NotificationActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'updatePieceListNotifications',
|
||||
'updateEditionListNotifications',
|
||||
'updateEditionNotifications',
|
||||
'updatePieceNotifications',
|
||||
'updateContractAgreementListNotifications'
|
||||
);
|
||||
}
|
||||
|
||||
fetchPieceListNotifications() {
|
||||
NotificationFetcher
|
||||
.fetchPieceListNotifications()
|
||||
.then((res) => {
|
||||
this.actions.updatePieceListNotifications(res);
|
||||
})
|
||||
.catch((err) => console.logGlobal(err));
|
||||
}
|
||||
|
||||
fetchPieceNotifications(pieceId) {
|
||||
NotificationFetcher
|
||||
.fetchPieceNotifications(pieceId)
|
||||
.then((res) => {
|
||||
this.actions.updatePieceNotifications(res);
|
||||
})
|
||||
.catch((err) => console.logGlobal(err));
|
||||
}
|
||||
|
||||
fetchEditionListNotifications() {
|
||||
NotificationFetcher
|
||||
.fetchEditionListNotifications()
|
||||
.then((res) => {
|
||||
this.actions.updateEditionListNotifications(res);
|
||||
})
|
||||
.catch((err) => console.logGlobal(err));
|
||||
}
|
||||
|
||||
fetchEditionNotifications(editionId) {
|
||||
NotificationFetcher
|
||||
.fetchEditionNotifications(editionId)
|
||||
.then((res) => {
|
||||
this.actions.updateEditionNotifications(res);
|
||||
})
|
||||
.catch((err) => console.logGlobal(err));
|
||||
}
|
||||
|
||||
fetchContractAgreementListNotifications() {
|
||||
return Q.Promise((resolve, reject) => {
|
||||
NotificationFetcher
|
||||
.fetchContractAgreementListNotifications()
|
||||
.then((res) => {
|
||||
this.actions.updateContractAgreementListNotifications(res);
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err) => console.logGlobal(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(NotificationActions);
|
@ -57,7 +57,7 @@ class PieceListActions {
|
||||
PieceListFetcher
|
||||
.fetchRequestActions()
|
||||
.then((res) => {
|
||||
this.actions.updatePieceListRequestActions(res.piece_ids);
|
||||
this.actions.updatePieceListRequestActions(res);
|
||||
})
|
||||
.catch((err) => console.logGlobal(err));
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import EventActions from './actions/event_actions';
|
||||
import GoogleAnalyticsHandler from './third_party/ga';
|
||||
import RavenHandler from './third_party/raven';
|
||||
import IntercomHandler from './third_party/intercom';
|
||||
import NotificationsHandler from './third_party/notifications';
|
||||
/* eslint-enable */
|
||||
|
||||
initLogging();
|
||||
@ -71,8 +72,10 @@ class AppGateway {
|
||||
subdomain = settings.subdomain;
|
||||
}
|
||||
|
||||
window.document.body.classList.add('client--' + subdomain);
|
||||
|
||||
EventActions.applicationWillBoot(settings);
|
||||
Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
|
||||
window.appRouter = Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
|
||||
React.render(
|
||||
<App />,
|
||||
document.getElementById('main')
|
||||
|
@ -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 (
|
||||
<span>
|
||||
{this.props.children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
/* 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 (
|
||||
<span>
|
||||
{this.props.children}
|
||||
</span>
|
||||
);
|
||||
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.');
|
||||
|
@ -21,7 +21,7 @@ let AccordionList = React.createClass({
|
||||
);
|
||||
} else if(this.props.count === 0) {
|
||||
return (
|
||||
<div>
|
||||
<div className="ascribe-accordion-list-placeholder">
|
||||
<p className="text-center">{getLangText('We could not find any works related to you...')}</p>
|
||||
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
||||
</div>
|
||||
|
@ -6,7 +6,6 @@ import classNames from 'classnames';
|
||||
import EditionListActions from '../../actions/edition_list_actions';
|
||||
import EditionListStore from '../../stores/edition_list_store';
|
||||
|
||||
import PieceListActions from '../../actions/piece_list_actions';
|
||||
import PieceListStore from '../../stores/piece_list_store';
|
||||
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
@ -16,6 +15,7 @@ import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
|
||||
let AccordionListItemEditionWidget = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
|
@ -160,7 +160,7 @@ let AccordionListItemTableEditions = React.createClass({
|
||||
let content = item.acl;
|
||||
return {
|
||||
'content': content,
|
||||
'requestAction': item.request_action
|
||||
'notifications': item.notifications
|
||||
}; },
|
||||
'acl',
|
||||
getLangText('Actions'),
|
||||
|
@ -61,12 +61,12 @@ let AccordionListItemWallet = React.createClass({
|
||||
},
|
||||
|
||||
getGlyphicon(){
|
||||
if (this.props.content.requestAction && this.props.content.requestAction.length > 0) {
|
||||
if ((this.props.content.notifications && this.props.content.notifications.length > 0)){
|
||||
return (
|
||||
<OverlayTrigger
|
||||
delay={500}
|
||||
placement="left"
|
||||
overlay={<Tooltip>{getLangText('You have actions pending in one of your editions')}</Tooltip>}>
|
||||
overlay={<Tooltip>{getLangText('You have actions pending')}</Tooltip>}>
|
||||
<Glyphicon glyph='bell' color="green"/>
|
||||
</OverlayTrigger>);
|
||||
}
|
||||
|
@ -162,21 +162,24 @@ let AclButton = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let shouldDisplay = this.props.availableAcls[this.props.action];
|
||||
let aclProps = this.actionProperties();
|
||||
let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : '';
|
||||
return (
|
||||
<ModalWrapper
|
||||
trigger={
|
||||
<button className={shouldDisplay ? 'btn btn-default btn-sm ' + buttonClassName : 'hidden'}>
|
||||
{this.sanitizeAction()}
|
||||
</button>
|
||||
}
|
||||
handleSuccess={aclProps.handleSuccess}
|
||||
title={aclProps.title}>
|
||||
{aclProps.form}
|
||||
</ModalWrapper>
|
||||
);
|
||||
if (this.props.availableAcls){
|
||||
let shouldDisplay = this.props.availableAcls[this.props.action];
|
||||
let aclProps = this.actionProperties();
|
||||
let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : '';
|
||||
return (
|
||||
<ModalWrapper
|
||||
trigger={
|
||||
<button className={shouldDisplay ? 'btn btn-default btn-sm ' + buttonClassName : 'hidden'}>
|
||||
{this.sanitizeAction()}
|
||||
</button>
|
||||
}
|
||||
handleSuccess={aclProps.handleSuccess}
|
||||
title={aclProps.title}>
|
||||
{aclProps.form}
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -17,7 +17,7 @@ const CollapsibleParagraph = React.createClass({
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
show: false
|
||||
show: true
|
||||
};
|
||||
},
|
||||
|
||||
@ -38,14 +38,14 @@ const CollapsibleParagraph = React.createClass({
|
||||
if(this.props.show) {
|
||||
return (
|
||||
<div className="ascribe-detail-header">
|
||||
<div className="ascribe-edition-collapsible-wrapper">
|
||||
<div className="ascribe-collapsible-wrapper">
|
||||
<div onClick={this.handleToggle}>
|
||||
<span>{text} {this.props.title}</span>
|
||||
</div>
|
||||
<Panel
|
||||
collapsible
|
||||
expanded={this.state.expanded}
|
||||
className="ascribe-edition-collapsible-content">
|
||||
className="ascribe-collapsible-content">
|
||||
{this.props.children}
|
||||
</Panel>
|
||||
</div>
|
||||
|
@ -88,10 +88,8 @@ let Edition = React.createClass({
|
||||
},
|
||||
|
||||
handleDeleteSuccess(response) {
|
||||
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
|
||||
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
|
||||
this.refreshCollection();
|
||||
|
||||
EditionListActions.refreshEditionList({pieceId: this.props.edition.parent});
|
||||
EditionListActions.closeAllEditionLists();
|
||||
EditionListActions.clearAllEditionSelections();
|
||||
|
||||
@ -101,6 +99,13 @@ let Edition = React.createClass({
|
||||
this.transitionTo('pieces');
|
||||
},
|
||||
|
||||
refreshCollection() {
|
||||
console.log('freshing');
|
||||
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});
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Row>
|
||||
@ -118,6 +123,7 @@ let Edition = React.createClass({
|
||||
</div>
|
||||
<EditionSummary
|
||||
handleSuccess={this.props.loadEdition}
|
||||
refreshCollection={this.refreshCollection}
|
||||
currentUser={this.state.currentUser}
|
||||
edition={this.props.edition}
|
||||
handleDeleteSuccess={this.handleDeleteSuccess}/>
|
||||
@ -152,8 +158,9 @@ let Edition = React.createClass({
|
||||
|
||||
<CollapsibleParagraph
|
||||
title="Notes"
|
||||
show={(this.state.currentUser.username && true || false) ||
|
||||
(this.props.edition.acl.acl_edit || this.props.edition.public_note)}>
|
||||
show={!!(this.state.currentUser.username
|
||||
|| this.props.edition.acl.acl_edit
|
||||
|| this.props.edition.public_note)}>
|
||||
<Note
|
||||
id={() => {return {'bitcoin_id': this.props.edition.bitcoin_id}; }}
|
||||
label={getLangText('Personal note (private)')}
|
||||
@ -205,14 +212,22 @@ let EditionSummary = React.createClass({
|
||||
edition: React.PropTypes.object,
|
||||
handleSuccess: React.PropTypes.func,
|
||||
currentUser: React.PropTypes.object,
|
||||
handleDeleteSuccess: React.PropTypes.func
|
||||
handleDeleteSuccess: React.PropTypes.func,
|
||||
refreshCollection: React.PropTypes.func
|
||||
},
|
||||
|
||||
getTransferWithdrawData(){
|
||||
return {'bitcoin_id': this.props.edition.bitcoin_id};
|
||||
},
|
||||
|
||||
handleSuccess() {
|
||||
this.props.refreshCollection();
|
||||
this.props.handleSuccess();
|
||||
},
|
||||
|
||||
showNotification(response){
|
||||
this.props.handleSuccess();
|
||||
|
||||
if (response){
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
@ -234,13 +249,15 @@ let EditionSummary = React.createClass({
|
||||
|
||||
getActions(){
|
||||
let actions = null;
|
||||
if (this.props.edition.request_action && this.props.edition.request_action.length > 0){
|
||||
if (this.props.edition &&
|
||||
this.props.edition.notifications &&
|
||||
this.props.edition.notifications.length > 0){
|
||||
actions = (
|
||||
<ListRequestActions
|
||||
pieceOrEditions={[this.props.edition]}
|
||||
currentUser={this.props.currentUser}
|
||||
handleSuccess={this.showNotification}
|
||||
requestActions={this.props.edition.request_action}/>);
|
||||
notifications={this.props.edition.notifications}/>);
|
||||
}
|
||||
|
||||
else {
|
||||
@ -275,7 +292,7 @@ let EditionSummary = React.createClass({
|
||||
className="text-center ascribe-button-list"
|
||||
availableAcls={this.props.edition.acl}
|
||||
editions={[this.props.edition]}
|
||||
handleSuccess={this.props.handleSuccess}>
|
||||
handleSuccess={this.handleSuccess}>
|
||||
{withdrawButton}
|
||||
<DeleteButton
|
||||
handleSuccess={this.props.handleDeleteSuccess}
|
||||
@ -305,7 +322,6 @@ let EditionSummary = React.createClass({
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -9,6 +9,8 @@ import Edition from './edition';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is the component that implements resource/data specific functionality
|
||||
*/
|
||||
@ -34,6 +36,15 @@ let EditionContainer = React.createClass({
|
||||
EditionActions.fetchOne(this.props.params.editionId);
|
||||
},
|
||||
|
||||
// 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.updateEdition({});
|
||||
EditionActions.fetchOne(nextProps.params.editionId);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
// Every time we're leaving the edition detail page,
|
||||
// just reset the edition that is saved in the edition store
|
||||
|
@ -15,7 +15,7 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
||||
|
||||
import FurtherDetailsFileuploader from './further_details_fileuploader';
|
||||
|
||||
import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||
|
||||
let FurtherDetails = React.createClass({
|
||||
propTypes: {
|
||||
@ -38,9 +38,9 @@ let FurtherDetails = React.createClass({
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
submitKey(key){
|
||||
submitFile(file){
|
||||
this.setState({
|
||||
otherDataKey: key
|
||||
otherDataKey: file.key
|
||||
});
|
||||
},
|
||||
|
||||
@ -78,9 +78,9 @@ let FurtherDetails = React.createClass({
|
||||
extraData={this.props.extraData} />
|
||||
<Form>
|
||||
<FurtherDetailsFileuploader
|
||||
submitKey={this.submitKey}
|
||||
submitFile={this.submitFile}
|
||||
setIsUploadReady={this.setIsUploadReady}
|
||||
isReadyForFormSubmission={isReadyForFormSubmission}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
editable={this.props.editable}
|
||||
overrideForm={true}
|
||||
pieceId={this.props.pieceId}
|
||||
|
@ -17,7 +17,7 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
pieceId: React.PropTypes.number,
|
||||
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
setIsUploadReady: React.PropTypes.func,
|
||||
submitKey: React.PropTypes.func,
|
||||
submitFile: React.PropTypes.func,
|
||||
isReadyForFormSubmission: React.PropTypes.func,
|
||||
editable: React.PropTypes.bool,
|
||||
multiple: React.PropTypes.bool
|
||||
@ -55,11 +55,8 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
url: ApiUrls.blob_otherdatas,
|
||||
pieceId: this.props.pieceId
|
||||
}}
|
||||
validation={{
|
||||
itemLimit: 100000,
|
||||
sizeLimit: '50000000'
|
||||
}}
|
||||
submitKey={this.props.submitKey}
|
||||
validation={AppConstants.fineUploader.validation.additionalData}
|
||||
submitFile={this.props.submitFile}
|
||||
setIsUploadReady={this.props.setIsUploadReady}
|
||||
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
|
||||
session={{
|
||||
|
@ -39,19 +39,18 @@ let Note = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
if (!!this.props.currentUser.username && this.props.show) {
|
||||
if ((!!this.props.currentUser.username && this.props.editable || !this.props.editable ) && this.props.show) {
|
||||
return (
|
||||
<Form
|
||||
url={this.props.url}
|
||||
getFormData={this.props.id}
|
||||
handleSuccess={this.showNotification}>
|
||||
handleSuccess={this.showNotification}
|
||||
disabled={!this.props.editable}>
|
||||
<Property
|
||||
name='note'
|
||||
label={this.props.label}
|
||||
editable={this.props.editable}>
|
||||
label={this.props.label}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={this.props.editable}
|
||||
defaultValue={this.props.defaultValue}
|
||||
placeholder={this.props.placeholder}/>
|
||||
</Property>
|
||||
@ -63,4 +62,4 @@ let Note = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
export default Note
|
||||
export default Note;
|
@ -172,17 +172,16 @@ let PieceContainer = React.createClass({
|
||||
return {'id': this.state.piece.id};
|
||||
},
|
||||
|
||||
getActions(){
|
||||
getActions() {
|
||||
if (this.state.piece &&
|
||||
this.state.piece.request_action &&
|
||||
this.state.piece.request_action.length > 0) {
|
||||
this.state.piece.notifications &&
|
||||
this.state.piece.notifications.length > 0) {
|
||||
return (
|
||||
<ListRequestActions
|
||||
pieceOrEditions={this.state.piece}
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.loadPiece}
|
||||
requestActions={this.state.piece.request_action}/>
|
||||
);
|
||||
notifications={this.state.piece.notifications}/>);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
@ -238,12 +237,11 @@ let PieceContainer = React.createClass({
|
||||
</CollapsibleParagraph>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Notes')}
|
||||
show={(this.state.currentUser.username && true || false) ||
|
||||
(this.state.piece.public_note)}>
|
||||
show={!!(this.state.currentUser.username || this.state.piece.public_note)}>
|
||||
<Note
|
||||
id={this.getId}
|
||||
label={getLangText('Personal note (private)')}
|
||||
defaultValue={this.state.piece.private_note ? this.state.piece.private_note : null}
|
||||
defaultValue={this.state.piece.private_note || null}
|
||||
placeholder={getLangText('Enter your comments ...')}
|
||||
editable={true}
|
||||
successMessage={getLangText('Private note saved')}
|
||||
|
@ -1,141 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import LoanContractListActions from '../../actions/loan_contract_list_actions';
|
||||
import LoanContractListStore from '../../stores/loan_contract_list_store';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import Form from './form';
|
||||
import Property from './property';
|
||||
import PropertyCollapsible from './property_collapsible';
|
||||
import InputTextAreaToggable from './input_textarea_toggable';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
|
||||
|
||||
let ContractForm = React.createClass({
|
||||
propTypes: {
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
LoanContractListStore.getState(),
|
||||
{
|
||||
selectedContract: 0
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
LoanContractListStore.listen(this.onChange);
|
||||
LoanContractListActions.fetchLoanContractList();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
LoanContractListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
onContractChange(event){
|
||||
this.setState({selectedContract: event.target.selectedIndex});
|
||||
},
|
||||
|
||||
handleSubmitSuccess(response) {
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
getContracts() {
|
||||
if (this.state.contractList && this.state.contractList.length > 0) {
|
||||
return (
|
||||
<Property
|
||||
name='contract'
|
||||
label={getLangText('Contract Type')}
|
||||
onChange={this.onContractChange}
|
||||
footer={
|
||||
<a
|
||||
className="pull-right"
|
||||
href={this.state.contractList[this.state.selectedContract].s3UrlSafe}
|
||||
target="_blank">
|
||||
{getLangText('Learn more')}
|
||||
</a>
|
||||
}>
|
||||
<select name="contract">
|
||||
{this.state.contractList.map((contract, i) => {
|
||||
return (
|
||||
<option
|
||||
name={i}
|
||||
key={i}
|
||||
value={ contract.name }>
|
||||
{ contract.name }
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</Property>);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Form
|
||||
className="ascribe-form-bordered ascribe-form-wrapper"
|
||||
ref='form'
|
||||
url={ApiUrls.ownership_loans_contract}
|
||||
handleSuccess={this.props.handleSuccess}
|
||||
buttons={<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
{getLangText('Send loan request')}
|
||||
</button>}
|
||||
spinner={
|
||||
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
|
||||
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
|
||||
</span>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
<h3>{getLangText('Contract form')}</h3>
|
||||
</div>
|
||||
<Property
|
||||
name='artist_name'
|
||||
label={getLangText('Artist Name')}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={getLangText('(e.g. Andy Warhol)')}
|
||||
required/>
|
||||
</Property>
|
||||
<Property
|
||||
name='artist_email'
|
||||
label={getLangText('Artist Email')}>
|
||||
<input
|
||||
type="email"
|
||||
placeholder={getLangText('(e.g. andy@warhol.co.uk)')}
|
||||
required/>
|
||||
</Property>
|
||||
{this.getContracts()}
|
||||
<PropertyCollapsible
|
||||
name='appendix'
|
||||
checkboxLabel={getLangText('Add appendix to the contract')}>
|
||||
<span>{getLangText('Appendix')}</span>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
placeholder={getLangText('This will be appended to the contract selected above')}/>
|
||||
</PropertyCollapsible>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ContractForm;
|
@ -40,6 +40,12 @@ let CreateEditionsForm = React.createClass({
|
||||
url={ApiUrls.editions}
|
||||
getFormData={this.getFormData}
|
||||
handleSuccess={this.handleSuccess}
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
{getLangText('Create editions')}
|
||||
</button>}
|
||||
spinner={
|
||||
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
|
||||
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
|
||||
|
@ -100,6 +100,20 @@ let Form = React.createClass({
|
||||
.catch(this.handleError);
|
||||
},
|
||||
|
||||
put() {
|
||||
requests
|
||||
.put(this.props.url, { body: this.getFormData() })
|
||||
.then(this.handleSuccess)
|
||||
.catch(this.handleError);
|
||||
},
|
||||
|
||||
patch() {
|
||||
requests
|
||||
.patch(this.props.url, { body: this.getFormData() })
|
||||
.then(this.handleSuccess)
|
||||
.catch(this.handleError);
|
||||
},
|
||||
|
||||
delete() {
|
||||
requests
|
||||
.delete(this.props.url, this.getFormData())
|
||||
@ -189,7 +203,7 @@ let Form = React.createClass({
|
||||
}
|
||||
let buttons = null;
|
||||
|
||||
if (this.state.edited){
|
||||
if (this.state.edited && !this.props.disabled){
|
||||
buttons = (
|
||||
<div className="row" style={{margin: 0}}>
|
||||
<p className="pull-right">
|
||||
|
@ -56,10 +56,10 @@ let ConsignForm = React.createClass({
|
||||
<Property
|
||||
name='consign_message'
|
||||
label={getLangText('Personal Message')}
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
defaultValue={this.props.message}
|
||||
placeholder={getLangText('Enter a message...')}
|
||||
required="required"/>
|
||||
|
149
js/components/ascribe_forms/form_contract_agreement.js
Normal file
149
js/components/ascribe_forms/form_contract_agreement.js
Normal file
@ -0,0 +1,149 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import ContractListActions from '../../actions/contract_list_actions';
|
||||
import ContractListStore from '../../stores/contract_list_store';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import Form from './form';
|
||||
import Property from './property';
|
||||
import PropertyCollapsible from './property_collapsible';
|
||||
import InputTextAreaToggable from './input_textarea_toggable';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
|
||||
|
||||
let ContractAgreementForm = React.createClass({
|
||||
propTypes: {
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
ContractListStore.getState(),
|
||||
{
|
||||
selectedContract: 0
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
ContractListStore.listen(this.onChange);
|
||||
ContractListActions.fetchContractList(true, false);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
ContractListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
onContractChange(event){
|
||||
this.setState({selectedContract: event.target.selectedIndex});
|
||||
},
|
||||
|
||||
handleSubmitSuccess() {
|
||||
let notification = 'Contract agreement send';
|
||||
notification = new GlobalNotificationModel(notification, 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
this.transitionTo('pieces');
|
||||
},
|
||||
|
||||
getFormData(){
|
||||
return {'appendix': {'default': this.refs.form.refs.appendix.state.value}};
|
||||
},
|
||||
|
||||
getContracts() {
|
||||
if (this.state.contractList && this.state.contractList.length > 0) {
|
||||
let contractList = this.state.contractList;
|
||||
return (
|
||||
<Property
|
||||
name='contract'
|
||||
label={getLangText('Contract Type')}
|
||||
onChange={this.onContractChange}>
|
||||
<select name="contract">
|
||||
{contractList.map((contract, i) => {
|
||||
return (
|
||||
<option
|
||||
name={i}
|
||||
key={i}
|
||||
value={ contract.id }>
|
||||
{ contract.name }
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</Property>);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.state.contractList && this.state.contractList.length > 0) {
|
||||
return (
|
||||
<Form
|
||||
className="ascribe-form-bordered ascribe-form-wrapper"
|
||||
ref='form'
|
||||
url={ApiUrls.ownership_contract_agreements}
|
||||
getFormData={this.getFormData}
|
||||
handleSuccess={this.handleSubmitSuccess}
|
||||
buttons={<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
{getLangText('Send contract')}
|
||||
</button>}
|
||||
spinner={
|
||||
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
|
||||
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
|
||||
</span>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
<h3>{getLangText('Contract form')}</h3>
|
||||
</div>
|
||||
<Property
|
||||
name='signee'
|
||||
label={getLangText('Artist Email')}>
|
||||
<input
|
||||
type="email"
|
||||
placeholder={getLangText('(e.g. andy@warhol.co.uk)')}
|
||||
required/>
|
||||
</Property>
|
||||
{this.getContracts()}
|
||||
<PropertyCollapsible
|
||||
name='appendix'
|
||||
checkboxLabel={getLangText('Add appendix to the contract')}>
|
||||
<span>{getLangText('Appendix')}</span>
|
||||
{/* We're using disabled on a form here as PropertyCollapsible currently
|
||||
does not support the disabled + overrideForm functionality */}
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
disabled={false}
|
||||
placeholder={getLangText('This will be appended to the contract selected above')}/>
|
||||
</PropertyCollapsible>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<p className="text-center">
|
||||
{getLangText('No contracts uploaded yet, please go to the ')}
|
||||
<a href="settings">{getLangText('settings page')}</a>
|
||||
{getLangText(' and create them.')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ContractAgreementForm;
|
80
js/components/ascribe_forms/form_copyright_association.js
Normal file
80
js/components/ascribe_forms/form_copyright_association.js
Normal file
@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import Form from './form';
|
||||
import Property from './property';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let CopyrightAssociationForm = React.createClass({
|
||||
propTypes: {
|
||||
currentUser: React.PropTypes.object
|
||||
},
|
||||
|
||||
handleSubmitSuccess(){
|
||||
let notification = getLangText('Copyright association updated');
|
||||
notification = new GlobalNotificationModel(notification, 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
getProfileFormData(){
|
||||
return {email: this.props.currentUser.email};
|
||||
},
|
||||
|
||||
render() {
|
||||
let selectedState;
|
||||
let selectDefaultValue = ' -- ' + getLangText('select an association') + ' -- ';
|
||||
|
||||
if (this.props.currentUser && this.props.currentUser.profile
|
||||
&& this.props.currentUser.profile.copyright_association) {
|
||||
selectedState = AppConstants.copyrightAssociations.indexOf(this.props.currentUser.profile.copyright_association);
|
||||
selectedState = selectedState !== -1 ? AppConstants.copyrightAssociations[selectedState] : selectDefaultValue;
|
||||
}
|
||||
|
||||
if (this.props.currentUser && this.props.currentUser.email){
|
||||
return (
|
||||
<Form
|
||||
ref='form'
|
||||
url={ApiUrls.users_profile}
|
||||
getFormData={this.getProfileFormData}
|
||||
handleSuccess={this.handleSubmitSuccess}>
|
||||
<Property
|
||||
name="copyright_association"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
label={getLangText('Copyright Association')}
|
||||
style={{paddingBottom: 0}}>
|
||||
<select defaultValue={selectedState} name="contract">
|
||||
<option
|
||||
name={0}
|
||||
key={0}
|
||||
value={selectDefaultValue}>
|
||||
{selectDefaultValue}
|
||||
</option>
|
||||
{AppConstants.copyrightAssociations.map((association, i) => {
|
||||
return (
|
||||
<option
|
||||
name={i + 1}
|
||||
key={i + 1}
|
||||
value={association}>
|
||||
{ association }
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
export default CopyrightAssociationForm;
|
111
js/components/ascribe_forms/form_create_contract.js
Normal file
111
js/components/ascribe_forms/form_create_contract.js
Normal file
@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Form from '../ascribe_forms/form';
|
||||
import Property from '../ascribe_forms/property';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import ContractListActions from '../../actions/contract_list_actions';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
import InputFineUploader from './input_fineuploader';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||
|
||||
|
||||
let CreateContractForm = React.createClass({
|
||||
propTypes: {
|
||||
isPublic: React.PropTypes.bool,
|
||||
|
||||
// A class of a file the user has to upload
|
||||
// Needs to be defined both in singular as well as in plural
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
})
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
isUploadReady: false,
|
||||
contractName: ''
|
||||
};
|
||||
},
|
||||
|
||||
setIsUploadReady(isReady) {
|
||||
this.setState({
|
||||
isUploadReady: isReady
|
||||
});
|
||||
},
|
||||
|
||||
handleCreateSuccess(response) {
|
||||
ContractListActions.fetchContractList(true);
|
||||
let notification = new GlobalNotificationModel(getLangText('Contract %s successfully created', response.name), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
this.refs.form.reset();
|
||||
},
|
||||
|
||||
submitFileName(fileName) {
|
||||
this.setState({
|
||||
contractName: fileName
|
||||
});
|
||||
|
||||
this.refs.form.submit();
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Form
|
||||
ref='form'
|
||||
url={ApiUrls.ownership_contract_list}
|
||||
handleSuccess={this.handleCreateSuccess}>
|
||||
<Property
|
||||
name="blob"
|
||||
label={getLangText('Contract file (*.pdf only, max. 50MB per contract)')}>
|
||||
<InputFineUploader
|
||||
submitFileName={this.submitFileName}
|
||||
keyRoutine={{
|
||||
url: AppConstants.serverUrl + 's3/key/',
|
||||
fileClass: 'contract'
|
||||
}}
|
||||
createBlobRoutine={{
|
||||
url: ApiUrls.blob_contracts
|
||||
}}
|
||||
validation={{
|
||||
itemLimit: AppConstants.fineUploader.validation.additionalData.itemLimit,
|
||||
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
|
||||
allowedExtensions: ['pdf']
|
||||
}}
|
||||
areAssetsDownloadable={true}
|
||||
areAssetsEditable={true}
|
||||
setIsUploadReady={this.setIsUploadReady}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
fileClassToUpload={this.props.fileClassToUpload}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='name'
|
||||
label={getLangText('Contract name')}
|
||||
hidden={true}>
|
||||
<input
|
||||
type="text"
|
||||
value={this.state.contractName}/>
|
||||
</Property>
|
||||
<Property
|
||||
name="is_public"
|
||||
hidden={true}>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={this.props.isPublic} />
|
||||
</Property>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default CreateContractForm;
|
@ -12,11 +12,12 @@ import InputTextAreaToggable from './input_textarea_toggable';
|
||||
import InputDate from './input_date';
|
||||
import InputCheckbox from './input_checkbox';
|
||||
|
||||
import LoanContractStore from '../../stores/loan_contract_store';
|
||||
import LoanContractActions from '../../actions/loan_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';
|
||||
|
||||
|
||||
@ -34,6 +35,7 @@ let LoanForm = React.createClass({
|
||||
url: React.PropTypes.string,
|
||||
id: React.PropTypes.object,
|
||||
message: React.PropTypes.string,
|
||||
createPublicContractAgreement: React.PropTypes.bool,
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
@ -43,62 +45,117 @@ let LoanForm = React.createClass({
|
||||
showPersonalMessage: true,
|
||||
showEndDate: true,
|
||||
showStartDate: true,
|
||||
showPassword: true
|
||||
showPassword: true,
|
||||
createPublicContractAgreement: true
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return LoanContractStore.getState();
|
||||
return ContractAgreementListStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
LoanContractStore.listen(this.onChange);
|
||||
LoanContractActions.flushLoanContract.defer();
|
||||
ContractAgreementListStore.listen(this.onChange);
|
||||
this.getContractAgreementsOrCreatePublic(this.props.email);
|
||||
},
|
||||
|
||||
/**
|
||||
* This method needs to be in form_loan as some whitelabel pages (Cyland) load
|
||||
* the loanee's email async!
|
||||
*
|
||||
* SO LEAVE IT IN!
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(nextProps && nextProps.email && this.props.email !== nextProps.email) {
|
||||
this.getContractAgreementsOrCreatePublic(nextProps.email);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
LoanContractStore.unlisten(this.onChange);
|
||||
ContractAgreementListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
getContractAgreementsOrCreatePublic(email){
|
||||
ContractAgreementListActions.flushContractAgreementList.defer();
|
||||
if (email) {
|
||||
// fetch the available contractagreements (pending/accepted)
|
||||
ContractAgreementListActions.fetchAvailableContractAgreementList(email, true);
|
||||
}
|
||||
},
|
||||
|
||||
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(/.*@.*/)) {
|
||||
LoanContractActions.fetchLoanContract(event.target.value);
|
||||
if(event && event.target && event.target.value && event.target.value.match(/.*@.*\..*/)) {
|
||||
this.getContractAgreementsOrCreatePublic(event.target.value);
|
||||
} else {
|
||||
LoanContractActions.flushLoanContract();
|
||||
ContractAgreementListActions.flushContractAgreementList();
|
||||
}
|
||||
},
|
||||
|
||||
getContractAgreementId() {
|
||||
if (this.state.contractAgreementList && this.state.contractAgreementList.length > 0) {
|
||||
return {'contract_agreement_id': this.state.contractAgreementList[0].id};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
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)
|
||||
return (
|
||||
<Property
|
||||
name="terms"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox
|
||||
key="terms_explicitly"
|
||||
defaultChecked={false}>
|
||||
<span>
|
||||
{getLangText('I agree to the')}
|
||||
<a href={this.state.contractUrl} target="_blank">
|
||||
{getLangText('terms of')} {this.state.contractEmail}
|
||||
</a>
|
||||
</span>
|
||||
</InputCheckbox>
|
||||
</Property>
|
||||
);
|
||||
let contractAgreement = this.state.contractAgreementList[0];
|
||||
let contract = contractAgreement.contract;
|
||||
|
||||
if(contractAgreement.datetime_accepted) {
|
||||
return (
|
||||
<Property
|
||||
name="terms"
|
||||
label={getLangText('Loan Contract')}
|
||||
hidden={false}
|
||||
className="notification-contract-pdf">
|
||||
<embed
|
||||
className="loan-form"
|
||||
src={contract.blob.url_safe}
|
||||
alt="pdf"
|
||||
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
|
||||
{/* We still need to send the server information that we're accepting */}
|
||||
<InputCheckbox
|
||||
style={{'display': 'none'}}
|
||||
key="terms_implicitly"
|
||||
defaultChecked={true} />
|
||||
</Property>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Property
|
||||
name="terms"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox
|
||||
key="terms_explicitly"
|
||||
defaultChecked={false}>
|
||||
<span>
|
||||
{getLangText('I agree to the')}
|
||||
<a href={contract.blob.url_safe} target="_blank">
|
||||
{getLangText('terms of ')} {contract.issuer}
|
||||
</a>
|
||||
</span>
|
||||
</InputCheckbox>
|
||||
</Property>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<Property
|
||||
@ -113,6 +170,22 @@ let LoanForm = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
getAppendix() {
|
||||
if(this.state.contractAgreementList && this.state.contractAgreementList.length > 0) {
|
||||
let appendix = this.state.contractAgreementList[0].appendix;
|
||||
if (appendix && appendix.default) {
|
||||
return (
|
||||
<Property
|
||||
name='appendix'
|
||||
label={getLangText('Appendix')}>
|
||||
<pre className="ascribe-pre">{appendix.default}</pre>
|
||||
</Property>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getButtons() {
|
||||
if(this.props.loanHeading) {
|
||||
return (
|
||||
@ -157,8 +230,8 @@ let LoanForm = React.createClass({
|
||||
<Property
|
||||
name='loanee'
|
||||
label={getLangText('Loanee Email')}
|
||||
onChange={this.handleOnChange}
|
||||
editable={!this.props.email}
|
||||
onChange={this.handleOnChange}
|
||||
overrideForm={!!this.props.email}>
|
||||
<input
|
||||
value={this.props.email}
|
||||
@ -200,14 +273,16 @@ let LoanForm = React.createClass({
|
||||
name='loan_message'
|
||||
label={getLangText('Personal Message')}
|
||||
editable={true}
|
||||
overrideForm={true}
|
||||
hidden={!this.props.showPersonalMessage}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
defaultValue={this.props.message}
|
||||
placeholder={getLangText('Enter a message...')}
|
||||
required={this.props.showPersonalMessage ? 'required' : ''}/>
|
||||
</Property>
|
||||
{this.getContractCheckbox()}
|
||||
{this.getAppendix()}
|
||||
<Property
|
||||
name='password'
|
||||
label={getLangText('Password')}
|
||||
@ -217,7 +292,6 @@ let LoanForm = React.createClass({
|
||||
placeholder={getLangText('Enter your password')}
|
||||
required={this.props.showPassword ? 'required' : ''}/>
|
||||
</Property>
|
||||
{this.getContractCheckbox()}
|
||||
{this.props.children}
|
||||
</Form>
|
||||
);
|
||||
|
@ -18,7 +18,7 @@ let LoanRequestAnswerForm = React.createClass({
|
||||
url: React.PropTypes.string,
|
||||
id: React.PropTypes.object,
|
||||
message: React.PropTypes.string,
|
||||
handleSuccess: React.PropTypes.func.required
|
||||
handleSuccess: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
|
@ -27,7 +27,7 @@ let LoginForm = React.createClass({
|
||||
onLogin: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -95,6 +95,7 @@ let LoginForm = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let email = this.getQuery().email || null;
|
||||
return (
|
||||
<Form
|
||||
className="ascribe-form-bordered"
|
||||
@ -122,7 +123,8 @@ let LoginForm = React.createClass({
|
||||
<input
|
||||
type="email"
|
||||
placeholder={getLangText('Enter your email')}
|
||||
name="username"
|
||||
name="email"
|
||||
defaultValue={email}
|
||||
required/>
|
||||
</Property>
|
||||
<Property
|
||||
|
@ -41,14 +41,13 @@ let PieceExtraDataForm = React.createClass({
|
||||
ref='form'
|
||||
url={url}
|
||||
handleSuccess={this.props.handleSuccess}
|
||||
getFormData={this.getFormData}>
|
||||
getFormData={this.getFormData}
|
||||
disabled={!this.props.editable}>
|
||||
<Property
|
||||
name={this.props.name}
|
||||
label={this.props.title}
|
||||
editable={this.props.editable}>
|
||||
label={this.props.title}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={this.props.editable}
|
||||
defaultValue={defaultValue}
|
||||
placeholder={getLangText('Fill in%s', ' ') + this.props.title}
|
||||
required="required"/>
|
||||
|
@ -10,10 +10,11 @@ import Property from './property';
|
||||
import InputFineUploader from './input_fineuploader';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||
|
||||
|
||||
let RegisterPieceForm = React.createClass({
|
||||
@ -99,11 +100,19 @@ let RegisterPieceForm = React.createClass({
|
||||
name="digital_work_key"
|
||||
ignoreFocus={true}>
|
||||
<InputFineUploader
|
||||
keyRoutine={{
|
||||
url: AppConstants.serverUrl + 's3/key/',
|
||||
fileClass: 'digitalwork'
|
||||
}}
|
||||
createBlobRoutine={{
|
||||
url: ApiUrls.blob_digitalworks
|
||||
}}
|
||||
validation={AppConstants.fineUploader.validation.registerWork}
|
||||
setIsUploadReady={this.setIsUploadReady}
|
||||
isReadyForFormSubmission={isReadyForFormSubmission}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
isFineUploaderActive={this.props.isFineUploaderActive}
|
||||
onLoggedOut={this.props.onLoggedOut}
|
||||
editable={this.props.isFineUploaderEditable}
|
||||
disabled={!this.props.isFineUploaderEditable}
|
||||
enableLocalHashing={enableLocalHashing}/>
|
||||
</Property>
|
||||
<Property
|
||||
|
@ -6,6 +6,8 @@ import AclButton from './../ascribe_buttons/acl_button';
|
||||
import ActionPanel from '../ascribe_panel/action_panel';
|
||||
import Form from './form';
|
||||
|
||||
import NotificationActions from '../../actions/notification_actions';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
@ -20,8 +22,7 @@ let RequestActionForm = React.createClass({
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.array
|
||||
]).isRequired,
|
||||
requestAction: React.PropTypes.string,
|
||||
requestUser: React.PropTypes.string,
|
||||
notifications: React.PropTypes.object,
|
||||
currentUser: React.PropTypes.object,
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
@ -33,19 +34,19 @@ let RequestActionForm = React.createClass({
|
||||
getUrls() {
|
||||
let urls = {};
|
||||
|
||||
if (this.props.requestAction === 'consign'){
|
||||
if (this.props.notifications.action === 'consign'){
|
||||
urls.accept = ApiUrls.ownership_consigns_confirm;
|
||||
urls.deny = ApiUrls.ownership_consigns_deny;
|
||||
} else if (this.props.requestAction === 'unconsign'){
|
||||
} else if (this.props.notifications.action === 'unconsign'){
|
||||
urls.accept = ApiUrls.ownership_unconsigns;
|
||||
urls.deny = ApiUrls.ownership_unconsigns_deny;
|
||||
} else if (this.props.requestAction === 'loan' && !this.isPiece()){
|
||||
} else if (this.props.notifications.action === 'loan' && !this.isPiece()){
|
||||
urls.accept = ApiUrls.ownership_loans_confirm;
|
||||
urls.deny = ApiUrls.ownership_loans_deny;
|
||||
} else if (this.props.requestAction === 'loan' && this.isPiece()){
|
||||
} else if (this.props.notifications.action === 'loan' && this.isPiece()){
|
||||
urls.accept = ApiUrls.ownership_loans_pieces_confirm;
|
||||
urls.deny = ApiUrls.ownership_loans_pieces_deny;
|
||||
} else if (this.props.requestAction === 'loan_request' && this.isPiece()){
|
||||
} else if (this.props.notifications.action === 'loan_request' && this.isPiece()){
|
||||
urls.accept = ApiUrls.ownership_loans_pieces_request_confirm;
|
||||
urls.deny = ApiUrls.ownership_loans_pieces_request_deny;
|
||||
}
|
||||
@ -68,30 +69,36 @@ let RequestActionForm = React.createClass({
|
||||
return () => {
|
||||
let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner;
|
||||
|
||||
let notification = new GlobalNotificationModel(message, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
let notifications = new GlobalNotificationModel(message, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notifications);
|
||||
|
||||
this.handleSuccess();
|
||||
|
||||
if(this.props.handleSuccess) {
|
||||
this.props.handleSuccess();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getContent() {
|
||||
let pieceOrEditionStr = this.isPiece() ? getLangText('this work%s', '.') : getLangText('this edition%s', '.');
|
||||
let message = this.props.requestUser + ' ' + getLangText('requests you') + ' ' + this.props.requestAction + ' ' + pieceOrEditionStr;
|
||||
if (this.props.requestAction === 'loan_request'){
|
||||
message = this.props.requestUser + ' ' + getLangText('requests you to loan') + ' ' + pieceOrEditionStr;
|
||||
handleSuccess() {
|
||||
if (this.isPiece()){
|
||||
NotificationActions.fetchPieceListNotifications();
|
||||
}
|
||||
else {
|
||||
NotificationActions.fetchEditionListNotifications();
|
||||
}
|
||||
if(this.props.handleSuccess) {
|
||||
this.props.handleSuccess();
|
||||
}
|
||||
},
|
||||
|
||||
getContent() {
|
||||
return (
|
||||
<span>
|
||||
{message}
|
||||
{this.props.notifications.action_str + ' by ' + this.props.notifications.by}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
||||
getAcceptButtonForm(urls) {
|
||||
if(this.props.requestAction === 'unconsign') {
|
||||
if(this.props.notifications.action === 'unconsign') {
|
||||
return (
|
||||
<AclButton
|
||||
availableAcls={{'acl_unconsign': true}}
|
||||
@ -99,9 +106,9 @@ let RequestActionForm = React.createClass({
|
||||
buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px'
|
||||
pieceOrEditions={this.props.pieceOrEditions}
|
||||
currentUser={this.props.currentUser}
|
||||
handleSuccess={this.props.handleSuccess} />
|
||||
handleSuccess={this.handleSuccess} />
|
||||
);
|
||||
} else if(this.props.requestAction === 'loan_request') {
|
||||
} else if(this.props.notifications.action === 'loan_request') {
|
||||
return (
|
||||
<AclButton
|
||||
availableAcls={{'acl_loan_request': true}}
|
||||
@ -110,7 +117,7 @@ let RequestActionForm = React.createClass({
|
||||
buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px'
|
||||
pieceOrEditions={this.props.pieceOrEditions}
|
||||
currentUser={this.props.currentUser}
|
||||
handleSuccess={this.props.handleSuccess} />
|
||||
handleSuccess={this.handleSuccess} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
@ -118,7 +125,7 @@ let RequestActionForm = React.createClass({
|
||||
url={urls.accept}
|
||||
getFormData={this.getFormData}
|
||||
handleSuccess={
|
||||
this.showNotification(getLangText('accepted'), this.props.requestAction, this.props.requestUser)
|
||||
this.showNotification(getLangText('accepted'), this.props.notifications.action, this.props.notifications.by)
|
||||
}
|
||||
isInline={true}
|
||||
className='inline pull-right'>
|
||||
@ -143,7 +150,7 @@ let RequestActionForm = React.createClass({
|
||||
isInline={true}
|
||||
getFormData={this.getFormData}
|
||||
handleSuccess={
|
||||
this.showNotification(getLangText('denied'), this.props.requestAction, this.props.requestUser)
|
||||
this.showNotification(getLangText('denied'), this.props.notifications.action, this.props.notifications.by)
|
||||
}
|
||||
className='inline pull-right'>
|
||||
<button
|
||||
|
@ -60,10 +60,10 @@ let ShareForm = React.createClass({
|
||||
<Property
|
||||
name='share_message'
|
||||
label='Personal Message'
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
defaultValue={this.props.message}
|
||||
placeholder={getLangText('Enter a message...')}
|
||||
required="required"/>
|
||||
|
@ -66,17 +66,24 @@ let SignupForm = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
getFormData() {
|
||||
if (this.getQuery().token){
|
||||
return {token: this.getQuery().token};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
render() {
|
||||
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
|
||||
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
|
||||
getLangText('Store it in a safe place') + '!';
|
||||
let email = this.getQuery().email ? this.getQuery().email : null;
|
||||
let email = this.getQuery().email || null;
|
||||
return (
|
||||
<Form
|
||||
className="ascribe-form-bordered"
|
||||
ref='form'
|
||||
url={ApiUrls.users_signup}
|
||||
getFormData={this.getQuery}
|
||||
getFormData={this.getFormData}
|
||||
handleSuccess={this.handleSuccess}
|
||||
buttons={
|
||||
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
|
||||
|
@ -45,20 +45,20 @@ let PieceSubmitToPrizeForm = React.createClass({
|
||||
<Property
|
||||
name='artist_statement'
|
||||
label={getLangText('Artist statement')}
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
placeholder={getLangText('Enter your statement')}
|
||||
required="required"/>
|
||||
</Property>
|
||||
<Property
|
||||
name='work_description'
|
||||
label={getLangText('Work description')}
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
placeholder={getLangText('Enter the description for your work')}
|
||||
required="required"/>
|
||||
</Property>
|
||||
|
@ -61,10 +61,10 @@ let TransferForm = React.createClass({
|
||||
<Property
|
||||
name='transfer_message'
|
||||
label={getLangText('Personal Message')}
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
defaultValue={this.props.message}
|
||||
placeholder={getLangText('Enter a message...')}
|
||||
required="required"/>
|
||||
|
@ -50,10 +50,10 @@ let UnConsignForm = React.createClass({
|
||||
<Property
|
||||
name='unconsign_message'
|
||||
label={getLangText('Personal Message')}
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
defaultValue={this.props.message}
|
||||
placeholder={getLangText('Enter a message...')}
|
||||
required="required"/>
|
||||
|
@ -50,10 +50,10 @@ let UnConsignRequestForm = React.createClass({
|
||||
<Property
|
||||
name='unconsign_request_message'
|
||||
label={getLangText('Personal Message')}
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
defaultValue={this.props.message}
|
||||
placeholder={getLangText('Enter a message...')}
|
||||
required="required"/>
|
||||
|
@ -25,7 +25,10 @@ let InputCheckbox = React.createClass({
|
||||
|
||||
// provided by Property
|
||||
disabled: React.PropTypes.bool,
|
||||
onChange: React.PropTypes.func
|
||||
onChange: React.PropTypes.func,
|
||||
|
||||
// can be used to style the component from the outside
|
||||
style: React.PropTypes.object
|
||||
},
|
||||
|
||||
// As HTML inputs, we're setting the default value for an input to checked === false
|
||||
@ -98,6 +101,7 @@ let InputCheckbox = React.createClass({
|
||||
|
||||
return (
|
||||
<span
|
||||
style={this.props.style}
|
||||
onClick={this.onChange}>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
@ -5,26 +5,48 @@ import React from 'react';
|
||||
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
import { getCookie } from '../../utils/fetch_api_utils';
|
||||
|
||||
let InputFileUploader = React.createClass({
|
||||
let InputFineUploader = React.createClass({
|
||||
propTypes: {
|
||||
setIsUploadReady: React.PropTypes.func,
|
||||
isReadyForFormSubmission: React.PropTypes.func,
|
||||
submitFileName: React.PropTypes.func,
|
||||
|
||||
areAssetsDownloadable: React.PropTypes.bool,
|
||||
|
||||
onClick: React.PropTypes.func,
|
||||
keyRoutine: React.PropTypes.shape({
|
||||
url: React.PropTypes.string,
|
||||
fileClass: React.PropTypes.string
|
||||
}),
|
||||
createBlobRoutine: React.PropTypes.shape({
|
||||
url: React.PropTypes.string
|
||||
}),
|
||||
validation: React.PropTypes.shape({
|
||||
itemLimit: React.PropTypes.number,
|
||||
sizeLimit: React.PropTypes.string,
|
||||
allowedExtensions: React.PropTypes.arrayOf(React.PropTypes.string)
|
||||
}),
|
||||
|
||||
// isFineUploaderActive is used to lock react fine uploader in case
|
||||
// a user is actually not logged in already to prevent him from droping files
|
||||
// before login in
|
||||
isFineUploaderActive: React.PropTypes.bool,
|
||||
onLoggedOut: React.PropTypes.func,
|
||||
editable: React.PropTypes.bool,
|
||||
|
||||
enableLocalHashing: React.PropTypes.bool,
|
||||
|
||||
// provided by Property
|
||||
disabled: React.PropTypes.bool
|
||||
disabled: React.PropTypes.bool,
|
||||
|
||||
// A class of a file the user has to upload
|
||||
// Needs to be defined both in singular as well as in plural
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
})
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -33,10 +55,14 @@ let InputFileUploader = React.createClass({
|
||||
};
|
||||
},
|
||||
|
||||
submitKey(key){
|
||||
submitFile(file){
|
||||
this.setState({
|
||||
value: key
|
||||
value: file.key
|
||||
});
|
||||
|
||||
if(typeof this.props.submitFileName === 'function') {
|
||||
this.props.submitFileName(file.originalName);
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
@ -56,21 +82,13 @@ let InputFileUploader = React.createClass({
|
||||
<ReactS3FineUploader
|
||||
ref="fineuploader"
|
||||
onClick={this.props.onClick}
|
||||
keyRoutine={{
|
||||
url: AppConstants.serverUrl + 's3/key/',
|
||||
fileClass: 'digitalwork'
|
||||
}}
|
||||
createBlobRoutine={{
|
||||
url: ApiUrls.blob_digitalworks
|
||||
}}
|
||||
submitKey={this.submitKey}
|
||||
validation={{
|
||||
itemLimit: 100000,
|
||||
sizeLimit: '25000000000'
|
||||
}}
|
||||
keyRoutine={this.props.keyRoutine}
|
||||
createBlobRoutine={this.props.createBlobRoutine}
|
||||
validation={this.props.validation}
|
||||
submitFile={this.submitFile}
|
||||
setIsUploadReady={this.props.setIsUploadReady}
|
||||
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
|
||||
areAssetsDownloadable={false}
|
||||
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
||||
areAssetsEditable={editable}
|
||||
signature={{
|
||||
endpoint: AppConstants.serverUrl + 's3/signature/',
|
||||
@ -87,9 +105,10 @@ let InputFileUploader = React.createClass({
|
||||
}
|
||||
}}
|
||||
onInactive={this.props.onLoggedOut}
|
||||
enableLocalHashing={this.props.enableLocalHashing} />
|
||||
enableLocalHashing={this.props.enableLocalHashing}
|
||||
fileClassToUpload={this.props.fileClassToUpload}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default InputFileUploader;
|
||||
export default InputFineUploader;
|
@ -7,7 +7,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
||||
|
||||
let InputTextAreaToggable = React.createClass({
|
||||
propTypes: {
|
||||
editable: React.PropTypes.bool.isRequired,
|
||||
disabled: React.PropTypes.bool,
|
||||
rows: React.PropTypes.number.isRequired,
|
||||
required: React.PropTypes.string,
|
||||
defaultValue: React.PropTypes.string
|
||||
@ -15,10 +15,26 @@ let InputTextAreaToggable = React.createClass({
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
value: this.props.defaultValue
|
||||
value: null
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
value: this.props.defaultValue
|
||||
});
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
// If the initial value of state.value is null, we want to set props.defaultValue
|
||||
// as a value. In all other cases TextareaAutosize.onChange is updating.handleChange already
|
||||
if(this.state.value === null && this.props.defaultValue) {
|
||||
this.setState({
|
||||
value: this.props.defaultValue
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({value: event.target.value});
|
||||
this.props.onChange(event);
|
||||
@ -28,7 +44,7 @@ let InputTextAreaToggable = React.createClass({
|
||||
let className = 'form-control ascribe-textarea';
|
||||
let textarea = null;
|
||||
|
||||
if(this.props.editable) {
|
||||
if(!this.props.disabled) {
|
||||
className = className + ' ascribe-textarea-editable';
|
||||
textarea = (
|
||||
<TextareaAutosize
|
||||
|
@ -12,20 +12,19 @@ let ListRequestActions = React.createClass({
|
||||
]).isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
handleSuccess: React.PropTypes.func.isRequired,
|
||||
requestActions: React.PropTypes.array.isRequired
|
||||
notifications: React.PropTypes.array.isRequired
|
||||
},
|
||||
|
||||
render () {
|
||||
if (this.props.requestActions &&
|
||||
this.props.requestActions.length > 0) {
|
||||
if (this.props.notifications &&
|
||||
this.props.notifications.length > 0) {
|
||||
return (
|
||||
<div>
|
||||
{this.props.requestActions.map((requestAction) =>
|
||||
{this.props.notifications.map((notification) =>
|
||||
<RequestActionForm
|
||||
currentUser={this.props.currentUser}
|
||||
pieceOrEditions={ this.props.pieceOrEditions }
|
||||
requestAction={requestAction.action}
|
||||
requestUser={requestAction.by}
|
||||
notifications={notification}
|
||||
handleSuccess={this.props.handleSuccess}/>)}
|
||||
</div>
|
||||
);
|
||||
|
@ -70,7 +70,7 @@ let Property = React.createClass({
|
||||
// In order to set this.state.value from another component
|
||||
// the state of value should only be set if its not undefined and
|
||||
// actually references something
|
||||
if(typeof childInput.getDOMNode().value !== 'undefined') {
|
||||
if(childInput && typeof childInput.getDOMNode().value !== 'undefined') {
|
||||
this.setState({
|
||||
value: childInput.getDOMNode().value
|
||||
});
|
||||
|
@ -12,7 +12,10 @@ let ActionPanel = React.createClass({
|
||||
]),
|
||||
buttons: React.PropTypes.element,
|
||||
onClick: React.PropTypes.func,
|
||||
ignoreFocus: React.PropTypes.bool
|
||||
ignoreFocus: React.PropTypes.bool,
|
||||
|
||||
leftColumnWidth: React.PropTypes.string,
|
||||
rightColumnWidth: React.PropTypes.string
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -41,14 +44,21 @@ let ActionPanel = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
|
||||
let { leftColumnWidth, rightColumnWidth } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classnames('ascribe-panel-wrapper', {'is-focused': this.state.isFocused})}>
|
||||
<div className="ascribe-panel-table">
|
||||
<div
|
||||
className="ascribe-panel-table"
|
||||
style={{width: leftColumnWidth}}>
|
||||
<div className="ascribe-panel-content">
|
||||
{this.props.content}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ascribe-panel-table">
|
||||
<div
|
||||
className="ascribe-panel-table"
|
||||
style={{width: rightColumnWidth}}>
|
||||
<div className="ascribe-panel-content">
|
||||
{this.props.buttons}
|
||||
</div>
|
||||
|
@ -14,7 +14,20 @@ let PieceListToolbar = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
searchFor: React.PropTypes.func,
|
||||
filterParams: React.PropTypes.array,
|
||||
filterParams: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
label: React.PropTypes.string,
|
||||
items: React.PropTypes.arrayOf(
|
||||
React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.shape({
|
||||
key: React.PropTypes.string,
|
||||
label: React.PropTypes.string
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
),
|
||||
filterBy: React.PropTypes.object,
|
||||
applyFilterBy: React.PropTypes.func,
|
||||
orderParams: React.PropTypes.array,
|
||||
|
@ -3,20 +3,26 @@
|
||||
import React from 'react';
|
||||
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
|
||||
|
||||
let PieceListToolbarFilterWidgetFilter = React.createClass({
|
||||
propTypes: {
|
||||
// An array of either strings (which represent acl enums) or objects of the form
|
||||
//
|
||||
// {
|
||||
// key: <acl enum>,
|
||||
// label: <a human readable string>
|
||||
// }
|
||||
//
|
||||
filterParams: React.PropTypes.arrayOf(React.PropTypes.any).isRequired,
|
||||
filterParams: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
label: React.PropTypes.string,
|
||||
items: React.PropTypes.arrayOf(
|
||||
React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.shape({
|
||||
key: React.PropTypes.string,
|
||||
label: React.PropTypes.string
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
).isRequired,
|
||||
filterBy: React.PropTypes.object,
|
||||
applyFilterBy: React.PropTypes.func
|
||||
},
|
||||
@ -79,35 +85,53 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
|
||||
<DropdownButton
|
||||
title={filterIcon}
|
||||
className="ascribe-piece-list-toolbar-filter-widget">
|
||||
<li style={{'textAlign': 'center'}}>
|
||||
<em>{getLangText('Show works I can')}:</em>
|
||||
</li>
|
||||
{this.props.filterParams.map((param, i) => {
|
||||
let label;
|
||||
|
||||
if(typeof param !== 'string') {
|
||||
label = param.label;
|
||||
param = param.key;
|
||||
} else {
|
||||
param = param;
|
||||
label = param.split('_')[1];
|
||||
}
|
||||
|
||||
{/* 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 (
|
||||
<MenuItem
|
||||
key={i}
|
||||
onClick={this.filterBy(param)}
|
||||
className="filter-widget-item">
|
||||
<div className="checkbox-line">
|
||||
<span>
|
||||
{getLangText(label)}
|
||||
</span>
|
||||
<input
|
||||
readOnly
|
||||
type="checkbox"
|
||||
checked={this.props.filterBy[param]} />
|
||||
</div>
|
||||
</MenuItem>
|
||||
<div>
|
||||
<li
|
||||
style={{'textAlign': 'center'}}
|
||||
key={i}>
|
||||
<em>{label}:</em>
|
||||
</li>
|
||||
{items.map((param, j) => {
|
||||
|
||||
// As can be seen in the PropTypes, a param can either
|
||||
// be a string or an object of the shape:
|
||||
//
|
||||
// {
|
||||
// key: <String>,
|
||||
// label: <String>
|
||||
// }
|
||||
//
|
||||
// This is why we need to distinguish between both here.
|
||||
if(typeof param !== 'string') {
|
||||
label = param.label;
|
||||
param = param.key;
|
||||
} else {
|
||||
param = param;
|
||||
label = param.split('acl_')[1].replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
key={j}
|
||||
onClick={this.filterBy(param)}
|
||||
className="filter-widget-item">
|
||||
<div className="checkbox-line">
|
||||
<span>
|
||||
{getLangText(label)}
|
||||
</span>
|
||||
<input
|
||||
readOnly
|
||||
type="checkbox"
|
||||
checked={this.props.filterBy[param]} />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</DropdownButton>
|
||||
|
106
js/components/ascribe_settings/account_settings.js
Normal file
106
js/components/ascribe_settings/account_settings.js
Normal file
@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import Form from '../ascribe_forms/form';
|
||||
import Property from '../ascribe_forms/property';
|
||||
import InputCheckbox from '../ascribe_forms/input_checkbox';
|
||||
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import AclProxy from '../acl_proxy';
|
||||
|
||||
import CopyrightAssociationForm from '../ascribe_forms/form_copyright_association';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let AccountSettings = React.createClass({
|
||||
propTypes: {
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
loadUser: React.PropTypes.func.isRequired,
|
||||
whitelabel: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
handleSuccess(){
|
||||
this.props.loadUser();
|
||||
let notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
getFormDataProfile(){
|
||||
return {'email': this.props.currentUser.email};
|
||||
},
|
||||
|
||||
render() {
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
let profile = null;
|
||||
|
||||
if (this.props.currentUser.username) {
|
||||
content = (
|
||||
<Form
|
||||
url={ApiUrls.users_username}
|
||||
handleSuccess={this.handleSuccess}>
|
||||
<Property
|
||||
name='username'
|
||||
label={getLangText('Username')}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={this.props.currentUser.username}
|
||||
placeholder={getLangText('Enter your username')}
|
||||
required/>
|
||||
</Property>
|
||||
<Property
|
||||
name='email'
|
||||
label={getLangText('Email')}
|
||||
overrideForm={true}
|
||||
editable={false}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={this.props.currentUser.email}
|
||||
placeholder={getLangText('Enter your username')}
|
||||
required/>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>
|
||||
);
|
||||
profile = (
|
||||
<AclProxy
|
||||
aclObject={this.props.whitelabel}
|
||||
aclName="acl_view_settings_account_hash">
|
||||
<Form
|
||||
url={ApiUrls.users_profile}
|
||||
handleSuccess={this.handleSuccess}
|
||||
getFormData={this.getFormDataProfile}>
|
||||
<Property
|
||||
name="hash_locally"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox
|
||||
defaultChecked={this.props.currentUser.profile.hash_locally}>
|
||||
<span>
|
||||
{' ' + getLangText('Enable hash option, e.g. slow connections or to keep piece private')}
|
||||
</span>
|
||||
</InputCheckbox>
|
||||
</Property>
|
||||
</Form>
|
||||
</AclProxy>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Account')}
|
||||
defaultExpanded={true}>
|
||||
{content}
|
||||
<CopyrightAssociationForm currentUser={this.props.currentUser}/>
|
||||
{profile}
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default AccountSettings;
|
123
js/components/ascribe_settings/api_settings.js
Normal file
123
js/components/ascribe_settings/api_settings.js
Normal file
@ -0,0 +1,123 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import ApplicationStore from '../../stores/application_store';
|
||||
import ApplicationActions from '../../actions/application_actions';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import Form from '../ascribe_forms/form';
|
||||
import Property from '../ascribe_forms/property';
|
||||
|
||||
import ActionPanel from '../ascribe_panel/action_panel';
|
||||
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
|
||||
let APISettings = React.createClass({
|
||||
propTypes: {
|
||||
defaultExpanded: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return ApplicationStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
ApplicationStore.listen(this.onChange);
|
||||
ApplicationActions.fetchApplication();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
ApplicationStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
handleCreateSuccess() {
|
||||
ApplicationActions.fetchApplication();
|
||||
let notification = new GlobalNotificationModel(getLangText('Application successfully created'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
handleTokenRefresh(event) {
|
||||
let applicationName = event.target.getAttribute('data-id');
|
||||
ApplicationActions.refreshApplicationToken(applicationName);
|
||||
|
||||
let notification = new GlobalNotificationModel(getLangText('Token refreshed'), 'success', 2000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
getApplications(){
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
|
||||
if (this.state.applications.length > -1) {
|
||||
content = this.state.applications.map(function(app, i) {
|
||||
return (
|
||||
<ActionPanel
|
||||
name={app.name}
|
||||
key={i}
|
||||
content={
|
||||
<div>
|
||||
<div className='ascribe-panel-title'>
|
||||
{app.name}
|
||||
</div>
|
||||
<div className="ascribe-panel-subtitle">
|
||||
{'Bearer ' + app.bearer_token.token}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
buttons={
|
||||
<div className="pull-right">
|
||||
<div className="pull-right">
|
||||
<button
|
||||
className="pull-right btn btn-default btn-sm"
|
||||
onClick={this.handleTokenRefresh}
|
||||
data-id={app.name}>
|
||||
{getLangText('REFRESH')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}/>
|
||||
);
|
||||
}, this);
|
||||
}
|
||||
return content;
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('API Integration')}
|
||||
defaultExpanded={this.props.defaultExpanded}>
|
||||
<Form
|
||||
url={ApiUrls.applications}
|
||||
handleSuccess={this.handleCreateSuccess}>
|
||||
<Property
|
||||
name='name'
|
||||
label={getLangText('Application Name')}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={getLangText('Enter the name of your app')}
|
||||
required/>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>
|
||||
<pre>
|
||||
Usage: curl <url> -H 'Authorization: Bearer <token>'
|
||||
</pre>
|
||||
{this.getApplications()}
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default APISettings;
|
71
js/components/ascribe_settings/bitcoin_wallet_settings.js
Normal file
71
js/components/ascribe_settings/bitcoin_wallet_settings.js
Normal file
@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import WalletSettingsStore from '../../stores/wallet_settings_store';
|
||||
import WalletSettingsActions from '../../actions/wallet_settings_actions';
|
||||
|
||||
import Form from '../ascribe_forms/form';
|
||||
import Property from '../ascribe_forms/property';
|
||||
|
||||
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
|
||||
let BitcoinWalletSettings = React.createClass({
|
||||
propTypes: {
|
||||
defaultExpanded: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return WalletSettingsStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
WalletSettingsStore.listen(this.onChange);
|
||||
WalletSettingsActions.fetchWalletSettings();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
WalletSettingsStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
render() {
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
|
||||
if (this.state.walletSettings.btc_public_key) {
|
||||
content = (
|
||||
<Form >
|
||||
<Property
|
||||
name='btc_public_key'
|
||||
label={getLangText('Bitcoin public key')}
|
||||
editable={false}>
|
||||
<pre className="ascribe-pre">{this.state.walletSettings.btc_public_key}</pre>
|
||||
</Property>
|
||||
<Property
|
||||
name='btc_root_address'
|
||||
label={getLangText('Root Address')}
|
||||
editable={false}>
|
||||
<pre className="ascribe-pre">{this.state.walletSettings.btc_root_address}</pre>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>);
|
||||
}
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Crypto Wallet')}
|
||||
defaultExpanded={this.props.defaultExpanded}>
|
||||
{content}
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default BitcoinWalletSettings;
|
186
js/components/ascribe_settings/contract_settings.js
Normal file
186
js/components/ascribe_settings/contract_settings.js
Normal file
@ -0,0 +1,186 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||
import CreateContractForm from '../ascribe_forms/form_create_contract';
|
||||
|
||||
import ContractListStore from '../../stores/contract_list_store';
|
||||
import ContractListActions from '../../actions/contract_list_actions';
|
||||
|
||||
import UserStore from '../../stores/user_store';
|
||||
import UserActions from '../../actions/user_actions';
|
||||
|
||||
import WhitelabelStore from '../../stores/whitelabel_store';
|
||||
import WhitelabelActions from '../../actions/whitelabel_actions';
|
||||
|
||||
import ActionPanel from '../ascribe_panel/action_panel';
|
||||
import ContractSettingsUpdateButton from './contract_settings_update_button';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import AclProxy from '../acl_proxy';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { mergeOptions, truncateTextAtCharIndex } from '../../utils/general_utils';
|
||||
|
||||
|
||||
let ContractSettings = React.createClass({
|
||||
getInitialState(){
|
||||
return mergeOptions(
|
||||
ContractListStore.getState(),
|
||||
UserStore.getState()
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
ContractListStore.listen(this.onChange);
|
||||
UserStore.listen(this.onChange);
|
||||
WhitelabelStore.listen(this.onChange);
|
||||
|
||||
WhitelabelActions.fetchWhitelabel();
|
||||
UserActions.fetchCurrentUser();
|
||||
ContractListActions.fetchContractList(true);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
WhitelabelStore.unlisten(this.onChange);
|
||||
UserStore.unlisten(this.onChange);
|
||||
ContractListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
removeContract(contract) {
|
||||
return () => {
|
||||
ContractListActions.removeContract(contract.id)
|
||||
.then((response) => {
|
||||
ContractListActions.fetchContractList(true);
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success', 4000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
})
|
||||
.catch((err) => {
|
||||
let notification = new GlobalNotificationModel(err, 'danger', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
getPublicContracts(){
|
||||
return this.state.contractList.filter((contract) => contract.is_public);
|
||||
},
|
||||
|
||||
getPrivateContracts(){
|
||||
return this.state.contractList.filter((contract) => !contract.is_public);
|
||||
},
|
||||
|
||||
render() {
|
||||
let publicContracts = this.getPublicContracts();
|
||||
let privateContracts = this.getPrivateContracts();
|
||||
let createPublicContractForm = null;
|
||||
|
||||
if(publicContracts.length === 0) {
|
||||
createPublicContractForm = (
|
||||
<CreateContractForm
|
||||
isPublic={true}
|
||||
fileClassToUpload={{
|
||||
singular: 'new contract',
|
||||
plural: 'new contracts'
|
||||
}}/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="settings-container">
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Contracts')}
|
||||
defaultExpanded={true}>
|
||||
<AclProxy
|
||||
aclName="acl_edit_public_contract"
|
||||
aclObject={this.state.currentUser.acl}>
|
||||
<div>
|
||||
{createPublicContractForm}
|
||||
{publicContracts.map((contract, i) => {
|
||||
return (
|
||||
<ActionPanel
|
||||
key={i}
|
||||
title={contract.name}
|
||||
content={truncateTextAtCharIndex(contract.name, 120, '(...).pdf')}
|
||||
buttons={
|
||||
<div className="pull-right">
|
||||
<AclProxy
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_update_public_contract">
|
||||
<ContractSettingsUpdateButton contract={contract}/>
|
||||
</AclProxy>
|
||||
<a
|
||||
className="btn btn-default btn-sm margin-left-2px"
|
||||
href={contract.blob.url_safe}
|
||||
target="_blank">
|
||||
{getLangText('PREVIEW')}
|
||||
</a>
|
||||
<button
|
||||
className="btn btn-danger btn-sm margin-left-2px"
|
||||
onClick={this.removeContract(contract)}>
|
||||
{getLangText('REMOVE')}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
leftColumnWidth="40%"
|
||||
rightColumnWidth="60%"/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AclProxy>
|
||||
<AclProxy
|
||||
aclName="acl_edit_private_contract"
|
||||
aclObject={this.state.currentUser.acl}>
|
||||
<div>
|
||||
<CreateContractForm
|
||||
isPublic={false}
|
||||
fileClassToUpload={{
|
||||
singular: getLangText('new contract'),
|
||||
plural: getLangText('new contracts')
|
||||
}}/>
|
||||
{privateContracts.map((contract, i) => {
|
||||
return (
|
||||
<ActionPanel
|
||||
key={i}
|
||||
title={contract.name}
|
||||
content={truncateTextAtCharIndex(contract.name, 120, '(...).pdf')}
|
||||
buttons={
|
||||
<div className="pull-right">
|
||||
<AclProxy
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_update_private_contract">
|
||||
<ContractSettingsUpdateButton contract={contract}/>
|
||||
</AclProxy>
|
||||
<a
|
||||
className="btn btn-default btn-sm margin-left-2px"
|
||||
href={contract.blob.url_safe}
|
||||
target="_blank">
|
||||
{getLangText('PREVIEW')}
|
||||
</a>
|
||||
<button
|
||||
className="btn btn-danger btn-sm margin-left-2px"
|
||||
onClick={this.removeContract(contract)}>
|
||||
{getLangText('REMOVE')}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
leftColumnWidth="60%"
|
||||
rightColumnWidth="40%"/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AclProxy>
|
||||
</CollapsibleParagraph>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ContractSettings;
|
@ -0,0 +1,98 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
|
||||
import UploadButton from '../ascribe_uploader/ascribe_upload_button/upload_button';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
import ContractListActions from '../../actions/contract_list_actions';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||
import { getCookie } from '../../utils/fetch_api_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
|
||||
let ContractSettingsUpdateButton = React.createClass({
|
||||
propTypes: {
|
||||
contract: React.PropTypes.object
|
||||
},
|
||||
|
||||
submitFile(file) {
|
||||
let contract = this.props.contract;
|
||||
|
||||
// override the blob with the key's value
|
||||
contract.blob = file.key;
|
||||
|
||||
// send it to the server
|
||||
ContractListActions
|
||||
.changeContract(contract)
|
||||
.then((res) => {
|
||||
|
||||
// Display feedback to the user
|
||||
let notification = new GlobalNotificationModel(getLangText('Contract %s successfully updated', res.name), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
// and refresh the contract list to get the updated contracs
|
||||
return ContractListActions.fetchContractList(true);
|
||||
})
|
||||
.then(() => {
|
||||
// Also, reset the fineuploader component so that the user can again 'update' his contract
|
||||
this.refs.fineuploader.reset();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
let notification = new GlobalNotificationModel(getLangText('Contract could not be updated'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ReactS3FineUploader
|
||||
ref="fineuploader"
|
||||
fileInputElement={UploadButton}
|
||||
keyRoutine={{
|
||||
url: AppConstants.serverUrl + 's3/key/',
|
||||
fileClass: 'contract'
|
||||
}}
|
||||
createBlobRoutine={{
|
||||
url: ApiUrls.blob_contracts
|
||||
}}
|
||||
validation={{
|
||||
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
|
||||
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
|
||||
allowedExtensions: ['pdf']
|
||||
}}
|
||||
setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}}
|
||||
signature={{
|
||||
endpoint: AppConstants.serverUrl + 's3/signature/',
|
||||
customHeaders: {
|
||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
||||
}
|
||||
}}
|
||||
deleteFile={{
|
||||
enabled: true,
|
||||
method: 'DELETE',
|
||||
endpoint: AppConstants.serverUrl + 's3/delete',
|
||||
customHeaders: {
|
||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
||||
}
|
||||
}}
|
||||
fileClassToUpload={{
|
||||
singular: getLangText('UPDATE'),
|
||||
plural: getLangText('UPDATE')
|
||||
}}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
submitFile={this.submitFile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ContractSettingsUpdateButton;
|
84
js/components/ascribe_settings/settings_container.js
Normal file
84
js/components/ascribe_settings/settings_container.js
Normal file
@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import UserStore from '../../stores/user_store';
|
||||
import UserActions from '../../actions/user_actions';
|
||||
|
||||
import WhitelabelStore from '../../stores/whitelabel_store';
|
||||
import WhitelabelActions from '../../actions/whitelabel_actions';
|
||||
|
||||
import AccountSettings from './account_settings';
|
||||
import BitcoinWalletSettings from './bitcoin_wallet_settings';
|
||||
import APISettings from './api_settings';
|
||||
|
||||
import AclProxy from '../acl_proxy';
|
||||
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
|
||||
|
||||
let SettingsContainer = React.createClass({
|
||||
propTypes: {
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
React.PropTypes.element])
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
UserStore.getState(),
|
||||
WhitelabelStore.getState()
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.listen(this.onChange);
|
||||
WhitelabelStore.listen(this.onChange);
|
||||
|
||||
WhitelabelActions.fetchWhitelabel();
|
||||
UserActions.fetchCurrentUser();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
WhitelabelStore.unlisten(this.onChange);
|
||||
UserStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
loadUser(){
|
||||
UserActions.fetchCurrentUser();
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.state.currentUser && this.state.currentUser.username) {
|
||||
return (
|
||||
<div className="settings-container">
|
||||
<AccountSettings
|
||||
currentUser={this.state.currentUser}
|
||||
loadUser={this.loadUser}
|
||||
whitelabel={this.state.whitelabel}/>
|
||||
{this.props.children}
|
||||
<AclProxy
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_view_settings_api">
|
||||
<APISettings />
|
||||
</AclProxy>
|
||||
<AclProxy
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_view_settings_bitcoin">
|
||||
<BitcoinWalletSettings />
|
||||
</AclProxy>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
export default SettingsContainer;
|
@ -178,7 +178,7 @@ let SlidesContainer = React.createClass({
|
||||
let breadcrumbs = [];
|
||||
|
||||
ReactAddons.Children.map(this.props.children, (child, i) => {
|
||||
if(i >= this.state.startFrom && child.props['data-slide-title']) {
|
||||
if(child && i >= this.state.startFrom && child.props['data-slide-title']) {
|
||||
breadcrumbs.push(child.props['data-slide-title']);
|
||||
}
|
||||
});
|
||||
@ -229,7 +229,7 @@ let SlidesContainer = React.createClass({
|
||||
|
||||
// since the default parameter of startFrom is -1, we do not need to check
|
||||
// if its actually present in the url bar, as it will just not match
|
||||
if(i >= this.state.startFrom) {
|
||||
if(child && i >= this.state.startFrom) {
|
||||
return ReactAddons.addons.cloneWithProps(child, {
|
||||
className: 'ascribe-slide',
|
||||
style: {
|
||||
|
@ -6,15 +6,15 @@ import React from 'react';
|
||||
let TableItemAclFiltered = React.createClass({
|
||||
propTypes: {
|
||||
content: React.PropTypes.object,
|
||||
requestAction: React.PropTypes.string
|
||||
notifications: React.PropTypes.string
|
||||
},
|
||||
|
||||
render() {
|
||||
var availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete'];
|
||||
if (this.props.requestAction && this.props.requestAction.length > 0){
|
||||
if (this.props.notifications && this.props.notifications.length > 0){
|
||||
return (
|
||||
<span>
|
||||
{this.props.requestAction[0].action + ' request pending'}
|
||||
{this.props.notifications[0].action_str}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -6,20 +6,14 @@ import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||
import FileDragAndDropDialog from './file_drag_and_drop_dialog';
|
||||
import FileDragAndDropPreviewIterator from './file_drag_and_drop_preview_iterator';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
|
||||
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
|
||||
let FileDragAndDrop = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
onDragStart: React.PropTypes.func,
|
||||
onDrop: React.PropTypes.func.isRequired,
|
||||
onDrag: React.PropTypes.func,
|
||||
onDragEnter: React.PropTypes.func,
|
||||
onLeave: React.PropTypes.func,
|
||||
onDragLeave: React.PropTypes.func,
|
||||
onDragOver: React.PropTypes.func,
|
||||
onDragEnd: React.PropTypes.func,
|
||||
onInactive: React.PropTypes.func,
|
||||
filesToUpload: React.PropTypes.array,
|
||||
handleDeleteFile: React.PropTypes.func,
|
||||
@ -37,37 +31,16 @@ let FileDragAndDrop = React.createClass({
|
||||
hashingProgress: React.PropTypes.number,
|
||||
// sets the value of this.state.hashingProgress in reactfineuploader
|
||||
// to -1 which is code for: aborted
|
||||
handleCancelHashing: React.PropTypes.func
|
||||
},
|
||||
handleCancelHashing: React.PropTypes.func,
|
||||
|
||||
handleDragStart(event) {
|
||||
if (typeof this.props.onDragStart === 'function') {
|
||||
this.props.onDragStart(event);
|
||||
}
|
||||
},
|
||||
// A class of a file the user has to upload
|
||||
// Needs to be defined both in singular as well as in plural
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
|
||||
handleDrag(event) {
|
||||
if (typeof this.props.onDrag === 'function') {
|
||||
this.props.onDrag(event);
|
||||
}
|
||||
},
|
||||
|
||||
handleDragEnd(event) {
|
||||
if (typeof this.props.onDragEnd === 'function') {
|
||||
this.props.onDragEnd(event);
|
||||
}
|
||||
},
|
||||
|
||||
handleDragEnter(event) {
|
||||
if (typeof this.props.onDragEnter === 'function') {
|
||||
this.props.onDragEnter(event);
|
||||
}
|
||||
},
|
||||
|
||||
handleDragLeave(event) {
|
||||
if (typeof this.props.onDragLeave === 'function') {
|
||||
this.props.onDragLeave(event);
|
||||
}
|
||||
allowedExtensions: React.PropTypes.string
|
||||
},
|
||||
|
||||
handleDragOver(event) {
|
||||
@ -159,14 +132,27 @@ let FileDragAndDrop = React.createClass({
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let { filesToUpload,
|
||||
dropzoneInactive,
|
||||
className,
|
||||
hashingProgress,
|
||||
handleCancelHashing,
|
||||
multiple,
|
||||
enableLocalHashing,
|
||||
fileClassToUpload,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
allowedExtensions
|
||||
} = this.props;
|
||||
|
||||
// has files only is true if there are files that do not have the status deleted or canceled
|
||||
let hasFiles = this.props.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0;
|
||||
let className = hasFiles ? 'has-files ' : '';
|
||||
className += this.props.dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
|
||||
className += this.props.className ? ' ' + this.props.className : '';
|
||||
let hasFiles = filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0;
|
||||
let updatedClassName = hasFiles ? 'has-files ' : '';
|
||||
updatedClassName += dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
|
||||
updatedClassName += ' file-drag-and-drop';
|
||||
|
||||
// if !== -2: triggers a FileDragAndDrop-global spinner
|
||||
if(this.props.hashingProgress !== -2) {
|
||||
if(hashingProgress !== -2) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="file-drag-and-drop-hashing-dialog">
|
||||
@ -184,29 +170,26 @@ let FileDragAndDrop = React.createClass({
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
onDragStart={this.handleDragStart}
|
||||
className={updatedClassName}
|
||||
onDrag={this.handleDrop}
|
||||
onDragEnter={this.handleDragEnter}
|
||||
onDragLeave={this.handleDragLeave}
|
||||
onDragOver={this.handleDragOver}
|
||||
onDrop={this.handleDrop}
|
||||
onDragEnd={this.handleDragEnd}>
|
||||
onDrop={this.handleDrop}>
|
||||
<FileDragAndDropDialog
|
||||
multipleFiles={this.props.multiple}
|
||||
multipleFiles={multiple}
|
||||
hasFiles={hasFiles}
|
||||
onClick={this.handleOnClick}
|
||||
enableLocalHashing={this.props.enableLocalHashing}/>
|
||||
enableLocalHashing={enableLocalHashing}
|
||||
fileClassToUpload={fileClassToUpload}/>
|
||||
<FileDragAndDropPreviewIterator
|
||||
files={this.props.filesToUpload}
|
||||
files={filesToUpload}
|
||||
handleDeleteFile={this.handleDeleteFile}
|
||||
handleCancelFile={this.handleCancelFile}
|
||||
handlePauseFile={this.handlePauseFile}
|
||||
handleResumeFile={this.handleResumeFile}
|
||||
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
||||
areAssetsEditable={this.props.areAssetsEditable}/>
|
||||
areAssetsDownloadable={areAssetsDownloadable}
|
||||
areAssetsEditable={areAssetsEditable}/>
|
||||
<input
|
||||
multiple={this.props.multiple}
|
||||
multiple={multiple}
|
||||
ref="fileinput"
|
||||
type="file"
|
||||
style={{
|
||||
@ -214,7 +197,8 @@ let FileDragAndDrop = React.createClass({
|
||||
height: 0,
|
||||
width: 0
|
||||
}}
|
||||
onChange={this.handleDrop} />
|
||||
onChange={this.handleDrop}
|
||||
accept={allowedExtensions}/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
@ -12,7 +12,14 @@ let FileDragAndDropDialog = React.createClass({
|
||||
hasFiles: React.PropTypes.bool,
|
||||
multipleFiles: React.PropTypes.bool,
|
||||
onClick: React.PropTypes.func,
|
||||
enableLocalHashing: React.PropTypes.bool
|
||||
enableLocalHashing: React.PropTypes.bool,
|
||||
|
||||
// A class of a file the user has to upload
|
||||
// Needs to be defined both in singular as well as in plural
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
})
|
||||
},
|
||||
|
||||
mixins: [Router.State],
|
||||
@ -56,29 +63,29 @@ let FileDragAndDropDialog = React.createClass({
|
||||
} else {
|
||||
if(this.props.multipleFiles) {
|
||||
return (
|
||||
<div className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag files here')}</p>
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag %s here', this.props.fileClassToUpload.plural)}</p>
|
||||
<p>{getLangText('or')}</p>
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
{getLangText('choose files to upload')}
|
||||
{getLangText('choose %s to upload', this.props.fileClassToUpload.plural)}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
let dialog = queryParams.method === 'hash' ? getLangText('choose a file to hash') : getLangText('choose a file to upload');
|
||||
let dialog = queryParams.method === 'hash' ? getLangText('choose a %s to hash', this.props.fileClassToUpload.singular) : getLangText('choose a %s to upload', this.props.fileClassToUpload.singular);
|
||||
|
||||
return (
|
||||
<div className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag a file here')}</p>
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag a %s here', this.props.fileClassToUpload.singular)}</p>
|
||||
<p>{getLangText('or')}</p>
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
{dialog}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
@ -4,7 +4,9 @@ import React from 'react';
|
||||
|
||||
import FileDragAndDropPreviewImage from './file_drag_and_drop_preview_image';
|
||||
import FileDragAndDropPreviewOther from './file_drag_and_drop_preview_other';
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
|
||||
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
let FileDragAndDropPreview = React.createClass({
|
||||
|
||||
@ -43,6 +45,7 @@ let FileDragAndDropPreview = React.createClass({
|
||||
|
||||
handleDownloadFile() {
|
||||
if(this.props.file.s3Url) {
|
||||
// This simply opens a new browser tab with the url provided
|
||||
open(this.props.file.s3Url);
|
||||
}
|
||||
},
|
@ -3,8 +3,8 @@
|
||||
import React from 'react';
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
import AppConstants from '../../../constants/application_constants';
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
let FileDragAndDropPreviewImage = React.createClass({
|
||||
propTypes: {
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||
import FileDragAndDropPreview from './file_drag_and_drop_preview';
|
||||
import FileDragAndDropPreviewProgress from './file_drag_and_drop_preview_progress';
|
||||
|
||||
import { displayValidFilesFilter } from './react_s3_fine_uploader_utils';
|
||||
import { displayValidFilesFilter } from '../react_s3_fine_uploader_utils';
|
||||
|
||||
|
||||
let FileDragAndDropPreviewIterator = React.createClass({
|
@ -3,8 +3,8 @@
|
||||
import React from 'react';
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
import AppConstants from '../../../constants/application_constants';
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
let FileDragAndDropPreviewOther = React.createClass({
|
||||
propTypes: {
|
||||
@ -61,7 +61,7 @@ let FileDragAndDropPreviewOther = React.createClass({
|
||||
<div className="file-drag-and-drop-preview-table-wrapper">
|
||||
<div className="file-drag-and-drop-preview-other">
|
||||
{actionSymbol}
|
||||
<span>{'.' + this.props.type}</span>
|
||||
<p>{'.' + this.props.type}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -4,7 +4,8 @@ import React from 'react';
|
||||
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||
|
||||
import { displayValidProgressFilesFilter } from './react_s3_fine_uploader_utils';
|
||||
import { displayValidProgressFilesFilter } from '../react_s3_fine_uploader_utils';
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
|
||||
let FileDragAndDropPreviewProgress = React.createClass({
|
||||
@ -54,7 +55,7 @@ let FileDragAndDropPreviewProgress = React.createClass({
|
||||
return (
|
||||
<ProgressBar
|
||||
now={Math.ceil(overallProgress)}
|
||||
label="Overall progress: %(percent)s%"
|
||||
label={getLangText('Overall progress%s', ': %(percent)s%')}
|
||||
className="ascribe-progress-bar"
|
||||
style={style} />
|
||||
);
|
@ -0,0 +1,103 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { displayValidProgressFilesFilter } from '../react_s3_fine_uploader_utils';
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
|
||||
let UploadButton = React.createClass({
|
||||
propTypes: {
|
||||
onDrop: React.PropTypes.func.isRequired,
|
||||
filesToUpload: React.PropTypes.array,
|
||||
multiple: React.PropTypes.bool,
|
||||
|
||||
// For simplification purposes we're just going to use this prop as a
|
||||
// label for the upload button
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
|
||||
allowedExtensions: React.PropTypes.string
|
||||
},
|
||||
|
||||
handleDrop(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
let files = event.target.files;
|
||||
|
||||
if(typeof this.props.onDrop === 'function' && files) {
|
||||
this.props.onDrop(files);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
getUploadingFiles() {
|
||||
return this.props.filesToUpload.filter((file) => file.status === 'uploading');
|
||||
},
|
||||
|
||||
handleOnClick() {
|
||||
let uploadingFiles = this.getUploadingFiles();
|
||||
|
||||
// We only want the button to be clickable if there are no files currently uploading
|
||||
if(uploadingFiles.length === 0) {
|
||||
// Firefox only recognizes the simulated mouse click if bubbles is set to true,
|
||||
// but since Google Chrome propagates the event much further than needed, we
|
||||
// need to stop propagation as soon as the event is created
|
||||
var evt = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
evt.stopPropagation();
|
||||
this.refs.fileinput.getDOMNode().dispatchEvent(evt);
|
||||
}
|
||||
},
|
||||
|
||||
getButtonLabel() {
|
||||
let { filesToUpload, fileClassToUpload } = this.props;
|
||||
|
||||
// filter invalid files that might have been deleted or canceled...
|
||||
filesToUpload = filesToUpload.filter(displayValidProgressFilesFilter);
|
||||
|
||||
// Depending on wether there is an upload going on or not we
|
||||
// display the progress
|
||||
if(filesToUpload.length > 0) {
|
||||
return getLangText('Upload progress') + ': ' + Math.ceil(filesToUpload[0].progress) + '%';
|
||||
} else {
|
||||
return fileClassToUpload.singular;
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
multiple,
|
||||
fileClassToUpload,
|
||||
allowedExtensions
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={this.handleOnClick}
|
||||
className="btn btn-default btn-sm margin-left-2px"
|
||||
disabled={this.getUploadingFiles().length !== 0}>
|
||||
{this.getButtonLabel()}
|
||||
<input
|
||||
multiple={multiple}
|
||||
ref="fileinput"
|
||||
type="file"
|
||||
style={{
|
||||
display: 'none',
|
||||
height: 0,
|
||||
width: 0
|
||||
}}
|
||||
onChange={this.handleDrop}
|
||||
accept={allowedExtensions}/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default UploadButton;
|
@ -7,7 +7,7 @@ import Q from 'q';
|
||||
|
||||
import S3Fetcher from '../../fetchers/s3_fetcher';
|
||||
|
||||
import FileDragAndDrop from './file_drag_and_drop';
|
||||
import FileDragAndDrop from './ascribe_file_drag_and_drop/file_drag_and_drop';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
@ -15,12 +15,12 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { computeHashOfFile } from '../../utils/file_utils';
|
||||
import { displayValidFilesFilter } from './react_s3_fine_uploader_utils';
|
||||
import { displayValidFilesFilter, transformAllowedExtensionsToInputAcceptProp } from './react_s3_fine_uploader_utils';
|
||||
import { getCookie } from '../../utils/fetch_api_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
|
||||
var ReactS3FineUploader = React.createClass({
|
||||
let ReactS3FineUploader = React.createClass({
|
||||
propTypes: {
|
||||
keyRoutine: React.PropTypes.shape({
|
||||
url: React.PropTypes.string,
|
||||
@ -37,7 +37,7 @@ var ReactS3FineUploader = React.createClass({
|
||||
React.PropTypes.number
|
||||
])
|
||||
}),
|
||||
submitKey: React.PropTypes.func,
|
||||
submitFile: React.PropTypes.func,
|
||||
autoUpload: React.PropTypes.bool,
|
||||
debug: React.PropTypes.bool,
|
||||
objectProperties: React.PropTypes.shape({
|
||||
@ -84,7 +84,8 @@ var ReactS3FineUploader = React.createClass({
|
||||
}),
|
||||
validation: React.PropTypes.shape({
|
||||
itemLimit: React.PropTypes.number,
|
||||
sizeLimit: React.PropTypes.string
|
||||
sizeLimit: React.PropTypes.string,
|
||||
allowedExtensions: React.PropTypes.arrayOf(React.PropTypes.string)
|
||||
}),
|
||||
messages: React.PropTypes.shape({
|
||||
unsupportedBrowser: React.PropTypes.string
|
||||
@ -111,7 +112,22 @@ var ReactS3FineUploader = React.createClass({
|
||||
enableLocalHashing: React.PropTypes.bool,
|
||||
|
||||
// automatically injected by React-Router
|
||||
query: React.PropTypes.object
|
||||
query: React.PropTypes.object,
|
||||
|
||||
// A class of a file the user has to upload
|
||||
// Needs to be defined both in singular as well as in plural
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
|
||||
// Uploading functionality of react fineuploader is disconnected from its UI
|
||||
// layer, which means that literally every (properly adjusted) react element
|
||||
// can handle the UI handling.
|
||||
fileInputElement: React.PropTypes.oneOfType([
|
||||
React.PropTypes.func,
|
||||
React.PropTypes.element
|
||||
])
|
||||
},
|
||||
|
||||
mixins: [Router.State],
|
||||
@ -163,7 +179,12 @@ var ReactS3FineUploader = React.createClass({
|
||||
return name;
|
||||
},
|
||||
multiple: false,
|
||||
defaultErrorMessage: getLangText('Unexpected error. Please contact us if this happens repeatedly.')
|
||||
defaultErrorMessage: getLangText('Unexpected error. Please contact us if this happens repeatedly.'),
|
||||
fileClassToUpload: {
|
||||
singular: getLangText('file'),
|
||||
plural: getLangText('files')
|
||||
},
|
||||
fileInputElement: FileDragAndDrop
|
||||
};
|
||||
},
|
||||
|
||||
@ -313,6 +334,9 @@ var ReactS3FineUploader = React.createClass({
|
||||
} else if(res.digitalwork) {
|
||||
file.s3Url = res.digitalwork.url_safe;
|
||||
file.s3UrlSafe = res.digitalwork.url_safe;
|
||||
} else if(res.contractblob) {
|
||||
file.s3Url = res.contractblob.url_safe;
|
||||
file.s3UrlSafe = res.contractblob.url_safe;
|
||||
} else {
|
||||
throw new Error(getLangText('Could not find a url to download.'));
|
||||
}
|
||||
@ -384,12 +408,12 @@ var ReactS3FineUploader = React.createClass({
|
||||
// Only after the blob has been created server-side, we can make the form submittable.
|
||||
this.createBlob(files[id])
|
||||
.then(() => {
|
||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
|
||||
// are optional, we'll only trigger them when they're actually defined
|
||||
if(this.props.submitKey) {
|
||||
this.props.submitKey(files[id].key);
|
||||
if(this.props.submitFile) {
|
||||
this.props.submitFile(files[id]);
|
||||
} else {
|
||||
console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
|
||||
console.warn('You didn\'t define submitFile in as a prop in react-s3-fine-uploader');
|
||||
}
|
||||
|
||||
// for explanation, check comment of if statement above
|
||||
@ -424,7 +448,7 @@ var ReactS3FineUploader = React.createClass({
|
||||
});
|
||||
this.state.uploader.cancelAll();
|
||||
|
||||
let notification = new GlobalNotificationModel(this.props.defaultErrorMessage, 'danger', 5000);
|
||||
let notification = new GlobalNotificationModel(errorReason || this.props.defaultErrorMessage, 'danger', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
@ -449,7 +473,7 @@ var ReactS3FineUploader = React.createClass({
|
||||
let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
|
||||
// are optional, we'll only trigger them when they're actually defined
|
||||
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
||||
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
|
||||
@ -516,7 +540,7 @@ var ReactS3FineUploader = React.createClass({
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
}
|
||||
|
||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
|
||||
// are optional, we'll only trigger them when they're actually defined
|
||||
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
||||
// also, lets check if after the completion of this upload,
|
||||
@ -541,7 +565,7 @@ var ReactS3FineUploader = React.createClass({
|
||||
this.setStatusOfFile(fileId, 'deleted');
|
||||
|
||||
// In some instances (when the file was already uploaded and is just displayed to the user
|
||||
// - for example in the loan contract or additional files dialog)
|
||||
// - for example in the contract or additional files dialog)
|
||||
// fineuploader does not register an id on the file (we do, don't be confused by this!).
|
||||
// Since you can only delete a file by its id, we have to implement this method ourselves
|
||||
//
|
||||
@ -816,27 +840,48 @@ var ReactS3FineUploader = React.createClass({
|
||||
|
||||
},
|
||||
|
||||
getAllowedExtensions() {
|
||||
let { validation } = this.props;
|
||||
|
||||
if(validation && validation.allowedExtensions && validation.allowedExtensions.length > 0) {
|
||||
return transformAllowedExtensionsToInputAcceptProp(validation.allowedExtensions);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<FileDragAndDrop
|
||||
className="file-drag-and-drop"
|
||||
onDrop={this.handleUploadFile}
|
||||
filesToUpload={this.state.filesToUpload}
|
||||
handleDeleteFile={this.handleDeleteFile}
|
||||
handleCancelFile={this.handleCancelFile}
|
||||
handlePauseFile={this.handlePauseFile}
|
||||
handleResumeFile={this.handleResumeFile}
|
||||
handleCancelHashing={this.handleCancelHashing}
|
||||
multiple={this.props.multiple}
|
||||
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
||||
areAssetsEditable={this.props.areAssetsEditable}
|
||||
onInactive={this.props.onInactive}
|
||||
dropzoneInactive={this.isDropzoneInactive()}
|
||||
hashingProgress={this.state.hashingProgress}
|
||||
enableLocalHashing={this.props.enableLocalHashing} />
|
||||
</div>
|
||||
);
|
||||
let {
|
||||
multiple,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
onInactive,
|
||||
enableLocalHashing,
|
||||
fileClassToUpload,
|
||||
validation,
|
||||
fileInputElement
|
||||
} = this.props;
|
||||
|
||||
// Here we initialize the template that has been either provided from the outside
|
||||
// or the default input that is FileDragAndDrop.
|
||||
return React.createElement(fileInputElement, {
|
||||
onDrop: this.handleUploadFile,
|
||||
filesToUpload: this.state.filesToUpload,
|
||||
handleDeleteFile: this.handleDeleteFile,
|
||||
handleCancelFile: this.handleCancelFile,
|
||||
handlePauseFile: this.handlePauseFile,
|
||||
handleResumeFile: this.handleResumeFile,
|
||||
handleCancelHashing: this.handleCancelHashing,
|
||||
multiple: multiple,
|
||||
areAssetsDownloadable: areAssetsDownloadable,
|
||||
areAssetsEditable: areAssetsEditable,
|
||||
onInactive: onInactive,
|
||||
dropzoneInactive: this.isDropzoneInactive(),
|
||||
hashingProgress: this.state.hashingProgress,
|
||||
enableLocalHashing: enableLocalHashing,
|
||||
fileClassToUpload: fileClassToUpload,
|
||||
allowedExtensions: this.getAllowedExtensions()
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -1,5 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
export const formSubmissionValidation = {
|
||||
/**
|
||||
* Returns a boolean if there has been at least one file uploaded
|
||||
* successfully without it being deleted or canceled.
|
||||
* @param {array of files} files provided by react fine uploader
|
||||
* @return {boolean}
|
||||
*/
|
||||
atLeastOneUploadedFile(files) {
|
||||
files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled');
|
||||
if (files.length > 0 && files[0].status === 'upload successful') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* File submission for the form is optional, but if the user decides to submit a file
|
||||
* the form is not ready until there are no more files currently uploading.
|
||||
* @param {array of files} files files provided by react fine uploader
|
||||
* @return {boolean} [description]
|
||||
*/
|
||||
fileOptional(files) {
|
||||
let uploadingFiles = files.filter((file) => file.status === 'submitting');
|
||||
|
||||
if (uploadingFiles.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter function for filtering all deleted and canceled files
|
||||
* @param {object} file A file from filesToUpload that has status as a prop.
|
||||
@ -9,20 +42,6 @@ export function displayValidFilesFilter(file) {
|
||||
return file.status !== 'deleted' && file.status !== 'canceled';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean if there has been at least one file uploaded
|
||||
* successfully without it being deleted or canceled.
|
||||
* @param {array of files} files provided by react fine uploader
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isReadyForFormSubmission(files) {
|
||||
files = files.filter(displayValidFilesFilter);
|
||||
if (files.length > 0 && files[0].status === 'upload successful') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter function for which files to integrate in the progress process
|
||||
@ -32,3 +51,23 @@ export function isReadyForFormSubmission(files) {
|
||||
export function displayValidProgressFilesFilter(file) {
|
||||
return file.status !== 'deleted' && file.status !== 'canceled' && file.status !== 'online';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fineuploader allows to specify the file extensions that are allowed to upload.
|
||||
* For our self defined input, we can reuse those declarations to restrict which files
|
||||
* the user can pick from his hard drive.
|
||||
*
|
||||
* Takes an array of file extensions (['pdf', 'png', ...]) and transforms them into a string
|
||||
* that can be passed into an html5 input via its 'accept' prop.
|
||||
* @param {array} allowedExtensions Array of strings without a dot prefixed
|
||||
* @return {string} Joined string (comma-separated) of the passed-in array
|
||||
*/
|
||||
export function transformAllowedExtensionsToInputAcceptProp(allowedExtensions) {
|
||||
// add a dot in front of the extension
|
||||
let prefixedAllowedExtensions = allowedExtensions.map((ext) => '.' + ext);
|
||||
|
||||
// generate a comma separated list to add them to the DOM element
|
||||
// See: http://stackoverflow.com/questions/4328947/limit-file-format-when-using-input-type-file
|
||||
return prefixedAllowedExtensions.join(', ');
|
||||
}
|
||||
|
@ -84,10 +84,11 @@ let CoaVerifyForm = React.createClass({
|
||||
</Property>
|
||||
<Property
|
||||
name='signature'
|
||||
label="Signature">
|
||||
label="Signature"
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={3}
|
||||
editable={true}
|
||||
placeholder={getLangText('Copy paste the signature on the bottom of your Certificate of Authenticity')}
|
||||
required/>
|
||||
</Property>
|
||||
|
36
js/components/contract_notification.js
Normal file
36
js/components/contract_notification.js
Normal file
@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import NotificationStore from '../stores/notification_store';
|
||||
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
|
||||
let ContractNotification = React.createClass({
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
NotificationStore.getState()
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
NotificationStore.listen(this.onChange);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
NotificationStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
null
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ContractNotification;
|
43
js/components/global_action.js
Normal file
43
js/components/global_action.js
Normal file
@ -0,0 +1,43 @@
|
||||
'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 (
|
||||
<div className="ascribe-global-action">
|
||||
{item}
|
||||
</div>);
|
||||
});
|
||||
}
|
||||
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 (
|
||||
<div className="ascribe-global-action">
|
||||
{item}
|
||||
</div>);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (pieceActions || editionActions) {
|
||||
return (
|
||||
<div className="ascribe-global-action-wrapper">
|
||||
{pieceActions}
|
||||
{editionActions}
|
||||
</div>);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
export default GlobalAction;
|
@ -3,13 +3,6 @@
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import UserActions from '../actions/user_actions';
|
||||
import UserStore from '../stores/user_store';
|
||||
|
||||
import WhitelabelActions from '../actions/whitelabel_actions';
|
||||
import WhitelabelStore from '../stores/whitelabel_store';
|
||||
import EventActions from '../actions/event_actions';
|
||||
|
||||
import Nav from 'react-bootstrap/lib/Nav';
|
||||
import Navbar from 'react-bootstrap/lib/Navbar';
|
||||
import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav';
|
||||
@ -18,6 +11,17 @@ import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
||||
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
||||
|
||||
import AclProxy from './acl_proxy';
|
||||
|
||||
import UserActions from '../actions/user_actions';
|
||||
import UserStore from '../stores/user_store';
|
||||
|
||||
import WhitelabelActions from '../actions/whitelabel_actions';
|
||||
import WhitelabelStore from '../stores/whitelabel_store';
|
||||
import EventActions from '../actions/event_actions';
|
||||
|
||||
import HeaderNotifications from './header_notification';
|
||||
|
||||
import HeaderNotificationDebug from './header_notification_debug';
|
||||
|
||||
import NavRoutesLinks from './nav_routes_links';
|
||||
@ -41,7 +45,10 @@ let Header = React.createClass({
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(WhitelabelStore.getState(), UserStore.getState());
|
||||
return mergeOptions(
|
||||
WhitelabelStore.getState(),
|
||||
UserStore.getState()
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
@ -90,19 +97,61 @@ let Header = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
onMenuItemClick() {
|
||||
/*
|
||||
This is a hack to make the dropdown close after clicking on an item
|
||||
The function just need to be defined
|
||||
|
||||
from https://github.com/react-bootstrap/react-bootstrap/issues/368:
|
||||
|
||||
@jvillasante - Have you tried to use onSelect with the DropdownButton?
|
||||
I don't have a working example that is exactly like yours,
|
||||
but I just noticed that the Dropdown closes when I've attached an event handler to OnSelect:
|
||||
|
||||
<DropdownButton eventKey={3} title="Admin" onSelect={ this.OnSelected } >
|
||||
|
||||
onSelected: function(e) {
|
||||
// doesn't need to have functionality (necessarily) ... just wired up
|
||||
}
|
||||
Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu.
|
||||
So, you should be able to call that directly on the DropdownButton instance as well if needed.
|
||||
|
||||
NOW, THAT DIDN'T WORK - the onSelect routine isnt triggered in all cases
|
||||
Hence, we do this manually
|
||||
*/
|
||||
this.refs.dropdownbutton.setDropdownState(false);
|
||||
},
|
||||
|
||||
render() {
|
||||
let account;
|
||||
let signup;
|
||||
let navRoutesLinks;
|
||||
if (this.state.currentUser.username){
|
||||
account = (
|
||||
<DropdownButton eventKey="1" title={this.state.currentUser.username}>
|
||||
<MenuItemLink eventKey="2" to="settings">{getLangText('Account Settings')}</MenuItemLink>
|
||||
<DropdownButton
|
||||
ref='dropdownbutton'
|
||||
eventKey="1"
|
||||
title={this.state.currentUser.username}>
|
||||
<MenuItemLink
|
||||
eventKey="2"
|
||||
to="settings"
|
||||
onClick={this.onMenuItemClick}>
|
||||
{getLangText('Account Settings')}
|
||||
</MenuItemLink>
|
||||
<AclProxy
|
||||
aclObject={this.state.currentUser.acl}
|
||||
aclName="acl_view_settings_contract">
|
||||
<MenuItemLink
|
||||
to="contract_settings"
|
||||
onClick={this.onMenuItemClick}>
|
||||
{getLangText('Contract Settings')}
|
||||
</MenuItemLink>
|
||||
</AclProxy>
|
||||
<MenuItem divider />
|
||||
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink>
|
||||
</DropdownButton>
|
||||
</DropdownButton>
|
||||
);
|
||||
navRoutesLinks = <NavRoutesLinks routes={this.props.routes} navbar right/>;
|
||||
navRoutesLinks = <NavRoutesLinks routes={this.props.routes} userAcl={this.state.currentUser.acl} navbar right/>;
|
||||
}
|
||||
else {
|
||||
account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>;
|
||||
@ -126,6 +175,7 @@ let Header = React.createClass({
|
||||
{account}
|
||||
{signup}
|
||||
</Nav>
|
||||
<HeaderNotifications />
|
||||
{navRoutesLinks}
|
||||
</CollapsibleNav>
|
||||
</Navbar>
|
||||
|
218
js/components/header_notification.js
Normal file
218
js/components/header_notification.js
Normal file
@ -0,0 +1,218 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
|
||||
import Nav from 'react-bootstrap/lib/Nav';
|
||||
|
||||
import NotificationActions from '../actions/notification_actions';
|
||||
import NotificationStore from '../stores/notification_store';
|
||||
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
|
||||
let HeaderNotifications = React.createClass({
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
NotificationStore.getState()
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
NotificationStore.listen(this.onChange);
|
||||
NotificationActions.fetchPieceListNotifications();
|
||||
NotificationActions.fetchEditionListNotifications();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
NotificationStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
onMenuItemClick(event) {
|
||||
/*
|
||||
This is a hack to make the dropdown close after clicking on an item
|
||||
The function just need to be defined
|
||||
|
||||
from https://github.com/react-bootstrap/react-bootstrap/issues/368:
|
||||
|
||||
@jvillasante - Have you tried to use onSelect with the DropdownButton?
|
||||
I don't have a working example that is exactly like yours,
|
||||
but I just noticed that the Dropdown closes when I've attached an event handler to OnSelect:
|
||||
|
||||
<DropdownButton eventKey={3} title="Admin" onSelect={ this.OnSelected } >
|
||||
|
||||
onSelected: function(e) {
|
||||
// doesn't need to have functionality (necessarily) ... just wired up
|
||||
}
|
||||
Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu.
|
||||
So, you should be able to call that directly on the DropdownButton instance as well if needed.
|
||||
|
||||
NOW, THAT DIDN'T WORK - the onSelect routine isnt triggered in all cases
|
||||
Hence, we do this manually
|
||||
*/
|
||||
this.refs.dropdownbutton.setDropdownState(false);
|
||||
},
|
||||
|
||||
getPieceNotifications(){
|
||||
if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) {
|
||||
return (
|
||||
<div>
|
||||
<div className="notification-header">
|
||||
Artworks ({this.state.pieceListNotifications.length})
|
||||
</div>
|
||||
{this.state.pieceListNotifications.map((pieceNotification, i) => {
|
||||
return (
|
||||
<MenuItem eventKey={i + 2}>
|
||||
<NotificationListItem
|
||||
ref={i}
|
||||
notification={pieceNotification.notification}
|
||||
pieceOrEdition={pieceNotification.piece}
|
||||
onClick={this.onMenuItemClick}/>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getEditionNotifications(){
|
||||
if (this.state.editionListNotifications && this.state.editionListNotifications.length > 0) {
|
||||
return (
|
||||
<div>
|
||||
<div className="notification-header">
|
||||
Editions ({this.state.editionListNotifications.length})
|
||||
</div>
|
||||
{this.state.editionListNotifications.map((editionNotification, i) => {
|
||||
return (
|
||||
<MenuItem eventKey={i + 2}>
|
||||
<NotificationListItem
|
||||
ref={'edition' + i}
|
||||
notification={editionNotification.notification}
|
||||
pieceOrEdition={editionNotification.edition}
|
||||
onClick={this.onMenuItemClick}/>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
render() {
|
||||
if ((this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) ||
|
||||
(this.state.editionListNotifications && this.state.editionListNotifications.length > 0)){
|
||||
let numNotifications = 0;
|
||||
if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) {
|
||||
numNotifications += this.state.pieceListNotifications.length;
|
||||
}
|
||||
if (this.state.editionListNotifications && this.state.editionListNotifications.length > 0) {
|
||||
numNotifications += this.state.editionListNotifications.length;
|
||||
}
|
||||
return (
|
||||
<Nav navbar right>
|
||||
<DropdownButton
|
||||
ref='dropdownbutton'
|
||||
eventKey="1"
|
||||
title={
|
||||
<span>
|
||||
<Glyphicon glyph='envelope' color="green"/>
|
||||
<span className="notification-amount">({numNotifications})</span>
|
||||
</span>
|
||||
}
|
||||
className="notification-menu">
|
||||
{this.getPieceNotifications()}
|
||||
{this.getEditionNotifications()}
|
||||
</DropdownButton>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
let NotificationListItem = React.createClass({
|
||||
propTypes: {
|
||||
notification: React.PropTypes.array,
|
||||
pieceOrEdition: React.PropTypes.object,
|
||||
onClick: React.PropTypes.func
|
||||
},
|
||||
|
||||
isPiece() {
|
||||
return !(this.props.pieceOrEdition && this.props.pieceOrEdition.parent);
|
||||
},
|
||||
|
||||
getLinkData() {
|
||||
|
||||
if (this.isPiece()) {
|
||||
return {
|
||||
to: 'piece',
|
||||
params: {
|
||||
pieceId: this.props.pieceOrEdition.id
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
to: 'edition',
|
||||
params: {
|
||||
editionId: this.props.pieceOrEdition.bitcoin_id
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onClick(event){
|
||||
this.props.onClick(event);
|
||||
},
|
||||
|
||||
getNotificationText(){
|
||||
let numNotifications = null;
|
||||
if (this.props.notification.length > 1){
|
||||
numNotifications = <div>+ {this.props.notification.length - 1} more...</div>;
|
||||
}
|
||||
return (
|
||||
<div className="notification-action">
|
||||
{this.props.notification[0].action_str}
|
||||
{numNotifications}
|
||||
</div>);
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.props.pieceOrEdition) {
|
||||
return (
|
||||
<Link {...this.getLinkData()} onClick={this.onClick}>
|
||||
<div className="row notification-wrapper">
|
||||
<div className="col-xs-4 clear-paddings">
|
||||
<div className="thumbnail-wrapper">
|
||||
<img src={this.props.pieceOrEdition.thumbnail.url_safe}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xs-8 notification-list-item-header">
|
||||
<h1>{this.props.pieceOrEdition.title}</h1>
|
||||
<div className="sub-header">by {this.props.pieceOrEdition.artist_name}</div>
|
||||
{this.getNotificationText()}
|
||||
</div>
|
||||
</div>
|
||||
</Link>);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
export default HeaderNotifications;
|
@ -19,7 +19,7 @@ let LogoutContainer = React.createClass({
|
||||
Alt.flush();
|
||||
// kill intercom (with fire)
|
||||
window.Intercom('shutdown');
|
||||
this.transitionTo(baseUrl);
|
||||
this.replaceWith(baseUrl);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
|
@ -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 (
|
||||
<DropdownButton title={child.props.headerTitle} key={j}>
|
||||
{this.extractLinksFromRoutes(child, i++)}
|
||||
</DropdownButton>
|
||||
);
|
||||
} 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 (
|
||||
<MenuItemLink to={child.props.name} key={j}>{child.props.headerTitle}</MenuItemLink>
|
||||
);
|
||||
} else if(i === 0) {
|
||||
return (
|
||||
<NavItemLink to={child.props.name} key={j}>{child.props.headerTitle}</NavItemLink>
|
||||
<AclProxy
|
||||
key={j}
|
||||
aclName={aclName}
|
||||
aclObject={this.props.userAcl}>
|
||||
<NavRoutesLinksLink
|
||||
headerTitle={headerTitle}
|
||||
routeName={name}
|
||||
depth={i}
|
||||
children={childrenFn}/>
|
||||
</AclProxy>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
return (
|
||||
<NavRoutesLinksLink
|
||||
key={j}
|
||||
headerTitle={headerTitle}
|
||||
routeName={name}
|
||||
depth={i}
|
||||
children={childrenFn}/>
|
||||
);
|
||||
}
|
||||
} 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 (
|
||||
<Nav {...this.props}>
|
||||
{this.extractLinksFromRoutes(this.props.routes, 0)}
|
||||
{this.extractLinksFromRoutes(routes, userAcl, 0)}
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
|
51
js/components/nav_routes_links_link.js
Normal file
51
js/components/nav_routes_links_link.js
Normal file
@ -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 (
|
||||
<DropdownButton title={headerTitle}>
|
||||
{children}
|
||||
</DropdownButton>
|
||||
);
|
||||
} 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 (
|
||||
<MenuItemLink to={routeName}>{headerTitle}</MenuItemLink>
|
||||
);
|
||||
} else if(depth === 0) {
|
||||
return (
|
||||
<NavItemLink to={routeName}>{headerTitle}</NavItemLink>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default NavRoutesLinksLink;
|
@ -15,12 +15,16 @@ import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_l
|
||||
|
||||
import Pagination from './ascribe_pagination/pagination';
|
||||
|
||||
import PieceListFilterDisplay from './piece_list_filter_display';
|
||||
|
||||
import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal';
|
||||
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
||||
|
||||
import AppConstants from '../constants/application_constants';
|
||||
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
|
||||
let PieceList = React.createClass({
|
||||
propTypes: {
|
||||
@ -30,7 +34,6 @@ let PieceList = React.createClass({
|
||||
filterParams: React.PropTypes.array,
|
||||
orderParams: React.PropTypes.array,
|
||||
orderBy: React.PropTypes.string
|
||||
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
@ -39,13 +42,14 @@ let PieceList = React.createClass({
|
||||
return {
|
||||
accordionListItemType: AccordionListItemWallet,
|
||||
orderParams: ['artist_name', 'title'],
|
||||
filterParams: [
|
||||
'acl_transfer',
|
||||
'acl_consign',
|
||||
{
|
||||
key: 'acl_create_editions',
|
||||
label: 'create editions'
|
||||
}]
|
||||
filterParams: [{
|
||||
label: getLangText('Show works I can'),
|
||||
items: [
|
||||
'acl_transfer',
|
||||
'acl_consign',
|
||||
'acl_create_editions'
|
||||
]
|
||||
}]
|
||||
};
|
||||
},
|
||||
getInitialState() {
|
||||
@ -60,18 +64,18 @@ let PieceList = React.createClass({
|
||||
|
||||
PieceListStore.listen(this.onChange);
|
||||
EditionListStore.listen(this.onChange);
|
||||
|
||||
let orderBy = this.props.orderBy ? this.props.orderBy : this.state.orderBy;
|
||||
if (this.state.pieceList.length === 0 || this.state.page !== page){
|
||||
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
|
||||
orderBy, this.state.orderAsc, this.state.filterBy)
|
||||
.then(() => PieceListActions.fetchPieceRequestActions());
|
||||
orderBy, this.state.orderAsc, this.state.filterBy);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) {
|
||||
// FIXME: hack to redirect out of the dispatch cycle
|
||||
window.setTimeout(() => this.transitionTo(this.props.redirectTo), 0);
|
||||
window.setTimeout(() => this.transitionTo(this.props.redirectTo, this.getQuery()));
|
||||
}
|
||||
},
|
||||
|
||||
@ -90,8 +94,8 @@ let PieceList = React.createClass({
|
||||
// the site should go to the top
|
||||
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
|
||||
this.state.orderBy, this.state.orderAsc,
|
||||
this.state.filterBy);
|
||||
this.state.orderBy, this.state.orderAsc,
|
||||
this.state.filterBy);
|
||||
};
|
||||
},
|
||||
|
||||
@ -147,6 +151,7 @@ let PieceList = React.createClass({
|
||||
render() {
|
||||
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
|
||||
let AccordionListItemType = this.props.accordionListItemType;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PieceListToolbar
|
||||
@ -161,6 +166,9 @@ let PieceList = React.createClass({
|
||||
{this.props.customSubmitButton}
|
||||
</PieceListToolbar>
|
||||
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
|
||||
<PieceListFilterDisplay
|
||||
filterBy={this.state.filterBy}
|
||||
filterParams={this.props.filterParams}/>
|
||||
<AccordionList
|
||||
className="ascribe-accordion-list"
|
||||
changeOrder={this.accordionChangeOrder}
|
||||
|
118
js/components/piece_list_filter_display.js
Normal file
118
js/components/piece_list_filter_display.js
Normal file
@ -0,0 +1,118 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
|
||||
let PieceListFilterDisplay = React.createClass({
|
||||
propTypes: {
|
||||
filterBy: React.PropTypes.object,
|
||||
filterParams: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
label: React.PropTypes.string,
|
||||
items: React.PropTypes.arrayOf(
|
||||
React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.shape({
|
||||
key: React.PropTypes.string,
|
||||
label: React.PropTypes.string
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes the above described filterParams prop,
|
||||
* assigns it it's true filterBy value that is derived from the filterBy prop
|
||||
* and also - if there wasn't already one defined - generates a label
|
||||
* @return {object}
|
||||
*/
|
||||
transformFilterParamsItemsToBools() {
|
||||
let { filterParams, filterBy } = this.props;
|
||||
|
||||
return filterParams.map((filterParam) => {
|
||||
return {
|
||||
label: filterParam.label,
|
||||
items: filterParam.items.map((item) => {
|
||||
if(typeof item !== 'string' && typeof item.key === 'string' && typeof item.label === 'string') {
|
||||
return {
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
value: filterBy[item.key] || false
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
key: item,
|
||||
label: item.split('acl_')[1].replace(/_/g, ' '),
|
||||
value: filterBy[item] || false
|
||||
};
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes the list of filters generated in transformFilterParamsItemsToBools and
|
||||
* transforms them into human readable text.
|
||||
* @param {Object} filtersWithLabel An object of the shape {key: <String>, label: <String>, value: <Bool>}
|
||||
* @return {string} A human readable string
|
||||
*/
|
||||
getFilterText(filtersWithLabel) {
|
||||
let filterTextList = filtersWithLabel
|
||||
// Iterate over all provided filterLabels and generate a list
|
||||
// of human readable strings
|
||||
.map((filterWithLabel) => {
|
||||
let activeFilterWithLabel = filterWithLabel
|
||||
.items
|
||||
// If the filter is active (which it is when its value is true),
|
||||
// we're going to include it's label into a list,
|
||||
// otherwise we'll just return nothing
|
||||
.map((filter) => {
|
||||
if(filter.value) {
|
||||
return filter.label;
|
||||
}
|
||||
})
|
||||
// if nothing is returned, that index is 'undefined'.
|
||||
// As we only want active filter, we filter out all falsy values e.g. undefined
|
||||
.filter((filterName) => !!filterName)
|
||||
// and join the result to a string
|
||||
.join(', ');
|
||||
|
||||
// If this actually didn't generate an empty string,
|
||||
// we take the label and concat it to the result.
|
||||
if(activeFilterWithLabel) {
|
||||
return filterWithLabel.label + ': ' + activeFilterWithLabel;
|
||||
}
|
||||
})
|
||||
// filter out strings that are undefined, as their filter's were not activated
|
||||
.filter((filterText) => !!filterText)
|
||||
// if there are multiple sentences, capitalize the first one and lowercase the others
|
||||
.map((filterText, i) => i === 0 ? filterText.charAt(0).toUpperCase() + filterText.substr(1) : filterText.charAt(0).toLowerCase() + filterText.substr(1))
|
||||
.join(' and ');
|
||||
|
||||
return filterTextList;
|
||||
},
|
||||
|
||||
render() {
|
||||
let { filterBy } = this.props;
|
||||
let filtersWithLabel = this.transformFilterParamsItemsToBools();
|
||||
|
||||
// do not show the FilterDisplay if there are no filters applied
|
||||
if(filterBy && Object.keys(filterBy).length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="ascribe-piece-list-filter-display col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
|
||||
{this.getFilterText(filtersWithLabel)}
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default PieceListFilterDisplay;
|
@ -1,408 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import UserActions from '../actions/user_actions';
|
||||
import UserStore from '../stores/user_store';
|
||||
|
||||
import WalletSettingsActions from '../actions/wallet_settings_actions';
|
||||
import WalletSettingsStore from '../stores/wallet_settings_store';
|
||||
|
||||
import ApplicationActions from '../actions/application_actions';
|
||||
import ApplicationStore from '../stores/application_store';
|
||||
|
||||
import GlobalNotificationModel from '../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../actions/global_notification_actions';
|
||||
|
||||
import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader';
|
||||
|
||||
import CollapsibleParagraph from './ascribe_collapsible/collapsible_paragraph';
|
||||
import Form from './ascribe_forms/form';
|
||||
import Property from './ascribe_forms/property';
|
||||
import InputCheckbox from './ascribe_forms/input_checkbox';
|
||||
|
||||
import ActionPanel from './ascribe_panel/action_panel';
|
||||
|
||||
import ApiUrls from '../constants/api_urls';
|
||||
import AppConstants from '../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { getCookie } from '../utils/fetch_api_utils';
|
||||
|
||||
let SettingsContainer = React.createClass({
|
||||
propTypes: {
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
React.PropTypes.element])
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="settings-container">
|
||||
<AccountSettings />
|
||||
{this.props.children}
|
||||
<APISettings />
|
||||
<BitcoinWalletSettings />
|
||||
<LoanContractSettings />
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let AccountSettings = React.createClass({
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.listen(this.onChange);
|
||||
UserActions.fetchCurrentUser();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
UserStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
handleSuccess(){
|
||||
UserActions.fetchCurrentUser();
|
||||
let notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
getFormDataProfile(){
|
||||
return {'email': this.state.currentUser.email};
|
||||
},
|
||||
|
||||
render() {
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
let profile = null;
|
||||
|
||||
if (this.state.currentUser.username) {
|
||||
content = (
|
||||
<Form
|
||||
url={ApiUrls.users_username}
|
||||
handleSuccess={this.handleSuccess}>
|
||||
<Property
|
||||
name='username'
|
||||
label={getLangText('Username')}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={this.state.currentUser.username}
|
||||
placeholder={getLangText('Enter your username')}
|
||||
required/>
|
||||
</Property>
|
||||
<Property
|
||||
name='email'
|
||||
label={getLangText('Email')}
|
||||
editable={false}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={this.state.currentUser.email}
|
||||
placeholder={getLangText('Enter your username')}
|
||||
required/>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>
|
||||
);
|
||||
profile = (
|
||||
<Form
|
||||
url={ApiUrls.users_profile}
|
||||
handleSuccess={this.handleSuccess}
|
||||
getFormData={this.getFormDataProfile}>
|
||||
<Property
|
||||
name="hash_locally"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox
|
||||
defaultChecked={this.state.currentUser.profile.hash_locally}>
|
||||
<span>
|
||||
{' ' + getLangText('Enable hash option, e.g. slow connections or to keep piece private')}
|
||||
</span>
|
||||
</InputCheckbox>
|
||||
</Property>
|
||||
<hr />
|
||||
{/*<Property
|
||||
name='language'
|
||||
label={getLangText('Choose your Language')}
|
||||
editable={true}>
|
||||
<select id="select-lang" name="language">
|
||||
<option value="fr">
|
||||
Français
|
||||
</option>
|
||||
<option value="en"
|
||||
selected="selected">
|
||||
English
|
||||
</option>
|
||||
</select>
|
||||
</Property>*/}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Account')}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
{content}
|
||||
{profile}
|
||||
{/*<Form
|
||||
url={AppConstants.serverUrl + 'api/users/set_language/'}>
|
||||
<Property
|
||||
name='language'
|
||||
label={getLangText('Choose your Language')}
|
||||
editable={true}>
|
||||
<select id="select-lang" name="language">
|
||||
<option value="fr">
|
||||
Français
|
||||
</option>
|
||||
<option value="en"
|
||||
selected="selected">
|
||||
English
|
||||
</option>
|
||||
</select>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>*/}
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
let BitcoinWalletSettings = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
defaultExpanded: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return WalletSettingsStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
WalletSettingsStore.listen(this.onChange);
|
||||
WalletSettingsActions.fetchWalletSettings();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
WalletSettingsStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
render() {
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
if (this.state.walletSettings.btc_public_key) {
|
||||
content = (
|
||||
<Form >
|
||||
<Property
|
||||
name='btc_public_key'
|
||||
label={getLangText('Bitcoin public key')}
|
||||
editable={false}>
|
||||
<pre className="ascribe-pre">{this.state.walletSettings.btc_public_key}</pre>
|
||||
</Property>
|
||||
<Property
|
||||
name='btc_root_address'
|
||||
label={getLangText('Root Address')}
|
||||
editable={false}>
|
||||
<pre className="ascribe-pre">{this.state.walletSettings.btc_root_address}</pre>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>);
|
||||
}
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Crypto Wallet')}
|
||||
show={true}
|
||||
defaultExpanded={this.props.defaultExpanded}>
|
||||
{content}
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let LoanContractSettings = React.createClass({
|
||||
propTypes: {
|
||||
defaultExpanded: React.PropTypes.bool
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title="Loan Contract Settings"
|
||||
show={true}
|
||||
defaultExpanded={this.props.defaultExpanded}>
|
||||
<FileUploader />
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let FileUploader = React.createClass({
|
||||
propTypes: {
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Form>
|
||||
<Property
|
||||
label="Contract file">
|
||||
<ReactS3FineUploader
|
||||
keyRoutine={{
|
||||
url: AppConstants.serverUrl + 's3/key/',
|
||||
fileClass: 'contract'
|
||||
}}
|
||||
createBlobRoutine={{
|
||||
url: ApiUrls.ownership_loans_contract
|
||||
}}
|
||||
validation={{
|
||||
itemLimit: 100000,
|
||||
sizeLimit: '50000000'
|
||||
}}
|
||||
session={{
|
||||
endpoint: ApiUrls.ownership_loans_contract,
|
||||
customHeaders: {
|
||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
||||
},
|
||||
cors: {
|
||||
expected: true,
|
||||
sendCredentials: true
|
||||
}
|
||||
}}
|
||||
signature={{
|
||||
endpoint: AppConstants.serverUrl + 's3/signature/',
|
||||
customHeaders: {
|
||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
||||
}
|
||||
}}
|
||||
deleteFile={{
|
||||
enabled: true,
|
||||
method: 'DELETE',
|
||||
endpoint: AppConstants.serverUrl + 's3/delete',
|
||||
customHeaders: {
|
||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
||||
}
|
||||
}}
|
||||
areAssetsDownloadable={true}
|
||||
areAssetsEditable={true}/>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let APISettings = React.createClass({
|
||||
propTypes: {
|
||||
defaultExpanded: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return ApplicationStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
ApplicationStore.listen(this.onChange);
|
||||
ApplicationActions.fetchApplication();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
ApplicationStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
handleCreateSuccess() {
|
||||
ApplicationActions.fetchApplication();
|
||||
let notification = new GlobalNotificationModel(getLangText('Application successfully created'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
handleTokenRefresh(event) {
|
||||
let applicationName = event.target.getAttribute('data-id');
|
||||
ApplicationActions.refreshApplicationToken(applicationName);
|
||||
|
||||
let notification = new GlobalNotificationModel(getLangText('Token refreshed'), 'success', 2000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
getApplications(){
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
if (this.state.applications.length > -1) {
|
||||
content = this.state.applications.map(function(app, i) {
|
||||
return (
|
||||
<ActionPanel
|
||||
name={app.name}
|
||||
key={i}
|
||||
content={
|
||||
<div>
|
||||
<div className='ascribe-panel-title'>
|
||||
{app.name}
|
||||
</div>
|
||||
<div className="ascribe-panel-subtitle">
|
||||
{'Bearer ' + app.bearer_token.token}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
buttons={
|
||||
<div className="pull-right">
|
||||
<div className="pull-right">
|
||||
<button
|
||||
className="pull-right btn btn-default btn-sm"
|
||||
onClick={this.handleTokenRefresh}
|
||||
data-id={app.name}>
|
||||
{getLangText('REFRESH')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}/>
|
||||
);
|
||||
}, this);
|
||||
}
|
||||
return content;
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('API Integration')}
|
||||
show={true}
|
||||
defaultExpanded={this.props.defaultExpanded}>
|
||||
<Form
|
||||
url={ApiUrls.applications}
|
||||
handleSuccess={this.handleCreateSuccess}>
|
||||
<Property
|
||||
name='name'
|
||||
label={getLangText('Application Name')}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={getLangText('Enter the name of your app')}
|
||||
required/>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>
|
||||
<pre>
|
||||
Usage: curl <url> -H 'Authorization: Bearer <token>'
|
||||
</pre>
|
||||
{this.getApplications()}
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default SettingsContainer;
|
@ -42,6 +42,7 @@ let SignupContainer = React.createClass({
|
||||
{getLangText('Already an ascribe user')}? <Link to="login">{getLangText('Log in')}...</Link><br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -124,7 +124,7 @@ let AccordionListItemPrize = React.createClass({
|
||||
<div>
|
||||
<AclProxy
|
||||
aclObject={this.props.content.acl}
|
||||
aclName="acl_submit">
|
||||
aclName="acl_wallet_submit">
|
||||
<SubmitToPrizeButton
|
||||
className="pull-right"
|
||||
piece={this.props.content}
|
||||
|
@ -86,7 +86,19 @@ let PieceContainer = React.createClass({
|
||||
|
||||
loadPiece() {
|
||||
PieceActions.fetchOne(this.props.params.pieceId);
|
||||
this.setState(this.state);
|
||||
},
|
||||
|
||||
getActions() {
|
||||
if (this.state.piece &&
|
||||
this.state.piece.notifications &&
|
||||
this.state.piece.notifications.length > 0) {
|
||||
return (
|
||||
<ListRequestActions
|
||||
pieceOrEditions={this.state.piece}
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.loadPiece}
|
||||
notifications={this.state.piece.notifications}/>);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
@ -121,11 +133,7 @@ let PieceContainer = React.createClass({
|
||||
<DetailProperty label={getLangText('BY')} value={artistName} />
|
||||
<DetailProperty label={getLangText('DATE')} value={ this.state.piece.date_created.slice(0, 4) } />
|
||||
{artistEmail}
|
||||
<ListRequestActions
|
||||
pieceOrEditions={this.state.piece}
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.loadPiece}
|
||||
requestActions={this.state.piece.request_action}/>
|
||||
{this.getActions()}
|
||||
<hr/>
|
||||
</div>
|
||||
}
|
||||
@ -301,7 +309,6 @@ let PrizePieceRatings = React.createClass({
|
||||
<div>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Shortlisting')}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
<div className="row no-margin">
|
||||
<span className="ascribe-checkbox-wrapper" style={{marginLeft: '1.5em'}}>
|
||||
@ -321,7 +328,6 @@ let PrizePieceRatings = React.createClass({
|
||||
</CollapsibleParagraph>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Average Rating')}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
<div id="list-rating" style={{marginLeft: '1.5em', marginBottom: '1em'}}>
|
||||
<StarRating
|
||||
@ -367,7 +373,6 @@ let PrizePieceRatings = React.createClass({
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Rating')}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
<div style={{marginLeft: '1.5em', marginBottom: '1em'}}>
|
||||
<StarRating
|
||||
@ -409,7 +414,6 @@ let PrizePieceDetails = React.createClass({
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Prize Details')}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
<Form ref='form'>
|
||||
{Object.keys(this.props.piece.extra_data).map((data) => {
|
||||
@ -418,10 +422,10 @@ let PrizePieceDetails = React.createClass({
|
||||
<Property
|
||||
name={data}
|
||||
label={label}
|
||||
editable={false}>
|
||||
editable={false}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={false}
|
||||
defaultValue={this.props.piece.extra_data[data]}/>
|
||||
</Property>);
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ let PrizePieceList = React.createClass({
|
||||
accordionListItemType={AccordionListItemPrize}
|
||||
orderParams={orderParams}
|
||||
orderBy={this.state.currentUser.is_jury ? 'rating' : null}
|
||||
filterParams={null}
|
||||
filterParams={[]}
|
||||
customSubmitButton={this.getButtonSubmit()}/>
|
||||
</div>
|
||||
);
|
||||
|
@ -41,20 +41,20 @@ let PrizeRegisterPiece = React.createClass({
|
||||
<Property
|
||||
name='artist_statement'
|
||||
label={getLangText('Artist statement')}
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
placeholder={getLangText('Enter your statement')}
|
||||
required="required"/>
|
||||
</Property>
|
||||
<Property
|
||||
name='work_description'
|
||||
label={getLangText('Work description')}
|
||||
editable={true}>
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={true}
|
||||
placeholder={getLangText('Enter the description for your work')}
|
||||
required="required"/>
|
||||
</Property>
|
||||
|
@ -9,7 +9,7 @@ import PrizeStore from '../stores/prize_store';
|
||||
import PrizeJuryActions from '../actions/prize_jury_actions';
|
||||
import PrizeJuryStore from '../stores/prize_jury_store';
|
||||
|
||||
import SettingsContainer from '../../../settings_container';
|
||||
import SettingsContainer from '../../../ascribe_settings/settings_container';
|
||||
import CollapsibleParagraph from '../../../ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import Form from '../../../ascribe_forms/form';
|
||||
@ -79,7 +79,6 @@ let PrizeSettings = React.createClass({
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={'Prize Settings for ' + this.state.prize.name}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
<Form >
|
||||
<Property
|
||||
@ -190,7 +189,7 @@ let PrizeJurySettings = React.createClass({
|
||||
{getLangText('RESEND')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-default btn-sm ascribe-btn-gray margin-left-2px"
|
||||
className="btn btn-warning btn-sm margin-left-2px"
|
||||
onClick={this.handleRevoke}
|
||||
data-id={member.email}>
|
||||
{getLangText('REVOKE')}
|
||||
@ -218,7 +217,7 @@ let PrizeJurySettings = React.createClass({
|
||||
}
|
||||
buttons={
|
||||
<button
|
||||
className="btn btn-default btn-sm ascribe-btn-gray"
|
||||
className="btn btn-warning btn-sm"
|
||||
onClick={this.handleRevoke}
|
||||
data-id={member.email}>
|
||||
{getLangText('REVOKE')}
|
||||
@ -265,22 +264,19 @@ let PrizeJurySettings = React.createClass({
|
||||
|
||||
if (this.state.members.length > -1) {
|
||||
content = (
|
||||
<div style={{padding: '1em'}}>
|
||||
<div>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Active Jury Members')}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
{this.getMembersActive()}
|
||||
</CollapsibleParagraph>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Pending Jury Invitations')}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
{this.getMembersPending()}
|
||||
</CollapsibleParagraph>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Deactivated Jury Members')}
|
||||
show={true}
|
||||
defaultExpanded={false}>
|
||||
{this.getMembersInactive()}
|
||||
</CollapsibleParagraph>
|
||||
@ -318,4 +314,4 @@ let PrizeJurySettings = React.createClass({
|
||||
});
|
||||
|
||||
|
||||
export default Settings;
|
||||
export default Settings;
|
||||
|
@ -27,7 +27,7 @@ let PrizeApp = React.createClass({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container ascribe-prize-app">
|
||||
<div className={'container ascribe-prize-app client--' + subdomain}>
|
||||
{header}
|
||||
<RouteHandler />
|
||||
<GlobalNotification />
|
||||
|
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
|
||||
import ListRequestActions from '../../../../ascribe_forms/list_form_request_actions';
|
||||
import AclButtonList from '../../../../ascribe_buttons/acl_button_list';
|
||||
import DeleteButton from '../../../../ascribe_buttons/delete_button';
|
||||
|
||||
import AclProxy from '../../../../acl_proxy';
|
||||
|
||||
|
||||
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
|
||||
|
||||
let WalletActionPanel = React.createClass({
|
||||
propTypes: {
|
||||
piece: React.PropTypes.object.isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
loadPiece: React.PropTypes.func.isRequired,
|
||||
submitButtonType: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
render(){
|
||||
if (this.props.piece &&
|
||||
this.props.piece.notifications &&
|
||||
this.props.piece.notifications.length > 0) {
|
||||
return (
|
||||
<ListRequestActions
|
||||
pieceOrEditions={this.props.piece}
|
||||
currentUser={this.props.currentUser}
|
||||
handleSuccess={this.props.loadPiece}
|
||||
notifications={this.props.piece.notifications}/>);
|
||||
}
|
||||
else {
|
||||
|
||||
//We need to disable the normal acl_loan because we're inserting a custom acl_loan button
|
||||
let availableAcls;
|
||||
|
||||
if (this.props.piece && this.props.piece.acl && typeof this.props.piece.acl.acl_loan !== 'undefined') {
|
||||
// make a copy to not have side effects
|
||||
availableAcls = mergeOptions({}, this.props.piece.acl);
|
||||
availableAcls.acl_loan = false;
|
||||
}
|
||||
let SubmitButtonType = this.props.submitButtonType;
|
||||
|
||||
return (
|
||||
<AclButtonList
|
||||
className="text-center ascribe-button-list"
|
||||
availableAcls={availableAcls}
|
||||
editions={this.props.piece}
|
||||
handleSuccess={this.loadPiece}>
|
||||
<AclProxy
|
||||
aclObject={this.props.currentUser.acl}
|
||||
aclName="acl_wallet_submit">
|
||||
<AclProxy
|
||||
aclObject={availableAcls}
|
||||
aclName="acl_wallet_submit">
|
||||
<SubmitButtonType
|
||||
className="btn-sm"
|
||||
handleSuccess={this.handleSubmitSuccess}
|
||||
piece={this.props.piece}/>
|
||||
</AclProxy>
|
||||
</AclProxy>
|
||||
<DeleteButton
|
||||
handleSuccess={this.handleDeleteSuccess}
|
||||
piece={this.props.piece}/>
|
||||
</AclButtonList>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default WalletActionPanel;
|
@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Piece from '../../../../../components/ascribe_detail/piece';
|
||||
|
||||
import WalletActionPanel from './wallet_action_panel';
|
||||
import CollapsibleParagraph from '../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import HistoryIterator from '../../../../ascribe_detail/history_iterator';
|
||||
import Note from '../../../../ascribe_detail/note';
|
||||
|
||||
import DetailProperty from '../../../../ascribe_detail/detail_property';
|
||||
|
||||
import ApiUrls from '../../../../../constants/api_urls';
|
||||
import AppConstants from '../../../../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
|
||||
|
||||
let WalletPieceContainer = React.createClass({
|
||||
propTypes: {
|
||||
piece: React.PropTypes.object.isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
loadPiece: React.PropTypes.func.isRequired,
|
||||
submitButtonType: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
|
||||
render() {
|
||||
if(this.props.piece && this.props.piece.title) {
|
||||
return (
|
||||
<Piece
|
||||
piece={this.props.piece}
|
||||
loadPiece={this.props.loadPiece}
|
||||
header={
|
||||
<div className="ascribe-detail-header">
|
||||
<hr style={{marginTop: 0}}/>
|
||||
<h1 className="ascribe-detail-title">{this.props.piece.title}</h1>
|
||||
<DetailProperty label="BY" value={this.props.piece.artist_name} />
|
||||
<DetailProperty label="DATE" value={ this.props.piece.date_created.slice(0, 4) } />
|
||||
<hr/>
|
||||
</div>
|
||||
}
|
||||
subheader={
|
||||
<div className="ascribe-detail-header">
|
||||
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
|
||||
<DetailProperty label={getLangText('ID')} value={ this.props.piece.bitcoin_id } ellipsis={true} />
|
||||
<hr/>
|
||||
</div>
|
||||
}>
|
||||
<WalletActionPanel
|
||||
piece={this.props.piece}
|
||||
currentUser={this.props.currentUser}
|
||||
loadPiece={this.props.loadPiece}
|
||||
submitButtonType={this.props.submitButtonType}/>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Loan History')}
|
||||
show={this.props.piece.loan_history && this.props.piece.loan_history.length > 0}>
|
||||
<HistoryIterator
|
||||
history={this.props.piece.loan_history}/>
|
||||
</CollapsibleParagraph>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Notes')}
|
||||
show={!!(this.props.currentUser.username || this.props.piece.public_note)}>
|
||||
<Note
|
||||
id={() => {return {'id': this.props.piece.id}; }}
|
||||
label={getLangText('Personal note (private)')}
|
||||
defaultValue={this.props.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}/>
|
||||
</CollapsibleParagraph>
|
||||
|
||||
{this.props.children}
|
||||
</Piece>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div className="fullpage-spinner">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export default WalletPieceContainer;
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece';
|
||||
|
||||
@ -64,7 +63,7 @@ let CylandAccordionListItem = React.createClass({
|
||||
<div>
|
||||
<AclProxy
|
||||
aclObject={this.props.content.acl}
|
||||
aclName="acl_submit">
|
||||
aclName="acl_wallet_submit">
|
||||
<CylandSubmitButton
|
||||
className="pull-right"
|
||||
piece={this.props.content}
|
||||
@ -72,7 +71,7 @@ let CylandAccordionListItem = React.createClass({
|
||||
</AclProxy>
|
||||
<AclProxy
|
||||
aclObject={this.props.content.acl}
|
||||
aclName="acl_submitted">
|
||||
aclName="acl_wallet_submitted">
|
||||
<button
|
||||
disabled
|
||||
className="btn btn-default btn-xs pull-right">
|
||||
@ -80,6 +79,16 @@ let CylandAccordionListItem = React.createClass({
|
||||
aria-hidden="true"></span>
|
||||
</button>
|
||||
</AclProxy>
|
||||
<AclProxy
|
||||
aclObject={this.props.content.acl}
|
||||
aclName="acl_wallet_accepted">
|
||||
<button
|
||||
disabled
|
||||
className="btn btn-default btn-xs pull-right">
|
||||
{getLangText('Loaned to Cyland')} <span className="glyphicon glyphicon-ok"
|
||||
aria-hidden="true"></span>
|
||||
</button>
|
||||
</AclProxy>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -7,27 +7,19 @@ import PieceStore from '../../../../../../stores/piece_store';
|
||||
|
||||
import UserStore from '../../../../../../stores/user_store';
|
||||
|
||||
import Piece from '../../../../../../components/ascribe_detail/piece';
|
||||
import CylandSubmitButton from '../ascribe_buttons/cyland_submit_button';
|
||||
|
||||
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import CylandAdditionalDataForm from '../ascribe_forms/cyland_additional_data_form';
|
||||
|
||||
import WalletPieceContainer from '../../ascribe_detail/wallet_piece_container';
|
||||
|
||||
import AppConstants from '../../../../../../constants/application_constants';
|
||||
|
||||
import Form from '../../../../../../components/ascribe_forms/form';
|
||||
import Property from '../../../../../../components/ascribe_forms/property';
|
||||
import InputTextAreaToggable from '../../../../../../components/ascribe_forms/input_textarea_toggable';
|
||||
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import HistoryIterator from '../../../../../ascribe_detail/history_iterator';
|
||||
import Note from '../../../../../ascribe_detail/note';
|
||||
|
||||
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
|
||||
import DetailProperty from '../../../../../ascribe_detail/detail_property';
|
||||
|
||||
import ApiUrls from '../../../../../../constants/api_urls';
|
||||
|
||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../../../../../utils/general_utils';
|
||||
|
||||
|
||||
let CylandPieceContainer = React.createClass({
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -38,23 +30,18 @@ let CylandPieceContainer = React.createClass({
|
||||
|
||||
componentDidMount() {
|
||||
PieceStore.listen(this.onChange);
|
||||
PieceActions.fetchOne(this.props.params.pieceId);
|
||||
UserStore.listen(this.onChange);
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.props.params.pieceId !== nextProps.params.pieceId) {
|
||||
PieceActions.updatePiece({});
|
||||
PieceActions.fetchOne(nextProps.params.pieceId);
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
PieceActions.updatePiece({});
|
||||
|
||||
this.loadPiece();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
PieceStore.unlisten(this.onChange);
|
||||
UserStore.unlisten(this.onChange);
|
||||
},
|
||||
@ -70,50 +57,23 @@ let CylandPieceContainer = React.createClass({
|
||||
render() {
|
||||
if(this.state.piece && this.state.piece.title) {
|
||||
return (
|
||||
<Piece
|
||||
<WalletPieceContainer
|
||||
piece={this.state.piece}
|
||||
currentUser={this.state.currentUser}
|
||||
loadPiece={this.loadPiece}
|
||||
header={
|
||||
<div className="ascribe-detail-header">
|
||||
<hr style={{marginTop: 0}}/>
|
||||
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
|
||||
<DetailProperty label="BY" value={this.state.piece.artist_name} />
|
||||
<DetailProperty label="DATE" value={ this.state.piece.date_created.slice(0, 4) } />
|
||||
<hr/>
|
||||
</div>
|
||||
}
|
||||
subheader={
|
||||
<div className="ascribe-detail-header">
|
||||
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } />
|
||||
<DetailProperty label={getLangText('ID')} value={ this.state.piece.bitcoin_id } ellipsis={true} />
|
||||
<hr/>
|
||||
</div>
|
||||
}>
|
||||
|
||||
submitButtonType={CylandSubmitButton}>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Loan History')}
|
||||
show={this.state.piece.loan_history && this.state.piece.loan_history.length > 0}>
|
||||
<HistoryIterator
|
||||
history={this.state.piece.loan_history} />
|
||||
title={getLangText('Further Details')}
|
||||
defaultExpanded={true}>
|
||||
<CylandAdditionalDataForm
|
||||
piece={this.state.piece}
|
||||
disabled={!this.state.piece.acl.acl_edit}
|
||||
isInline={true} />
|
||||
</CollapsibleParagraph>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Notes')}
|
||||
show={(this.state.currentUser.username && true || false) ||
|
||||
(this.state.piece.public_note)}>
|
||||
<Note
|
||||
id={() => {return {'id': this.state.piece.id}; }}
|
||||
label={getLangText('Personal note (private)')}
|
||||
defaultValue={this.state.piece.private_note ? this.state.piece.private_note : null}
|
||||
placeholder={getLangText('Enter your comments ...')}
|
||||
editable={true}
|
||||
successMessage={getLangText('Private note saved')}
|
||||
url={ApiUrls.note_private_piece}
|
||||
currentUser={this.state.currentUser}/>
|
||||
</CollapsibleParagraph>
|
||||
<CylandPieceDetails piece={this.state.piece}/>
|
||||
</Piece>
|
||||
</WalletPieceContainer>
|
||||
);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div className="fullpage-spinner">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
@ -123,47 +83,4 @@ let CylandPieceContainer = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let CylandPieceDetails = React.createClass({
|
||||
propTypes: {
|
||||
piece: React.PropTypes.object
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.props.piece && Object.keys(this.props.piece.extra_data).length !== 0){
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Further Details')}
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
<Form ref='form'>
|
||||
{Object.keys(this.props.piece.extra_data).map((data, i) => {
|
||||
let label = data.replace('_', ' ');
|
||||
return (
|
||||
<Property
|
||||
key={i}
|
||||
name={data}
|
||||
label={label}
|
||||
editable={false}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={false}
|
||||
defaultValue={this.props.piece.extra_data[data]}/>
|
||||
</Property>);
|
||||
}
|
||||
)}
|
||||
<FurtherDetailsFileuploader
|
||||
editable={false}
|
||||
pieceId={this.props.piece.id}
|
||||
otherData={this.props.piece.other_data}
|
||||
multiple={false}/>
|
||||
<hr />
|
||||
</Form>
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
export default CylandPieceContainer;
|
||||
|
@ -9,19 +9,35 @@ import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_t
|
||||
|
||||
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
|
||||
|
||||
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
|
||||
|
||||
import ApiUrls from '../../../../../../constants/api_urls';
|
||||
import AppConstants from '../../../../../../constants/application_constants';
|
||||
|
||||
import requests from '../../../../../../utils/requests';
|
||||
|
||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||
import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||
|
||||
|
||||
let CylandAdditionalDataForm = React.createClass({
|
||||
propTypes: {
|
||||
handleSuccess: React.PropTypes.func.isRequired,
|
||||
handleSuccess: React.PropTypes.func,
|
||||
piece: React.PropTypes.object.isRequired,
|
||||
disabled: React.PropTypes.bool,
|
||||
isInline: React.PropTypes.bool
|
||||
},
|
||||
|
||||
disabled: React.PropTypes.bool
|
||||
getDefaultProps() {
|
||||
return {
|
||||
isInline: false
|
||||
};
|
||||
},
|
||||
|
||||
handleSuccess() {
|
||||
let notification = new GlobalNotificationModel('Further details successfully updated', 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -60,72 +76,70 @@ let CylandAdditionalDataForm = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
isReadyForFormSubmission(files) {
|
||||
let uploadingFiles = files.filter((file) => file.status === 'submitting');
|
||||
|
||||
if (uploadingFiles.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
if(this.props.piece && this.props.piece.id) {
|
||||
let { piece, isInline, disabled, handleSuccess } = this.props;
|
||||
let buttons, spinner, heading;
|
||||
|
||||
if(!isInline) {
|
||||
buttons = (
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login"
|
||||
disabled={!this.state.isUploadReady || disabled}>
|
||||
{getLangText('Proceed to loan')}
|
||||
</button>
|
||||
);
|
||||
|
||||
spinner = (
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
</div>
|
||||
);
|
||||
|
||||
heading = (
|
||||
<div className="ascribe-form-header">
|
||||
<h3>
|
||||
{getLangText('Provide supporting materials')}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(piece && piece.id) {
|
||||
return (
|
||||
<Form
|
||||
disabled={this.props.disabled}
|
||||
disabled={disabled}
|
||||
className="ascribe-form-bordered"
|
||||
ref='form'
|
||||
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})}
|
||||
handleSuccess={this.props.handleSuccess}
|
||||
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: piece.id})}
|
||||
handleSuccess={handleSuccess || this.handleSuccess}
|
||||
getFormData={this.getFormData}
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login"
|
||||
disabled={!this.state.isUploadReady || this.props.disabled}>
|
||||
{getLangText('Proceed to loan')}
|
||||
</button>
|
||||
}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
</div>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
<h3>
|
||||
{getLangText('Provide supporting materials')}
|
||||
</h3>
|
||||
</div>
|
||||
buttons={buttons}
|
||||
spinner={spinner}>
|
||||
{heading}
|
||||
<Property
|
||||
name='artist_bio'
|
||||
label={getLangText('Artist Biography')}
|
||||
editable={!this.props.disabled}>
|
||||
label={getLangText('Artist Biography')}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={!this.props.disabled}
|
||||
placeholder={getLangText('Enter the artist\'s biography...')}
|
||||
required="required"/>
|
||||
defaultValue={piece.extra_data.artist_bio}
|
||||
placeholder={getLangText('Enter the artist\'s biography...')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='conceptual_overview'
|
||||
label={getLangText('Conceptual Overview')}
|
||||
editable={!this.props.disabled}>
|
||||
label={getLangText('Conceptual Overview')}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
editable={!this.props.disabled}
|
||||
placeholder={getLangText('Enter a conceptual overview...')}
|
||||
required="required"/>
|
||||
defaultValue={piece.extra_data.conceptual_overview}
|
||||
placeholder={getLangText('Enter a conceptual overview...')}/>
|
||||
</Property>
|
||||
<FurtherDetailsFileuploader
|
||||
uploadStarted={this.uploadStarted}
|
||||
submitKey={this.submitKey}
|
||||
submitFile={this.submitFile}
|
||||
setIsUploadReady={this.setIsUploadReady}
|
||||
isReadyForFormSubmission={this.isReadyForFormSubmission}
|
||||
editable={!this.props.disabled}
|
||||
pieceId={this.props.piece.id}
|
||||
otherData={this.props.piece.other_data}
|
||||
isReadyForFormSubmission={formSubmissionValidation.fileOptional}
|
||||
pieceId={piece.id}
|
||||
otherData={piece.other_data}
|
||||
multiple={true}/>
|
||||
</Form>
|
||||
);
|
||||
|
@ -8,6 +8,8 @@ import UserStore from '../../../../../stores/user_store';
|
||||
|
||||
import CylandAccordionListItem from './ascribe_accordion_list/cyland_accordion_list_item';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
|
||||
|
||||
let CylandPieceList = React.createClass({
|
||||
getInitialState() {
|
||||
@ -33,6 +35,13 @@ let CylandPieceList = React.createClass({
|
||||
<PieceList
|
||||
redirectTo="register_piece"
|
||||
accordionListItemType={CylandAccordionListItem}
|
||||
filterParams={[{
|
||||
label: getLangText('Show works I have'),
|
||||
items: [{
|
||||
key: 'acl_loaned',
|
||||
label: getLangText('loaned to Cyland')
|
||||
}]
|
||||
}]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -10,9 +10,6 @@ import Row from 'react-bootstrap/lib/Row';
|
||||
|
||||
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
|
||||
|
||||
import Property from '../../../../ascribe_forms/property';
|
||||
import InputCheckbox from '../../../../ascribe_forms/input_checkbox';
|
||||
|
||||
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
|
||||
import WhitelabelStore from '../../../../../stores/whitelabel_store';
|
||||
|
||||
@ -136,7 +133,7 @@ let CylandRegisterPiece = React.createClass({
|
||||
this.transitionTo('piece', {pieceId: this.state.piece.id});
|
||||
},
|
||||
|
||||
// We need to increase the step to lock the forms that are already filed out
|
||||
// We need to increase the step to lock the forms that are already filled out
|
||||
incrementStep() {
|
||||
// also increase step
|
||||
let newStep = this.state.step + 1;
|
||||
@ -222,21 +219,7 @@ let CylandRegisterPiece = React.createClass({
|
||||
showStartDate={false}
|
||||
showEndDate={false}
|
||||
showPersonalMessage={false}
|
||||
handleSuccess={this.handleLoanSuccess}>
|
||||
<Property
|
||||
name="terms"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox>
|
||||
<span>
|
||||
{' ' + getLangText('I agree to the Terms of Service of Cyland Archive') + ' '}
|
||||
(<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/cyland/terms_and_contract.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
|
||||
{getLangText('read')}
|
||||
</a>)
|
||||
</span>
|
||||
</InputCheckbox>
|
||||
</Property>
|
||||
</LoanForm>
|
||||
handleSuccess={this.handleLoanSuccess} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -63,16 +63,30 @@ let IkonotvAccordionListItem = React.createClass({
|
||||
return (
|
||||
<div>
|
||||
<AclProxy
|
||||
aclObject={this.props.content.acl}
|
||||
aclName="acl_submit">
|
||||
<IkonotvSubmitButton
|
||||
className="btn-xs pull-right"
|
||||
handleSuccess={this.handleSubmitSuccess}
|
||||
piece={this.props.content}/>
|
||||
aclObject={this.state.currentUser.acl}
|
||||
aclName="acl_wallet_submit">
|
||||
<AclProxy
|
||||
aclObject={this.props.content.acl}
|
||||
aclName="acl_wallet_submit">
|
||||
<IkonotvSubmitButton
|
||||
className="btn-xs pull-right"
|
||||
handleSuccess={this.handleSubmitSuccess}
|
||||
piece={this.props.content}/>
|
||||
</AclProxy>
|
||||
</AclProxy>
|
||||
<AclProxy
|
||||
aclObject={this.props.content.acl}
|
||||
aclName="acl_submitted">
|
||||
aclName="acl_wallet_submitted">
|
||||
<button
|
||||
disabled
|
||||
className="btn btn-default btn-xs pull-right">
|
||||
{getLangText('Submitted to IkonoTV')} <span className="glyphicon glyphicon-ok"
|
||||
aria-hidden="true"></span>
|
||||
</button>
|
||||
</AclProxy>
|
||||
<AclProxy
|
||||
aclObject={this.props.content.acl}
|
||||
aclName="acl_wallet_accepted">
|
||||
<button
|
||||
disabled
|
||||
className="btn btn-default btn-xs pull-right">
|
||||
|
@ -1,17 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Moment from 'moment';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
|
||||
|
||||
import LoanForm from '../../../../../ascribe_forms/form_loan';
|
||||
|
||||
import Property from '../../../../../ascribe_forms/property';
|
||||
import InputCheckbox from '../../../../../ascribe_forms/input_checkbox';
|
||||
|
||||
import ApiUrls from '../../../../../../constants/api_urls';
|
||||
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
|
||||
|
||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||
|
||||
@ -22,51 +13,36 @@ let IkonotvSubmitButton = React.createClass({
|
||||
piece: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
getSubmitButton() {
|
||||
return (
|
||||
<button
|
||||
className={classNames('btn', 'btn-default', this.props.className)}>
|
||||
{getLangText('Loan to IkonoTV')}
|
||||
</button>
|
||||
);
|
||||
getDefaultProps() {
|
||||
return {
|
||||
className: 'btn-xs'
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
let piece = this.props.piece;
|
||||
let startFrom = 1;
|
||||
|
||||
let today = new Moment();
|
||||
let enddate = new Moment();
|
||||
enddate.add(1, 'years');
|
||||
// In the Ikonotv loan page a user has to complete two steps.
|
||||
// Since every one of those steps is atomic a user should always be able to continue
|
||||
// where he left of.
|
||||
// This is why we start the process form slide 1/2 if the user has already finished
|
||||
// it in another session.
|
||||
if(piece && piece.extra_data && Object.keys(piece.extra_data).length > 0) {
|
||||
startFrom = 1;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
trigger={this.getSubmitButton()}
|
||||
handleSuccess={this.props.handleSuccess}
|
||||
title={getLangText('Loan to IkonoTV archive')}>
|
||||
<LoanForm
|
||||
id={{piece_id: this.props.piece.id}}
|
||||
url={ApiUrls.ownership_loans_pieces}
|
||||
email="submissions@ikono.org"
|
||||
startdate={today}
|
||||
enddate={enddate}
|
||||
gallery="IkonoTV archive"
|
||||
showPersonalMessage={false}
|
||||
handleSuccess={this.props.handleSuccess}>
|
||||
<Property
|
||||
name="terms"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox>
|
||||
<span>
|
||||
{' ' + getLangText('I agree to the Terms of Service of IkonoTV Archive') + ' '}
|
||||
(<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono-tos.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
|
||||
{getLangText('read')}
|
||||
</a>)
|
||||
</span>
|
||||
</InputCheckbox>
|
||||
</Property>
|
||||
</LoanForm>
|
||||
</ModalWrapper>
|
||||
|
||||
<ButtonLink
|
||||
to="register_piece"
|
||||
query={{
|
||||
'slide_num': 0,
|
||||
'start_from': startFrom,
|
||||
'piece_id': piece.id
|
||||
}}
|
||||
className={classNames('ascribe-margin-1px', this.props.className)}>
|
||||
{getLangText('Loan to IkonoTV')}
|
||||
</ButtonLink>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -5,53 +5,45 @@ import React from 'react';
|
||||
import PieceActions from '../../../../../../actions/piece_actions';
|
||||
import PieceStore from '../../../../../../stores/piece_store';
|
||||
|
||||
import PieceListActions from '../../../../../../actions/piece_list_actions';
|
||||
import PieceListStore from '../../../../../../stores/piece_list_store';
|
||||
|
||||
import UserStore from '../../../../../../stores/user_store';
|
||||
|
||||
import Piece from '../../../../../../components/ascribe_detail/piece';
|
||||
|
||||
import ListRequestActions from '../../../../../ascribe_forms/list_form_request_actions';
|
||||
import AclButtonList from '../../../../../ascribe_buttons/acl_button_list';
|
||||
import DeleteButton from '../../../../../ascribe_buttons/delete_button';
|
||||
|
||||
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import IkonotvSubmitButton from '../ascribe_buttons/ikonotv_submit_button';
|
||||
|
||||
import HistoryIterator from '../../../../../ascribe_detail/history_iterator';
|
||||
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import DetailProperty from '../../../../../ascribe_detail/detail_property';
|
||||
import IkonotvArtistDetailsForm from '../ascribe_forms/ikonotv_artist_details_form';
|
||||
import IkonotvArtworkDetailsForm from '../ascribe_forms/ikonotv_artwork_details_form';
|
||||
|
||||
|
||||
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
|
||||
|
||||
import AclProxy from '../../../../../acl_proxy';
|
||||
import WalletPieceContainer from '../../ascribe_detail/wallet_piece_container';
|
||||
|
||||
import AppConstants from '../../../../../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../../../../../utils/general_utils';
|
||||
|
||||
|
||||
let IkonotvPieceContainer = React.createClass({
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
PieceStore.getState(),
|
||||
UserStore.getState(),
|
||||
PieceListStore.getState()
|
||||
UserStore.getState()
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
PieceStore.listen(this.onChange);
|
||||
PieceActions.fetchOne(this.props.params.pieceId);
|
||||
UserStore.listen(this.onChange);
|
||||
PieceListStore.listen(this.onChange);
|
||||
|
||||
// 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
|
||||
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.updatePiece({});
|
||||
@ -60,14 +52,8 @@ let IkonotvPieceContainer = React.createClass({
|
||||
},
|
||||
|
||||
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
|
||||
PieceActions.updatePiece({});
|
||||
PieceStore.unlisten(this.onChange);
|
||||
UserStore.unlisten(this.onChange);
|
||||
PieceListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
@ -78,92 +64,44 @@ let IkonotvPieceContainer = React.createClass({
|
||||
PieceActions.fetchOne(this.props.params.pieceId);
|
||||
},
|
||||
|
||||
handleSubmitSuccess(response) {
|
||||
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
|
||||
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
|
||||
render() {
|
||||
let furtherDetails = (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Further Details')}
|
||||
defaultExpanded={true}>
|
||||
<span>{getLangText('This piece has been loaned before we started to collect further details.')}</span>
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
|
||||
this.loadPiece();
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
getActions(){
|
||||
if (this.state.piece &&
|
||||
this.state.piece.request_action &&
|
||||
this.state.piece.request_action.length > 0) {
|
||||
return (
|
||||
<ListRequestActions
|
||||
pieceOrEditions={this.state.piece}
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.loadPiece}
|
||||
requestActions={this.state.piece.request_action}/>
|
||||
);
|
||||
}
|
||||
else {
|
||||
|
||||
//We need to disable the normal acl_loan because we're inserting a custom acl_loan button
|
||||
let availableAcls;
|
||||
|
||||
if(this.state.piece && this.state.piece.acl && typeof this.state.piece.acl.acl_loan !== 'undefined') {
|
||||
// make a copy to not have side effects
|
||||
availableAcls = mergeOptions({}, this.state.piece.acl);
|
||||
availableAcls.acl_loan = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<AclButtonList
|
||||
className="text-center ascribe-button-list"
|
||||
availableAcls={availableAcls}
|
||||
editions={this.state.piece}
|
||||
handleSuccess={this.loadPiece}>
|
||||
<AclProxy
|
||||
aclObject={availableAcls}
|
||||
aclName="acl_submit">
|
||||
<IkonotvSubmitButton
|
||||
className="btn-sm"
|
||||
handleSuccess={this.handleSubmitSuccess}
|
||||
piece={this.state.piece}/>
|
||||
</AclProxy>
|
||||
<DeleteButton
|
||||
handleSuccess={this.handleDeleteSuccess}
|
||||
piece={this.state.piece}/>
|
||||
</AclButtonList>
|
||||
if(this.state.piece.extra_data && Object.keys(this.state.piece.extra_data).length > 0 && this.state.piece.acl) {
|
||||
furtherDetails = (
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Further Details')}
|
||||
defaultExpanded={true}>
|
||||
<IkonotvArtistDetailsForm
|
||||
piece={this.state.piece}
|
||||
isInline={true}
|
||||
disabled={!this.state.piece.acl.acl_edit} />
|
||||
<IkonotvArtworkDetailsForm
|
||||
piece={this.state.piece}
|
||||
isInline={true}
|
||||
disabled={!this.state.piece.acl.acl_edit} />
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
if(this.state.piece && this.state.piece.title) {
|
||||
return (
|
||||
<Piece
|
||||
<WalletPieceContainer
|
||||
piece={this.state.piece}
|
||||
currentUser={this.state.currentUser}
|
||||
loadPiece={this.loadPiece}
|
||||
header={
|
||||
<div className="ascribe-detail-header">
|
||||
<hr style={{marginTop: 0}}/>
|
||||
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
|
||||
<DetailProperty label="BY" value={this.state.piece.artist_name} />
|
||||
<DetailProperty label="DATE" value={ this.state.piece.date_created.slice(0, 4) } />
|
||||
<hr/>
|
||||
</div>
|
||||
}
|
||||
subheader={
|
||||
<div className="ascribe-detail-header">
|
||||
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } />
|
||||
<DetailProperty label={getLangText('ID')} value={ this.state.piece.bitcoin_id } ellipsis={true} />
|
||||
<hr/>
|
||||
</div>
|
||||
}
|
||||
buttons={this.getActions()}>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Loan History')}
|
||||
show={this.state.piece.loan_history && this.state.piece.loan_history.length > 0}>
|
||||
<HistoryIterator
|
||||
history={this.state.piece.loan_history} />
|
||||
</CollapsibleParagraph>
|
||||
</Piece>
|
||||
submitButtonType={IkonotvSubmitButton}>
|
||||
{furtherDetails}
|
||||
</WalletPieceContainer>
|
||||
);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div className="fullpage-spinner">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
|
@ -0,0 +1,151 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Form from '../../../../../ascribe_forms/form';
|
||||
import Property from '../../../../../ascribe_forms/property';
|
||||
|
||||
import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
|
||||
|
||||
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
|
||||
|
||||
import ApiUrls from '../../../../../../constants/api_urls';
|
||||
import AppConstants from '../../../../../../constants/application_constants';
|
||||
|
||||
import requests from '../../../../../../utils/requests';
|
||||
|
||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||
|
||||
|
||||
let IkonotvArtistDetailsForm = React.createClass({
|
||||
propTypes: {
|
||||
handleSuccess: React.PropTypes.func,
|
||||
piece: React.PropTypes.object.isRequired,
|
||||
|
||||
disabled: React.PropTypes.bool,
|
||||
|
||||
isInline: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
isInline: false
|
||||
};
|
||||
},
|
||||
|
||||
getFormData() {
|
||||
let extradata = {};
|
||||
let formRefs = this.refs.form.refs;
|
||||
|
||||
// Put additional fields in extra data object
|
||||
Object
|
||||
.keys(formRefs)
|
||||
.forEach((fieldName) => {
|
||||
extradata[fieldName] = formRefs[fieldName].state.value;
|
||||
});
|
||||
|
||||
return {
|
||||
extradata: extradata,
|
||||
piece_id: this.props.piece.id
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
handleSuccess() {
|
||||
let notification = new GlobalNotificationModel('Artist details successfully updated', 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
render() {
|
||||
let buttons, spinner, heading;
|
||||
let { isInline, handleSuccess } = this.props;
|
||||
|
||||
|
||||
if(!isInline) {
|
||||
buttons = (
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login"
|
||||
disabled={this.props.disabled}>
|
||||
{getLangText('Proceed to loan')}
|
||||
</button>
|
||||
);
|
||||
|
||||
spinner = (
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
</div>
|
||||
);
|
||||
|
||||
heading = (
|
||||
<div className="ascribe-form-header">
|
||||
<h3>
|
||||
{getLangText('Artist Details')}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(this.props.piece && this.props.piece.id && this.props.piece.extra_data) {
|
||||
return (
|
||||
<Form
|
||||
disabled={this.props.disabled}
|
||||
className="ascribe-form-bordered"
|
||||
ref='form'
|
||||
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})}
|
||||
handleSuccess={handleSuccess || this.handleSuccess}
|
||||
getFormData={this.getFormData}
|
||||
buttons={buttons}
|
||||
spinner={spinner}>
|
||||
{heading}
|
||||
<Property
|
||||
name='artist_website'
|
||||
label={getLangText('Artist Website')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.artist_website}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.artist_website}
|
||||
placeholder={getLangText('The artist\'s website if present...')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='gallery_website'
|
||||
label={getLangText('Website of related Gallery, Museum, etc.')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.gallery_website}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.gallery_website}
|
||||
placeholder={getLangText('The website of any related Gallery or Museum')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='additional_websites'
|
||||
label={getLangText('Additional Websites/Publications/Museums/Galleries')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.additional_websites}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.additional_websites}
|
||||
placeholder={getLangText('Enter additional Websites/Publications if any')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='conceptual_overview'
|
||||
label={getLangText('Short text about the Artist')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.conceptual_overview}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.conceptual_overview}
|
||||
placeholder={getLangText('Enter a short bio about the Artist')}
|
||||
/>
|
||||
</Property>
|
||||
</Form>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="ascribe-loading-position">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default IkonotvArtistDetailsForm;
|
@ -0,0 +1,167 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Form from '../../../../../ascribe_forms/form';
|
||||
import Property from '../../../../../ascribe_forms/property';
|
||||
|
||||
import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
|
||||
|
||||
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
|
||||
|
||||
import ApiUrls from '../../../../../../constants/api_urls';
|
||||
import AppConstants from '../../../../../../constants/application_constants';
|
||||
|
||||
import requests from '../../../../../../utils/requests';
|
||||
|
||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||
|
||||
|
||||
let IkonotvArtworkDetailsForm = React.createClass({
|
||||
propTypes: {
|
||||
handleSuccess: React.PropTypes.func,
|
||||
piece: React.PropTypes.object.isRequired,
|
||||
|
||||
disabled: React.PropTypes.bool,
|
||||
|
||||
isInline: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
isInline: false
|
||||
};
|
||||
},
|
||||
|
||||
getFormData() {
|
||||
let extradata = {};
|
||||
let formRefs = this.refs.form.refs;
|
||||
|
||||
// Put additional fields in extra data object
|
||||
Object
|
||||
.keys(formRefs)
|
||||
.forEach((fieldName) => {
|
||||
extradata[fieldName] = formRefs[fieldName].state.value;
|
||||
});
|
||||
|
||||
return {
|
||||
extradata: extradata,
|
||||
piece_id: this.props.piece.id
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
handleSuccess() {
|
||||
let notification = new GlobalNotificationModel('Artwork details successfully updated', 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
||||
render() {
|
||||
let buttons, spinner, heading;
|
||||
let { isInline, handleSuccess } = this.props;
|
||||
|
||||
if(!isInline) {
|
||||
buttons = (
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login"
|
||||
disabled={this.props.disabled}>
|
||||
{getLangText('Proceed to artist details')}
|
||||
</button>
|
||||
);
|
||||
|
||||
spinner = (
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
</div>
|
||||
);
|
||||
|
||||
heading = (
|
||||
<div className="ascribe-form-header">
|
||||
<h3>
|
||||
{getLangText('Artwork Details')}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(this.props.piece && this.props.piece.id && this.props.piece.extra_data) {
|
||||
return (
|
||||
<Form
|
||||
disabled={this.props.disabled}
|
||||
className="ascribe-form-bordered"
|
||||
ref='form'
|
||||
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})}
|
||||
handleSuccess={handleSuccess || this.handleSuccess}
|
||||
getFormData={this.getFormData}
|
||||
buttons={buttons}
|
||||
spinner={spinner}>
|
||||
{heading}
|
||||
<Property
|
||||
name='medium'
|
||||
label={getLangText('Medium')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.medium}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.medium}
|
||||
placeholder={getLangText('The medium of the file (i.e. photo, video, other, ...)')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='size_duration'
|
||||
label={getLangText('Size/Duration')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.size_duration}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.size_duration}
|
||||
placeholder={getLangText('Size in centimeters. Duration in minutes.')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='copyright'
|
||||
label={getLangText('Copyright')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.copyright}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.copyright}
|
||||
placeholder={getLangText('Which copyright is attached to this work?')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='courtesy_of'
|
||||
label={getLangText('Courtesy of')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.courtesy_of}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.courtesy_of}
|
||||
placeholder={getLangText('The current owner of the artwork')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='copyright_of_photography'
|
||||
label={getLangText('Copyright of Photography')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.copyright_of_photography}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.copyright_of_photography}
|
||||
placeholder={getLangText('Who should be attributed for the photography?')}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='additional_details'
|
||||
label={getLangText('Additional Details about the artwork')}
|
||||
hidden={this.props.disabled && !this.props.piece.extra_data.additional_details}>
|
||||
<InputTextAreaToggable
|
||||
rows={1}
|
||||
defaultValue={this.props.piece.extra_data.additional_details}
|
||||
placeholder={getLangText('Insert artwork overview')}/>
|
||||
</Property>
|
||||
</Form>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="ascribe-loading-position">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default IkonotvArtworkDetailsForm;
|
@ -0,0 +1,196 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
import NotificationActions from '../../../../../actions/notification_actions';
|
||||
import NotificationStore from '../../../../../stores/notification_store';
|
||||
|
||||
import UserActions from '../../../../../actions/user_actions';
|
||||
import UserStore from '../../../../../stores/user_store';
|
||||
|
||||
import OwnershipFetcher from '../../../../../fetchers/ownership_fetcher';
|
||||
|
||||
import WhitelabelStore from '../../../../../stores/whitelabel_store';
|
||||
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
|
||||
|
||||
import GlobalNotificationModel from '../../../../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../../../../actions/global_notification_actions';
|
||||
|
||||
import CopyrightAssociationForm from '../../../../ascribe_forms/form_copyright_association';
|
||||
|
||||
import Property from '../../../../ascribe_forms/property';
|
||||
|
||||
import AppConstants from '../../../../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
|
||||
let Navigation = Router.Navigation;
|
||||
|
||||
let IkonotvContractNotifications = React.createClass({
|
||||
|
||||
mixins: [Navigation],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
NotificationStore.getState(),
|
||||
UserStore.getState(),
|
||||
WhitelabelStore.getState()
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
NotificationStore.listen(this.onChange);
|
||||
UserStore.listen(this.onChange);
|
||||
UserActions.fetchCurrentUser();
|
||||
WhitelabelStore.listen(this.onChange);
|
||||
WhitelabelActions.fetchWhitelabel();
|
||||
if (this.state.contractAgreementListNotifications === null){
|
||||
NotificationActions.fetchContractAgreementListNotifications();
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
NotificationStore.unlisten(this.onChange);
|
||||
WhitelabelStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
getContract(){
|
||||
let notifications = this.state.contractAgreementListNotifications[0];
|
||||
let blob = notifications.contract_agreement.contract.blob;
|
||||
if (blob.mime === 'pdf') {
|
||||
return (
|
||||
<div className='notification-contract-pdf'>
|
||||
<embed
|
||||
height
|
||||
src={blob.url_safe}
|
||||
alt="pdf"
|
||||
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className='notification-contract-download'>
|
||||
<a href={blob.url_safe} target="_blank">
|
||||
<Glyphicon glyph='download-alt'/>
|
||||
<span style={{padding: '0.3em'}}>
|
||||
Download contract
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
getAppendix() {
|
||||
let notifications = this.state.contractAgreementListNotifications[0];
|
||||
let appendix = notifications.contract_agreement.appendix;
|
||||
if (appendix && appendix.default) {
|
||||
return (
|
||||
<Property
|
||||
name='appendix'
|
||||
label={getLangText('Appendix')}>
|
||||
<pre className="ascribe-pre">{appendix.default}</pre>
|
||||
</Property>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
handleConfirm() {
|
||||
let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement;
|
||||
OwnershipFetcher.confirmContractAgreement(contractAgreement).then(
|
||||
() => this.handleConfirmSuccess()
|
||||
);
|
||||
},
|
||||
|
||||
handleConfirmSuccess() {
|
||||
let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
this.transitionTo('pieces');
|
||||
},
|
||||
|
||||
handleDeny() {
|
||||
let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement;
|
||||
OwnershipFetcher.denyContractAgreement(contractAgreement).then(
|
||||
() => this.handleDenySuccess()
|
||||
);
|
||||
},
|
||||
|
||||
handleDenySuccess() {
|
||||
let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
this.transitionTo('pieces');
|
||||
},
|
||||
|
||||
getCopyrightAssociationForm(){
|
||||
let currentUser = this.state.currentUser;
|
||||
|
||||
if (currentUser && currentUser.profile && !currentUser.profile.copyright_association) {
|
||||
return (
|
||||
<div className='notification-contract-footer'>
|
||||
<h1>{getLangText('Are you a member of any copyright societies?')}</h1>
|
||||
|
||||
<p>
|
||||
{AppConstants.copyrightAssociations.join(', ')}
|
||||
</p>
|
||||
<CopyrightAssociationForm currentUser={currentUser}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.state.contractAgreementListNotifications &&
|
||||
this.state.contractAgreementListNotifications.length > 0) {
|
||||
|
||||
let notifications = this.state.contractAgreementListNotifications[0];
|
||||
let blob = notifications.contract_agreement.contract.blob;
|
||||
|
||||
return (
|
||||
<div className='container'>
|
||||
<div className='notification-contract-wrapper'>
|
||||
<div className='notification-contract-logo'>
|
||||
<img src={this.state.whitelabel.logo}/>
|
||||
<div className='notification-contract-header'>
|
||||
{getLangText('Contract')}
|
||||
</div>
|
||||
</div>
|
||||
{this.getContract()}
|
||||
<div className='notification-contract-footer'>
|
||||
{this.getAppendix()}
|
||||
<div className='notification-contract-pdf-download'>
|
||||
<a href={blob.url_safe} target="_blank">
|
||||
<Glyphicon glyph='download-alt'/>
|
||||
<span style={{padding: '0.3em'}}>
|
||||
Download PDF version
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
{this.getCopyrightAssociationForm()}
|
||||
<p style={{marginTop: '1em'}}>
|
||||
<Button type="submit" onClick={this.handleConfirm}>
|
||||
{getLangText('I agree with the conditions')}
|
||||
</Button>
|
||||
<Button bsStyle="danger" className="btn-delete" bsSize="medium" onClick={this.handleDeny}>
|
||||
{getLangText('I disagree')}
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
export default IkonotvContractNotifications;
|
@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
|
||||
|
||||
import UserStore from '../../../../../stores/user_store';
|
||||
import UserActions from '../../../../../actions/user_actions';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
|
||||
|
||||
let IkonotvLanding = React.createClass({
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.listen(this.onChange);
|
||||
UserActions.fetchCurrentUser();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
UserStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
getEnterButton() {
|
||||
let redirect = 'login';
|
||||
|
||||
if(this.state.currentUser && this.state.currentUser.email) {
|
||||
redirect = 'pieces';
|
||||
}
|
||||
else if (this.getQuery() && this.getQuery().redirect) {
|
||||
redirect = this.getQuery().redirect;
|
||||
}
|
||||
return (
|
||||
<ButtonLink to={redirect} query={this.getQuery()}>
|
||||
{getLangText('ENTER TO START')}
|
||||
</ButtonLink>
|
||||
);
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="ikonotv-landing">
|
||||
<header>
|
||||
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono_tv.png" />
|
||||
<div className="tagline">
|
||||
<h1>PROTECT</h1>
|
||||
<div className="poster">
|
||||
<div className="content">
|
||||
</div>
|
||||
</div>
|
||||
<h1>& SHARE</h1>
|
||||
</div>
|
||||
<h2>Welcome to the ikonoTV<br />Registration Page</h2>
|
||||
</header>
|
||||
<article>
|
||||
<section>
|
||||
<h1>
|
||||
ONLINE-ONLY FOR YOUR PROTECTION
|
||||
</h1>
|
||||
<p>
|
||||
As an entirely digital broadcasting and licensing company we’re always keen to properly handle the content that artists, museums, and archives consign to us. The main concern with art online is the risk it will be misused. Thanks to our partnership with ascribe.io, we can address that issue in a way that is faster and more efficient for our users.
|
||||
Using ascribe means we can do away with paper contracts and replace them with an online-only version. Partnering with ascribe also means we can encrypt digital work once it is uploaded. This revolutionary service will allow you to keep track of your works and share without worry.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h1>
|
||||
NEW SUBSCRIPTION SERVICE
|
||||
</h1>
|
||||
<p>
|
||||
IkonoTV has developed an app that provides playlists on demand—soon to be available on all online devices and SmartTVs. We can now offer the possibility of a share in revenue to compensate for the artist’s work.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h1>
|
||||
THE RAPID GROWTH OF IkonoTV
|
||||
</h1>
|
||||
<p>
|
||||
In October 2014, our first app was installed on Amazon Fire TV. During the first month it was downloaded 200 times, and jumped to 5,000 by the second month. Today, we’re well over the 285,000 mark, making us the number one app in our category in the US, Canada, UK and Germany.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h1>
|
||||
FULL TRANSPARENCY
|
||||
</h1>
|
||||
<p>
|
||||
We expect a similar success with each SmartTV brand. For us, this marks the beginning of a new way to offer hassle-free licensing to visual artists—and we’re very proud to be an integral part of this virtual market. In the future, should we plan anything not directly mentioned in the contract, we will always make sure to secure the artist’s approval first. ikonoTV was developed to serve art and artists, and for this reason it’s of the utmost importance to us to respect this relationship. Thanks to you, we now look forward to our next big step.
|
||||
</p>
|
||||
</section>
|
||||
<footer>
|
||||
<p>Elizabeth Markevitch</p>
|
||||
<p>Founder & CEO Markevitch Media GmbH</p>
|
||||
{this.getEnterButton()}
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default IkonotvLanding;
|
@ -8,6 +8,9 @@ import UserStore from '../../../../../stores/user_store';
|
||||
|
||||
import IkonotvAccordionListItem from './ascribe_accordion_list/ikonotv_accordion_list_item';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
|
||||
|
||||
let IkonotvPieceList = React.createClass({
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
@ -32,7 +35,19 @@ let IkonotvPieceList = React.createClass({
|
||||
<PieceList
|
||||
redirectTo="register_piece"
|
||||
accordionListItemType={IkonotvAccordionListItem}
|
||||
/>
|
||||
filterParams={[{
|
||||
label: getLangText('Show works I have'),
|
||||
items: [
|
||||
{
|
||||
key: 'submitted',
|
||||
label: getLangText('submitted')
|
||||
},
|
||||
{
|
||||
key: 'accepted',
|
||||
label: getLangText('loaned')
|
||||
}
|
||||
]
|
||||
}]}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user