mirror of
https://github.com/ascribe/onion.git
synced 2024-12-22 17:33:14 +01:00
loan
This commit is contained in:
parent
2c7535a73a
commit
d90a4f97db
@ -7,6 +7,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="build/css/main.css">
|
<link rel="stylesheet" href="build/css/main.css">
|
||||||
<link rel="stylesheet" href="//brick.a.ssl.fastly.net/Source+Sans+Pro:400,600,700,900">
|
<link rel="stylesheet" href="//brick.a.ssl.fastly.net/Source+Sans+Pro:400,600,700,900">
|
||||||
|
<link rel="stylesheet" href="node_modules/react-datepicker/dist/react-datepicker.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main" class="container"></div>
|
<div id="main" class="container"></div>
|
||||||
|
@ -5,46 +5,122 @@ import React from 'react';
|
|||||||
import ApiUrls from '../../constants/api_urls';
|
import ApiUrls from '../../constants/api_urls';
|
||||||
import FormMixin from '../../mixins/form_mixin';
|
import FormMixin from '../../mixins/form_mixin';
|
||||||
import InputText from './input_text';
|
import InputText from './input_text';
|
||||||
|
import InputHidden from './input_hidden';
|
||||||
|
import InputCheckbox from './input_checkbox';
|
||||||
|
//import InputDate from './input_date';
|
||||||
import InputTextArea from './input_textarea';
|
import InputTextArea from './input_textarea';
|
||||||
|
import OwnershipFetcher from '../../fetchers/ownership_fetcher'
|
||||||
import ButtonSubmitOrClose from './button_submit_close';
|
import ButtonSubmitOrClose from './button_submit_close';
|
||||||
|
|
||||||
let ConsignForm = React.createClass({
|
let LoanForm = React.createClass({
|
||||||
mixins: [FormMixin],
|
mixins: [FormMixin],
|
||||||
|
|
||||||
url() {
|
url() {
|
||||||
return ApiUrls.ownership_consigns
|
return ApiUrls.ownership_loans
|
||||||
|
},
|
||||||
|
componentDidMount(){
|
||||||
|
this.setState({contract_key: null,
|
||||||
|
contract_url: null,
|
||||||
|
loaneeHasContract: false});
|
||||||
},
|
},
|
||||||
getFormData() {
|
getFormData() {
|
||||||
return {
|
return {
|
||||||
bitcoin_id: this.props.edition.bitcoin_id,
|
bitcoin_id: this.props.edition.bitcoin_id,
|
||||||
consignee: this.refs.consignee.state.value,
|
loanee: this.refs.loanee.state.value,
|
||||||
consign_message: this.refs.consign_message.state.value,
|
gallery_name: this.refs.gallery_name.state.value,
|
||||||
password: this.refs.password.state.value
|
startdate: this.refs.startdate.state.value,
|
||||||
|
enddate: this.refs.enddate.state.value,
|
||||||
|
loan_message: this.refs.loan_message.state.value,
|
||||||
|
password: this.refs.password.state.value,
|
||||||
|
terms: this.refs.terms.state.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleLoanEmailBlur(e){
|
||||||
|
OwnershipFetcher.fetchLoanContract(this.refs.loanee.state.value)
|
||||||
|
.then((res) => {
|
||||||
|
if (res && res.length > 0) {
|
||||||
|
this.setState({contract_key: res[0].s3Key,
|
||||||
|
contract_url: res[0].s3Url,
|
||||||
|
loaneeHasContract: true});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.resetLoanContract();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.resetLoanContract();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetLoanContract(){
|
||||||
|
this.setState({contract_key: null,
|
||||||
|
contract_url: null,
|
||||||
|
loaneeHasContract: false
|
||||||
|
});
|
||||||
|
},
|
||||||
renderForm() {
|
renderForm() {
|
||||||
let title = this.props.edition.title;
|
let title = this.props.edition.title;
|
||||||
let username = this.props.currentUser.username;
|
let username = this.props.currentUser.username;
|
||||||
let message =
|
let message =
|
||||||
`Hi,
|
`Hi,
|
||||||
|
|
||||||
I consign \" ${title} \" to you.
|
I loan \" ${title} \" to you.
|
||||||
|
|
||||||
Truly yours,
|
Truly yours,
|
||||||
${username}`;
|
${username}`;
|
||||||
|
let contract = <InputHidden ref="terms" value="True"/>;
|
||||||
|
if (this.state.loaneeHasContract){
|
||||||
|
let label = <div>
|
||||||
|
I agree to the
|
||||||
|
<a href={this.state.contract_url} target="_blank">
|
||||||
|
terms of {this.refs.loanee.state.value}
|
||||||
|
</a>
|
||||||
|
</div>;
|
||||||
|
contract = <InputCheckbox
|
||||||
|
ref="terms"
|
||||||
|
required="required"
|
||||||
|
label={label}
|
||||||
|
/>
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<form id="consign_modal_content" role="form" onSubmit={this.submit}>
|
<form id="loan_modal_content" role="form" onSubmit={this.submit}>
|
||||||
<input className="invisible" type="email" name="fake_consignee"/>
|
<input className="invisible" type="email" name="fake_loanee"/>
|
||||||
<input className="invisible" type="password" name="fake_password"/>
|
<input className="invisible" type="password" name="fake_password"/>
|
||||||
<InputText
|
<InputText
|
||||||
ref="consignee"
|
ref="loanee"
|
||||||
placeHolder="Consignee email"
|
placeHolder="Loanee email"
|
||||||
required="required"
|
required="required"
|
||||||
type="email"
|
type="email"
|
||||||
|
submitted={this.state.submitted}
|
||||||
|
onBlur={this.handleLoanEmailBlur}/>
|
||||||
|
<InputText
|
||||||
|
ref="gallery_name"
|
||||||
|
placeHolder="Gallery/exhibition (optional)"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
submitted={this.state.submitted}/>
|
submitted={this.state.submitted}/>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<InputText
|
||||||
|
ref="startdate"
|
||||||
|
name="startdate"
|
||||||
|
placeHolder="Loan start date"
|
||||||
|
required="required"
|
||||||
|
type="text"
|
||||||
|
submitted={this.state.submitted}/>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6 form-group">
|
||||||
|
<InputText
|
||||||
|
ref="enddate"
|
||||||
|
name="enddate"
|
||||||
|
placeHolder="Loan end date"
|
||||||
|
required="required"
|
||||||
|
type="text"
|
||||||
|
submitted={this.state.submitted}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<InputTextArea
|
<InputTextArea
|
||||||
ref="consign_message"
|
ref="loan_message"
|
||||||
defaultValue={message}
|
defaultValue={message}
|
||||||
required=""
|
required=""
|
||||||
/>
|
/>
|
||||||
@ -54,8 +130,9 @@ ${username}`;
|
|||||||
required="required"
|
required="required"
|
||||||
type="password"
|
type="password"
|
||||||
submitted={this.state.submitted}/>
|
submitted={this.state.submitted}/>
|
||||||
|
{contract}
|
||||||
<ButtonSubmitOrClose
|
<ButtonSubmitOrClose
|
||||||
text="CONSIGN"
|
text="LOAN"
|
||||||
onClose={this.props.onRequestHide}
|
onClose={this.props.onRequestHide}
|
||||||
submitted={this.state.submitted} />
|
submitted={this.state.submitted} />
|
||||||
</form>
|
</form>
|
||||||
@ -63,4 +140,4 @@ ${username}`;
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ConsignForm;
|
export default LoanForm;
|
40
js/components/ascribe_forms/input_checkbox.js
Normal file
40
js/components/ascribe_forms/input_checkbox.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AlertMixin from '../../mixins/alert_mixin'
|
||||||
|
|
||||||
|
let InputCheckbox = React.createClass({
|
||||||
|
|
||||||
|
mixins : [AlertMixin],
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {value: null,
|
||||||
|
alerts: null // needed in AlertMixin
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleChange(event) {
|
||||||
|
this.setState({value: event.target.value});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
||||||
|
return (
|
||||||
|
<div className="form-group">
|
||||||
|
{alerts}
|
||||||
|
<div className="input-checkbox-ascribe">
|
||||||
|
<div className="checkbox">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
required={this.props.required}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
{this.props.label}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InputCheckbox;
|
53
js/components/ascribe_forms/input_date.js
Normal file
53
js/components/ascribe_forms/input_date.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AlertMixin from '../../mixins/alert_mixin'
|
||||||
|
import DatePicker from 'react-datepicker/dist/react-datepicker'
|
||||||
|
|
||||||
|
let InputDate = React.createClass({
|
||||||
|
|
||||||
|
mixins : [AlertMixin],
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {value: '2015-01-01',
|
||||||
|
alerts: null // needed in AlertMixin
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleChange(moment_date) {
|
||||||
|
this.setState({value: moment_date.format("YYYY-MM-DD")});
|
||||||
|
},
|
||||||
|
isValidDate: function (str) {
|
||||||
|
return (
|
||||||
|
/^[0-9]{4}$/.test(str) &&
|
||||||
|
moment(str, 'YYYY-MM-DD').isValid()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
let className = "form-control input-text-ascribe";
|
||||||
|
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
key="example2"
|
||||||
|
dateFormat="YYYY-MM-DD"
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
//return (
|
||||||
|
// <div className="input-group date"
|
||||||
|
// ref={this.props.name + "_picker"}
|
||||||
|
// onChange={this.handleChange}>
|
||||||
|
// <input className={className}
|
||||||
|
// ref={this.props.name}
|
||||||
|
// placeholder={this.props.placeholder}
|
||||||
|
// required={this.props.required}
|
||||||
|
// type="text"/>
|
||||||
|
// <span className="input-group-addon input-text-ascribe">
|
||||||
|
// <span className="glyphicon glyphicon-calendar" style={{"color": "black"}}></span>
|
||||||
|
// </span>
|
||||||
|
// </div>
|
||||||
|
//)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InputDate;
|
33
js/components/ascribe_forms/input_hidden.js
Normal file
33
js/components/ascribe_forms/input_hidden.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AlertMixin from '../../mixins/alert_mixin'
|
||||||
|
|
||||||
|
let InputHidden = React.createClass({
|
||||||
|
|
||||||
|
mixins : [AlertMixin],
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {value: this.props.value,
|
||||||
|
alerts: null // needed in AlertMixin
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleChange(event) {
|
||||||
|
this.setState({value: event.target.value});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
||||||
|
return (
|
||||||
|
<div className="form-group">
|
||||||
|
{alerts}
|
||||||
|
<input
|
||||||
|
value={this.props.value}
|
||||||
|
type="hidden"
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InputHidden;
|
@ -24,7 +24,8 @@ let InputText = React.createClass({
|
|||||||
placeholder={this.props.placeHolder}
|
placeholder={this.props.placeHolder}
|
||||||
required={this.props.required}
|
required={this.props.required}
|
||||||
type={this.props.type}
|
type={this.props.type}
|
||||||
onChange={this.handleChange}/>
|
onChange={this.handleChange}
|
||||||
|
onBlur={this.props.onBlur}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4,18 +4,18 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
|||||||
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||||
|
|
||||||
import ConsignForm from '../ascribe_forms/form_consign'
|
import LoanForm from '../ascribe_forms/form_loan'
|
||||||
import ModalMixin from '../../mixins/modal_mixin'
|
import ModalMixin from '../../mixins/modal_mixin'
|
||||||
|
|
||||||
let ConsignModalButton = React.createClass({
|
let LoanModalButton = React.createClass({
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<OverlayTrigger delay={500} placement="left"
|
<OverlayTrigger delay={500} placement="left"
|
||||||
overlay={<Tooltip>Have someone else sell the artwork</Tooltip>}>
|
overlay={<Tooltip>Loan your artwork for a limited period of time</Tooltip>}>
|
||||||
<ModalTrigger modal={<ConsignModal edition={this.props.edition}
|
<ModalTrigger modal={<LoanModal edition={this.props.edition}
|
||||||
currentUser={this.props.currentUser}/>}>
|
currentUser={this.props.currentUser}/>}>
|
||||||
<div className="btn btn-ascribe-inv">
|
<div className="btn btn-ascribe-inv">
|
||||||
CONSIGN
|
LOAN
|
||||||
</div>
|
</div>
|
||||||
</ModalTrigger>
|
</ModalTrigger>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
@ -23,14 +23,14 @@ let ConsignModalButton = React.createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let ConsignModal = React.createClass({
|
let LoanModal = React.createClass({
|
||||||
mixins : [ModalMixin],
|
mixins : [ModalMixin],
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Modal {...this.props} title="Consign artwork">
|
<Modal {...this.props} title="Loan artwork">
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
<ConsignForm edition={this.props.edition}
|
<LoanForm edition={this.props.edition}
|
||||||
currentUser={this.props.currentUser}
|
currentUser={this.props.currentUser}
|
||||||
onRequestHide={this.onRequestHide}/>
|
onRequestHide={this.onRequestHide}/>
|
||||||
</div>
|
</div>
|
||||||
@ -40,4 +40,4 @@ let ConsignModal = React.createClass({
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export default ConsignModalButton;
|
export default LoanModalButton;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import ImageViewer from './ascribe_media/image_viewer';
|
import ImageViewer from './ascribe_media/image_viewer';
|
||||||
|
import LoanModalButton from './ascribe_modal/modal_loan';
|
||||||
import ConsignModalButton from './ascribe_modal/modal_consign';
|
import ConsignModalButton from './ascribe_modal/modal_consign';
|
||||||
import UnConsignModalButton from './ascribe_modal/modal_unconsign';
|
import UnConsignModalButton from './ascribe_modal/modal_unconsign';
|
||||||
import UnConsignRequestModalButton from './ascribe_modal/modal_unconsign_request';
|
import UnConsignRequestModalButton from './ascribe_modal/modal_unconsign_request';
|
||||||
@ -50,6 +51,7 @@ let EditionDetails = React.createClass({
|
|||||||
<EditionDetailProperty label="id" value={ this.props.edition.bitcoin_id } />
|
<EditionDetailProperty label="id" value={ this.props.edition.bitcoin_id } />
|
||||||
<EditionDetailProperty label="owner" value={ this.props.edition.owner } />
|
<EditionDetailProperty label="owner" value={ this.props.edition.owner } />
|
||||||
<br/>
|
<br/>
|
||||||
|
<LoanModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
||||||
<ConsignModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
<ConsignModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
||||||
<UnConsignModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
<UnConsignModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
||||||
<UnConsignRequestModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
<UnConsignRequestModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
||||||
|
@ -3,6 +3,7 @@ import AppConstants from './application_constants';
|
|||||||
let apiUrls = {
|
let apiUrls = {
|
||||||
'ownership_shares_mail' : AppConstants.baseUrl + 'ownership/shares/mail/',
|
'ownership_shares_mail' : AppConstants.baseUrl + 'ownership/shares/mail/',
|
||||||
'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/',
|
'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/',
|
||||||
|
'ownership_loans' : AppConstants.baseUrl + 'ownership/loans/',
|
||||||
'ownership_consigns' : AppConstants.baseUrl + 'ownership/consigns/',
|
'ownership_consigns' : AppConstants.baseUrl + 'ownership/consigns/',
|
||||||
'ownership_unconsigns' : AppConstants.baseUrl + 'ownership/unconsigns/',
|
'ownership_unconsigns' : AppConstants.baseUrl + 'ownership/unconsigns/',
|
||||||
'ownership_unconsigns_request' : AppConstants.baseUrl + 'ownership/unconsigns/request/'
|
'ownership_unconsigns_request' : AppConstants.baseUrl + 'ownership/unconsigns/request/'
|
||||||
|
24
js/fetchers/ownership_fetcher.js
Normal file
24
js/fetchers/ownership_fetcher.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import fetch from 'isomorphic-fetch';
|
||||||
|
|
||||||
|
import AppConstants from '../constants/application_constants';
|
||||||
|
import FetchApiUtils from '../utils/fetch_api_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let OwnershipFetcher = {
|
||||||
|
/**
|
||||||
|
* Fetch one user from the API.
|
||||||
|
* If no arg is supplied, load the current user
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fetchLoanContract(email) {
|
||||||
|
return fetch(AppConstants.baseUrl + 'ownership/loans/contract/?loanee=' + email, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Basic ' + AppConstants.debugCredentialBase64
|
||||||
|
}
|
||||||
|
}).then(
|
||||||
|
(res) => res.json()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OwnershipFetcher;
|
@ -34,7 +34,8 @@
|
|||||||
"react": "^0.13.2",
|
"react": "^0.13.2",
|
||||||
"react-router": "^0.13.3",
|
"react-router": "^0.13.3",
|
||||||
"uglifyjs": "^2.4.10",
|
"uglifyjs": "^2.4.10",
|
||||||
"react-bootstrap": "~0.22.6"
|
"react-bootstrap": "~0.22.6",
|
||||||
|
"react-datepicker": "~0.8.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"scriptPreprocessor": "node_modules/babel-jest",
|
"scriptPreprocessor": "node_modules/babel-jest",
|
||||||
|
@ -163,6 +163,14 @@
|
|||||||
height: 13em !important;
|
height: 13em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-checkbox-ascribe {
|
||||||
|
text-align: left;
|
||||||
|
line-height: 1.6;
|
||||||
|
width: 90%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* columns of same height styles */
|
/* columns of same height styles */
|
||||||
/* http://www.minimit.com/articles/solutions-tutorials/bootstrap-3-responsive-columns-of-same-height */
|
/* http://www.minimit.com/articles/solutions-tutorials/bootstrap-3-responsive-columns-of-same-height */
|
||||||
.row-full-height {
|
.row-full-height {
|
||||||
|
Loading…
Reference in New Issue
Block a user