Move to using npm/browserify to package everything

This means it now requires the Javascript to be compiled before it can
be run. This move also required a massive reorganization and lots of
debugging/fixups to make everything work properly again.
This commit is contained in:
Aaron Lindsay 2016-02-12 20:36:59 -05:00
parent 6856d617ec
commit 2621f64cc7
23 changed files with 790 additions and 1799 deletions

13
README Normal file
View File

@ -0,0 +1,13 @@
MoneyGo README
Installation
============
First, install npm in your distribution:
$ sudo pacman -S npm
Install browserify globally:
$ sudo npm install -g browserify
Next, install browserify, babel, react, react-bootstrap, react-widgets, and globalize in our directory using npm:
$ cd static && npm install browserify react react-dom react-addons-update react-bootstrap react-widgets babelify babel-preset-react globalize cldr-data

37
static/AccountCombobox.js Normal file
View File

@ -0,0 +1,37 @@
var React = require('react');
var Combobox = require('react-widgets').Combobox;
module.exports = React.createClass({
displayName: "AccountCombobox",
getDefaultProps: function() {
return {
includeRoot: true,
rootName: "New Top-level Account"
};
},
handleAccountChange: function(account) {
if (this.props.onChange != null &&
account.hasOwnProperty('AccountId') &&
(this.props.account_map.hasOwnProperty([account.AccountId]) ||
account.AccountId == -1)) {
this.props.onChange(account)
}
},
render: function() {
var accounts = getAccountDisplayList(this.props.accounts, this.props.includeRoot, this.props.rootName);
var className = "";
if (this.props.className)
className = this.props.className;
return (
<Combobox
data={accounts}
valueField='AccountId'
textField='Name'
defaultValue={this.props.value}
onChange={this.handleAccountChange}
ref="account"
className={className} />
);
}
});

View File

@ -1,28 +1,35 @@
// Import all the objects we want to use from ReactBootstrap
var React = require('react');
var ReactDOM = require('react-dom');
var react_update = require('react-addons-update');
var ReactBootstrap = require('react-bootstrap');
var Alert = ReactBootstrap.Alert;
var Modal = ReactBootstrap.Modal;
var Pagination = ReactBootstrap.Pagination;
var Label = ReactBootstrap.Label;
var Table = ReactBootstrap.Table;
var Grid = ReactBootstrap.Grid;
var Row = ReactBootstrap.Row;
var Col = ReactBootstrap.Col;
var Panel = ReactBootstrap.Panel;
var Input = ReactBootstrap.Input;
var Button = ReactBootstrap.Button;
var ButtonGroup = ReactBootstrap.ButtonGroup;
var ButtonToolbar = ReactBootstrap.ButtonToolbar;
var ProgressBar = ReactBootstrap.ProgressBar;
var Glyphicon = ReactBootstrap.Glyphicon;
var DateTimePicker = ReactWidgets.DateTimePicker;
var DateTimePicker = require('react-widgets').DateTimePicker;
var Combobox = require('react-widgets').Combobox;
var AccountCombobox = require('./AccountCombobox.js');
const TransactionRow = React.createClass({
handleClick: function(e) {
const refs = ["date", "number", "description", "account", "status", "amount"];
for (var ref in refs) {
if (this.refs[refs[ref]].getDOMNode() == e.target) {
if (this.refs[refs[ref]] == e.target) {
this.props.onEdit(this.props.transaction, refs[ref]);
return;
}
@ -135,7 +142,7 @@ const AmountInput = React.createClass({
var symbol = "?";
if (this.props.security)
symbol = this.props.security.Symbol;
var bsStyle = "";
var bsStyle = undefined;
if (this.props.bsStyle)
bsStyle = this.props.bsStyle;
@ -173,7 +180,7 @@ const AddEditTransactionModal = React.createClass({
},
handleDescriptionChange: function() {
this.setState({
transaction: React.addons.update(this.state.transaction, {
transaction: react_update(this.state.transaction, {
Description: {$set: this.refs.description.getValue()}
})
});
@ -182,7 +189,7 @@ const AddEditTransactionModal = React.createClass({
if (date == null)
return;
this.setState({
transaction: React.addons.update(this.state.transaction, {
transaction: react_update(this.state.transaction, {
Date: {$set: date}
})
});
@ -190,7 +197,7 @@ const AddEditTransactionModal = React.createClass({
handleStatusChange: function(status) {
if (status.hasOwnProperty('StatusId')) {
this.setState({
transaction: React.addons.update(this.state.transaction, {
transaction: react_update(this.state.transaction, {
Status: {$set: status.StatusId}
})
});
@ -198,21 +205,21 @@ const AddEditTransactionModal = React.createClass({
},
handleAddSplit: function() {
this.setState({
transaction: React.addons.update(this.state.transaction, {
transaction: react_update(this.state.transaction, {
Splits: {$push: [new Split()]}
})
});
},
handleDeleteSplit: function(split) {
this.setState({
transaction: React.addons.update(this.state.transaction, {
transaction: react_update(this.state.transaction, {
Splits: {$splice: [[split, 1]]}
})
});
},
handleUpdateNumber: function(split) {
var transaction = this.state.transaction;
transaction.Splits[split] = React.addons.update(transaction.Splits[split], {
transaction.Splits[split] = react_update(transaction.Splits[split], {
Number: {$set: this.refs['number-'+split].getValue()}
});
this.setState({
@ -221,7 +228,7 @@ const AddEditTransactionModal = React.createClass({
},
handleUpdateMemo: function(split) {
var transaction = this.state.transaction;
transaction.Splits[split] = React.addons.update(transaction.Splits[split], {
transaction.Splits[split] = react_update(transaction.Splits[split], {
Memo: {$set: this.refs['memo-'+split].getValue()}
});
this.setState({
@ -230,7 +237,7 @@ const AddEditTransactionModal = React.createClass({
},
handleUpdateAccount: function(account, split) {
var transaction = this.state.transaction;
transaction.Splits[split] = React.addons.update(transaction.Splits[split], {
transaction.Splits[split] = react_update(transaction.Splits[split], {
SecurityId: {$set: -1},
AccountId: {$set: account.AccountId}
});
@ -240,7 +247,7 @@ const AddEditTransactionModal = React.createClass({
},
handleUpdateAmount: function(split) {
var transaction = this.state.transaction;
transaction.Splits[split] = React.addons.update(transaction.Splits[split], {
transaction.Splits[split] = react_update(transaction.Splits[split], {
Amount: {$set: new Big(this.refs['amount-'+split].getValue())}
});
this.setState({
@ -294,7 +301,7 @@ const AddEditTransactionModal = React.createClass({
var self = this;
var s = this.state.transaction.Splits[i];
var security = null;
var amountValidation = "";
var amountValidation = undefined;
var accountValidation = "";
if (s.AccountId in this.props.account_map) {
security = this.props.security_map[this.props.account_map[s.AccountId].SecurityId];
@ -355,7 +362,7 @@ const AddEditTransactionModal = React.createClass({
account_map={this.props.account_map}
value={s.AccountId}
includeRoot={false}
onSelect={updateAccountFn}
onChange={updateAccountFn}
ref={"account-"+i}
className={accountValidation}/></Col>
<Col xs={2}><AmountInput type="text"
@ -401,7 +408,7 @@ const AddEditTransactionModal = React.createClass({
data={TransactionStatusList}
valueField='StatusId'
textField='Name'
value={this.state.transaction.Status}
defaultValue={this.state.transaction.Status}
onSelect={this.handleStatusChange}
ref="status" />
</Input>
@ -536,7 +543,7 @@ const ImportTransactionsModal = React.createClass({
panel = (<Panel header="Successfully Imported Transactions" bsStyle="success">Your import is now complete.</Panel>);
}
var buttonsDisabled = (this.state.importing) ? "disabled" : "";
var buttonsDisabled = (this.state.importing) ? true : false;
var button1 = [];
var button2 = [];
if (!this.state.imported && this.state.error == null) {
@ -545,9 +552,9 @@ const ImportTransactionsModal = React.createClass({
} else {
button1 = (<Button onClick={this.handleCancel} disabled={buttonsDisabled} bsStyle="success">OK</Button>);
}
var inputDisabled = (this.state.importing || this.state.error != null || this.state.imported) ? "disabled" : "";
var inputDisabled = (this.state.importing || this.state.error != null || this.state.imported) ? true : false;
return (
<Modal show={this.props.show} onHide={this.handleCancel} bsSize="medium">
<Modal show={this.props.show} onHide={this.handleCancel} bsSize="small">
<Modal.Header closeButton>
<Modal.Title>Import Transactions</Modal.Title>
</Modal.Header>
@ -577,7 +584,8 @@ const ImportTransactionsModal = React.createClass({
}
});
const AccountRegister = React.createClass({
module.exports = React.createClass({
displayName: "AccountRegister",
getInitialState: function() {
return {
importingTransactions: false,
@ -591,7 +599,7 @@ const AccountRegister = React.createClass({
};
},
resize: function() {
var div = React.findDOMNode(this);
var div = ReactDOM.findDOMNode(this);
this.setState({height: div.parentElement.clientHeight - 64});
},
componentDidMount: function() {
@ -830,7 +838,7 @@ const AccountRegister = React.createClass({
);
}
var disabled = (this.props.selectedAccount == null) ? "disabled" : "";
var disabled = (this.props.selectedAccount == null) ? true : false;
return (
<div className="transactions-container">

View File

@ -0,0 +1,166 @@
var React = require('react');
var Modal = require('react-bootstrap').Modal;
var Button = require('react-bootstrap').Button;
var ButtonGroup = require('react-bootstrap').ButtonGroup;
var Input = require('react-bootstrap').Input;
module.exports = React.createClass({
displayName: "AccountSettingsModal",
_getInitialState: function(props) {
return {error: "",
name: props.user.Name,
username: props.user.Username,
email: props.user.Email,
password: BogusPassword,
confirm_password: BogusPassword,
passwordChanged: false,
initial_password: BogusPassword};
},
getInitialState: function() {
return this._getInitialState(this.props);
},
componentWillReceiveProps: function(nextProps) {
if (nextProps.show && !this.props.show) {
this.setState(this._getInitialState(nextProps));
}
},
passwordValidationState: function() {
if (this.state.passwordChanged) {
if (this.state.password.length >= 10)
return "success";
else if (this.state.password.length >= 6)
return "warning";
else
return "error";
}
},
confirmPasswordValidationState: function() {
if (this.state.confirm_password.length > 0) {
if (this.state.confirm_password == this.state.password)
return "success";
else
return "error";
}
},
handleCancel: function() {
if (this.props.onCancel != null)
this.props.onCancel();
},
handleChange: function() {
if (this.refs.password.getValue() != this.state.initial_password)
this.setState({passwordChanged: true});
this.setState({
name: this.refs.name.getValue(),
username: this.refs.username.getValue(),
email: this.refs.email.getValue(),
password: this.refs.password.getValue(),
confirm_password: this.refs.confirm_password.getValue()
});
},
handleSubmit: function(e) {
var u = new User();
var error = "";
e.preventDefault();
u.UserId = this.props.user.UserId;
u.Name = this.state.name;
u.Username = this.state.username;
u.Email = this.state.email;
if (this.state.passwordChanged) {
u.Password = this.state.password;
if (u.Password != this.state.confirm_password) {
this.setState({error: "Error: password do not match"});
return;
}
} else {
u.Password = BogusPassword;
}
this.handleSaveSettings(u);
},
handleSaveSettings: function(user) {
$.ajax({
type: "PUT",
dataType: "json",
url: "user/"+user.UserId+"/",
data: {user: user.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
user.Password = "";
this.props.onSubmit(user);
}
}.bind(this),
error: function(jqXHR, status, error) {
var e = new Error();
e.ErrorId = 5;
e.ErrorString = "Request Failed: " + status + error;
this.setState({error: e});
}.bind(this),
});
},
render: function() {
return (
<Modal show={this.props.show} onHide={this.handleCancel} bsSize="large">
<Modal.Header closeButton>
<Modal.Title>Edit Account Settings</Modal.Title>
</Modal.Header>
<Modal.Body>
<span color="red">{this.state.error}</span>
<form onSubmit={this.handleSubmit}
className="form-horizontal">
<Input type="text"
label="Name"
value={this.state.name}
onChange={this.handleChange}
ref="name"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="text"
label="Username"
value={this.state.username}
onChange={this.handleChange}
ref="username"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="email"
label="Email"
value={this.state.email}
onChange={this.handleChange}
ref="email"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="password"
label="Password"
value={this.state.password}
onChange={this.handleChange}
ref="password"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"
bsStyle={this.passwordValidationState()}
hasFeedback/>
<Input type="password"
label="Confirm Password"
value={this.state.confirm_password}
onChange={this.handleChange}
ref="confirm_password"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"
bsStyle={this.confirmPasswordValidationState()}
hasFeedback/>
</form>
</Modal.Body>
<Modal.Footer>
<ButtonGroup>
<Button onClick={this.handleCancel} bsStyle="warning">Cancel</Button>
<Button onClick={this.handleSubmit} bsStyle="success">Save Settings</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>
);
}
});

View File

@ -1,51 +1,24 @@
// Import all the objects we want to use from ReactBootstrap
var ListGroup = ReactBootstrap.ListGroup;
var ListGroupItem = ReactBootstrap.ListGroupItem;
var React = require('react');
var ReactDOM = require('react-dom');
var ReactBootstrap = require('react-bootstrap');
var Grid = ReactBootstrap.Grid;
var Row = ReactBootstrap.Row;
var Col = ReactBootstrap.Col;
var Input = ReactBootstrap.Input;
var Button = ReactBootstrap.Button;
var ButtonGroup = ReactBootstrap.ButtonGroup;
var Glyphicon = ReactBootstrap.Glyphicon;
var ListGroup = ReactBootstrap.ListGroup;
var ListGroupItem = ReactBootstrap.ListGroupItem;
var Collapse = ReactBootstrap.Collapse;
var Modal = ReactBootstrap.Modal;
var Collapse = ReactBootstrap.Collapse;
var CollapsibleMixin = ReactBootstrap.CollapsibleMixin;
var Combobox = require('react-widgets').Combobox;
var Combobox = ReactWidgets.Combobox;
const AccountCombobox = React.createClass({
getDefaultProps: function() {
return {
includeRoot: true,
rootName: "New Top-level Account"
};
},
handleAccountChange: function(account) {
if (this.props.onSelect != null &&
account.hasOwnProperty('AccountId') &&
(this.props.account_map.hasOwnProperty([account.AccountId]) ||
account.AccountId == -1)) {
this.props.onSelect(account)
}
},
render: function() {
var accounts = getAccountDisplayList(this.props.accounts, this.props.includeRoot, this.props.rootName);
var className = "";
if (this.props.className)
className = this.props.className;
return (
<Combobox
data={accounts}
valueField='AccountId'
textField='Name'
value={this.props.value}
onSelect={this.handleAccountChange}
ref="account"
className={className} />
);
}
});
var AccountCombobox = require('./AccountCombobox.js');
var AccountRegister = require('./AccountRegister.js');
const AddEditAccountModal = React.createClass({
getInitialState: function() {
@ -139,7 +112,7 @@ const AddEditAccountModal = React.createClass({
account_map={this.props.account_map}
value={this.state.parentaccountid}
rootName={rootName}
onSelect={this.handleParentChange}
onChange={this.handleParentChange}
ref="parent" />
</Input>
<Input wrapperClassName="wrapper"
@ -151,7 +124,7 @@ const AddEditAccountModal = React.createClass({
valueField='SecurityId'
textField='Name'
value={this.state.security}
onSelect={this.handleSecurityChange}
onChange={this.handleSecurityChange}
ref="security" />
</Input>
<Input wrapperClassName="wrapper"
@ -163,7 +136,7 @@ const AddEditAccountModal = React.createClass({
valueField='TypeId'
textField='Name'
value={this.state.type}
onSelect={this.handleTypeChange}
onChange={this.handleTypeChange}
ref="type" />
</Input>
</form>
@ -179,6 +152,7 @@ const AddEditAccountModal = React.createClass({
}
});
const DeleteAccountModal = React.createClass({
getInitialState: function() {
if (this.props.initialAccount != null)
@ -263,7 +237,7 @@ const DeleteAccountModal = React.createClass({
accounts={this.props.accounts}
account_map={this.props.account_map}
value={this.state.accountid}
onSelect={this.handleChange}/>
onChange={this.handleChange}/>
</Input>
{checkbox}
</form>
@ -280,12 +254,8 @@ const DeleteAccountModal = React.createClass({
});
const AccountTreeNode = React.createClass({
mixins: [CollapsibleMixin],
getCollapsibleDOMNode: function() {
return React.findDOMNode(this.refs.children);
},
getCollapsibleDimensionValue: function() {
return React.findDOMNode(this.refs.children).scrollHeight;
getInitialState: function() {
return {expanded: false};
},
handleToggle: function(e) {
e.preventDefault();
@ -300,8 +270,7 @@ const AccountTreeNode = React.createClass({
this.props.onSelect(this.props.account);
},
render: function() {
var styles = this.getCollapsibleClassSet();
var glyph = this.isExpanded() ? 'minus' : 'plus';
var glyph = this.state.expanded ? 'minus' : 'plus';
var active = (this.props.selectedAccount != null &&
this.props.account.AccountId == this.props.selectedAccount.AccountId);
var buttonStyle = active ? "info" : "link";
@ -317,7 +286,7 @@ const AccountTreeNode = React.createClass({
});
var accounttreeClasses = "accounttree"
var expandButton = [];
if (children.length > 0)
if (children.length > 0) {
expandButton.push((
<Button onClick={this.handleToggle}
bsSize="xsmall"
@ -326,8 +295,9 @@ const AccountTreeNode = React.createClass({
<Glyphicon glyph={glyph} bsSize="xsmall"/>
</Button>
));
else
} else {
accounttreeClasses += "-nochildren";
}
return (
<div className={accounttreeClasses}>
{expandButton}
@ -336,9 +306,11 @@ const AccountTreeNode = React.createClass({
className="accounttree-name">
{this.props.account.Name}
</Button>
<div ref='children' className={classNames(styles)}>
{children}
</div>
<Collapse in={this.state.expanded}>
<div>
{children}
</div>
</Collapse>
</div>
);
}
@ -356,7 +328,7 @@ const AccountTree = React.createClass({
}
},
resize: function() {
var div = React.findDOMNode(this);
var div = ReactDOM.findDOMNode(this);
this.setState({height: div.parentElement.clientHeight - 73});
},
componentDidMount: function() {
@ -386,7 +358,8 @@ const AccountTree = React.createClass({
}
});
const AccountsTab = React.createClass({
module.exports = React.createClass({
displayName: "AccountsTab",
getInitialState: function() {
return {
selectedAccount: null,
@ -436,7 +409,7 @@ const AccountsTab = React.createClass({
var accounts = this.props.accounts;
var account_map = this.props.account_map;
var disabled = (this.state.selectedAccount == null) ? "disabled" : "";
var disabled = (this.state.selectedAccount == null) ? true : false;
return (
<Grid fluid className="fullheight"><Row className="fullheight">

4
static/Makefile Normal file
View File

@ -0,0 +1,4 @@
all:
browserify -t [ babelify --presets [ react ] ] main.js -o bundle.js
.PHONY = all

324
static/MoneyGoApp.js Normal file
View File

@ -0,0 +1,324 @@
var React = require('react');
var ReactBootstrap = require('react-bootstrap');
var Jumbotron = ReactBootstrap.Jumbotron;
var Tabs = ReactBootstrap.Tabs;
var Tab = ReactBootstrap.Tab;
var Modal = ReactBootstrap.Modal;
var TopBar = require('./TopBar.js');
var NewUserForm = require('./NewUserForm.js');
var AccountSettingsModal = require('./AccountSettingsModal.js');
var AccountsTab = require('./AccountsTab.js');
module.exports = React.createClass({
displayName: "MoneyGoApp",
getInitialState: function() {
return {
hash: "home",
session: new Session(),
user: new User(),
accounts: [],
account_map: {},
securities: [],
security_map: {},
error: new Error(),
showAccountSettingsModal: false
};
},
componentDidMount: function() {
this.getSession();
this.handleHashChange();
if ("onhashchange" in window) {
window.onhashchange = this.handleHashChange;
}
},
handleHashChange: function() {
var hash = location.hash.replace(/^#/, '');
if (hash.length == 0)
hash = "home";
if (hash != this.state.hash)
this.setHash(hash);
},
setHash: function(hash) {
location.hash = hash;
if (this.state.hash != hash)
this.setState({hash: hash});
},
ajaxError: function(jqXHR, status, error) {
var e = new Error();
e.ErrorId = 5;
e.ErrorString = "Request Failed: " + status + error;
this.setState({error: e});
},
getSession: function() {
$.ajax({
type: "GET",
dataType: "json",
url: "session/",
success: function(data, status, jqXHR) {
var e = new Error();
var s = new Session();
e.fromJSON(data);
if (e.isError()) {
if (e.ErrorId != 1 /* Not Signed In*/)
this.setState({error: e});
} else {
s.fromJSON(data);
}
this.setState({session: s});
this.getUser();
this.getAccounts();
this.getSecurities();
}.bind(this),
error: this.ajaxError
});
},
getUser: function() {
if (!this.state.session.isSession())
return;
$.ajax({
type: "GET",
dataType: "json",
url: "user/"+this.state.session.UserId+"/",
success: function(data, status, jqXHR) {
var e = new Error();
var u = new User();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
u.fromJSON(data);
}
this.setState({user: u});
}.bind(this),
error: this.ajaxError
});
},
getSecurities: function() {
if (!this.state.session.isSession()) {
this.setState({securities: [], security_map: {}});
return;
}
$.ajax({
type: "GET",
dataType: "json",
url: "security/",
success: function(data, status, jqXHR) {
var e = new Error();
var securities = [];
var security_map = {};
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
for (var i = 0; i < data.securities.length; i++) {
var s = new Security();
s.fromJSON(data.securities[i]);
securities.push(s);
security_map[s.SecurityId] = s;
}
}
this.setState({securities: securities, security_map: security_map});
}.bind(this),
error: this.ajaxError
});
},
getAccounts: function() {
if (!this.state.session.isSession()) {
this.setState({accounts: [], account_map: {}});
return;
}
$.ajax({
type: "GET",
dataType: "json",
url: "account/",
success: function(data, status, jqXHR) {
var e = new Error();
var accounts = [];
var account_map = {};
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
for (var i = 0; i < data.accounts.length; i++) {
var a = new Account();
a.fromJSON(data.accounts[i]);
accounts.push(a);
account_map[a.AccountId] = a;
}
//Populate Children arrays in account objects
for (var i = 0; i < accounts.length; i++) {
var a = accounts[i];
if (!a.isRootAccount())
account_map[a.ParentAccountId].Children.push(a);
}
}
this.setState({accounts: accounts, account_map: account_map});
}.bind(this),
error: this.ajaxError
});
},
handleErrorClear: function() {
this.setState({error: new Error()});
},
handleLoginSubmit: function(user) {
$.ajax({
type: "POST",
dataType: "json",
url: "session/",
data: {user: user.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.getSession();
this.setHash("home");
}
}.bind(this),
error: this.ajaxError
});
},
handleLogoutSubmit: function() {
this.setState({accounts: [], account_map: {}});
$.ajax({
type: "DELETE",
dataType: "json",
url: "session/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
}
this.setState({session: new Session(), user: new User()});
}.bind(this),
error: this.ajaxError
});
},
handleAccountSettings: function() {
this.setState({showAccountSettingsModal: true});
},
handleSettingsSubmitted: function(user) {
this.setState({
user: user,
showAccountSettingsModal: false
});
},
handleSettingsCanceled: function(user) {
this.setState({showAccountSettingsModal: false});
},
handleCreateNewUser: function() {
this.setHash("new_user");
},
handleGoHome: function(user) {
this.setHash("home");
},
handleCreateAccount: function(account) {
$.ajax({
type: "POST",
dataType: "json",
url: "account/",
data: {account: account.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.getAccounts();
}
}.bind(this),
error: this.ajaxError
});
},
handleUpdateAccount: function(account) {
$.ajax({
type: "PUT",
dataType: "json",
url: "account/"+account.AccountId+"/",
data: {account: account.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.getAccounts();
}
}.bind(this),
error: this.ajaxError
});
},
handleDeleteAccount: function(account) {
$.ajax({
type: "DELETE",
dataType: "json",
url: "account/"+account.AccountId+"/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.getAccounts();
}
}.bind(this),
error: this.ajaxError
});
},
render: function() {
var mainContent;
if (this.state.hash == "new_user") {
mainContent = <NewUserForm onNewUser={this.handleGoHome} onCancel={this.handleGoHome}/>
} else {
if (this.state.user.isUser())
mainContent = (
<Tabs defaultActiveKey={1}>
<Tab title="Accounts" eventKey={1} tabClassName="fullheight">
<AccountsTab
className="fullheight"
accounts={this.state.accounts}
account_map={this.state.account_map}
securities={this.state.securities}
security_map={this.state.security_map}
onCreateAccount={this.handleCreateAccount}
onUpdateAccount={this.handleUpdateAccount}
onDeleteAccount={this.handleDeleteAccount} />
</Tab>
<Tab title="Scheduled Transactions" eventKey={2} tabClassName="fullheight">Scheduled transactions go here...</Tab>
<Tab title="Budgets" eventKey={3} tabClassName="fullheight">Budgets go here...</Tab>
<Tab title="Reports" eventKey={4} tabClassName="fullheight">Reports go here...</Tab>
</Tabs>);
else
mainContent = (
<Jumbotron>
<center>
<h1>Money<i>Go</i></h1>
<p><i>Go</i> manage your money.</p>
</center>
</Jumbotron>);
}
return (
<div className="fullheight ui">
<TopBar
error={this.state.error}
onErrorClear={this.handleErrorClear}
onLoginSubmit={this.handleLoginSubmit}
onCreateNewUser={this.handleCreateNewUser}
user={this.state.user}
onAccountSettings={this.handleAccountSettings}
onLogoutSubmit={this.handleLogoutSubmit} />
{mainContent}
<AccountSettingsModal
show={this.state.showAccountSettingsModal}
user={this.state.user}
onSubmit={this.handleSettingsSubmitted}
onCancel={this.handleSettingsCanceled}/>
</div>
);
}
});

147
static/NewUserForm.js Normal file
View File

@ -0,0 +1,147 @@
var React = require('react');
var Panel = require('react-bootstrap').Panel;
var Input = require('react-bootstrap').Input;
var Button = require('react-bootstrap').Button;
var ButtonGroup = require('react-bootstrap').ButtonGroup;
module.exports = React.createClass({
getInitialState: function() {
return {error: "",
name: "",
username: "",
email: "",
password: "",
confirm_password: "",
passwordChanged: false,
initial_password: ""};
},
passwordValidationState: function() {
if (this.state.passwordChanged) {
if (this.state.password.length >= 10)
return "success";
else if (this.state.password.length >= 6)
return "warning";
else
return "error";
}
},
confirmPasswordValidationState: function() {
if (this.state.confirm_password.length > 0) {
if (this.state.confirm_password == this.state.password)
return "success";
else
return "error";
}
},
handleCancel: function() {
if (this.props.onCancel != null)
this.props.onCancel();
},
handleChange: function() {
if (this.refs.password.getValue() != this.state.initial_password)
this.setState({passwordChanged: true});
this.setState({
name: this.refs.name.getValue(),
username: this.refs.username.getValue(),
email: this.refs.email.getValue(),
password: this.refs.password.getValue(),
confirm_password: this.refs.confirm_password.getValue()
});
},
handleSubmit: function(e) {
var u = new User();
var error = "";
e.preventDefault();
u.Name = this.state.name;
u.Username = this.state.username;
u.Email = this.state.email;
u.Password = this.state.password;
if (u.Password != this.state.confirm_password) {
this.setState({error: "Error: password do not match"});
return;
}
this.handleCreateNewUser(u);
},
handleCreateNewUser: function(user) {
$.ajax({
type: "POST",
dataType: "json",
url: "user/",
data: {user: user.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.props.onNewUser();
}
}.bind(this),
error: function(jqXHR, status, error) {
var e = new Error();
e.ErrorId = 5;
e.ErrorString = "Request Failed: " + status + error;
this.setState({error: e});
}.bind(this),
});
},
render: function() {
var title = <h3>Create New User</h3>;
return (
<Panel header={title} bsStyle="info">
<span color="red">{this.state.error}</span>
<form onSubmit={this.handleSubmit}
className="form-horizontal">
<Input type="text"
label="Name"
value={this.state.name}
onChange={this.handleChange}
ref="name"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="text"
label="Username"
value={this.state.username}
onChange={this.handleChange}
ref="username"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="email"
label="Email"
value={this.state.email}
onChange={this.handleChange}
ref="email"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="password"
label="Password"
value={this.state.password}
onChange={this.handleChange}
ref="password"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"
bsStyle={this.passwordValidationState()}
hasFeedback/>
<Input type="password"
label="Confirm Password"
value={this.state.confirm_password}
onChange={this.handleChange}
ref="confirm_password"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"
bsStyle={this.confirmPasswordValidationState()}
hasFeedback/>
<ButtonGroup className="pull-right">
<Button onClick={this.handleCancel}
bsStyle="warning">Cancel</Button>
<Button type="submit"
bsStyle="success">Create New User</Button>
</ButtonGroup>
</form>
</Panel>
);
}
});

View File

@ -1,11 +1,11 @@
// Import all the objects we want to use from ReactBootstrap
var Alert = ReactBootstrap.Alert;
var React = require('react');
var ReactBootstrap = require('react-bootstrap');
var Alert = ReactBootstrap.Alert;
var Input = ReactBootstrap.Input;
var Button = ReactBootstrap.Button;
var DropdownButton = ReactBootstrap.DropdownButton;
var MenuItem = ReactBootstrap.MenuItem;
var Row = ReactBootstrap.Row;
var Col = ReactBootstrap.Col;
@ -62,7 +62,7 @@ const LoginBar = React.createClass({
});
const LogoutBar = React.createClass({
handleOnSelect: function(key) {
handleOnSelect: function(e, key) {
if (key == 1) {
if (this.props.onAccountSettings != null)
this.props.onAccountSettings();
@ -79,7 +79,7 @@ const LogoutBar = React.createClass({
<Col xs={6}></Col>
<Col xs={4}>
<div className="pull-right">
<DropdownButton title={signedInString} onSelect={this.handleOnSelect} bsStyle="info">
<DropdownButton id="logout-settings-dropdown" title={signedInString} onSelect={this.handleOnSelect} bsStyle="info">
<MenuItem eventKey="1">Account Settings</MenuItem>
<MenuItem eventKey="2">Logout</MenuItem>
</DropdownButton>
@ -91,7 +91,8 @@ const LogoutBar = React.createClass({
}
});
const TopBar = React.createClass({
module.exports = React.createClass({
displayName: "TopBar",
render: function() {
var barContents;
var errorAlert;

Binary file not shown.

View File

@ -1,18 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2015 by original authors @ fontello.com</metadata>
<defs>
<font id="rw-widgets" horiz-adv-x="1000" >
<font-face font-family="rw-widgets" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="up-dir" unicode="&#xe800;" d="m571 171q0-14-10-25t-25-10h-500q-15 0-25 10t-11 25 11 26l250 250q10 10 25 10t25-10l250-250q10-11 10-26z" horiz-adv-x="571.4" />
<glyph glyph-name="left-dir" unicode="&#xe801;" d="m357 600v-500q0-14-10-25t-26-11-25 11l-250 250q-10 11-10 25t10 25l250 250q11 11 25 11t26-11 10-25z" horiz-adv-x="357.1" />
<glyph glyph-name="right-dir" unicode="&#xe802;" d="m321 350q0-14-10-25l-250-250q-11-11-25-11t-25 11-11 25v500q0 15 11 25t25 11 25-11l250-250q10-10 10-25z" horiz-adv-x="357.1" />
<glyph glyph-name="down-dir" unicode="&#xe803;" d="m571 457q0-14-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 11-11 25t11 25 25 11h500q14 0 25-11t10-25z" horiz-adv-x="571.4" />
<glyph glyph-name="calendar" unicode="&#xe804;" d="m71-79h161v161h-161v-161z m197 0h178v161h-178v-161z m-197 197h161v178h-161v-178z m197 0h178v178h-178v-178z m-197 214h161v161h-161v-161z m411-411h179v161h-179v-161z m-214 411h178v161h-178v-161z m428-411h161v161h-161v-161z m-214 197h179v178h-179v-178z m-196 482v161q0 7-6 12t-12 6h-36q-7 0-12-6t-6-12v-161q0-7 6-13t12-5h36q7 0 12 5t6 13z m410-482h161v178h-161v-178z m-214 214h179v161h-179v-161z m214 0h161v161h-161v-161z m18 268v161q0 7-5 12t-13 6h-35q-8 0-13-6t-5-12v-161q0-7 5-13t13-5h35q8 0 13 5t5 13z m215 36v-715q0-29-22-50t-50-21h-786q-29 0-50 21t-21 50v715q0 29 21 50t50 21h72v54q0 36 26 63t63 26h36q37 0 63-26t26-63v-54h214v54q0 36 27 63t63 26h35q37 0 63-26t27-63v-54h71q29 0 50-21t22-50z" horiz-adv-x="928.6" />
<glyph glyph-name="clock" unicode="&#xe805;" d="m500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="search" unicode="&#xe806;" d="m643 386q0 103-74 176t-176 74-177-74-73-176 73-177 177-73 176 73 74 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 152-31 126-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,307 +0,0 @@
/* for debugging */
.rw-widget {
outline: 0;
-moz-background-clip: border-box;
-webkit-background-clip: border-box;
background-clip: border-box;
}
.rw-btn {
color: #333333;
line-height: 2.286em;
display: inline-block;
margin: 0;
text-align: center;
vertical-align: middle;
background: none;
background-image: none;
border: 1px solid transparent;
padding: 0;
white-space: nowrap;
}
.rw-rtl {
direction: rtl;
}
.rw-input {
color: #555555;
height: 2.286em;
line-height: 2.286em;
padding: 0.429em 0.857em;
}
.rw-input[disabled] {
-webkit-box-shadow: none;
box-shadow: none;
cursor: not-allowed;
opacity: 1;
background-color: #eeeeee;
border-color: #cccccc;
}
.rw-input[readonly] {
cursor: not-allowed;
}
.rw-i.rw-loading {
background: url("../img/loading.gif") no-repeat center;
width: 16px;
height: 100%;
}
.rw-i.rw-loading:before {
content: "";
}
.rw-loading-mask {
border-radius: 4px;
position: relative;
}
.rw-loading-mask:after {
content: '';
background: url("../img/loader-big.gif") no-repeat center;
position: absolute;
background-color: #fff;
opacity: 0.7;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.rw-now {
font-weight: 600;
}
.rw-state-focus {
background-color: #ffffff;
border: #66afe9 1px solid;
color: #333333;
}
.rw-state-selected {
background-color: #adadad;
border: #adadad 1px solid;
color: #333333;
}
.rw-state-disabled {
-webkit-box-shadow: none;
box-shadow: none;
cursor: not-allowed;
opacity: 1;
}
.rw-btn,
.rw-dropdownlist {
cursor: pointer;
}
.rw-btn[disabled],
.rw-state-disabled .rw-btn,
.rw-state-readonly .rw-btn {
-webkit-box-shadow: none;
box-shadow: none;
pointer-events: none;
cursor: not-allowed;
filter: alpha(opacity=65);
opacity: .65;
}
ul.rw-list,
ul.rw-selectlist {
margin: 0;
padding-left: 0;
list-style: none;
padding: 5px 0;
overflow: auto;
outline: 0;
height: 100%;
}
ul.rw-list > li.rw-list-optgroup,
ul.rw-selectlist > li.rw-list-optgroup {
font-weight: bold;
}
ul.rw-list > li.rw-list-option,
ul.rw-selectlist > li.rw-list-option {
cursor: pointer;
border: 1px solid transparent;
padding-left: 10px;
padding-right: 10px;
border-radius: 3px;
}
ul.rw-list > li.rw-list-option:hover,
ul.rw-selectlist > li.rw-list-option:hover {
background-color: #e6e6e6;
border-color: #adadad;
}
ul.rw-list > li.rw-list-option.rw-state-focus,
ul.rw-selectlist > li.rw-list-option.rw-state-focus {
background-color: #ffffff;
border: #66afe9 1px solid;
color: #333333;
}
ul.rw-list > li.rw-list-option.rw-state-selected,
ul.rw-selectlist > li.rw-list-option.rw-state-selected {
background-color: #adadad;
border: #adadad 1px solid;
color: #333333;
}
ul.rw-list.rw-list-grouped > li.rw-list-optgroup {
padding-left: 10px;
}
ul.rw-list.rw-list-grouped > li.rw-list-option {
padding-left: 20px;
}
.rw-widget {
position: relative;
}
.rw-open.rw-widget,
.rw-open > .rw-multiselect-wrapper {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.rw-open-up.rw-widget,
.rw-open-up > .rw-multiselect-wrapper {
border-top-right-radius: 0;
border-top-left-radius: 0;
}
.rw-combobox .rw-list,
.rw-datetimepicker .rw-list,
.rw-numberpicker .rw-list,
.rw-dropdownlist .rw-list,
.rw-multiselect .rw-list {
max-height: 200px;
height: auto;
}
.rw-widget {
background-color: #ffffff;
border: #cccccc 1px solid;
border-radius: 4px;
}
.rw-widget .rw-input {
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
}
.rw-rtl.rw-widget .rw-input {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
}
.rw-widget > .rw-select {
border-left: #cccccc 1px solid;
}
.rw-rtl.rw-widget > .rw-select {
border-right: #cccccc 1px solid;
border-left: none;
}
.rw-widget.rw-state-focus,
.rw-widget.rw-state-focus:hover {
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
border-color: #66afe9;
outline: 0;
}
.rw-widget.rw-state-readonly,
.rw-widget.rw-state-readonly > .rw-multiselect-wrapper {
cursor: not-allowed;
}
.rw-widget.rw-state-disabled,
.rw-widget.rw-state-disabled:hover,
.rw-widget.rw-state-disabled:active {
-webkit-box-shadow: none;
box-shadow: none;
background-color: #eeeeee;
border-color: #cccccc;
}
.rw-combobox,
.rw-datetimepicker,
.rw-numberpicker,
.rw-dropdownlist {
padding-right: 1.9em;
}
.rw-combobox.rw-rtl,
.rw-datetimepicker.rw-rtl,
.rw-numberpicker.rw-rtl,
.rw-dropdownlist.rw-rtl {
padding-right: 0;
padding-left: 1.9em;
}
.rw-combobox > .rw-input,
.rw-datetimepicker > .rw-input,
.rw-numberpicker > .rw-input,
.rw-dropdownlist > .rw-input {
width: 100%;
border: none;
outline: 0;
}
.rw-combobox > .rw-input::-moz-placeholder,
.rw-datetimepicker > .rw-input::-moz-placeholder,
.rw-numberpicker > .rw-input::-moz-placeholder,
.rw-dropdownlist > .rw-input::-moz-placeholder {
color: #999999;
opacity: 1;
}
.rw-combobox > .rw-input:-ms-input-placeholder,
.rw-datetimepicker > .rw-input:-ms-input-placeholder,
.rw-numberpicker > .rw-input:-ms-input-placeholder,
.rw-dropdownlist > .rw-input:-ms-input-placeholder {
color: #999999;
}
.rw-combobox > .rw-input::-webkit-input-placeholder,
.rw-datetimepicker > .rw-input::-webkit-input-placeholder,
.rw-numberpicker > .rw-input::-webkit-input-placeholder,
.rw-dropdownlist > .rw-input::-webkit-input-placeholder {
color: #999999;
}
.rw-select {
position: absolute;
width: 1.9em;
height: 100%;
right: 0;
}
.rw-select.rw-btn,
.rw-select > .rw-btn {
height: 100%;
vertical-align: middle;
outline: 0;
}
.rw-rtl .rw-select {
left: 0;
right: auto;
}
.rw-multiselect,
.rw-combobox input.rw-input,
.rw-datetimepicker input.rw-input,
.rw-numberpicker input.rw-input {
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
}
.rw-combobox:active,
.rw-datetimepicker:active,
.rw-dropdownlist:active,
.rw-header > .rw-btn:active,
.rw-numberpicker .rw-btn.rw-state-active,
.rw-combobox:active.rw-state-focus,
.rw-datetimepicker:active.rw-state-focus,
.rw-dropdownlist:active.rw-state-focus,
.rw-header > .rw-btn:active.rw-state-focus,
.rw-numberpicker .rw-btn.rw-state-active.rw-state-focus {
background-image: none;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.rw-combobox:hover,
.rw-datetimepicker:hover,
.rw-numberpicker:hover,
.rw-dropdownlist:hover {
background-color: #e6e6e6;
border-color: #adadad;
}
.rw-dropdownlist.rw-state-disabled,
.rw-dropdownlist.rw-state-readonly {
cursor: not-allowed;
}
.rw-dropdownlist > .rw-input {
background-color: transparent;
padding-top: 0;
padding-bottom: 0;
}
.rw-dropdownlist > .rw-select,
.rw-dropdownlist > .rw-select.rw-rtl {
border-width: 0;
}
.rw-numberpicker .rw-btn {
display: block;
height: 1.143em;
line-height: 1.143em;
width: 100%;
border-width: 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

View File

@ -1,730 +0,0 @@
/* Noramlize.css */
.rw-btn,
.rw-input {
color: inherit;
font: inherit;
margin: 0;
}
button.rw-input {
overflow: visible;
}
button.rw-input,
select.rw-input {
text-transform: none;
}
button.rw-input,
html input[type="button"].rw-input,
input[type="reset"].rw-input,
input[type="submit"].rw-input {
-webkit-appearance: button;
cursor: pointer;
}
button[disabled].rw-input,
html input[disabled].rw-input {
cursor: not-allowed;
}
button.rw-input::-moz-focus-inner,
input.rw-input::-moz-focus-inner {
border: 0;
padding: 0;
}
/* -------------- */
.rw-sr {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.rw-widget,
.rw-widget * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.rw-widget:before,
.rw-widget *:before,
.rw-widget:after,
.rw-widget *:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
@font-face {
font-family: 'RwWidgets';
src: url('../fonts/rw-widgets.eot?v=4.1.0');
src: url('../fonts/rw-widgets.eot?#iefix&v=4.1.0') format('embedded-opentype'), url('../fonts/rw-widgets.woff?v=4.1.0') format('woff'), url('../fonts/rw-widgets.ttf?v=4.1.0') format('truetype'), url('../fonts/rw-widgets.svg?v=4.1.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
.rw-i {
display: inline-block;
font-family: RwWidgets;
font-style: normal;
font-weight: normal;
line-height: 1em;
font-variant: normal;
text-transform: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.rw-i-caret-down:before {
content: '\e803';
}
.rw-i-caret-up:before {
content: '\e800';
}
.rw-i-caret-left:before {
content: '\e801';
}
.rw-i-caret-right:before {
content: '\e802';
}
.rw-i-clock-o:before {
content: '\e805';
}
.rw-i-calendar:before {
content: '\e804';
}
.rw-i-search:before {
content: '\e806';
}
/* for debugging */
.rw-widget {
outline: 0;
-moz-background-clip: border-box;
-webkit-background-clip: border-box;
background-clip: border-box;
}
.rw-btn {
color: #333333;
line-height: 2.286em;
display: inline-block;
margin: 0;
text-align: center;
vertical-align: middle;
background: none;
background-image: none;
border: 1px solid transparent;
padding: 0;
white-space: nowrap;
}
.rw-rtl {
direction: rtl;
}
.rw-input {
color: #555555;
height: 2.286em;
padding: 0.429em 0.857em;
background-color: #ffffff;
}
.rw-input[disabled] {
-webkit-box-shadow: none;
box-shadow: none;
cursor: not-allowed;
opacity: 1;
background-color: #eeeeee;
border-color: #cccccc;
}
.rw-input[readonly] {
cursor: not-allowed;
}
.rw-filter-input {
position: relative;
width: 100%;
padding-right: 1.9em;
border: #cccccc 1px solid;
border-radius: 4px;
margin-bottom: 2px;
}
.rw-rtl .rw-filter-input {
padding-left: 1.9em;
padding-right: 0;
}
.rw-filter-input > .rw-input {
width: 100%;
border: none;
outline: none;
}
.rw-filter-input > span {
margin-top: -2px;
}
.rw-i.rw-loading {
background: url("../img/loading.gif") no-repeat center;
width: 16px;
height: 100%;
}
.rw-i.rw-loading:before {
content: "";
}
.rw-loading-mask {
border-radius: 4px;
position: relative;
}
.rw-loading-mask:after {
content: '';
background: url("../img/loader-big.gif") no-repeat center;
position: absolute;
background-color: #fff;
opacity: 0.7;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.rw-now {
font-weight: 600;
}
.rw-state-focus {
background-color: #ffffff;
border: #66afe9 1px solid;
color: #333333;
}
.rw-state-selected {
background-color: #adadad;
border: #adadad 1px solid;
color: #333333;
}
.rw-state-disabled {
-webkit-box-shadow: none;
box-shadow: none;
cursor: not-allowed;
opacity: 1;
}
.rw-btn,
.rw-dropdownlist {
cursor: pointer;
}
.rw-btn[disabled],
.rw-state-disabled .rw-btn,
.rw-state-readonly .rw-btn {
-webkit-box-shadow: none;
box-shadow: none;
pointer-events: none;
cursor: not-allowed;
filter: alpha(opacity=65);
opacity: .65;
}
ul.rw-list,
.rw-selectlist {
margin: 0;
padding-left: 0;
list-style: none;
padding: 5px 0;
overflow: auto;
outline: 0;
height: 100%;
}
ul.rw-list > li,
.rw-selectlist > li {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
ul.rw-list > li.rw-list-optgroup,
.rw-selectlist > li.rw-list-optgroup {
font-weight: bold;
}
ul.rw-list > li.rw-list-option,
ul.rw-list > li.rw-list-empty,
.rw-selectlist > li.rw-list-option,
.rw-selectlist > li.rw-list-empty {
padding-left: 10px;
padding-right: 10px;
}
ul.rw-list > li.rw-list-option,
.rw-selectlist > li.rw-list-option {
cursor: pointer;
border: 1px solid transparent;
border-radius: 3px;
}
ul.rw-list > li.rw-list-option:hover,
.rw-selectlist > li.rw-list-option:hover {
background-color: #e6e6e6;
border-color: #adadad;
}
ul.rw-list > li.rw-list-option.rw-state-focus,
.rw-selectlist > li.rw-list-option.rw-state-focus {
background-color: #ffffff;
border: #66afe9 1px solid;
color: #333333;
}
ul.rw-list > li.rw-list-option.rw-state-selected,
.rw-selectlist > li.rw-list-option.rw-state-selected {
background-color: #adadad;
border: #adadad 1px solid;
color: #333333;
}
ul.rw-list.rw-list-grouped > li.rw-list-optgroup,
.rw-selectlist.rw-list-grouped > li.rw-list-optgroup {
padding-left: 10px;
}
ul.rw-list.rw-list-grouped > li.rw-list-option,
.rw-selectlist.rw-list-grouped > li.rw-list-option {
padding-left: 20px;
}
.rw-widget {
position: relative;
}
.rw-open.rw-widget,
.rw-open > .rw-multiselect-wrapper {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.rw-open-up.rw-widget,
.rw-open-up > .rw-multiselect-wrapper {
border-top-right-radius: 0;
border-top-left-radius: 0;
}
.rw-combobox .rw-list,
.rw-datetimepicker .rw-list,
.rw-numberpicker .rw-list,
.rw-dropdownlist .rw-list,
.rw-multiselect .rw-list {
max-height: 200px;
height: auto;
}
.rw-widget {
background-color: #ffffff;
border: #cccccc 1px solid;
border-radius: 4px;
}
.rw-widget .rw-input {
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
}
.rw-rtl.rw-widget .rw-input {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
}
.rw-widget > .rw-select {
border-left: #cccccc 1px solid;
}
.rw-rtl.rw-widget > .rw-select {
border-right: #cccccc 1px solid;
border-left: none;
}
.rw-widget.rw-state-focus,
.rw-widget.rw-state-focus:hover {
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
border-color: #66afe9;
outline: 0;
}
.rw-widget.rw-state-readonly,
.rw-widget.rw-state-readonly > .rw-multiselect-wrapper {
cursor: not-allowed;
}
.rw-widget.rw-state-disabled,
.rw-widget.rw-state-disabled:hover,
.rw-widget.rw-state-disabled:active {
-webkit-box-shadow: none;
box-shadow: none;
background-color: #eeeeee;
border-color: #cccccc;
}
.rw-combobox,
.rw-datetimepicker,
.rw-numberpicker,
.rw-dropdownlist {
padding-right: 1.9em;
}
.rw-combobox.rw-rtl,
.rw-datetimepicker.rw-rtl,
.rw-numberpicker.rw-rtl,
.rw-dropdownlist.rw-rtl {
padding-right: 0;
padding-left: 1.9em;
}
.rw-combobox > .rw-input,
.rw-datetimepicker > .rw-input,
.rw-numberpicker > .rw-input,
.rw-dropdownlist > .rw-input {
width: 100%;
border: none;
outline: 0;
}
.rw-combobox > .rw-input::-moz-placeholder,
.rw-datetimepicker > .rw-input::-moz-placeholder,
.rw-numberpicker > .rw-input::-moz-placeholder,
.rw-dropdownlist > .rw-input::-moz-placeholder {
color: #999999;
opacity: 1;
}
.rw-combobox > .rw-input:-ms-input-placeholder,
.rw-datetimepicker > .rw-input:-ms-input-placeholder,
.rw-numberpicker > .rw-input:-ms-input-placeholder,
.rw-dropdownlist > .rw-input:-ms-input-placeholder {
color: #999999;
}
.rw-combobox > .rw-input::-webkit-input-placeholder,
.rw-datetimepicker > .rw-input::-webkit-input-placeholder,
.rw-numberpicker > .rw-input::-webkit-input-placeholder,
.rw-dropdownlist > .rw-input::-webkit-input-placeholder {
color: #999999;
}
.rw-placeholder {
color: #999999;
}
.rw-select {
position: absolute;
width: 1.9em;
height: 100%;
right: 0;
}
.rw-select.rw-btn,
.rw-select > .rw-btn {
height: 100%;
vertical-align: middle;
outline: 0;
}
.rw-rtl .rw-select {
left: 0;
right: auto;
}
.rw-multiselect,
.rw-combobox input.rw-input,
.rw-datetimepicker input.rw-input,
.rw-numberpicker input.rw-input {
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
}
.rw-combobox:active,
.rw-datetimepicker:active,
.rw-dropdownlist:active,
.rw-header > .rw-btn:active,
.rw-numberpicker .rw-btn.rw-state-active,
.rw-combobox:active.rw-state-focus,
.rw-datetimepicker:active.rw-state-focus,
.rw-dropdownlist:active.rw-state-focus,
.rw-header > .rw-btn:active.rw-state-focus,
.rw-numberpicker .rw-btn.rw-state-active.rw-state-focus {
background-image: none;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.rw-combobox:hover,
.rw-datetimepicker:hover,
.rw-numberpicker:hover,
.rw-dropdownlist:hover {
background-color: #e6e6e6;
border-color: #adadad;
}
.rw-dropdownlist.rw-state-disabled,
.rw-dropdownlist.rw-state-readonly {
cursor: not-allowed;
}
.rw-dropdownlist > .rw-input {
line-height: 2.286em;
background-color: transparent;
padding-top: 0;
padding-bottom: 0;
padding-right: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rw-dropdownlist.rw-rtl > .rw-input {
padding: 0.429em 0.857em;
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
}
.rw-dropdownlist > .rw-select,
.rw-dropdownlist.rw-rtl > .rw-select {
border-width: 0;
}
.rw-numberpicker .rw-btn {
display: block;
height: 1.143em;
line-height: 1.143em;
width: 100%;
border-width: 0;
}
.rw-popup {
position: absolute;
-webkit-box-shadow: 0 5px 6px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 6px rgba(0, 0, 0, 0.2);
border-top-right-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
border: #cccccc 1px solid;
background: #ffffff;
padding: 2px;
overflow: auto;
margin-bottom: 10px;
left: 10px;
right: 10px;
}
.rw-dropup > .rw-popup {
margin-bottom: 0;
margin-top: 10px;
border-top-right-radius: 3px;
border-top-left-radius: 3px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
-webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.2);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.2);
}
.rw-popup-container {
position: absolute;
top: 100%;
margin-top: 1px;
z-index: 1005;
left: -11px;
right: -11px;
}
.rw-popup-container.rw-dropup {
top: auto;
bottom: 100%;
}
.rw-popup-container.rw-calendar-popup {
right: auto;
width: 18em;
}
.rw-datetimepicker .rw-btn {
width: 1.8em;
}
.rw-datetimepicker.rw-has-neither {
padding-left: 0;
padding-right: 0;
}
.rw-datetimepicker.rw-has-neither .rw-input {
border-radius: 4px;
}
.rw-datetimepicker.rw-has-both {
padding-right: 3.8em;
}
.rw-datetimepicker.rw-has-both.rw-rtl {
padding-right: 0;
padding-left: 3.8em;
}
.rw-datetimepicker.rw-has-both > .rw-select {
width: 3.8em;
height: 100%;
}
.rw-calendar {
background-color: #ffffff;
}
.rw-calendar thead > tr {
border-bottom: 2px solid #cccccc;
}
.rw-calendar .rw-header {
padding-bottom: 5px;
}
.rw-calendar .rw-header .rw-btn-left,
.rw-calendar .rw-header .rw-btn-right {
width: 12.5%;
}
.rw-calendar .rw-header .rw-btn-view {
width: 75%;
background-color: #eeeeee;
border-radius: 4px;
}
.rw-calendar .rw-header .rw-btn-view[disabled] {
-webkit-box-shadow: none;
box-shadow: none;
cursor: not-allowed;
}
.rw-calendar .rw-footer {
border-top: 1px solid #cccccc;
}
.rw-calendar .rw-footer .rw-btn {
width: 100%;
white-space: normal;
}
.rw-calendar .rw-footer .rw-btn:hover {
background-color: #e6e6e6;
}
.rw-calendar .rw-footer .rw-btn[disabled] {
-webkit-box-shadow: none;
box-shadow: none;
cursor: not-allowed;
}
.rw-calendar-grid {
height: 14.28571429em;
table-layout: fixed;
width: 100%;
}
.rw-calendar-grid th {
text-align: right;
padding: 0 .4em 0 .1em;
}
.rw-calendar-grid .rw-btn {
width: 100%;
text-align: right;
}
.rw-calendar-grid td .rw-btn {
border-radius: 4px;
padding: 0 .4em 0 .1em;
outline: 0;
}
.rw-calendar-grid td .rw-btn:hover {
background-color: #e6e6e6;
}
.rw-calendar-grid td .rw-btn.rw-off-range {
color: #b3b3b3;
}
.rw-calendar-grid.rw-nav-view .rw-btn {
padding: .25em 0 .3em;
display: block;
overflow: hidden;
text-align: center;
white-space: normal;
}
.rw-selectlist {
padding: 2px;
}
.rw-selectlist > ul {
height: 100%;
overflow: auto;
}
.rw-selectlist > ul > li.rw-list-option {
position: relative;
min-height: 27px;
cursor: auto;
padding-left: 5px;
}
.rw-selectlist > ul > li.rw-list-option > label > input {
position: absolute;
margin: 4px 0 0 -20px;
}
.rw-selectlist > ul > li.rw-list-option > label {
padding-left: 20px;
line-height: 1.423em;
display: inline-block;
}
.rw-selectlist.rw-rtl > ul > li.rw-list-option {
padding-left: 0;
padding-right: 5px;
}
.rw-selectlist.rw-rtl > ul > li.rw-list-option > label > input {
margin: 4px -20px 0 0px;
}
.rw-selectlist.rw-rtl > ul > li.rw-list-option > label {
padding-left: 0;
padding-right: 20px;
}
.rw-selectlist.rw-rtl > ul > li.rw-list-option {
padding-left: 0;
padding-right: 5px;
}
.rw-selectlist.rw-rtl > ul > li.rw-list-option > label > input {
margin: 4px -20px 0 0px;
}
.rw-selectlist.rw-rtl > ul > li.rw-list-option > label {
padding-left: 0;
padding-right: 20px;
}
.rw-selectlist.rw-state-disabled > ul > li:hover,
.rw-selectlist.rw-state-readonly > ul > li:hover {
background: none;
border-color: transparent;
}
.rw-multiselect {
background-color: #ffffff;
}
.rw-multiselect:hover {
border-color: #adadad;
}
.rw-multiselect-wrapper {
border-radius: 4px;
position: relative;
cursor: text;
}
.rw-multiselect-wrapper:before,
.rw-multiselect-wrapper:after {
content: " ";
display: table;
}
.rw-multiselect-wrapper:after {
clear: both;
}
.rw-multiselect-wrapper i.rw-loading {
position: absolute;
right: 3px;
}
.rw-multiselect-wrapper > .rw-input {
float: left;
outline: 0;
border-width: 0;
line-height: normal;
width: auto;
max-width: 100%;
}
.rw-multiselect-wrapper > .rw-input::-moz-placeholder {
color: #999999;
opacity: 1;
}
.rw-multiselect-wrapper > .rw-input:-ms-input-placeholder {
color: #999999;
}
.rw-multiselect-wrapper > .rw-input::-webkit-input-placeholder {
color: #999999;
}
.rw-state-readonly > .rw-multiselect-wrapper,
.rw-state-disabled > .rw-multiselect-wrapper {
cursor: not-allowed;
}
.rw-rtl .rw-multiselect-wrapper > .rw-input {
float: right;
}
.rw-multiselect-create-tag {
border-top: 1px #cccccc solid;
padding-top: 5px;
margin-top: 5px;
}
.rw-multiselect-taglist {
margin: 0;
padding-left: 0;
list-style: none;
padding-right: 0;
}
.rw-multiselect-taglist > li {
display: inline-block;
padding-left: 5px;
padding-right: 5px;
}
.rw-multiselect-taglist > li {
float: left;
display: inline-block;
margin: 1px;
padding: 0.214em 0.15em 0.214em 0.4em;
line-height: 1.4em;
text-align: center;
vertical-align: middle;
white-space: nowrap;
border-radius: 3px;
border: 1px solid #cccccc;
background-color: #cccccc;
cursor: pointer;
}
.rw-multiselect-taglist > li.rw-state-focus {
background-color: #ffffff;
border: #66afe9 1px solid;
color: #333333;
}
.rw-multiselect-taglist > li.rw-state-readonly,
.rw-multiselect-taglist > li.rw-state-disabled,
.rw-multiselect.rw-state-readonly .rw-multiselect-taglist > li,
.rw-multiselect.rw-state-disabled .rw-multiselect-taglist > li {
cursor: not-allowed;
filter: alpha(opacity=65);
opacity: .65;
}
.rw-multiselect-taglist > li .rw-btn {
outline: 0;
font-size: 115%;
line-height: normal;
}
.rw-rtl .rw-multiselect-taglist > li {
float: right;
}

File diff suppressed because one or more lines are too long

View File

@ -2,28 +2,18 @@
<head>
<title>MoneyGo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="static/external/react-widgets/react-widgets.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="static/node_modules/react-widgets/dist/css/react-widgets.css">
<link rel="stylesheet" href="static/stylesheet.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/globalize/0.1.1/globalize.min.js"></script>
<script src="static/external/react-bootstrap/react-bootstrap.min.js"></script>
<script src="static/external/react-widgets/react-widgets.js"></script>
<script src="static/external/big/big.min.js"></script>
<script src="static/external/classnames/index.js"></script>
<script type="text/javascript" src="static/utils.js"></script>
<script type="text/javascript" src="static/models.js"></script>
<script type="text/jsx" src="static/top_bar.js"></script>
<script type="text/jsx" src="static/account_register.js"></script>
<script type="text/jsx" src="static/accounts.js"></script>
<script type="text/jsx" src="static/ui.js"></script>
<script type="text/javascript" src="static/bundle.js"></script>
</head>
<body>

23
static/main.js Normal file
View File

@ -0,0 +1,23 @@
var React = require('react');
var ReactDOM = require('react-dom');
var Globalize = require('globalize');
var globalizeLocalizer = require('react-widgets/lib/localizers/globalize');
var MoneyGoApp = require('./MoneyGoApp.js');
// Setup globalization for react-widgets
//Globalize.load(require("cldr-data").entireSupplemental());
Globalize.load(
require("cldr-data/main/en/ca-gregorian"),
require("cldr-data/main/en/numbers"),
require("cldr-data/supplemental/likelySubtags"),
require("cldr-data/supplemental/timeData"),
require("cldr-data/supplemental/weekData")
);
Globalize.locale('en');
globalizeLocalizer(Globalize);
$(document).ready(function() {
ReactDOM.render(<MoneyGoApp />, document.getElementById("content"));
});

View File

@ -1,620 +0,0 @@
// Import all the objects we want to use from ReactBootstrap
var Jumbotron = ReactBootstrap.Jumbotron;
var TabbedArea = ReactBootstrap.TabbedArea;
var TabPane = ReactBootstrap.TabPane;
var Panel = ReactBootstrap.Panel;
var ButtonGroup = ReactBootstrap.ButtonGroup;
var Modal = ReactBootstrap.Modal;
const NewUserForm = React.createClass({
getInitialState: function() {
return {error: "",
name: "",
username: "",
email: "",
password: "",
confirm_password: "",
passwordChanged: false,
initial_password: ""};
},
passwordValidationState: function() {
if (this.state.passwordChanged) {
if (this.state.password.length >= 10)
return "success";
else if (this.state.password.length >= 6)
return "warning";
else
return "error";
}
},
confirmPasswordValidationState: function() {
if (this.state.confirm_password.length > 0) {
if (this.state.confirm_password == this.state.password)
return "success";
else
return "error";
}
},
handleCancel: function() {
if (this.props.onCancel != null)
this.props.onCancel();
},
handleChange: function() {
if (this.refs.password.getValue() != this.state.initial_password)
this.setState({passwordChanged: true});
this.setState({
name: this.refs.name.getValue(),
username: this.refs.username.getValue(),
email: this.refs.email.getValue(),
password: this.refs.password.getValue(),
confirm_password: this.refs.confirm_password.getValue()
});
},
handleSubmit: function(e) {
var u = new User();
var error = "";
e.preventDefault();
u.Name = this.state.name;
u.Username = this.state.username;
u.Email = this.state.email;
u.Password = this.state.password;
if (u.Password != this.state.confirm_password) {
this.setState({error: "Error: password do not match"});
return;
}
this.handleCreateNewUser(u);
},
handleCreateNewUser: function(user) {
$.ajax({
type: "POST",
dataType: "json",
url: "user/",
data: {user: user.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.props.onNewUser();
}
}.bind(this),
error: function(jqXHR, status, error) {
var e = new Error();
e.ErrorId = 5;
e.ErrorString = "Request Failed: " + status + error;
this.setState({error: e});
}.bind(this),
});
},
render: function() {
var title = <h3>Create New User</h3>;
return (
<Panel header={title} bsStyle="info">
<span color="red">{this.state.error}</span>
<form onSubmit={this.handleSubmit}
className="form-horizontal">
<Input type="text"
label="Name"
value={this.state.name}
onChange={this.handleChange}
ref="name"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="text"
label="Username"
value={this.state.username}
onChange={this.handleChange}
ref="username"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="email"
label="Email"
value={this.state.email}
onChange={this.handleChange}
ref="email"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="password"
label="Password"
value={this.state.password}
onChange={this.handleChange}
ref="password"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"
bsStyle={this.passwordValidationState()}
hasFeedback/>
<Input type="password"
label="Confirm Password"
value={this.state.confirm_password}
onChange={this.handleChange}
ref="confirm_password"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"
bsStyle={this.confirmPasswordValidationState()}
hasFeedback/>
<ButtonGroup className="pull-right">
<Button onClick={this.handleCancel}
bsStyle="warning">Cancel</Button>
<Button type="submit"
bsStyle="success">Create New User</Button>
</ButtonGroup>
</form>
</Panel>
);
}
});
const AccountSettingsModal = React.createClass({
_getInitialState: function(props) {
return {error: "",
name: props.user.Name,
username: props.user.Username,
email: props.user.Email,
password: BogusPassword,
confirm_password: BogusPassword,
passwordChanged: false,
initial_password: BogusPassword};
},
getInitialState: function() {
return this._getInitialState(this.props);
},
componentWillReceiveProps: function(nextProps) {
if (nextProps.show && !this.props.show) {
this.setState(this._getInitialState(nextProps));
}
},
passwordValidationState: function() {
if (this.state.passwordChanged) {
if (this.state.password.length >= 10)
return "success";
else if (this.state.password.length >= 6)
return "warning";
else
return "error";
}
},
confirmPasswordValidationState: function() {
if (this.state.confirm_password.length > 0) {
if (this.state.confirm_password == this.state.password)
return "success";
else
return "error";
}
},
handleCancel: function() {
if (this.props.onCancel != null)
this.props.onCancel();
},
handleChange: function() {
if (this.refs.password.getValue() != this.state.initial_password)
this.setState({passwordChanged: true});
this.setState({
name: this.refs.name.getValue(),
username: this.refs.username.getValue(),
email: this.refs.email.getValue(),
password: this.refs.password.getValue(),
confirm_password: this.refs.confirm_password.getValue()
});
},
handleSubmit: function(e) {
var u = new User();
var error = "";
e.preventDefault();
u.UserId = this.props.user.UserId;
u.Name = this.state.name;
u.Username = this.state.username;
u.Email = this.state.email;
if (this.state.passwordChanged) {
u.Password = this.state.password;
if (u.Password != this.state.confirm_password) {
this.setState({error: "Error: password do not match"});
return;
}
} else {
u.Password = BogusPassword;
}
this.handleSaveSettings(u);
},
handleSaveSettings: function(user) {
$.ajax({
type: "PUT",
dataType: "json",
url: "user/"+user.UserId+"/",
data: {user: user.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
user.Password = "";
this.props.onSubmit(user);
}
}.bind(this),
error: function(jqXHR, status, error) {
var e = new Error();
e.ErrorId = 5;
e.ErrorString = "Request Failed: " + status + error;
this.setState({error: e});
}.bind(this),
});
},
render: function() {
return (
<Modal show={this.props.show} onHide={this.handleCancel} bsSize="large">
<Modal.Header closeButton>
<Modal.Title>Edit Account Settings</Modal.Title>
</Modal.Header>
<Modal.Body>
<span color="red">{this.state.error}</span>
<form onSubmit={this.handleSubmit}
className="form-horizontal">
<Input type="text"
label="Name"
value={this.state.name}
onChange={this.handleChange}
ref="name"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="text"
label="Username"
value={this.state.username}
onChange={this.handleChange}
ref="username"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="email"
label="Email"
value={this.state.email}
onChange={this.handleChange}
ref="email"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"/>
<Input type="password"
label="Password"
value={this.state.password}
onChange={this.handleChange}
ref="password"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"
bsStyle={this.passwordValidationState()}
hasFeedback/>
<Input type="password"
label="Confirm Password"
value={this.state.confirm_password}
onChange={this.handleChange}
ref="confirm_password"
labelClassName="col-xs-2"
wrapperClassName="col-xs-10"
bsStyle={this.confirmPasswordValidationState()}
hasFeedback/>
</form>
</Modal.Body>
<Modal.Footer>
<ButtonGroup>
<Button onClick={this.handleCancel} bsStyle="warning">Cancel</Button>
<Button onClick={this.handleSubmit} bsStyle="success">Save Settings</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>
);
}
});
const MoneyGoApp = React.createClass({
getInitialState: function() {
return {
hash: "home",
session: new Session(),
user: new User(),
accounts: [],
account_map: {},
securities: [],
security_map: {},
error: new Error(),
showAccountSettingsModal: false
};
},
componentDidMount: function() {
this.getSession();
this.handleHashChange();
if ("onhashchange" in window) {
window.onhashchange = this.handleHashChange;
}
},
handleHashChange: function() {
var hash = location.hash.replace(/^#/, '');
if (hash.length == 0)
hash = "home";
if (hash != this.state.hash)
this.setHash(hash);
},
setHash: function(hash) {
location.hash = hash;
if (this.state.hash != hash)
this.setState({hash: hash});
},
ajaxError: function(jqXHR, status, error) {
var e = new Error();
e.ErrorId = 5;
e.ErrorString = "Request Failed: " + status + error;
this.setState({error: e});
},
getSession: function() {
$.ajax({
type: "GET",
dataType: "json",
url: "session/",
success: function(data, status, jqXHR) {
var e = new Error();
var s = new Session();
e.fromJSON(data);
if (e.isError()) {
if (e.ErrorId != 1 /* Not Signed In*/)
this.setState({error: e});
} else {
s.fromJSON(data);
}
this.setState({session: s});
this.getUser();
this.getAccounts();
this.getSecurities();
}.bind(this),
error: this.ajaxError
});
},
getUser: function() {
if (!this.state.session.isSession())
return;
$.ajax({
type: "GET",
dataType: "json",
url: "user/"+this.state.session.UserId+"/",
success: function(data, status, jqXHR) {
var e = new Error();
var u = new User();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
u.fromJSON(data);
}
this.setState({user: u});
}.bind(this),
error: this.ajaxError
});
},
getSecurities: function() {
if (!this.state.session.isSession()) {
this.setState({securities: [], security_map: {}});
return;
}
$.ajax({
type: "GET",
dataType: "json",
url: "security/",
success: function(data, status, jqXHR) {
var e = new Error();
var securities = [];
var security_map = {};
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
for (var i = 0; i < data.securities.length; i++) {
var s = new Security();
s.fromJSON(data.securities[i]);
securities.push(s);
security_map[s.SecurityId] = s;
}
}
this.setState({securities: securities, security_map: security_map});
}.bind(this),
error: this.ajaxError
});
},
getAccounts: function() {
if (!this.state.session.isSession()) {
this.setState({accounts: [], account_map: {}});
return;
}
$.ajax({
type: "GET",
dataType: "json",
url: "account/",
success: function(data, status, jqXHR) {
var e = new Error();
var accounts = [];
var account_map = {};
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
for (var i = 0; i < data.accounts.length; i++) {
var a = new Account();
a.fromJSON(data.accounts[i]);
accounts.push(a);
account_map[a.AccountId] = a;
}
//Populate Children arrays in account objects
for (var i = 0; i < accounts.length; i++) {
var a = accounts[i];
if (!a.isRootAccount())
account_map[a.ParentAccountId].Children.push(a);
}
}
this.setState({accounts: accounts, account_map: account_map});
}.bind(this),
error: this.ajaxError
});
},
handleErrorClear: function() {
this.setState({error: new Error()});
},
handleLoginSubmit: function(user) {
$.ajax({
type: "POST",
dataType: "json",
url: "session/",
data: {user: user.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.getSession();
this.setHash("home");
}
}.bind(this),
error: this.ajaxError
});
},
handleLogoutSubmit: function() {
this.setState({accounts: [], account_map: {}});
$.ajax({
type: "DELETE",
dataType: "json",
url: "session/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
}
this.setState({session: new Session(), user: new User()});
}.bind(this),
error: this.ajaxError
});
},
handleAccountSettings: function() {
this.setState({showAccountSettingsModal: true});
},
handleSettingsSubmitted: function(user) {
this.setState({
user: user,
showAccountSettingsModal: false
});
},
handleSettingsCanceled: function(user) {
this.setState({showAccountSettingsModal: false});
},
handleCreateNewUser: function() {
this.setHash("new_user");
},
handleGoHome: function(user) {
this.setHash("home");
},
handleCreateAccount: function(account) {
$.ajax({
type: "POST",
dataType: "json",
url: "account/",
data: {account: account.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.getAccounts();
}
}.bind(this),
error: this.ajaxError
});
},
handleUpdateAccount: function(account) {
$.ajax({
type: "PUT",
dataType: "json",
url: "account/"+account.AccountId+"/",
data: {account: account.toJSON()},
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.getAccounts();
}
}.bind(this),
error: this.ajaxError
});
},
handleDeleteAccount: function(account) {
$.ajax({
type: "DELETE",
dataType: "json",
url: "account/"+account.AccountId+"/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
this.setState({error: e});
} else {
this.getAccounts();
}
}.bind(this),
error: this.ajaxError
});
},
render: function() {
var mainContent;
if (this.state.hash == "new_user") {
mainContent = <NewUserForm onNewUser={this.handleGoHome} onCancel={this.handleGoHome}/>
} else {
if (this.state.user.isUser())
mainContent =
<TabbedArea defaultActiveKey='1' className="">
<TabPane tab="Accounts" eventKey='1' className="fullheight">
<AccountsTab
className="fullheight"
accounts={this.state.accounts}
account_map={this.state.account_map}
securities={this.state.securities}
security_map={this.state.security_map}
onCreateAccount={this.handleCreateAccount}
onUpdateAccount={this.handleUpdateAccount}
onDeleteAccount={this.handleDeleteAccount} />
</TabPane>
<TabPane tab="Scheduled Transactions" eventKey='2' className="fullheight">Scheduled transactions go here...</TabPane>
<TabPane tab="Budgets" eventKey='3' className="fullheight">Budgets go here...</TabPane>
<TabPane tab="Reports" eventKey='4' className="fullheight">Reports go here...</TabPane>
</TabbedArea>
else
mainContent =
<Jumbotron>
<center>
<h1>Money<i>Go</i></h1>
<p><i>Go</i> manage your money.</p>
</center>
</Jumbotron>
}
return (
<div className="fullheight ui">
<TopBar
error={this.state.error}
onErrorClear={this.handleErrorClear}
onLoginSubmit={this.handleLoginSubmit}
onCreateNewUser={this.handleCreateNewUser}
user={this.state.user}
onAccountSettings={this.handleAccountSettings}
onLogoutSubmit={this.handleLogoutSubmit} />
{mainContent}
<AccountSettingsModal
show={this.state.showAccountSettingsModal}
user={this.state.user}
onSubmit={this.handleSettingsSubmitted}
onCancel={this.handleSettingsCanceled}/>
</div>
);
}
});
React.render(<MoneyGoApp />, document.getElementById("content"));