-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from mehl321/theo
Wallet
- Loading branch information
Showing
14 changed files
with
517 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
compiled.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
{accounting.formatMoney(this.props.balance, '')} | ||
</h1> | ||
); | ||
} | ||
|
||
}); | ||
|
||
module.exports = Balance; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.