1
0
mirror of https://github.com/ascribe/onion.git synced 2025-01-03 10:25:08 +01:00
This commit is contained in:
ddejongh 2015-06-18 19:03:03 +02:00
parent 9b3c7e9f21
commit c2d8b78136
7 changed files with 423 additions and 179 deletions

View File

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

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

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

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

View File

@ -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'} />
);
}
}
});

View File

@ -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/'
};

View File

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