diff --git a/index.html b/index.html index b61794b4..066b36cc 100644 --- a/index.html +++ b/index.html @@ -5,8 +5,9 @@ ascribe - + +
diff --git a/js/components/acl_button.js b/js/components/acl_button.js index 6d4f1756..5f2a0708 100644 --- a/js/components/acl_button.js +++ b/js/components/acl_button.js @@ -1,27 +1,66 @@ import React from 'react'; +import ConsignForm from './ascribe_forms/form_consign'; +import TransferForm from './ascribe_forms/form_transfer'; +import LoanForm from './ascribe_forms/form_loan'; +import ShareForm from './ascribe_forms/form_share_email'; +import ModalWrapper from './ascribe_modal/modal_wrapper'; import AppConstants from '../constants/application_constants'; let AclButton = React.createClass({ propTypes: { action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, availableAcls: React.PropTypes.array.isRequired, - actionFunction: React.PropTypes.func.isRequired + editions: React.PropTypes.array.isRequired, + currentUser: React.PropTypes.object, + handleSuccess: React.PropTypes.func.isRequired }, - actionFunction() { - this.props.actionFunction(this.props.action); + actionProperties(){ + if (this.props.action == 'consign'){ + return { + title: "Consign artwork", + tooltip: "Have someone else sell the artwork", + form: + } + } + else if (this.props.action == 'transfer') { + return { + title: "Transfer artwork", + tooltip: "Transfer the ownership of the artwork", + form: + } + } + else if (this.props.action == 'loan'){ + return { + title: "Loan artwork", + tooltip: "Loan your artwork for a limited period of time", + form: } + } + else if (this.props.action == 'share'){ + return { + title: "Share artwork", + tooltip: "Share the artwork", + form: } + } }, - render() { let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1; + let aclProps = this.actionProperties(); return ( - + + {this.props.action.toUpperCase()} + + } + currentUser={ this.props.currentUser } + editions={ this.props.editions } + handleSuccess={ this.props.handleSuccess } + title={ aclProps.title } + tooltip={ aclProps.tooltip }> + { aclProps.form } + ); } }); diff --git a/js/components/ascribe_forms/button_submit_close.js b/js/components/ascribe_forms/button_submit_close.js index 60091f2f..e811cab5 100644 --- a/js/components/ascribe_forms/button_submit_close.js +++ b/js/components/ascribe_forms/button_submit_close.js @@ -3,11 +3,11 @@ import React from 'react'; let ButtonSubmitOrClose = React.createClass({ render() { if (this.props.submitted){ - return ( -
- Loading -
- ) + //return ( + //
+ // Loading + //
+ //) } return (
diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js new file mode 100644 index 00000000..dbb1eee0 --- /dev/null +++ b/js/components/ascribe_forms/form_consign.js @@ -0,0 +1,68 @@ +import fetch from 'isomorphic-fetch'; + +import React from 'react'; + +import ApiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; +import InputText from './input_text'; +import InputTextArea from './input_textarea'; +import ButtonSubmitOrClose from './button_submit_close'; + +let ConsignForm = React.createClass({ + mixins: [FormMixin], + + url() { + return ApiUrls.ownership_consigns + }, + getFormData() { + return { + bitcoin_id: this.getBitcoinIds().join(), + consignee: this.refs.consignee.state.value, + consign_message: this.refs.consign_message.state.value, + password: this.refs.password.state.value + } + }, + + renderForm() { + let title = this.getTitlesString().join(""); + let username = this.props.currentUser.username; + let message = +`Hi, + +I consign : +${title}to you. + +Truly yours, +${username}`; + + return ( +
+ + + + + + + + ); + } +}); + +export default ConsignForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js new file mode 100644 index 00000000..483d0f0a --- /dev/null +++ b/js/components/ascribe_forms/form_loan.js @@ -0,0 +1,137 @@ +import fetch from 'isomorphic-fetch'; + +import React from 'react'; + +import ApiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; +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 OwnershipFetcher from '../../fetchers/ownership_fetcher' +import ButtonSubmitOrClose from './button_submit_close'; + +let LoanForm = React.createClass({ + mixins: [FormMixin], + + url() { + return ApiUrls.ownership_loans + }, + componentDidMount(){ + this.setState({contract_key: null, + contract_url: null, + loaneeHasContract: false}); + }, + getFormData() { + return { + bitcoin_id: this.getBitcoinIds().join(), + loanee: this.refs.loanee.state.value, + gallery_name: this.refs.gallery_name.state.value, + startdate: this.refs.startdate.state.value.format("YYYY-MM-DD"), + enddate: this.refs.enddate.state.value.format("YYYY-MM-DD"), + 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() { + let title = this.getTitlesString().join(""); + let username = this.props.currentUser.username; + let message = +`Hi, + +I loan : +${title}to you. + +Truly yours, +${username}`; + + let contract = ; + if (this.state.loaneeHasContract){ + let label = ; + contract = + } + return ( +
+ + + + +
+
+ +
+
+ +
+
+ + + {contract} + + + ); + } +}); + +export default LoanForm; diff --git a/js/components/ascribe_forms/form_share_email.js b/js/components/ascribe_forms/form_share_email.js index dd61bc7b..cbcfc7a1 100644 --- a/js/components/ascribe_forms/form_share_email.js +++ b/js/components/ascribe_forms/form_share_email.js @@ -16,18 +16,23 @@ let ShareForm = React.createClass({ }, getFormData() { return { - bitcoin_id: this.props.edition.bitcoin_id, + bitcoin_id: this.getBitcoinIds().join(), share_emails: this.refs.share_emails.state.value, share_message: this.refs.share_message.state.value } }, renderForm() { - let message = "Hi,\n" + - "\n" + - "I am sharing \"" + this.props.edition.title + "\" with you.\n" + - "\n" + - "Truly yours,\n" + - this.props.currentUser.username; + let title = this.getTitlesString().join(""); + let username = this.props.currentUser.username; + let message = +`Hi, + +I am sharing : +${title}with you. + +Truly yours, +${username}`; + return (
diff --git a/js/components/ascribe_forms/form_unconsign.js b/js/components/ascribe_forms/form_unconsign.js new file mode 100644 index 00000000..60112334 --- /dev/null +++ b/js/components/ascribe_forms/form_unconsign.js @@ -0,0 +1,58 @@ +import fetch from 'isomorphic-fetch'; + +import React from 'react'; + +import ApiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; +import InputText from './input_text'; +import InputTextArea from './input_textarea'; +import ButtonSubmitOrClose from './button_submit_close'; + +let UnConsignForm = React.createClass({ + mixins: [FormMixin], + + url() { + return ApiUrls.ownership_unconsigns + }, + getFormData() { + return { + bitcoin_id: this.props.edition.bitcoin_id, + unconsign_message: this.refs.unconsign_message.state.value, + password: this.refs.password.state.value + } + }, + renderForm() { + let title = this.props.edition.title; + let username = this.props.currentUser.username; + let message = +`Hi, + +I un-consign \" ${title} \" from you. + +Truly yours, +${username}`; + + return ( + + + + + + + + ); + } +}); + +export default UnConsignForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_unconsign_request.js b/js/components/ascribe_forms/form_unconsign_request.js new file mode 100644 index 00000000..f572a287 --- /dev/null +++ b/js/components/ascribe_forms/form_unconsign_request.js @@ -0,0 +1,49 @@ +import fetch from 'isomorphic-fetch'; + +import React from 'react'; + +import ApiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; +import InputText from './input_text'; +import InputTextArea from './input_textarea'; +import ButtonSubmitOrClose from './button_submit_close'; + +let UnConsignRequestForm = React.createClass({ + mixins: [FormMixin], + + url() { + return ApiUrls.ownership_unconsigns_request + }, + getFormData() { + return { + bitcoin_id: this.props.edition.bitcoin_id, + unconsign_request_message: this.refs.unconsign_request_message.state.value + } + }, + renderForm() { + let title = this.props.edition.title; + let username = this.props.currentUser.username; + let message = +`Hi, + +I request you to un-consign \" ${title} \". + +Truly yours, +${username}`; + + return ( +
+ + + + ); + } +}); + +export default UnConsignRequestForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_checkbox.js b/js/components/ascribe_forms/input_checkbox.js new file mode 100644 index 00000000..c1ecda1a --- /dev/null +++ b/js/components/ascribe_forms/input_checkbox.js @@ -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 ( +
+ {alerts} +
+
+ +
+
+
+ ); + + } +}); + +export default InputCheckbox; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_date.js b/js/components/ascribe_forms/input_date.js new file mode 100644 index 00000000..98422c72 --- /dev/null +++ b/js/components/ascribe_forms/input_date.js @@ -0,0 +1,51 @@ +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: null, + alerts: null // needed in AlertMixin + }; + }, + + handleChange(date) { + this.setState({value: date}); + }, + + render: function () { + let className = "form-control input-text-ascribe"; + let alerts = (this.props.submitted) ? null : this.state.alerts; + return ( + + ); + //return ( + //
+ // + // + // + // + //
+ //) + + + } +}); + +export default InputDate; diff --git a/js/components/ascribe_forms/input_hidden.js b/js/components/ascribe_forms/input_hidden.js new file mode 100644 index 00000000..96ff95ce --- /dev/null +++ b/js/components/ascribe_forms/input_hidden.js @@ -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 ( +
+ {alerts} + +
+ ); + + } +}); + +export default InputHidden; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_text.js b/js/components/ascribe_forms/input_text.js index a65bcc8f..b9e7287a 100644 --- a/js/components/ascribe_forms/input_text.js +++ b/js/components/ascribe_forms/input_text.js @@ -8,8 +8,7 @@ let InputText = React.createClass({ getInitialState() { return {value: null, - alerts: null, // needed in AlertMixin - retry: 0 // needed in AlertMixin for generating unique alerts + alerts: null // needed in AlertMixin }; }, handleChange(event) { @@ -25,7 +24,8 @@ let InputText = React.createClass({ placeholder={this.props.placeHolder} required={this.props.required} type={this.props.type} - onChange={this.handleChange}/> + onChange={this.handleChange} + onBlur={this.props.onBlur}/>
); diff --git a/js/components/ascribe_forms/input_textarea.js b/js/components/ascribe_forms/input_textarea.js index b5f64b66..cf5cfc4a 100644 --- a/js/components/ascribe_forms/input_textarea.js +++ b/js/components/ascribe_forms/input_textarea.js @@ -8,8 +8,7 @@ let InputTextArea = React.createClass({ getInitialState() { return {value: this.props.defaultValue, - alerts: null, // needed in AlertMixin - retry: 0 // needed in AlertMixin for generating unique alerts + alerts: null // needed in AlertMixin }; }, handleChange(event) { @@ -28,7 +27,6 @@ let InputTextArea = React.createClass({ onChange={this.handleChange}> ); - } }); diff --git a/js/components/ascribe_media/image_viewer.js b/js/components/ascribe_media/image_viewer.js deleted file mode 100644 index fdbd0342..00000000 --- a/js/components/ascribe_media/image_viewer.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -/** - * This is the component that implements display-specific functionality - */ -let ImageViewer = React.createClass({ - propTypes: { - thumbnail: React.PropTypes.string.isRequired - }, - - render() { - let thumbnail = ; - let aligner = ; - return ( -
-
- {aligner} - {thumbnail} -
-
- ); - } -}); - -export default ImageViewer; \ No newline at end of file diff --git a/js/components/ascribe_media/resource_viewer.js b/js/components/ascribe_media/resource_viewer.js new file mode 100644 index 00000000..4519bcb7 --- /dev/null +++ b/js/components/ascribe_media/resource_viewer.js @@ -0,0 +1,40 @@ +import React from 'react'; +import InjectInHeadMixin from '../../mixins/inject_in_head_mixin'; + +/** + * This is the component that implements display-specific functionality. + * + * ResourceViewer handles the following mimetypes: + * - image + * - video + * - audio + * - pdf + * - other + */ + +let resourceMap = { + 'image': 1 +} + +let ResourceViewer = React.createClass({ + propTypes: { + thumbnail: React.PropTypes.string.isRequired, + mimetype: React.PropTypes.oneOf(['image', 'video', 'audio', 'pdf', 'other']).isRequired + }, + + mixins: [InjectInHeadMixin], + + componentDidMount() { + this.inject('http://antani.com'); + }, + + render() { + return ( +
+ resourceviewer {this.props.thumbnail} {this.props.mimetype} +
+ ); + } +}); + +export default ResourceViewer; diff --git a/js/components/ascribe_modal/modal_loan.js b/js/components/ascribe_modal/modal_loan.js new file mode 100644 index 00000000..aed475b1 --- /dev/null +++ b/js/components/ascribe_modal/modal_loan.js @@ -0,0 +1,43 @@ +import React from 'react'; +import Modal from 'react-bootstrap/lib/Modal'; +import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; +import ModalTrigger from 'react-bootstrap/lib/ModalTrigger'; +import Tooltip from 'react-bootstrap/lib/Tooltip'; + +import LoanForm from '../ascribe_forms/form_loan' +import ModalMixin from '../../mixins/modal_mixin' + +let LoanModalButton = React.createClass({ + render() { + return ( + Loan your artwork for a limited period of time}> + }> +
+ LOAN +
+
+
+ ) + } +}); + +let LoanModal = React.createClass({ + mixins : [ModalMixin], + + render() { + return ( + +
+ +
+
+ ) + } +}); + + +export default LoanModalButton; diff --git a/js/components/ascribe_modal/modal_share.js b/js/components/ascribe_modal/modal_share.js index 176541ee..4ada244c 100644 --- a/js/components/ascribe_modal/modal_share.js +++ b/js/components/ascribe_modal/modal_share.js @@ -4,6 +4,7 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import ModalTrigger from 'react-bootstrap/lib/ModalTrigger'; import Tooltip from 'react-bootstrap/lib/Tooltip'; +import ModalMixin from '../../mixins/modal_mixin' import ShareForm from '../ascribe_forms/form_share_email' @@ -24,11 +25,8 @@ let ShareModalButton = React.createClass({ }); let ShareModal = React.createClass({ - onRequestHide(e){ - if (e) - e.preventDefault(); - this.props.onRequestHide(); - }, + mixins : [ModalMixin], + render() { return ( diff --git a/js/components/ascribe_modal/modal_transfer.js b/js/components/ascribe_modal/modal_transfer.js deleted file mode 100644 index b57064ff..00000000 --- a/js/components/ascribe_modal/modal_transfer.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import Modal from 'react-bootstrap/lib/Modal'; -import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; -import ModalTrigger from 'react-bootstrap/lib/ModalTrigger'; -import Tooltip from 'react-bootstrap/lib/Tooltip'; - -import TransferForm from '../ascribe_forms/form_transfer' - - -let TransferModalButton = React.createClass({ - render() { - return ( - Transfer the ownership of the artwork}> - }> -
- TRANSFER -
-
-
- ) - } -}); - -let TransferModal = React.createClass({ - onRequestHide(e){ - e.preventDefault(); - this.props.onRequestHide(); - }, - render() { - return ( - -
- -
-
- ) - } -}); - - -export default TransferModalButton; diff --git a/js/components/ascribe_modal/modal_unconsign.js b/js/components/ascribe_modal/modal_unconsign.js new file mode 100644 index 00000000..1245fc01 --- /dev/null +++ b/js/components/ascribe_modal/modal_unconsign.js @@ -0,0 +1,43 @@ +import React from 'react'; +import Modal from 'react-bootstrap/lib/Modal'; +import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; +import ModalTrigger from 'react-bootstrap/lib/ModalTrigger'; +import Tooltip from 'react-bootstrap/lib/Tooltip'; + +import UnConsignForm from '../ascribe_forms/form_unconsign' +import ModalMixin from '../../mixins/modal_mixin' + +let UnConsignModalButton = React.createClass({ + render() { + return ( + Unconsign this artwork}> + }> +
+ UNCONSIGN +
+
+
+ ) + } +}); + +let UnConsignModal = React.createClass({ + mixins : [ModalMixin], + + render() { + return ( + +
+ +
+
+ ) + } +}); + + +export default UnConsignModalButton; diff --git a/js/components/ascribe_modal/modal_unconsign_request.js b/js/components/ascribe_modal/modal_unconsign_request.js new file mode 100644 index 00000000..c74e637b --- /dev/null +++ b/js/components/ascribe_modal/modal_unconsign_request.js @@ -0,0 +1,43 @@ +import React from 'react'; +import Modal from 'react-bootstrap/lib/Modal'; +import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; +import ModalTrigger from 'react-bootstrap/lib/ModalTrigger'; +import Tooltip from 'react-bootstrap/lib/Tooltip'; + +import UnConsignRequestForm from '../ascribe_forms/form_unconsign_request' +import ModalMixin from '../../mixins/modal_mixin' + +let UnConsignRequestModalButton = React.createClass({ + render() { + return ( + Request to unconsign the artwork}> + }> +
+ UNCONSIGN REQUEST +
+
+
+ ) + } +}); + +let UnConsignRequestModal = React.createClass({ + mixins : [ModalMixin], + + render() { + return ( + +
+ +
+
+ ) + } +}); + + +export default UnConsignRequestModalButton; diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js new file mode 100644 index 00000000..a5d20389 --- /dev/null +++ b/js/components/ascribe_modal/modal_wrapper.js @@ -0,0 +1,64 @@ +import React from 'react'; +import ReactAddons from 'react/addons'; + +import Modal from 'react-bootstrap/lib/Modal'; +import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; +import ModalTrigger from 'react-bootstrap/lib/ModalTrigger'; +import Tooltip from 'react-bootstrap/lib/Tooltip'; + +import ModalMixin from '../../mixins/modal_mixin' + +let ModalWrapper = React.createClass({ + + render() { + return ( + {this.props.tooltip}}> + + {this.props.children} + + }> + {this.props.button} + + + ) + } +}); + +// +let ModalBody = React.createClass({ + mixins : [ModalMixin], + + handleSuccess(){ + this.props.handleSuccess(); + this.props.onRequestHide(); + }, + renderChildren() { + return ReactAddons.Children.map(this.props.children, (child, i) => { + return ReactAddons.addons.cloneWithProps(child, { + editions: this.props.editions, + currentUser: this.props.currentUser, + onRequestHide: this.onRequestHide, + handleSuccess: this.handleSuccess + }); + }); + }, + render() { + return ( + +
+ {this.renderChildren()} +
+
+ ) + } +}); + + +export default ModalWrapper; diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index 43330538..f229ef07 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -3,6 +3,9 @@ import React from 'react'; import EditionListStore from '../../stores/edition_list_store'; import EditionListActions from '../../actions/edition_list_actions'; +import UserActions from '../../actions/user_actions'; +import UserStore from '../../stores/user_store'; + import AclButton from '../acl_button'; import PieceListBulkModalSelectedEditionsWidget from './piece_list_bulk_modal_selected_editions_widget'; @@ -20,11 +23,14 @@ let PieceListBulkModal = React.createClass({ }, componentDidMount() { - EditionListStore.listen(this.onChange) + UserActions.fetchCurrentUser(); + EditionListStore.listen(this.onChange); + UserStore.listen(this.onChange); }, componentDidUnmount() { - EditionListStore.unlisten(this.onChange) + EditionListStore.unlisten(this.onChange); + UserStore.unlisten(this.onChange); }, filterForSelected(edition) { @@ -75,8 +81,13 @@ let PieceListBulkModal = React.createClass({ EditionListActions.clearAllEditionSelections(); }, + handleSuccess(){ + + }, + render() { let availableAcls = this.getAvailableAcls(); + let selectedEditions = this.fetchSelectedEditionList(); if(availableAcls.length > 0) { return ( @@ -97,10 +108,30 @@ let PieceListBulkModal = React.createClass({

- - - - + + + +
diff --git a/js/components/edition.js b/js/components/edition.js index 0ac97d33..ee305ca1 100644 --- a/js/components/edition.js +++ b/js/components/edition.js @@ -1,17 +1,24 @@ import React from 'react'; -import ImageViewer from './ascribe_media/image_viewer'; -import TransferModalButton from './ascribe_modal/modal_transfer'; -import ShareModalButton from './ascribe_modal/modal_share'; + +import ResourceViewer from './ascribe_media/resource_viewer'; + +import EditionActions from '../actions/edition_actions' +import AclButton from './acl_button' /** * This is the component that implements display-specific functionality */ let Edition = React.createClass({ render() { + let thumbnail = this.props.edition.thumbnail; + let mimetype = this.props.edition.digital_work.mime; + return (
- +
@@ -38,6 +45,9 @@ let EditionHeader = React.createClass({ }); let EditionDetails = React.createClass({ + handleSuccess(){ + EditionActions.fetchOne(this.props.edition.id); + }, render() { return (
@@ -46,8 +56,20 @@ let EditionDetails = React.createClass({
- - + +
); @@ -74,4 +96,3 @@ let EditionDetailProperty = React.createClass({ export default Edition; - diff --git a/js/components/edition_container.js b/js/components/edition_container.js index 77d28962..6887fd20 100644 --- a/js/components/edition_container.js +++ b/js/components/edition_container.js @@ -27,7 +27,6 @@ let EditionContainer = React.createClass({ UserActions.fetchCurrentUser(); UserStore.listen(this.onChange); }, - componentDidUnmount() { EditionStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index 9c17de7c..db846a72 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -3,14 +3,15 @@ import AppConstants from './application_constants'; let apiUrls = { 'ownership_shares_mail' : AppConstants.baseUrl + 'ownership/shares/mail/', 'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/', - 'user': AppConstants.baseUrl + 'users/', - 'pieces_list': AppConstants.baseUrl + 'pieces/', 'piece': AppConstants.baseUrl + 'pieces/${piece_id}', - 'edition': AppConstants.baseUrl + 'editions/${bitcoin_id}/', - 'editions_list': AppConstants.baseUrl + 'pieces/${piece_id}/editions/' + 'editions_list': AppConstants.baseUrl + 'pieces/${piece_id}/editions/', + 'ownership_loans' : AppConstants.baseUrl + 'ownership/loans/', + 'ownership_consigns' : AppConstants.baseUrl + 'ownership/consigns/', + 'ownership_unconsigns' : AppConstants.baseUrl + 'ownership/unconsigns/', + 'ownership_unconsigns_request' : AppConstants.baseUrl + 'ownership/unconsigns/request/' }; export default apiUrls; diff --git a/js/fetchers/ownership_fetcher.js b/js/fetchers/ownership_fetcher.js new file mode 100644 index 00000000..94050860 --- /dev/null +++ b/js/fetchers/ownership_fetcher.js @@ -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; diff --git a/js/mixins/alert_mixin.js b/js/mixins/alert_mixin.js index efabed0e..f52890c7 100644 --- a/js/mixins/alert_mixin.js +++ b/js/mixins/alert_mixin.js @@ -5,12 +5,15 @@ let AlertMixin = { setAlerts(errors){ let alerts = errors.map( function(error) { - let key = error + this.state.retry; - return ; + return ; }.bind(this) ); - this.setState({alerts: alerts, retry: this.state.retry + 1}); + this.setState({alerts: alerts}); + }, + clearAlerts(){ + this.setState({alerts: null}); } + }; export default AlertMixin; \ No newline at end of file diff --git a/js/mixins/form_mixin.js b/js/mixins/form_mixin.js index 73f50b62..6954ba4c 100644 --- a/js/mixins/form_mixin.js +++ b/js/mixins/form_mixin.js @@ -9,6 +9,7 @@ export const FormMixin = { return { submitted: false , status: null + , errors: [] } }, @@ -19,7 +20,6 @@ export const FormMixin = { .post(this.url(), { body: this.getFormData() }) .then(response => { this.props.onRequestHide(); }) .catch(this.handleError); - }, handleError(err){ @@ -27,19 +27,37 @@ export const FormMixin = { for (var input in errors){ if (this.refs && this.refs[input] && this.refs[input].state) { this.refs[input].setAlerts(errors[input]); + } else { + this.setState({errors: this.state.errors.concat(errors[input])}); } } - this.setState({submitted: false}); - } else { - this.setState({submitted: false, status: response.status}); } }, + getBitcoinIds(){ + return this.props.editions.map(function(edition){ + return edition.bitcoin_id + }) + }, + + getTitlesString(){ + return this.props.editions.map(function(edition){ + return '- \"' + edition.title + ', edition ' + edition.edition_number + '\"\n' + }) + }, + render(){ let alert = null; if (this.state.status >= 500){ alert = ; } + if (this.state.errors.length > 0){ + alert = this.state.errors.map( + function(error) { + return ; + }.bind(this) + ); + } return (
{alert} diff --git a/js/mixins/inject_in_head_mixin.js b/js/mixins/inject_in_head_mixin.js new file mode 100644 index 00000000..72ecdfe1 --- /dev/null +++ b/js/mixins/inject_in_head_mixin.js @@ -0,0 +1,57 @@ +let mapAttr = { + link: 'href', + source: 'src' +} + +let mapExt = { + js: 'source', + css: 'link' +} + + +let InjectInHeadMixin = { + /** + * Provide functions to inject `