1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-29 00:58:03 +02:00

Merged in AD-943-add-custom-additional-fields (pull request #60)

Ad 943 add custom additional fields
This commit is contained in:
TimDaubenschuetz 2015-09-23 14:16:16 +02:00
commit 93e61cfff5
52 changed files with 1415 additions and 569 deletions

View File

@ -15,8 +15,8 @@ class ContractAgreementListActions {
}
fetchContractAgreementList(issuer, accepted, pending) {
this.actions.updateContractAgreementList(null);
return Q.Promise((resolve, reject) => {
this.actions.updateContractAgreementList(null);
OwnershipFetcher.fetchContractAgreementList(issuer, accepted, pending)
.then((contractAgreementList) => {
if (contractAgreementList.count > 0) {
@ -35,27 +35,47 @@ class ContractAgreementListActions {
);
}
fetchAvailableContractAgreementList(issuer){
fetchAvailableContractAgreementList(issuer, createContractAgreement) {
return Q.Promise((resolve, reject) => {
this.actions.fetchContractAgreementList(issuer, true, null)
.then((contractAgreementListAccepted) => {
if (!contractAgreementListAccepted) {
// fetch pending agreements if no accepted ones
return this.actions.fetchContractAgreementList(issuer, null, true);
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);
});
}
else {
resolve(contractAgreementListAccepted);
}
}).then((contractAgreementListPending) => {
resolve(contractAgreementListPending);
}).catch((err) => {
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
);
}
createContractAgreementFromPublicContract(issuer){
createContractAgreementFromPublicContract(issuer) {
ContractListActions.fetchContractList(null, null, issuer)
.then((publicContract) => {
// create an agreement with the public contract if there is one
@ -88,7 +108,6 @@ class ContractAgreementListActions {
});
});
}
}
export default alt.createActions(ContractAgreementListActions);

View File

@ -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;
}
});

View File

@ -152,8 +152,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)')}

View File

@ -44,14 +44,13 @@ let Note = React.createClass({
<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;

View File

@ -237,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')}

View File

@ -203,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">

View File

@ -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"/>

View File

@ -35,7 +35,7 @@ let ContractAgreementForm = React.createClass({
componentDidMount() {
ContractListStore.listen(this.onChange);
ContractListActions.fetchContractList(true);
ContractListActions.fetchContractList(true, false);
},
componentWillUnmount() {
@ -68,15 +68,7 @@ let ContractAgreementForm = React.createClass({
<Property
name='contract'
label={getLangText('Contract Type')}
onChange={this.onContractChange}
footer={
<a
className="pull-right"
href={contractList[this.state.selectedContract].blob}
target="_blank">
{getLangText('Learn more')}
</a>
}>
onChange={this.onContractChange}>
<select name="contract">
{contractList.map((contract, i) => {
return (
@ -95,45 +87,58 @@ let ContractAgreementForm = React.createClass({
},
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 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='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 (
<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 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='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>
<InputTextAreaToggable
rows={1}
editable={true}
placeholder={getLangText('This will be appended to the contract selected above')}/>
</PropertyCollapsible>
</Form>
<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>
);
}
});

View 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;

View File

@ -35,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
},
@ -44,7 +45,8 @@ let LoanForm = React.createClass({
showPersonalMessage: true,
showEndDate: true,
showStartDate: true,
showPassword: true
showPassword: true,
createPublicContractAgreement: true
};
},
@ -57,11 +59,14 @@ let LoanForm = React.createClass({
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) {
// however, it can also be that at the time the component is mounting,
// the email is not defined (because it's asynchronously fetched from the server).
// Then we need to update it as soon as it is included into LoanForm's props.
if(nextProps && nextProps.email) {
if(nextProps && nextProps.email && this.props.email !== nextProps.email) {
this.getContractAgreementsOrCreatePublic(nextProps.email);
}
},
@ -75,15 +80,10 @@ let LoanForm = React.createClass({
},
getContractAgreementsOrCreatePublic(email){
ContractAgreementListActions.flushContractAgreementList();
ContractAgreementListActions.flushContractAgreementList.defer();
if (email) {
ContractAgreementListActions.fetchAvailableContractAgreementList(email).then(
(contractAgreementList) => {
if (!contractAgreementList) {
ContractAgreementListActions.createContractAgreementFromPublicContract(email);
}
}
);
// fetch the available contractagreements (pending/accepted)
ContractAgreementListActions.fetchAvailableContractAgreementList(email, true);
}
},
@ -115,25 +115,46 @@ let LoanForm = React.createClass({
// we need to define a key on the InputCheckboxes as otherwise
// react is not rerendering them on a store switch and is keeping
// the default value of the component (which is in that case true)
let contract = this.state.contractAgreementList[0].contract;
let contractAgreement = this.state.contractAgreementList[0];
let contract = contractAgreement.contract;
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')}&nbsp;
<a href={contract.blob.url_safe} target="_blank">
{getLangText('terms of ')} {contract.issuer}
</a>
</span>
</InputCheckbox>
</Property>
);
if(contractAgreement.datetime_accepted) {
return (
<Property
name="terms"
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')}&nbsp;
<a href={contract.blob.url_safe} target="_blank">
{getLangText('terms of ')} {contract.issuer}
</a>
</span>
</InputCheckbox>
</Property>
);
}
} else {
return (
<Property
@ -148,6 +169,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 (
@ -193,7 +230,7 @@ let LoanForm = React.createClass({
name='loanee'
label={getLangText('Loanee Email')}
editable={!this.props.email}
onBlur={this.handleOnChange}
onChange={this.handleOnChange}
overrideForm={!!this.props.email}>
<input
value={this.props.email}
@ -235,14 +272,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')}
@ -252,7 +291,6 @@ let LoanForm = React.createClass({
placeholder={getLangText('Enter your password')}
required={this.props.showPassword ? 'required' : ''}/>
</Property>
{this.getContractCheckbox()}
{this.props.children}
</Form>
);

View File

@ -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() {

View File

@ -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

View File

@ -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"/>

View File

@ -112,7 +112,7 @@ let RegisterPieceForm = React.createClass({
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
isFineUploaderActive={this.props.isFineUploaderActive}
onLoggedOut={this.props.onLoggedOut}
editable={this.props.isFineUploaderEditable}
disabled={!this.props.isFineUploaderEditable}
enableLocalHashing={enableLocalHashing}/>
</Property>
<Property

View File

@ -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"/>

View File

@ -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">

View File

@ -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>

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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"

View File

@ -8,12 +8,14 @@ import AppConstants from '../../constants/application_constants';
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,
@ -33,7 +35,7 @@ let InputFileUploader = React.createClass({
// before login in
isFineUploaderActive: React.PropTypes.bool,
onLoggedOut: React.PropTypes.func,
editable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool,
// provided by Property
@ -86,7 +88,7 @@ let InputFileUploader = React.createClass({
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/',
@ -109,4 +111,4 @@ let InputFileUploader = React.createClass({
}
});
export default InputFileUploader;
export default InputFineUploader;

View File

@ -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

View File

@ -10,6 +10,8 @@ import Property from '../ascribe_forms/property';
import InputCheckbox from '../ascribe_forms/input_checkbox';
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
import CopyrightAssociationForm from '../ascribe_forms/form_copyright_association';
import ApiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants';
@ -17,8 +19,8 @@ import { getLangText } from '../../utils/lang_utils';
let AccountSettings = React.createClass({
propTypes: {
currentUser: React.PropTypes.object.required,
loadUser: React.PropTypes.func.required
currentUser: React.PropTypes.object.isRequired,
loadUser: React.PropTypes.func.isRequired
},
handleSuccess(){
@ -102,6 +104,7 @@ let AccountSettings = React.createClass({
show={true}
defaultExpanded={true}>
{content}
<CopyrightAssociationForm currentUser={this.props.currentUser}/>
{profile}
{/*<Form
url={AppConstants.serverUrl + 'api/users/set_language/'}>

View File

@ -83,7 +83,7 @@ let ContractSettings = React.createClass({
return (
<AclProxy
aclName="acl_view_contract_settings"
aclName="acl_view_settings_contract"
aclObject={this.props.currentUser.acl}>
<CollapsibleParagraph
title={getLangText('Contracts')}

View File

@ -43,15 +43,18 @@ let SettingsContainer = React.createClass({
},
render() {
return (
<div className="settings-container">
<AccountSettings currentUser={this.state.currentUser} loadUser={this.loadUser}/>
{this.props.children}
<APISettings />
<BitcoinWalletSettings />
<ContractSettings currentUser={this.state.currentUser} loadUser={this.loadUser}/>
</div>
);
if (this.state.currentUser && this.state.currentUser.username) {
return (
<div className="settings-container">
<AccountSettings currentUser={this.state.currentUser} loadUser={this.loadUser}/>
{this.props.children}
<APISettings />
<BitcoinWalletSettings />
<ContractSettings currentUser={this.state.currentUser} loadUser={this.loadUser}/>
</div>
);
}
return null;
}
});

View File

@ -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: {

View File

@ -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>

View File

@ -42,6 +42,7 @@ let SignupContainer = React.createClass({
{getLangText('Already an ascribe user')}&#63; <Link to="login">{getLangText('Log in')}...</Link><br/>
</div>
</div>
);
}
});

View File

@ -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}

View File

@ -426,10 +426,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>);
}

View File

@ -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>

View File

@ -0,0 +1,70 @@
'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={availableAcls}
aclName="acl_wallet_submit">
<SubmitButtonType
className="btn-sm"
handleSuccess={this.handleSubmitSuccess}
piece={this.props.piece}/>
</AclProxy>
<DeleteButton
handleSuccess={this.handleDeleteSuccess}
piece={this.props.piece}/>
</AclButtonList>
);
}
}
});
export default WalletActionPanel;

View File

@ -0,0 +1,94 @@
'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 { getLangText } from '../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../utils/general_utils';
import AppConstants from '../../../../../constants/application_constants';
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.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.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;

View File

@ -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">
@ -82,7 +81,7 @@ let CylandAccordionListItem = React.createClass({
</AclProxy>
<AclProxy
aclObject={this.props.content.acl}
aclName="acl_accepted">
aclName="acl_wallet_accepted">
<button
disabled
className="btn btn-default btn-xs pull-right">

View File

@ -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,24 @@ 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')}
show={true}
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 +84,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;

View File

@ -9,6 +9,9 @@ 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';
@ -20,10 +23,21 @@ import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_
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() {
@ -63,61 +77,69 @@ let CylandAdditionalDataForm = React.createClass({
},
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}
submitFile={this.submitFile}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.fileOptional}
editable={!this.props.disabled}
pieceId={this.props.piece.id}
otherData={this.props.piece.other_data}
pieceId={piece.id}
otherData={piece.other_data}
multiple={true}/>
</Form>
);

View File

@ -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;

View File

@ -63,16 +63,20 @@ 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">
@ -82,7 +86,7 @@ let IkonotvAccordionListItem = React.createClass({
</AclProxy>
<AclProxy
aclObject={this.props.content.acl}
aclName="acl_accepted">
aclName="acl_wallet_accepted">
<button
disabled
className="btn btn-default btn-xs pull-right">

View File

@ -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,37 +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} />
</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>
);
}
});

View File

@ -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,91 +64,46 @@ 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')}
show={true}
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.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}/>);
}
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')}
show={true}
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'} />

View File

@ -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;

View File

@ -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;

View File

@ -6,20 +6,26 @@ import Router from 'react-router';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Button from 'react-bootstrap/lib/Button';
import Form from '../../../../ascribe_forms/form';
import Property from '../../../../ascribe_forms/property';
import InputCheckbox from '../../../../ascribe_forms/input_checkbox';
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 apiUrls from '../../../../../constants/api_urls';
import CopyrightAssociationForm from '../../../../ascribe_forms/form_copyright_association';
import Property from '../../../../ascribe_forms/property';
import AppConstants from '../../../../../constants/application_constants';
import requests from '../../../../../utils/requests';
import { getLangText } from '../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../utils/general_utils';
@ -32,13 +38,17 @@ let IkonotvContractNotifications = React.createClass({
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();
}
@ -59,16 +69,11 @@ let IkonotvContractNotifications = React.createClass({
if (blob.mime === 'pdf') {
return (
<div className='notification-contract-pdf'>
<embed src={blob.url_safe} alt="pdf"
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
<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>
<embed
height
src={blob.url_safe}
alt="pdf"
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
</div>
);
}
@ -87,87 +92,98 @@ let IkonotvContractNotifications = React.createClass({
getAppendix() {
let notifications = this.state.contractAgreementListNotifications[0];
let appendix = notifications.contract_agreement.appendix;
if (appendix) {
return (<div>
<h1>{getLangText('Appendix')}</h1>
<pre>
{appendix.default}
</pre>
</div>
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', 10000);
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;
requests.put(apiUrls.ownership_contract_agreements_deny, {contract_agreement_id: contractAgreement.id}).then(
OwnershipFetcher.denyContractAgreement(contractAgreement).then(
() => this.handleDenySuccess()
);
},
handleDenySuccess() {
let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 10000);
let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces');
},
render() {
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 contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement;
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('Production Contract')}
{getLangText('Contract')}
</div>
</div>
{this.getContract()}
<div className='notification-contract-footer'>
{this.getAppendix}
<h1>{getLangText('Are you a member of any copyright societies?')}</h1>
<p>
ARS, DACS, Bildkunst, Pictoright, SODRAC, Copyright Agency/Viscopy, SAVA, Bildrecht GmbH,
SABAM, AUTVIS, CREAIMAGEN, SONECA, Copydan, EAU, Kuvasto, GCA, HUNGART, IVARO, SIAE, JASPAR-SPDA,
AKKA/LAA, LATGA-A, SOMAAP, ARTEGESTION, CARIER, BONO, APSAV, SPA, GESTOR, VISaRTA, RAO, LITA,
DALRO, VeGaP, BUS, ProLitteris, AGADU, AUTORARTE, BUBEDRA, BBDA, BCDA, BURIDA, ADAVIS, BSDA
{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>
<Form
ref='form'
url={requests.prepareUrl(apiUrls.ownership_contract_agreements_confirm, {contract_agreement_id: contractAgreement.id})}
handleSuccess={this.handleConfirmSuccess}
method='put'
buttons={
<p style={{marginTop: '1em'}}>
<Button type="submit">{getLangText('I agree with the conditions')}</Button>
<Button bsStyle="danger" className="btn-delete" bsSize="medium" onClick={this.handleDeny}>
{getLangText('I disagree')}
</Button>
</p>
}>
<Property
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox>
<span>
{' ' + getLangText('Yes') }
</span>
</InputCheckbox>
</Property>
</Form>
</div>
</div>
</div>

View File

@ -1,10 +1,10 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
import UserActions from '../../../../../actions/user_actions';
import UserStore from '../../../../../stores/user_store';
import { getLangText } from '../../../../../utils/lang_utils';
@ -12,6 +12,8 @@ import { getLangText } from '../../../../../utils/lang_utils';
let IkonotvLanding = React.createClass({
mixins: [Router.Navigation, Router.State],
getInitialState() {
return UserStore.getState();
},
@ -29,19 +31,19 @@ let IkonotvLanding = React.createClass({
},
getEnterButton() {
let redirect = 'login';
if(this.state.currentUser && this.state.currentUser.email) {
return (
<ButtonLink to="pieces">
{getLangText('ENTER TO START')}
</ButtonLink>
);
} else {
return (
<ButtonLink to="signup">
{getLangText('ENTER TO START')}
</ButtonLink>
);
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() {
@ -55,7 +57,7 @@ let IkonotvLanding = React.createClass({
<div className="content">
</div>
</div>
<h1>& SHARE</h1>
<h1>&amp; SHARE</h1>
</div>
<h2>Welcome to the ikonoTV<br />Registration Page</h2>
</header>
@ -74,7 +76,7 @@ let IkonotvLanding = React.createClass({
NEW SUBSCRIPTION SERVICE
</h1>
<p>
IkonoTV has developed an app that provides playlists on demandsoon to be available on all online devices and SmartTVs. The app is a paid service; in view of the interest in distributing this service in public spaces (hospitals, airports, hotels, etc.), we can now offer the possibility of a share in revenue to compensate for the artists work.
IkonoTV has developed an app that provides playlists on demandsoon to be available on all online devices and SmartTVs. We can now offer the possibility of a share in revenue to compensate for the artists work.
</p>
</section>
<section>
@ -82,7 +84,7 @@ let IkonotvLanding = React.createClass({
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, were well over the 175,000 mark, making us the number one app in our category in the US, Canada, UK and Germany.
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, were 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>
@ -95,7 +97,7 @@ let IkonotvLanding = React.createClass({
</section>
<footer>
<p>Elizabeth Markevitch</p>
<p>Founder & CEO Markevitch Media GmbH</p>
<p>Founder &amp; CEO Markevitch Media GmbH</p>
{this.getEnterButton()}
</footer>
</article>

View File

@ -0,0 +1,271 @@
'use strict';
import React from 'react';
import Moment from 'moment';
import Router from 'react-router';
import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row';
import PieceListStore from '../../../../../stores/piece_list_store';
import PieceListActions from '../../../../../actions/piece_list_actions';
import UserStore from '../../../../../stores/user_store';
import UserActions from '../../../../../actions/user_actions';
import PieceStore from '../../../../../stores/piece_store';
import PieceActions from '../../../../../actions/piece_actions';
import GlobalNotificationModel from '../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../actions/global_notification_actions';
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
import LoanForm from '../../../../ascribe_forms/form_loan';
import IkonotvArtistDetailsForm from './ascribe_forms/ikonotv_artist_details_form';
import IkonotvArtworkDetailsForm from './ascribe_forms/ikonotv_artwork_details_form';
import SlidesContainer from '../../../../ascribe_slides_container/slides_container';
import ApiUrls from '../../../../../constants/api_urls';
import { mergeOptions } from '../../../../../utils/general_utils';
import { getLangText } from '../../../../../utils/lang_utils';
let IkonotvRegisterPiece = React.createClass({
propTypes: {
handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired
},
mixins: [Router.Navigation, Router.State],
getInitialState(){
return mergeOptions(
UserStore.getState(),
PieceListStore.getState(),
PieceStore.getState(),
{
step: 0
});
},
componentDidMount() {
PieceListStore.listen(this.onChange);
UserStore.listen(this.onChange);
PieceStore.listen(this.onChange);
UserActions.fetchCurrentUser();
// Before we load the new piece, we reset the piece store to delete old data that we do
// not want to display to the user.
PieceActions.updatePiece({});
let queryParams = this.getQuery();
// Since every step of this register process is atomic,
// we may need to enter the process at step 1 or 2.
// If this is the case, we'll need the piece number to complete submission.
// It is encoded in the URL as a queryParam and we're checking for it here.
//
// We're using 'in' here as we want to know if 'piece_id' is present in the url,
// we don't care about the value.
if (queryParams && 'piece_id' in queryParams) {
PieceActions.fetchOne(queryParams.piece_id);
}
},
componentWillUnmount() {
PieceListStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange);
PieceStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
if(this.state.currentUser && this.state.currentUser.email) {
// we should also make the fineuploader component editable again
this.setState({
isFineUploaderActive: true
});
}
},
handleRegisterSuccess(response){
this.refreshPieceList();
// also start loading the piece for the next step
if(response && response.piece) {
PieceActions.updatePiece(response.piece);
}
if (!this.canSubmit()) {
this.transitionTo('pieces');
}
else {
this.incrementStep();
this.refs.slidesContainer.nextSlide();
}
},
handleAdditionalDataSuccess() {
// We need to refetch the piece again after submitting the additional data
// since we want it's otherData to be displayed when the user choses to click
// on the browsers back button.
PieceActions.fetchOne(this.state.piece.id);
this.refreshPieceList();
this.incrementStep();
this.refs.slidesContainer.nextSlide();
},
handleLoanSuccess(response) {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.refreshPieceList();
PieceActions.fetchOne(this.state.piece.id);
this.transitionTo('piece', {pieceId: this.state.piece.id});
},
// We need to increase the step to lock the forms that are already filled out
incrementStep() {
// also increase step
let newStep = this.state.step + 1;
this.setState({
step: newStep
});
},
refreshPieceList() {
PieceListActions.fetchPieceList(
this.state.page,
this.state.pageSize,
this.state.searchTerm,
this.state.orderBy,
this.state.orderAsc,
this.state.filterBy
);
},
changeSlide() {
// only transition to the login store, if user is not logged in
// ergo the currentUser object is not properly defined
if(this.state.currentUser && !this.state.currentUser.email) {
this.onLoggedOut();
}
},
// basically redirects to the second slide (index: 1), when the user is not logged in
onLoggedOut() {
this.transitionTo('login');
},
canSubmit() {
let currentUser = this.state.currentUser;
return currentUser && currentUser.acl && currentUser.acl.acl_wallet_submit;
},
getSlideArtistDetails() {
if (this.canSubmit()) {
return (
<div data-slide-title={getLangText('Artist details')}>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<IkonotvArtistDetailsForm
handleSuccess={this.handleAdditionalDataSuccess}
piece={this.state.piece}/>
</Col>
</Row>
</div>
);
}
return null;
},
getSlideArtworkDetails() {
if (this.canSubmit()) {
return (
<div data-slide-title={getLangText('Artwork details')}>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<IkonotvArtworkDetailsForm
handleSuccess={this.handleAdditionalDataSuccess}
piece={this.state.piece}/>
</Col>
</Row>
</div>
);
}
return null;
},
getSlideLoan() {
if (this.canSubmit()) {
let today = new Moment();
let enddate = new Moment();
enddate.add(2, 'years');
return (
<div data-slide-title={getLangText('Loan')}>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<LoanForm
loanHeading={getLangText('Loan to IkonoTV archive')}
id={{piece_id: this.state.piece.id}}
url={ApiUrls.ownership_loans_pieces}
email="submissions@ikono.org"
startdate={today}
enddate={enddate}
showStartDate={false}
showEndDate={false}
gallery="IkonoTV archive"
showPersonalMessage={false}
createPublicContractAgreement={false}
handleSuccess={this.handleLoanSuccess}/>
</Col>
</Row>
</div>
);
}
return null;
},
render() {
return (
<SlidesContainer
ref="slidesContainer"
forwardProcess={true}
glyphiconClassNames={{
pending: 'glyphicon glyphicon-chevron-right',
completed: 'glyphicon glyphicon-lock'
}}>
<div data-slide-title={getLangText('Register work')}>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<RegisterPieceForm
disabled={this.state.step > 0}
enableLocalHashing={false}
headerMessage={getLangText('Register work')}
submitMessage={getLangText('Register')}
isFineUploaderActive={this.state.isFineUploaderActive}
handleSuccess={this.handleRegisterSuccess}
onLoggedOut={this.onLoggedOut} />
</Col>
</Row>
</div>
{this.getSlideArtworkDetails()}
{this.getSlideArtistDetails()}
{this.getSlideLoan()}
</SlidesContainer>
);
}
});
export default IkonotvRegisterPiece;

View File

@ -23,7 +23,7 @@ let WalletApp = React.createClass({
let activeRoutes = this.getRoutes().map(elem => 'route--' + elem.name);
let header = null;
if ((this.isActive('landing') || this.isActive('login') || this.isActive('signup'))
if ((this.isActive('landing') || this.isActive('login') || this.isActive('signup') || this.isActive('contract_notifications'))
&& (['ikonotv', 'cyland']).indexOf(subdomain) > -1) {
header = (
<div className="hero"/>);

View File

@ -22,6 +22,7 @@ import CylandPieceList from './components/cyland/cyland_piece_list';
import IkonotvLanding from './components/ikonotv/ikonotv_landing';
import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list';
import IkonotvRequestLoan from './components/ikonotv/ikonotv_request_loan';
import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece';
import IkonotvPieceContainer from './components/ikonotv/ascribe_detail/ikonotv_piece_container';
import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications';
@ -72,8 +73,8 @@ let ROUTES = {
<Route name="logout" path="logout" handler={LogoutContainer} />
<Route name="signup" path="signup" handler={SignupContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="request_loan" path="request_loan" handler={IkonotvRequestLoan} headerTitle="SEND NEW CONTRACT" aclName="acl_send_contract" />
<Route name="register_piece" path="register_piece" handler={RegisterPiece} headerTitle="+ NEW WORK"/>
<Route name="request_loan" path="request_loan" handler={IkonotvRequestLoan} headerTitle="SEND NEW CONTRACT" aclName="acl_create_contractagreement" />
<Route name="register_piece" path="register_piece" handler={IkonotvRegisterPiece} headerTitle="+ NEW WORK"/>
<Route name="pieces" path="collection" handler={IkonotvPieceList} headerTitle="COLLECTION"/>
<Route name="piece" path="pieces/:pieceId" handler={IkonotvPieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />

View File

@ -12,7 +12,7 @@ let constants = {
'baseUrl': window.BASE_URL,
'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_create_editions', 'acl_view_editions',
'acl_loan', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view',
'acl_withdraw_transfer', 'acl_submit'],
'acl_withdraw_transfer', 'acl_wallet_submit'],
'version': 0.1,
'csrftoken': 'csrftoken2',
@ -74,7 +74,11 @@ let constants = {
'whitelabel': {},
'raven': {
'url': 'https://0955da3388c64ab29bd32c2a429f9ef4@app.getsentry.com/48351'
}
},
'copyrightAssociations': ['ARS', 'DACS', 'Bildkunst', 'Pictoright', 'SODRAC', 'Copyright Agency/Viscopy', 'SAVA',
'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART',
'IVARO', 'SIAE', 'JASPAR-SPDA', 'AKKA/LAA', 'LATGA-A', 'SOMAAP', 'ARTEGESTION', 'CARIER', 'BONO', 'APSAV',
'SPA', 'GESTOR', 'VISaRTA', 'RAO', 'LITA', 'DALRO', 'VeGaP', 'BUS', 'ProLitteris', 'AGADU', 'AUTORARTE', 'BUBEDRA', 'BBDA', 'BCDA', 'BURIDA', 'ADAVIS', 'BSDA']
};
export default constants;

View File

@ -44,6 +44,14 @@ let OwnershipFetcher = {
return requests.get(ApiUrls.ownership_contract_agreements, queryParams);
},
confirmContractAgreement(contractAgreement){
return requests.put(ApiUrls.ownership_contract_agreements_confirm, {contract_agreement_id: contractAgreement.id});
},
denyContractAgreement(contractAgreement){
return requests.put(ApiUrls.ownership_contract_agreements_deny, {contract_agreement_id: contractAgreement.id});
},
fetchLoanPieceRequestList(){
return requests.get(ApiUrls.ownership_loans_pieces_request);
},

View File

@ -78,7 +78,7 @@
"react-router": "^0.13.3",
"react-router-bootstrap": "~0.16.0",
"react-star-rating": "~1.3.2",
"react-textarea-autosize": "^2.2.3",
"react-textarea-autosize": "^2.5.2",
"reactify": "^1.1.0",
"shmui": "^0.1.0",
"spark-md5": "~1.0.0",

View File

@ -1,10 +1,10 @@
.notification-contract-download {
.notification-contract-wrapper {
text-align: center;
}
.notification-contract-wrapper{
text-align: center;
.notification-contract-pdf-download {
text-align: right;
margin: 1em 0 1em 0;
}
.notification-contract-logo {
@ -29,8 +29,17 @@
border: 1px solid #cccccc;
width: 100%;
height: 60vh;
margin-bottom: 0.4em;
margin-bottom: 0;
.loan-form {
height: 45vh;
}
}
.loan-form {
height: 40vh;
}
.notification-contract-pdf-download {
text-align: left;
margin-left: 1em;
@ -39,7 +48,7 @@
.notification-contract-footer {
text-align: left;
padding: 1em;
padding: 0 0 1em 0;
> h1 {
margin-top: 0.4em;
font-size: 1.4em;