diff --git a/gulpfile.js b/gulpfile.js
index a055e765..5ab28046 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -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);
});
diff --git a/js/components/ascribe_collapsible/collapsible_paragraph.js b/js/components/ascribe_collapsible/collapsible_paragraph.js
new file mode 100644
index 00000000..5156c306
--- /dev/null
+++ b/js/components/ascribe_collapsible/collapsible_paragraph.js
@@ -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 (
+
+
+
+ {text} {this.props.title}
+
+
+ {this.props.children}
+
+
+
+ );
+ }
+});
+
+export default CollapsibleParagraph;
\ No newline at end of file
diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js
new file mode 100644
index 00000000..8d25531b
--- /dev/null
+++ b/js/components/ascribe_forms/form.js
@@ -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 = (
+
+ Save
+ Cancel
+
+ );
+
+ }
+ return buttons;
+ },
+ getErrors() {
+ let errors = null;
+ if (this.state.errors.length > 0){
+ errors = this.state.errors.map((error) => {
+ return ;
+ });
+ }
+ 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 (
+
+
+ );
+ }
+});
+
+
+export default Form;
\ No newline at end of file
diff --git a/js/components/ascribe_forms/property.js b/js/components/ascribe_forms/property.js
new file mode 100644
index 00000000..7e27c1fe
--- /dev/null
+++ b/js/components/ascribe_forms/property.js
@@ -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 {error} ;
+ })
+ });
+ },
+ 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 (
+
+
+ {this.state.errors}
+ { this.props.label}
+ {this.renderChildren()}
+
+
+ );
+ }
+});
+
+export default Property;
\ No newline at end of file
diff --git a/js/components/settings_container.js b/js/components/settings_container.js
index 7596fde0..b07c69c0 100644
--- a/js/components/settings_container.js
+++ b/js/components/settings_container.js
@@ -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 (
+
+ );
+ }
+});
+
+
+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 (
-
+ if (this.state.currentUser.username) {
+ return (
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-});
-
-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 (
-
-
-
- {text} {this.props.title}
-
-
- {this.props.children}
-
-
-
- );
- }
-});
-
-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 (
+
+ );
}
- },
-
- render() {
- return (
-
-
- { this.props.label}
-
-
-
- );
}
});
-let AccountSettings = React.createClass({
- propTypes: {
- currentUser: React.PropTypes.object
- },
-
- render() {
-
- return (
-
-
-
-
- );
- }
-});
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 (
-
-
-
-
- );
+ render() {
+ if (this.state.walletSettings.btc_public_key) {
+ return (
+
+
+
+ );
+ }
+ else {
+ return (
+
+ );
+ }
}
});
diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js
index e7f9c174..461ed453 100644
--- a/js/constants/api_urls.js
+++ b/js/constants/api_urls.js
@@ -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/'
};
diff --git a/sass/ascribe_settings.scss b/sass/ascribe_settings.scss
index abdabf03..c3ca7445 100644
--- a/sass/ascribe_settings.scss
+++ b/sass/ascribe_settings.scss
@@ -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;