diff --git a/js/components/ascribe_forms/alert.js b/js/components/ascribe_forms/alert.js new file mode 100644 index 00000000..72ced310 --- /dev/null +++ b/js/components/ascribe_forms/alert.js @@ -0,0 +1,31 @@ +import React from 'react'; +import Alert from 'react-bootstrap/lib/Alert'; + +let AlertDismissable = React.createClass({ + getInitialState() { + return { + alertVisible: true + }; + }, + show() { + this.setState({alertVisible: true}); + }, + hide() { + this.setState({alertVisible: false}); + }, + render() { + if (this.state.alertVisible) { + let key = this.props.error; + return ( + + {this.props.error} + + ); + } + return ( + + ); + } +}); + +export default AlertDismissable; \ No newline at end of file diff --git a/js/components/ascribe_forms/button_submit_close.js b/js/components/ascribe_forms/button_submit_close.js new file mode 100644 index 00000000..60091f2f --- /dev/null +++ b/js/components/ascribe_forms/button_submit_close.js @@ -0,0 +1,21 @@ +import React from 'react'; + +let ButtonSubmitOrClose = React.createClass({ + render() { + if (this.props.submitted){ + return ( +
+ Loading +
+ ) + } + return ( +
+ + +
+ ) + } +}); + +export default ButtonSubmitOrClose; diff --git a/js/components/ascribe_forms/form_share_email.js b/js/components/ascribe_forms/form_share_email.js index d78c02ec..dd61bc7b 100644 --- a/js/components/ascribe_forms/form_share_email.js +++ b/js/components/ascribe_forms/form_share_email.js @@ -2,53 +2,49 @@ import fetch from 'isomorphic-fetch'; import React from 'react'; -import AppConstants from '../../constants/application_constants'; -import FetchApiUtils from '../../utils/fetch_api_utils'; -import FormMixin from '../../mixins/form_alert_mixin'; +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 ShareForm = React.createClass({ mixins: [FormMixin], - submit(e) { - e.preventDefault(); - fetch(AppConstants.baseUrl + 'ownership/shares/mail/', { - method: 'post', - headers: { - 'Authorization': 'Basic ' + AppConstants.debugCredentialBase64, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(this.getFormData()) - }) - .then( - (response) => this.handleResponse(response) - ); + url() { + return ApiUrls.ownership_shares_mail }, getFormData() { return { bitcoin_id: this.props.edition.bitcoin_id, - share_emails: this.refs.share_emails.getDOMNode().value, - share_message: this.refs.share_message.getDOMNode().value + share_emails: this.refs.share_emails.state.value, + share_message: this.refs.share_message.state.value } }, - renderMessage() { - return "" + - "Hi,\n" + - "\n" + - "I am sharing \"" + this.props.edition.title + "\" with you.\n" + - "\n" + - "Truly yours,\n" + - this.props.currentUser.username; - }, - render() { + renderForm() { + let message = "Hi,\n" + + "\n" + + "I am sharing \"" + this.props.edition.title + "\" with you.\n" + + "\n" + + "Truly yours,\n" + + this.props.currentUser.username; return ( -
- {this.renderTextInput("share_emails", "email", "Comma separated emails", "required")} - {this.renderTextArea("share_message", this.renderMessage(), "")} -
- - -
+ + + + ); } diff --git a/js/components/ascribe_forms/form_transfer.js b/js/components/ascribe_forms/form_transfer.js new file mode 100644 index 00000000..28e86db3 --- /dev/null +++ b/js/components/ascribe_forms/form_transfer.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 TransferForm = React.createClass({ + mixins: [FormMixin], + + url() { + return ApiUrls.ownership_transfers + }, + getFormData() { + return { + bitcoin_id: this.props.edition.bitcoin_id, + transferee: this.refs.transferee.state.value, + transfer_message: this.refs.transfer_message.state.value, + password: this.refs.password.state.value + } + }, + renderForm() { + let message = "Hi,\n" + + "\n" + + "I transfer ownership of \"" + this.props.edition.title + "\" to you.\n" + + "\n" + + "Truly yours,\n" + + this.props.currentUser.username; + return ( +
+ + + + + +
+ Make sure that display instructions and technology details are correct. + They cannot be edited after the transfer. +
+ + + ); + } +}); + +export default TransferForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_text.js b/js/components/ascribe_forms/input_text.js new file mode 100644 index 00000000..a65bcc8f --- /dev/null +++ b/js/components/ascribe_forms/input_text.js @@ -0,0 +1,35 @@ +import React from 'react'; + +import AlertMixin from '../../mixins/alert_mixin' + +let InputText = React.createClass({ + + mixins : [AlertMixin], + + getInitialState() { + return {value: null, + alerts: null, // needed in AlertMixin + retry: 0 // needed in AlertMixin for generating unique alerts + }; + }, + handleChange(event) { + this.setState({value: event.target.value}); + }, + render() { + let className = "form-control input-text-ascribe"; + let alerts = (this.props.submitted) ? null : this.state.alerts; + return ( +
+ {alerts} + +
+ ); + + } +}); + +export default InputText; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_textarea.js b/js/components/ascribe_forms/input_textarea.js new file mode 100644 index 00000000..b5f64b66 --- /dev/null +++ b/js/components/ascribe_forms/input_textarea.js @@ -0,0 +1,35 @@ +import React from 'react'; + +import AlertMixin from '../../mixins/alert_mixin' + +let InputTextArea = React.createClass({ + + mixins : [AlertMixin], + + getInitialState() { + return {value: this.props.defaultValue, + alerts: null, // needed in AlertMixin + retry: 0 // needed in AlertMixin for generating unique alerts + }; + }, + handleChange(event) { + this.setState({value: event.target.value}); + }, + render() { + let className = "form-control input-text-ascribe textarea-ascribe-message"; + + let alerts = (this.props.submitted) ? null : this.state.alerts; + return ( +
+ {alerts} + +
+ ); + + } +}); + +export default InputTextArea; \ No newline at end of file diff --git a/js/components/ascribe_modal/modal_share.js b/js/components/ascribe_modal/modal_share.js index 4cc05d5a..176541ee 100644 --- a/js/components/ascribe_modal/modal_share.js +++ b/js/components/ascribe_modal/modal_share.js @@ -13,7 +13,7 @@ let ShareModalButton = React.createClass({ return ( Share the artwork}> }> + currentUser={this.props.currentUser}/>}>
@@ -25,7 +25,8 @@ let ShareModalButton = React.createClass({ let ShareModal = React.createClass({ onRequestHide(e){ - e.preventDefault(); + if (e) + e.preventDefault(); this.props.onRequestHide(); }, render() { diff --git a/js/components/ascribe_modal/modal_transfer.js b/js/components/ascribe_modal/modal_transfer.js new file mode 100644 index 00000000..b57064ff --- /dev/null +++ b/js/components/ascribe_modal/modal_transfer.js @@ -0,0 +1,45 @@ +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/edition.js b/js/components/edition.js index 938293e8..ffeb9b25 100644 --- a/js/components/edition.js +++ b/js/components/edition.js @@ -1,6 +1,7 @@ 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'; /** @@ -46,6 +47,7 @@ let EditionDetails = React.createClass({
+
diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js new file mode 100644 index 00000000..f0c7f9b6 --- /dev/null +++ b/js/constants/api_urls.js @@ -0,0 +1,8 @@ +import AppConstants from './application_constants'; + +let apiUrls = { + 'ownership_shares_mail' : AppConstants.baseUrl + 'ownership/shares/mail/', + 'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/' +}; + +export default apiUrls; \ No newline at end of file diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 8efb7196..55b9d5f0 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -1,6 +1,8 @@ let constants = { - 'baseUrl': 'http://staging.ascribe.io/api/', + 'baseUrl': 'http://localhost:8000/api/', + //'baseUrl': 'http://staging.ascribe.io/api/', 'debugCredentialBase64': 'ZGltaUBtYWlsaW5hdG9yLmNvbTowMDAwMDAwMDAw' // dimi@mailinator:0000000000 -}; -export default constants; \ No newline at end of file +}; +export default constants; + diff --git a/js/mixins/alert_mixin.js b/js/mixins/alert_mixin.js new file mode 100644 index 00000000..478c44db --- /dev/null +++ b/js/mixins/alert_mixin.js @@ -0,0 +1,21 @@ +import React from 'react'; +import AlertDismissable from '../components/ascribe_forms/alert'; + +let AlertMixin = { + setAlerts(errors){ + let alerts = errors.map( + function(error) { + let key = error + this.state.retry; + return ; + }.bind(this) + ); + this.setState({alerts: alerts, retry: this.state.retry + 1}); + }, + hideAlerts(){ + for (alert in this.state.alerts){ + alert.hide(); + } + } +}; + +export default AlertMixin; \ No newline at end of file diff --git a/js/mixins/form_alert_mixin.js b/js/mixins/form_alert_mixin.js deleted file mode 100644 index ff9a5d52..00000000 --- a/js/mixins/form_alert_mixin.js +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import Alert from 'react-bootstrap/lib/Alert'; - -let FormMixin = { - getInitialState() { - return {errors: null, - retry: 0 // used for unique keying - } - }, - handleResponse(response){ - if (response.status >= 200 && response.status < 300) - return response; - this.handleError(response); - }, - handleError(response){ - response.json().then((response) => this.setState({errors: response.errors, - retry: this.state.retry + 1})) - }, - renderAlert(id){ - if (this.state.errors && id in this.state.errors) { - return this.state.errors[id].map(function(error) { - let key = error + this.state.retry; - return ; - }.bind(this) - ) - } - return - }, - renderTextInput(id, type, placeHolder, required) { - return ( -
- {this.renderAlert(id)} - -
- ) - }, - renderTextArea(id, placeHolder, required) { - return ( -
- {this.renderAlert(id)} - -
- ) - } -}; - -let AlertDismissable = React.createClass({ - getInitialState() { - return { - alertVisible: true - }; - }, - show() { - this.setState({alertVisible: true}); - }, - hide() { - this.setState({alertVisible: false}); - }, - render() { - if (this.state.alertVisible) { - return ( - - {this.props.error} - - ); - } - return ( - - ); - } -}); - -export default FormMixin; \ No newline at end of file diff --git a/js/mixins/form_mixin.js b/js/mixins/form_mixin.js new file mode 100644 index 00000000..6bbf5efa --- /dev/null +++ b/js/mixins/form_mixin.js @@ -0,0 +1,67 @@ +import React from 'react'; + +import AppConstants from '../constants/application_constants' +import AlertDismissable from '../components/ascribe_forms/alert' + +export const FormMixin = { + getInitialState() { + return { + submitted: false + , status: null + } + }, + submit(e) { + e.preventDefault(); + this.setState({submitted: true}); + fetch(this.url(), { + method: 'post', + headers: { + 'Authorization': 'Basic ' + AppConstants.debugCredentialBase64, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(this.getFormData()) + }) + .then( + (response) => this.handleResponse(response) + ); + }, + handleResponse(response){ + if (response.status >= 200 && response.status < 300){ + this.props.onRequestHide(); + } + else if (response.status >= 400 && response.status < 500) { + this.handleError(response); + } + else { + this.setState({submitted: false, status: response.status}); + } + }, + handleError(response){ + response.json().then((response) => this.dispatchErrors(response.errors)); + + }, + dispatchErrors(errors){ + for (var input in errors){ + if (this.refs && this.refs[input] && this.refs[input].state){ + this.refs[input].setAlerts(errors[input]); + } + } + this.setState({submitted: false}); + }, + render(){ + let alert = null; + if (this.state.status >= 500){ + alert = ; + } + return ( +
+ {alert} + {this.renderForm()} +
+ ) + } +}; + +export default FormMixin; +