Skip to content

Commit

Permalink
Merge pull request #1 from mehl321/theo
Browse files Browse the repository at this point in the history
Wallet
  • Loading branch information
teawaterwire committed Sep 3, 2015
2 parents dcf66ff + 99673d6 commit b7068cf
Show file tree
Hide file tree
Showing 14 changed files with 517 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
compiled.js
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Wallet

<p align="center">
<img src="screenshot.png" alt="Wallet">
</p>

A nice little wallet app built with React. Deployed at https://code.soulas.net/wallet/.

The responsive design was implemented with [React-Bootstrap](http://react-bootstrap.github.io/).

## Installation

First make sure that you have `Git` and `npm` installed on your machine.

To setup the app, run those commands from your terminal:

```bash
git clone [email protected]:mehl321/wallet.git
cd wallet
npm install
```

To build for production:

```bash
npm run build
```

To run in development/watch mode:

```bash
npm run watch
```
8 changes: 8 additions & 0 deletions assets/css/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.main-wrapper {
max-width: 400px;
margin: 0 auto;
}

.smaller {
font-size: 0.9em
}
119 changes: 119 additions & 0 deletions components/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
let React = require('react');
let Immutable= require('immutable');
let fx = require('money');

let MainMenu = require('./MainMenu');
let CurrencySelection = require('./CurrencySelection');
let Balance = require('./Balance');
let TransactionAction = require('./TransactionAction');
let TransactionHistory = require('./TransactionHistory');

fx.base = 'GBP';
fx.rates = {
'BTC': 0.0073,
'EUR': 1.36,
}

// the App component contains all the other components
let App = React.createClass({

getInitialState() {

let transactions = localStorage.transactions ?
Immutable.List(JSON.parse(localStorage.transactions)) :
Immutable.List();

let currency = localStorage.currency ? JSON.parse(localStorage.currency) : 'GBP';
let balance = this.getNewBalance(transactions, currency);

return {
currency: currency,
transactions: transactions,
balance: balance,
};
},

// update local storage when currency, balance or transactions change
componentWillUpdate(nextProps, nextState) {
if (localStorage.currency !== nextState.currency) {
localStorage.currency = JSON.stringify(nextState.currency);
}

if (localStorage.transactions !== nextState.transactions) {
localStorage.transactions = JSON.stringify(nextState.transactions.toJS());
}

if (localStorage.balance !== nextState.balance) {
localStorage.balance = JSON.stringify(nextState.balance);
}
},

// reset everything
handleReset() {
localStorage.clear();
this.setState(this.getInitialState());
},

// update currency state and recalculate the balance
handleCurrencyChange(currency) {
let balance = this.getNewBalance(this.state.transactions, currency);

this.setState({
currency: currency,
balance: balance,
});
},

// add and save new transaction
// recalculate balance
handleNewTransaction(transaction) {
transaction.currency = this.state.currency;

let transacs = this.state.transactions.unshift(transaction);

let balance = this.getNewBalance(transacs, this.state.currency);

this.setState({
transactions: transacs,
balance: balance,
});
},

// return recalculated balance
getNewBalance(transactions, currency) {
return transactions.reduce((previous, current) => {
return previous + fx.convert(
current.amount,
{from: current.currency, to: currency}
);
}, 0);
},

render() {
return (
<div>
<MainMenu onResetClick={this.handleReset}></MainMenu>

<div className="main-wrapper container">
<CurrencySelection currency={this.state.currency} onCurrencyChange={this.handleCurrencyChange}>
</CurrencySelection>

<Balance currency={this.state.currency} balance={this.state.balance}></Balance>

<TransactionAction
currency={this.state.currency}
balance={this.state.balance}
onNewTransaction={this.handleNewTransaction}
>
</TransactionAction>

<TransactionHistory currency={this.state.currency} transactions={this.state.transactions}>
</TransactionHistory>
</div>
</div>
);
}

});

module.exports = App;
22 changes: 22 additions & 0 deletions components/Balance.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
let React = require('react');
let classNames = require('classnames');
let accounting = require('accounting');

// balance display component
let Balance = React.createClass({

render() {
let faCurrency = 'fa-' + this.props.currency.toLowerCase();
let classesCurrency = classNames('fa', faCurrency);

return (
<h1 className="lead">
Balance: <span className="smaller"><i className={classesCurrency}></i></span>&nbsp;
{accounting.formatMoney(this.props.balance, '')}
</h1>
);
}

});

module.exports = Balance;
40 changes: 40 additions & 0 deletions components/CurrencySelection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
let React = require('react');

let classNames = require('classnames');

let {ButtonGroup, Button} = require('react-bootstrap');

// currency selection component
let CurrencySelection = React.createClass({

handleClick(currency) {
this.props.onCurrencyChange(currency);
},

renderCurrencyButton(currencyButton, currentCurrency) {
let faCurrency = 'fa-' + currencyButton.toLowerCase();
let classesCurrency = classNames('fa', 'fa-fw', faCurrency);

return(
<Button active={currentCurrency === currencyButton}
href='#'
onClick={this.handleClick.bind(this, currencyButton)}
>
<i className={classesCurrency}></i> {currencyButton}
</Button>
);
},

render() {
return (
<ButtonGroup justified>
{this.renderCurrencyButton('GBP', this.props.currency)}
{this.renderCurrencyButton('EUR', this.props.currency)}
{this.renderCurrencyButton('BTC', this.props.currency)}
</ButtonGroup>
);
}

});

module.exports = CurrencySelection;
20 changes: 20 additions & 0 deletions components/MainMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
let React = require('react');

let {Navbar, Nav, NavItem} = require('react-bootstrap');

// top menu
let MainMenu = React.createClass({
render() {
return (
<Navbar brand='Wallet' toggleNavKey={0}>
<Nav eventKey={0}>
<NavItem eventKey={2} href='#' onClick={this.props.onResetClick}>Reset</NavItem>
<NavItem eventKey={3} href='https://github.com/mehl321/wallet/'>View source</NavItem>
</Nav>
</Navbar>
);
}

});

module.exports = MainMenu;
143 changes: 143 additions & 0 deletions components/TransactionAction.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
let React = require('react');

let {Grid, Row, Col, Input, Button} = require('react-bootstrap');

let classNames = require('classnames');
let accounting = require('accounting');

const DEPOSIT = 'deposit';
const WITHDRAWAL = 'withdrawal';

// component to deposit or withdraw money
let TransactionAction = React.createClass({

getInitialState() {
return {
depositValue: '',
withdrawalValue: '',
};
},

// check amount not null number
// impossible to withdraw more than balance amount
isAmountValid(amount, type) {
let amountRegex = /^\d*(\.\d*){0,1}$/;

if (amountRegex.test(amount) && accounting.unformat(amount) > 0) {
return type === DEPOSIT ||
(type === WITHDRAWAL && amount <= this.props.balance);
} else {
return false;
}

},

// return 'error' if an amount has been entered but is invalid
handleValidAmountStyling(type) {
let typeName = type + 'Value';
let fieldValue = this.state[typeName];

if (fieldValue && !this.isAmountValid(fieldValue, type)) {
return 'error';
}
},

// used to submit new transaction with 'enter' key
handleKeyboard(type, e) {
let typeName = type + 'Value';

if (e.key === 'Enter' && this.isAmountValid(this.state[typeName], type)) {
this.handleNewTransaction(type);
}
},

// update inputs values
handleChange(type) {
let refName = type + 'Input';
let stateName = type + 'Value';
let state = {};

state[stateName] = this.refs[refName].getValue();

this.setState(state);
},

// create transaction object
handleNewTransaction(type) {
let refName = type + 'Input';

// clear inputs
this.setState({
depositValue: '',
withdrawalValue: '',
});

let transaction = {
timestamp: Math.floor(Date.now() / 1000),
};

// add negative sign for withdrawals
let amount = accounting.unformat(this.refs[refName].getValue());
transaction.amount = type === DEPOSIT ? amount : -1 * amount

this.props.onNewTransaction(transaction);
},

render() {
let faCurrency = 'fa-' + this.props.currency.toLowerCase();
let classesCurrency = classNames('fa', faCurrency);

return (
<form>
<Row>
<Col xs={6}>
<Input
type="text"
value={this.state.depositValue}
bsStyle={this.handleValidAmountStyling(DEPOSIT)}
ref="depositInput"
addonBefore={<i className={classesCurrency}></i>}
onChange={this.handleChange.bind(this, DEPOSIT)}
onKeyDown={this.handleKeyboard.bind(this, DEPOSIT)}
buttonAfter={
<Button
href="#"
disabled={!this.isAmountValid(this.state.depositValue, DEPOSIT)}
onClick={this.handleNewTransaction.bind(this, DEPOSIT)}>
<i className="fa fa-plus text-success"></i>
</Button>
}
placeholder="Deposit"
style={{'zIndex': 10}}
/>
</Col>

<Col xs={6}>
<Input
type="text"
value={this.state.withdrawalValue}
bsStyle={this.handleValidAmountStyling(WITHDRAWAL)}
ref="withdrawalInput"
addonBefore={<i className={classesCurrency}></i>}
onChange={this.handleChange.bind(this, WITHDRAWAL)}
onKeyDown={this.handleKeyboard.bind(this, WITHDRAWAL)}
buttonAfter={
<Button
href="#"
disabled={!this.isAmountValid(this.state.withdrawalValue, WITHDRAWAL)}
onClick={this.handleNewTransaction.bind(this, WITHDRAWAL)}>
<i className="fa fa-minus text-danger"></i>
</Button>
}
placeholder="Withdraw"
style={{'zIndex': 10}}
/>
</Col>
</Row>
</form>
);
}

});

module.exports = TransactionAction;
Loading

0 comments on commit b7068cf

Please sign in to comment.