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

boilerplate for loan process

This commit is contained in:
Tim Daubenschütz 2015-08-12 13:34:41 +02:00
parent 9630d043db
commit d5dc164810
6 changed files with 360 additions and 118 deletions

View File

@ -5,21 +5,15 @@ import React from 'react';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import Form from './../ascribe_forms/form';
import Property from './../ascribe_forms/property';
import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
import ReactS3FineUploader from './../ascribe_uploader/react_s3_fine_uploader';
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 { getCookie } from '../../utils/fetch_api_utils';
import FurtherDetailsFileuploader from './further_details_fileuploader';
let FurtherDetails = React.createClass({
propTypes: {
@ -90,93 +84,21 @@ let FurtherDetails = React.createClass({
editable={this.props.editable}
pieceId={this.props.pieceId}
extraData={this.props.extraData} />
<FileUploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={this.isReadyForFormSubmission}
editable={this.props.editable}
pieceId={this.props.pieceId}
otherData={this.props.otherData}/>
<Form>
<FurtherDetailsFileuploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={this.isReadyForFormSubmission}
editable={this.props.editable}
pieceId={this.props.pieceId}
otherData={this.props.otherData}/>
</Form>
</Col>
</Row>
);
}
});
let FileUploader = React.createClass({
propTypes: {
pieceId: React.PropTypes.number,
otherData: React.PropTypes.object,
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
editable: React.PropTypes.bool
},
render() {
// Essentially there a three cases important to the fileuploader
//
// 1. there is no other_data => do not show the fileuploader at all
// 2. there is other_data, but user has no edit rights => show fileuploader but without action buttons
// 3. both other_data and editable are defined or true => show fileuploade with all action buttons
if (!this.props.editable && !this.props.otherData){
return null;
}
return (
<Form>
<Property
label="Additional files (max. 10MB)">
<ReactS3FineUploader
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'otherdata',
pieceId: this.props.pieceId
}}
createBlobRoutine={{
url: ApiUrls.blob_otherdatas,
pieceId: this.props.pieceId
}}
validation={{
itemLimit: 100000,
sizeLimit: '10000000'
}}
submitKey={this.props.submitKey}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
session={{
endpoint: AppConstants.serverUrl + 'api/blob/otherdatas/fineuploader_session/',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
},
params: {
'pk': this.props.otherData ? this.props.otherData.id : null
},
cors: {
expected: true,
sendCredentials: true
}
}}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
areAssetsDownloadable={true}
areAssetsEditable={this.props.editable}/>
</Property>
<hr />
</Form>
);
}
});
export default FurtherDetails;

View File

@ -0,0 +1,95 @@
'use strict';
import React from 'react';
import Property from './../ascribe_forms/property';
import ReactS3FineUploader from './../ascribe_uploader/react_s3_fine_uploader';
import ApiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants';
import { getCookie } from '../../utils/fetch_api_utils';
let FurtherDetailsFileuploader = React.createClass({
propTypes: {
pieceId: React.PropTypes.number,
otherData: React.PropTypes.object,
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
editable: React.PropTypes.bool,
multiple: React.PropTypes.bool
},
getDefaultProps() {
return {
multiple: false
};
},
render() {
// Essentially there a three cases important to the fileuploader
//
// 1. there is no other_data => do not show the fileuploader at all
// 2. there is other_data, but user has no edit rights => show fileuploader but without action buttons
// 3. both other_data and editable are defined or true => show fileuploade with all action buttons
if (!this.props.editable && !this.props.otherData){
return null;
}
return (
<Property
label="Additional files (max. 10MB)">
<ReactS3FineUploader
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'otherdata',
pieceId: this.props.pieceId
}}
createBlobRoutine={{
url: ApiUrls.blob_otherdatas,
pieceId: this.props.pieceId
}}
validation={{
itemLimit: 100000,
sizeLimit: '10000000'
}}
submitKey={this.props.submitKey}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
session={{
endpoint: AppConstants.serverUrl + 'api/blob/otherdatas/fineuploader_session/',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
},
params: {
'pk': this.props.otherData ? this.props.otherData.id : null
},
cors: {
expected: true,
sendCredentials: true
}
}}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
areAssetsDownloadable={true}
areAssetsEditable={this.props.editable}
multiple={this.props.multiple}/>
</Property>
);
}
});
export default FurtherDetailsFileuploader;

View File

@ -2,6 +2,8 @@
import React from 'react';
import classnames from 'classnames';
import Button from 'react-bootstrap/lib/Button';
import Form from './form';
@ -20,12 +22,25 @@ import { getLangText } from '../../utils/lang_utils';
let LoanForm = React.createClass({
propTypes: {
fullform: React.PropTypes.bool,
email: React.PropTypes.string,
gallery: React.PropTypes.string,
startdate: React.PropTypes.string,
enddate: React.PropTypes.string,
showPersonalMessage: React.PropTypes.bool,
url: React.PropTypes.string,
id: React.PropTypes.object,
message: React.PropTypes.string,
handleSuccess: React.PropTypes.func
},
getDefaultProps() {
return {
fullform: false
};
},
getInitialState() {
return LoanContractStore.getState();
},
@ -87,60 +102,87 @@ let LoanForm = React.createClass({
}
},
render() {
getButtons() {
if(this.props.fullform) {
return (
<button
type="submit"
className="btn ascribe-btn ascribe-btn-login">
{getLangText('Finish process')}
</button>
);
} else {
return (
<div className="modal-footer">
<p className="pull-right">
<Button
className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">
{getLangText('LOAN')}
</Button>
</p>
</div>
);
}
},
render() {
return (
<Form
className={classnames({'ascribe-form-bordered': this.props.fullform})}
ref='form'
url={this.props.url}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess}
buttons={
<div className="modal-footer">
<p className="pull-right">
<Button
className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">
{getLangText('LOAN')}
</Button>
</p>
</div>}
buttons={this.getButtons()}
spinner={
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div>}>
<div className={classnames({'ascribe-form-header': true, 'hidden': !this.props.fullform})}>
<h3>Loan</h3>
</div>
<Property
name='loanee'
label={getLangText('Loanee Email')}
onBlur={this.handleOnBlur}>
onBlur={this.handleOnBlur}
editable={!this.props.email}>
<input
value={this.props.email}
type="email"
placeholder={getLangText('Email of the loanee')}
required/>
</Property>
<Property
name='gallery_name'
label={getLangText('Gallery/exhibition (optional)')}>
label={getLangText('Gallery/exhibition (optional)')}
editable={!this.props.gallery}>
<input
value={this.props.gallery}
type="text"
placeholder={getLangText('Gallery/exhibition (optional)')}/>
</Property>
<Property
name='startdate'
label={getLangText('Start date')}>
label={getLangText('Start date')}
hidden={!this.props.startdate}>
<InputDate
value={this.props.startdate}
placeholderText={getLangText('Loan start date')} />
</Property>
<Property
name='enddate'
label={getLangText('End date')}>
label={getLangText('End date')}
hidden={!this.props.enddate}>
<InputDate
value={this.props.enddate}
placeholderText={getLangText('Loan end date')} />
</Property>
<Property
name='loan_message'
label={getLangText('Personal Message')}
editable={true}>
editable={true}
hidden={this.props.showPersonalMessage}>
<InputTextAreaToggable
rows={1}
editable={true}

View File

@ -0,0 +1,116 @@
'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 FurtherDetailsFileuploader from '../../../../ascribe_detail/further_details_fileuploader';
import ApiUrls from '../../../../../constants/api_urls';
import AppConstants from '../../../../../constants/application_constants';
import requests from '../../../../../utils/requests';
import { getLangText } from '../../../../../utils/lang_utils';
let CylandAdditionalDataForm = React.createClass({
propTypes: {
handleSuccess: React.PropTypes.func.isRequired,
piece: React.PropTypes.object.isRequired
},
getInitialState() {
return {
isUploadReady: 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
};
},
setIsUploadReady(isReady) {
this.setState({
isUploadReady: isReady
});
},
isReadyForFormSubmission() {
return true;
},
render() {
if(this.props.piece && this.props.piece.id) {
return (
<Form
className="ascribe-form-bordered"
ref='form'
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})}
handleSuccess={this.props.handleSuccess}
getFormData={this.getFormData}
buttons={
<button
type="submit"
className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady}>
{getLangText('Proceed to loan')}
</button>
}
spinner={
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div>
}>
<FurtherDetailsFileuploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={this.isReadyForFormSubmission}
editable={true}
pieceId={this.props.piece.id}
otherData={this.props.piece.other_data}
multiple={false}/>
<Property
name='artist_bio'
label={getLangText('Artist Biography')}
editable={true}>
<InputTextAreaToggable
rows={1}
editable={true}
placeholder={getLangText('Enter the artist\'s biography...')}
required="required"/>
</Property>
<Property
name='conceptual_overview'
label={getLangText('Conceptual Overview')}
editable={true}>
<InputTextAreaToggable
rows={1}
editable={true}
placeholder={getLangText('Enter a conceptual overview...')}
required="required"/>
</Property>
</Form>
);
} else {
return <span>First register the piece.</span>;
}
}
});
export default CylandAdditionalDataForm;

View File

@ -9,7 +9,6 @@ import Row from 'react-bootstrap/lib/Row';
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
import Property from '../../../../ascribe_forms/property';
import InputTextAreaToggable from '../../../../ascribe_forms/input_textarea_toggable';
import InputCheckbox from '../../../../ascribe_forms/input_checkbox';
import PieceListStore from '../../../../../stores/piece_list_store';
@ -18,36 +17,51 @@ 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 CylandAdditionalDataForm from '../ascribe_forms/cyland_additional_data_form';
import LoanForm from '../../../../ascribe_forms/form_loan';
import SlidesContainer from '../../../../ascribe_slides_container/slides_container';
import ApiUrls from '../../../../../constants/api_urls';
import { getLangText } from '../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../utils/general_utils';
import { mergeOptions, dateToString } from '../../../../../utils/general_utils';
import { getAclFormMessage } from '../../../../../utils/form_utils';
let CylandRegisterPiece = React.createClass({
mixins: [Router.Navigation],
getInitialState(){
return mergeOptions(
UserStore.getState(),
PieceListStore.getState(),
PieceStore.getState(),
{
selectedLicense: 0,
isFineUploaderActive: false
});
},
mixins: [Router.Navigation],
componentDidMount() {
PieceListStore.listen(this.onChange);
UserStore.listen(this.onChange);
PieceStore.listen(this.onChange);
UserActions.fetchCurrentUser();
},
componentWillUnmount() {
PieceListStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange);
PieceStore.unlisten(this.onChange);
},
onChange(state) {
@ -61,9 +75,7 @@ let CylandRegisterPiece = React.createClass({
}
},
handleSuccess(response){
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
handleRegisterSuccess(response){
// once the user was able to register a piece successfully, we need to make sure to keep
// the piece list up to date
@ -76,7 +88,16 @@ let CylandRegisterPiece = React.createClass({
this.state.filterBy
);
this.transitionTo('piece', {pieceId: response.piece.id});
// also start loading the piece for the next step
if(response && response.piece) {
PieceActions.updatePiece(response.piece);
}
this.refs.slidesContainer.setSlideNum(1);
},
handleAdditionalDataSuccess() {
this.refs.slidesContainer.setSlideNum(2);
},
changeSlide() {
@ -93,11 +114,14 @@ let CylandRegisterPiece = React.createClass({
},
render() {
let today = new Date();
let datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain = new Date();
datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain.setFullYear(3000);
return (
<SlidesContainer ref="slidesContainer">
<div
onClick={this.changeSlide}
onFocus={this.changeSlide}>
<div>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<RegisterPieceForm
@ -105,7 +129,7 @@ let CylandRegisterPiece = React.createClass({
headerMessage={getLangText('Submit to Cyland Archive')}
submitMessage={getLangText('Submit')}
isFineUploaderActive={this.state.isFineUploaderActive}
handleSuccess={this.handleSuccess}
handleSuccess={this.handleRegisterSuccess}
onLoggedOut={this.onLoggedOut}>
<Property
name="terms"
@ -114,7 +138,7 @@ let CylandRegisterPiece = React.createClass({
<InputCheckbox>
<span>
{' ' + getLangText('I agree to the Terms of Service the art price') + ' '}
(<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/sluice/terms.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
(<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/cyland/terms_and_contract.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
{getLangText('read')}
</a>)
</span>
@ -125,7 +149,28 @@ let CylandRegisterPiece = React.createClass({
</Row>
</div>
<div>
{/* next slide */}
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<CylandAdditionalDataForm
handleSuccess={this.handleAdditionalDataSuccess}
piece={this.state.piece}/>
</Col>
</Row>
</div>
<div>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<LoanForm
fullform={true}
message={getAclFormMessage('acl_loan', '\"' + this.state.piece.title + '\"', this.state.currentUser.username)}
id={{piece_id: this.state.piece.id}}
url={ApiUrls.ownership_loans_pieces}
email="videoarchive@cyland.org"
gallery="Cyland Archive"
startdate={dateToString(today)}
enddate={dateToString(datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain)}/>
</Col>
</Row>
</div>
</SlidesContainer>
);

View File

@ -178,3 +178,25 @@ function _mergeOptions(obj1, obj2) {
export function escapeHTML(s) {
return document.createElement('div').appendChild(document.createTextNode(s)).parentNode.innerHTML;
}
/**
* Converts a date object to a string.
* Taken from: http://stackoverflow.com/a/4929629/1263876
* @param {date} date a javascript date
* @return {string} a string, in format: DD-MM-YYY
*/
export function dateToString(date) {
var dd = date.getDate();
var mm = date.getMonth() + 1; //January is 0!
var yyyy = date.getFullYear();
if(dd < 10) {
dd = '0' + dd;
}
if(mm < 10) {
mm = '0' + mm;
}
return dd + '-' + mm + '-' + yyyy;
}