form framework

- inputtext, area, btn
 - alertmixin
 - formmixin
 - 500
 - transfer/share
This commit is contained in:
ddejongh 2015-05-29 01:54:56 +02:00
parent 0a4dae850b
commit c3ea9aecbe
14 changed files with 373 additions and 116 deletions

View File

@ -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 (
<Alert bsStyle='danger' onDismiss={this.hide}>
{this.props.error}
</Alert>
);
}
return (
<span />
);
}
});
export default AlertDismissable;

View File

@ -0,0 +1,21 @@
import React from 'react';
let ButtonSubmitOrClose = React.createClass({
render() {
if (this.props.submitted){
return (
<div className="modal-footer">
Loading
</div>
)
}
return (
<div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv">{this.props.text}</button>
<button className="btn btn-ascribe-inv" onClick={this.props.onClose}>CLOSE</button>
</div>
)
}
});
export default ButtonSubmitOrClose;

View File

@ -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 (
<form id="share_modal_content" role="form" onSubmit={this.submit}>
{this.renderTextInput("share_emails", "email", "Comma separated emails", "required")}
{this.renderTextArea("share_message", this.renderMessage(), "")}
<div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv">SHARE</button>
<button className="btn btn-ascribe-inv" onClick={this.props.onRequestHide}>CLOSE</button>
</div>
<form id="share_modal_content" role="form" key="share_modal_content" onSubmit={this.submit}>
<InputText
ref="share_emails"
placeHolder="Comma separated emails"
required="required"
type="text"
submitted={this.state.submitted}/>
<InputTextArea
ref="share_message"
defaultValue={message}
required=""
/>
<ButtonSubmitOrClose
text="SHARE"
onClose={this.props.onRequestHide}
submitted={this.state.submitted} />
</form>
);
}

View File

@ -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 (
<form id="transfer_modal_content" role="form" onSubmit={this.submit}>
<input className="invisible" type="email" name="fake_transferee"/>
<input className="invisible" type="password" name="fake_password"/>
<InputText
ref="transferee"
placeHolder="Transferee email"
required="required"
type="email"
submitted={this.state.submitted}/>
<InputTextArea
ref="transfer_message"
defaultValue={message}
required=""
/>
<InputText
ref="password"
placeHolder="Password"
required="required"
type="password"
submitted={this.state.submitted}/>
<div>
Make sure that display instructions and technology details are correct.
They cannot be edited after the transfer.
</div>
<ButtonSubmitOrClose
text="TRANSFER"
onClose={this.props.onRequestHide}
submitted={this.state.submitted} />
</form>
);
}
});
export default TransferForm;

View File

@ -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 (
<div className="form-group">
{alerts}
<input className={className}
placeholder={this.props.placeHolder}
required={this.props.required}
type={this.props.type}
onChange={this.handleChange}/>
</div>
);
}
});
export default InputText;

View File

@ -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 (
<div className="form-group">
{alerts}
<textarea className={className}
defaultValue={this.props.defaultValue}
required={this.props.required}
onChange={this.handleChange}></textarea>
</div>
);
}
});
export default InputTextArea;

View File

@ -13,7 +13,7 @@ let ShareModalButton = React.createClass({
return (
<OverlayTrigger delay={500} placement="left" overlay={<Tooltip>Share the artwork</Tooltip>}>
<ModalTrigger modal={<ShareModal edition={this.props.edition}
currentUser={this.props.currentUser}/>}>
currentUser={this.props.currentUser}/>}>
<div className="btn btn-ascribe-inv btn-glyph-ascribe">
<span className="glyph-ascribe-share2"></span>
</div>
@ -25,7 +25,8 @@ let ShareModalButton = React.createClass({
let ShareModal = React.createClass({
onRequestHide(e){
e.preventDefault();
if (e)
e.preventDefault();
this.props.onRequestHide();
},
render() {

View File

@ -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 (
<OverlayTrigger delay={500} placement="left"
overlay={<Tooltip>Transfer the ownership of the artwork</Tooltip>}>
<ModalTrigger modal={<TransferModal edition={this.props.edition}
currentUser={this.props.currentUser}/>}>
<div className="btn btn-ascribe-inv">
TRANSFER
</div>
</ModalTrigger>
</OverlayTrigger>
)
}
});
let TransferModal = React.createClass({
onRequestHide(e){
e.preventDefault();
this.props.onRequestHide();
},
render() {
return (
<Modal {...this.props} title="Transfer artwork">
<div className="modal-body">
<TransferForm edition={this.props.edition}
currentUser={this.props.currentUser}
onRequestHide={this.onRequestHide}/>
</div>
</Modal>
)
}
});
export default TransferModalButton;

View File

@ -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({
<EditionDetailProperty label="id" value={ this.props.edition.bitcoin_id } />
<EditionDetailProperty label="owner" value={ this.props.edition.owner } />
<br/>
<TransferModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
<ShareModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
<hr/>
</div>

8
js/constants/api_urls.js Normal file
View File

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

View File

@ -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;
};
export default constants;

21
js/mixins/alert_mixin.js Normal file
View File

@ -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 <AlertDismissable error={error} key={key}/>;
}.bind(this)
);
this.setState({alerts: alerts, retry: this.state.retry + 1});
},
hideAlerts(){
for (alert in this.state.alerts){
alert.hide();
}
}
};
export default AlertMixin;

View File

@ -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 <AlertDismissable error={error} key={key} />;
}.bind(this)
)
}
return <span />
},
renderTextInput(id, type, placeHolder, required) {
return (
<div className="form-group">
{this.renderAlert(id)}
<input className="form-control input-text-ascribe" name={id} ref={id}
placeholder={placeHolder} required={required} type={type} />
</div>
)
},
renderTextArea(id, placeHolder, required) {
return (
<div className="form-group">
{this.renderAlert(id)}
<textarea className="form-control input-text-ascribe textarea-ascribe-message" name={id} ref={id}
defaultValue={placeHolder} required={required}></textarea>
</div>
)
}
};
let AlertDismissable = React.createClass({
getInitialState() {
return {
alertVisible: true
};
},
show() {
this.setState({alertVisible: true});
},
hide() {
this.setState({alertVisible: false});
},
render() {
if (this.state.alertVisible) {
return (
<Alert bsStyle='danger' onDismiss={this.hide}>
{this.props.error}
</Alert>
);
}
return (
<span />
);
}
});
export default FormMixin;

67
js/mixins/form_mixin.js Normal file
View File

@ -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 = <AlertDismissable error="Something went wrong, please try again later"/>;
}
return (
<div>
{alert}
{this.renderForm()}
</div>
)
}
};
export default FormMixin;