mirror of
https://github.com/ascribe/onion.git
synced 2025-01-03 10:25:08 +01:00
settings
This commit is contained in:
parent
9b3c7e9f21
commit
c2d8b78136
@ -63,7 +63,7 @@ gulp.task('js:build', function() {
|
||||
bundle(false);
|
||||
});
|
||||
|
||||
gulp.task('serve', ['browser-sync', 'run-server', 'lint:watch', 'sass:build', 'sass:watch', 'copy'], function() {
|
||||
gulp.task('serve', ['browser-sync', 'run-server', 'sass:build', 'sass:watch', 'copy'], function() {
|
||||
bundle(true);
|
||||
});
|
||||
|
||||
|
54
js/components/ascribe_collapsible/collapsible_paragraph.js
Normal file
54
js/components/ascribe_collapsible/collapsible_paragraph.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
||||
const CollapsibleParagraph = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
title: React.PropTypes.string,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.array
|
||||
]),
|
||||
iconName: React.PropTypes.string
|
||||
},
|
||||
|
||||
mixins: [CollapsibleMixin],
|
||||
|
||||
getCollapsibleDOMNode(){
|
||||
return React.findDOMNode(this.refs.panel);
|
||||
},
|
||||
|
||||
getCollapsibleDimensionValue(){
|
||||
return React.findDOMNode(this.refs.panel).scrollHeight;
|
||||
},
|
||||
|
||||
handleToggle(e){
|
||||
e.preventDefault();
|
||||
this.setState({expanded: !this.state.expanded});
|
||||
},
|
||||
|
||||
render() {
|
||||
let styles = this.getCollapsibleClassSet();
|
||||
let text = this.isExpanded() ? '-' : '+';
|
||||
return (
|
||||
<div className="ascribe-detail-header">
|
||||
<div className="ascribe-edition-collapsible-wrapper">
|
||||
<div onClick={this.handleToggle}>
|
||||
<span>{text} {this.props.title} </span>
|
||||
</div>
|
||||
<div ref='panel' className={classNames(styles) + ' ascribe-edition-collapible-content'}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default CollapsibleParagraph;
|
137
js/components/ascribe_forms/form.js
Normal file
137
js/components/ascribe_forms/form.js
Normal file
@ -0,0 +1,137 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import ReactAddons from 'react/addons';
|
||||
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
import requests from '../../utils/requests';
|
||||
import AlertDismissable from './alert';
|
||||
|
||||
let Form = React.createClass({
|
||||
propTypes: {
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.array
|
||||
])
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
edited: false,
|
||||
submitted: false,
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
reset(){
|
||||
for (let ref in this.refs){
|
||||
if (typeof this.refs[ref].reset === 'function'){
|
||||
this.refs[ref].reset();
|
||||
}
|
||||
}
|
||||
},
|
||||
submit(event){
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
this.setState({submitted: true});
|
||||
this.clearErrors();
|
||||
let action = (this.httpVerb && this.httpVerb()) || 'post';
|
||||
this[action]();
|
||||
},
|
||||
post(){
|
||||
requests
|
||||
.post(this.props.url, { body: this.getFormData() })
|
||||
.then(this.handleSuccess)
|
||||
.catch(this.handleError);
|
||||
},
|
||||
|
||||
getFormData(){
|
||||
let data = {};
|
||||
for (let ref in this.refs){
|
||||
data[this.refs[ref].props.name] = this.refs[ref].state.value;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
handleChangeChild(){
|
||||
this.setState({edited: true});
|
||||
},
|
||||
handleSuccess(response){
|
||||
if ('handleSuccess' in this.props){
|
||||
this.props.handleSuccess(response);
|
||||
}
|
||||
for (var ref in this.refs){
|
||||
if ('handleSuccess' in this.refs[ref]){
|
||||
this.refs[ref].handleSuccess();
|
||||
}
|
||||
}
|
||||
this.setState({edited: false});
|
||||
},
|
||||
handleError(err){
|
||||
if (err.json) {
|
||||
for (var input in err.json.errors){
|
||||
if (this.refs && this.refs[input] && this.refs[input].state) {
|
||||
this.refs[input].setErrors( err.json.errors[input]);
|
||||
} else {
|
||||
this.setState({errors: this.state.errors.concat(err.json.errors[input])});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.setState({errors: ['Something went wrong, please try again later']});
|
||||
}
|
||||
this.setState({submitted: false});
|
||||
},
|
||||
clearErrors(){
|
||||
for (var ref in this.refs){
|
||||
if ('clearError' in this.refs[ref]){
|
||||
this.refs[ref].clearErrors();
|
||||
}
|
||||
}
|
||||
this.setState({errors: []});
|
||||
},
|
||||
getButtons() {
|
||||
let buttons = null;
|
||||
if (this.state.edited){
|
||||
buttons = (
|
||||
<div className="pull-right">
|
||||
<Button className="ascribe-btn" type="submit">Save</Button>
|
||||
<Button className="ascribe-btn" onClick={this.reset}>Cancel</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
return buttons;
|
||||
},
|
||||
getErrors() {
|
||||
let errors = null;
|
||||
if (this.state.errors.length > 0){
|
||||
errors = this.state.errors.map((error) => {
|
||||
return <AlertDismissable error={error} key={error}/>;
|
||||
});
|
||||
}
|
||||
return errors;
|
||||
},
|
||||
renderChildren() {
|
||||
return ReactAddons.Children.map(this.props.children, (child) => {
|
||||
return ReactAddons.addons.cloneWithProps(child, {
|
||||
handleChange: this.handleChangeChild,
|
||||
ref: child.props.name
|
||||
});
|
||||
});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<form role="form" onSubmit={this.submit}>
|
||||
{this.getErrors()}
|
||||
{this.renderChildren()}
|
||||
{this.getButtons()}
|
||||
</form>
|
||||
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default Form;
|
107
js/components/ascribe_forms/property.js
Normal file
107
js/components/ascribe_forms/property.js
Normal file
@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import ReactAddons from 'react/addons';
|
||||
|
||||
let Property = React.createClass({
|
||||
propTypes: {
|
||||
editable: React.PropTypes.bool,
|
||||
label: React.PropTypes.string,
|
||||
value: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.element
|
||||
]),
|
||||
handleChange: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
editable: true
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
initialValue: null,
|
||||
value: null,
|
||||
isFocused: false,
|
||||
errors: null
|
||||
};
|
||||
},
|
||||
componentWillReceiveProps(){
|
||||
this.setState({
|
||||
initialValue: this.refs.input.getDOMNode().defaultValue,
|
||||
value: this.refs.input.getDOMNode().value
|
||||
});
|
||||
},
|
||||
reset(){
|
||||
this.setState({value: this.state.initialValue});
|
||||
this.refs.input.getDOMNode().value = this.state.initialValue;
|
||||
},
|
||||
|
||||
handleChange(event) {
|
||||
this.props.handleChange(event);
|
||||
this.setState({value: event.target.value});
|
||||
},
|
||||
handleFocus() {
|
||||
this.refs.input.getDOMNode().focus();
|
||||
this.setState({
|
||||
isFocused: true
|
||||
});
|
||||
},
|
||||
handleSuccess(){
|
||||
this.setState({
|
||||
isFocused: false,
|
||||
errors: null
|
||||
});
|
||||
},
|
||||
setErrors(errors){
|
||||
this.setState({
|
||||
errors: errors.map((error) => {
|
||||
return <span className="pull-right" key={error}>{error}</span>;
|
||||
})
|
||||
});
|
||||
},
|
||||
clearErrors(){
|
||||
this.setState({errors: null});
|
||||
},
|
||||
getClassName() {
|
||||
if(!this.props.editable){
|
||||
return 'is-fixed';
|
||||
}
|
||||
if (this.state.errors){
|
||||
return 'is-error';
|
||||
}
|
||||
if(this.state.isFocused) {
|
||||
return 'is-focused';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
renderChildren() {
|
||||
return ReactAddons.Children.map(this.props.children, (child) => {
|
||||
return ReactAddons.addons.cloneWithProps(child, {
|
||||
value: this.state.value,
|
||||
onChange: this.handleChange,
|
||||
disabled: !this.props.editable,
|
||||
ref: 'input'
|
||||
});
|
||||
});
|
||||
},
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={'ascribe-settings-wrapper ' + this.getClassName()}
|
||||
onClick={this.handleFocus}>
|
||||
<div className="ascribe-settings-property">
|
||||
{this.state.errors}
|
||||
<span>{ this.props.label}</span>
|
||||
{this.renderChildren()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Property;
|
@ -3,8 +3,6 @@
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin';
|
||||
|
||||
import UserActions from '../actions/user_actions';
|
||||
import UserStore from '../stores/user_store';
|
||||
|
||||
@ -14,13 +12,29 @@ import WalletSettingsStore from '../stores/wallet_settings_store';
|
||||
import GlobalNotificationModel from '../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../actions/global_notification_actions';
|
||||
|
||||
import Input from 'react-bootstrap/lib/Input';
|
||||
import CollapsibleParagraph from './ascribe_collapsible/collapsible_paragraph';
|
||||
import Form from './ascribe_forms/form';
|
||||
import Property from './ascribe_forms/property';
|
||||
|
||||
import apiUrls from '../constants/api_urls';
|
||||
import AppConstants from '../constants/application_constants';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
let SettingsContainer = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<AccountSettings />
|
||||
<BitcoinWalletSettings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let AccountSettings = React.createClass({
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
},
|
||||
@ -39,186 +53,55 @@ let SettingsContainer = React.createClass({
|
||||
},
|
||||
|
||||
handleSuccess(){
|
||||
this.transitionTo('pieces');
|
||||
let notification = new GlobalNotificationModel('password succesfully updated', 'success', 10000);
|
||||
UserActions.fetchCurrentUser();
|
||||
let notification = new GlobalNotificationModel('username succesfully updated', 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
if (this.state.currentUser.username) {
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title="Account"
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
<AccountSettings
|
||||
currentUser={this.state.currentUser} />
|
||||
<Form
|
||||
url={apiUrls.users_username}
|
||||
handleSuccess={this.handleSuccess}>
|
||||
<Property
|
||||
name='username'
|
||||
label="Username">
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={this.state.currentUser.username}
|
||||
placeholder="Enter your username"
|
||||
required/>
|
||||
</Property>
|
||||
<Property
|
||||
name='email'
|
||||
label="Email"
|
||||
editable={false}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={this.state.currentUser.email}
|
||||
placeholder="Enter your username"
|
||||
required/>
|
||||
</Property>
|
||||
</Form>
|
||||
</CollapsibleParagraph>
|
||||
|
||||
<CollapsibleParagraph
|
||||
title="Bitcoin Wallet"
|
||||
show={true}>
|
||||
<BitcoinWalletSettings
|
||||
currentUser={this.state.currentUser} />
|
||||
</CollapsibleParagraph>
|
||||
|
||||
<CollapsibleParagraph
|
||||
title="Contracts"
|
||||
show={true}>
|
||||
<ContractSettings
|
||||
currentUser={this.state.currentUser} />
|
||||
</CollapsibleParagraph>
|
||||
|
||||
<CollapsibleParagraph
|
||||
title="API"
|
||||
show={true}>
|
||||
<APISettings
|
||||
currentUser={this.state.currentUser} />
|
||||
</CollapsibleParagraph>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const CollapsibleParagraph = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
title: React.PropTypes.string,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.array
|
||||
]),
|
||||
iconName: React.PropTypes.string
|
||||
},
|
||||
|
||||
mixins: [CollapsibleMixin],
|
||||
|
||||
getCollapsibleDOMNode(){
|
||||
return React.findDOMNode(this.refs.panel);
|
||||
},
|
||||
|
||||
getCollapsibleDimensionValue(){
|
||||
return React.findDOMNode(this.refs.panel).scrollHeight;
|
||||
},
|
||||
|
||||
onHandleToggle(e){
|
||||
e.preventDefault();
|
||||
this.setState({expanded: !this.state.expanded});
|
||||
},
|
||||
|
||||
render() {
|
||||
let styles = this.getCollapsibleClassSet();
|
||||
let text = this.isExpanded() ? '-' : '+';
|
||||
|
||||
return (
|
||||
<div className="ascribe-detail-header">
|
||||
<div className="ascribe-edition-collapsible-wrapper">
|
||||
<div onClick={this.onHandleToggle}>
|
||||
<span>{text} {this.props.title} </span>
|
||||
</div>
|
||||
<div ref='panel' className={classNames(styles) + ' ascribe-edition-collapible-content'}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let SettingsProperty = React.createClass({
|
||||
|
||||
|
||||
propTypes: {
|
||||
label: React.PropTypes.string,
|
||||
value: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.element
|
||||
]),
|
||||
separator: React.PropTypes.string,
|
||||
labelClassName: React.PropTypes.string,
|
||||
valueClassName: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
separator: ':',
|
||||
labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-1',
|
||||
valueClassName: 'col-xs-9 col-sm-9 col-md-10 col-lg-11'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
value: '',
|
||||
isFocused: false
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps() {
|
||||
this.setState({
|
||||
value: this.props.value
|
||||
});
|
||||
},
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({value: event.target.value});
|
||||
},
|
||||
|
||||
handleFocus() {
|
||||
this.refs.input.getDOMNode().focus();
|
||||
this.setState({
|
||||
isFocused: !this.state.isFocused
|
||||
});
|
||||
},
|
||||
|
||||
getClassName() {
|
||||
if(this.state.isFocused) {
|
||||
return 'is-focused';
|
||||
} else {
|
||||
return '';
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={'ascribe-settings-wrapper ' + this.getClassName()}
|
||||
onClick={this.handleFocus}>
|
||||
<div className="ascribe-settings-property">
|
||||
<span>{ this.props.label}</span>
|
||||
<input
|
||||
ref="input"
|
||||
type="text"
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let AccountSettings = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
currentUser: React.PropTypes.object
|
||||
},
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsProperty key={1} label="Username" value={this.props.currentUser.username}/>
|
||||
<SettingsProperty key={2} label="Email" value={this.props.currentUser.email}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let BitcoinWalletSettings = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
currentUser: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return WalletSettingsStore.getState();
|
||||
},
|
||||
@ -235,14 +118,44 @@ let BitcoinWalletSettings = React.createClass({
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsProperty label="Bitcoin public key" value={this.state.walletSettings.btc_public_key}/>
|
||||
<SettingsProperty label="Root Address" value={this.state.walletSettings.btc_root_address}/>
|
||||
</div>
|
||||
);
|
||||
render() {
|
||||
if (this.state.walletSettings.btc_public_key) {
|
||||
return (
|
||||
<CollapsibleParagraph
|
||||
title="Crypto Wallet"
|
||||
show={true}
|
||||
defaultExpanded={true}>
|
||||
<Form
|
||||
url={apiUrls.users_username}
|
||||
handleSuccess={this.handleSuccess}>
|
||||
<Property
|
||||
name='btc_public_key'
|
||||
label="Bitcoin public key"
|
||||
editable={false}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={this.state.walletSettings.btc_public_key}
|
||||
placeholder="Enter your username"
|
||||
required/>
|
||||
</Property>
|
||||
<Property
|
||||
name='btc_root_address'
|
||||
label="Root Address"
|
||||
editable={false}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={this.state.walletSettings.btc_root_address}/>
|
||||
</Property>
|
||||
</Form>
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -29,6 +29,7 @@ let apiUrls = {
|
||||
'users_password_reset': AppConstants.apiEndpoint + 'users/reset_password/',
|
||||
'users_password_reset_request': AppConstants.apiEndpoint + 'users/request_reset_password/',
|
||||
'users_signup': AppConstants.apiEndpoint + 'users/',
|
||||
'users_username': AppConstants.apiEndpoint + 'users/username/',
|
||||
'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/'
|
||||
};
|
||||
|
||||
|
@ -13,10 +13,42 @@
|
||||
}
|
||||
|
||||
.is-focused {
|
||||
background-color: rgba(2, 182, 163, 0.1);
|
||||
background-color: rgba(2, 182, 163, 0.05);
|
||||
border-left: 3px solid rgba(2, 182, 163, 1);
|
||||
}
|
||||
|
||||
.is-error {
|
||||
background-color: rgba(169, 68, 66, 0.05);
|
||||
border-left: 3px solid rgba(169, 68, 66, 1);
|
||||
div {
|
||||
span {
|
||||
color: rgba(169, 68, 66, 1);
|
||||
font-size: 0.9em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
span {
|
||||
|
||||
}
|
||||
input {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-fixed {
|
||||
cursor: default;
|
||||
div {
|
||||
cursor: default;
|
||||
span {
|
||||
cursor: default;
|
||||
}
|
||||
input {
|
||||
cursor: default;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ascribe-settings-property {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
@ -31,7 +63,7 @@
|
||||
span {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
font-size: 0.9em;
|
||||
color: rgba(0,0,0,.7);
|
||||
}
|
||||
|
||||
@ -40,7 +72,7 @@
|
||||
font-weight: 400;
|
||||
font-size: 1.1em;
|
||||
width:100%;
|
||||
margin-top: .7em;
|
||||
margin-top: .5em;
|
||||
border: 0;
|
||||
background-color: rgba(0,0,0,0);
|
||||
color: #38BAAD;
|
||||
|
Loading…
Reference in New Issue
Block a user