diff --git a/README.md b/README.md index c9c2da3..da2b443 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,26 @@ # ocean-balance -> Simple Electron-based desktop app to retrieve and display your total Ocean balances. +> Simple Electron-based desktop app to retrieve and display your total Ocean Token balances. +> https://oceanprotocol.com ## Usage -Clone and run: +Clone, add adresses, and run: ```bash # Clone this repository git clone git@github.com:kremalicious/ocean-balance.git cd ocean-balance +# Add one or more Ethereum addresses to config file +vi config.js + # Install dependencies npm install # Run the app in dev mode npm start ``` -## Configuration - ## Build package ```bash diff --git a/constants.js b/config.js similarity index 100% rename from constants.js rename to config.js diff --git a/src/App.css b/src/App.css index a62b091..65f612e 100644 --- a/src/App.css +++ b/src/App.css @@ -55,11 +55,25 @@ html.fullscreen { transform: translate3d(0, -36px, 0); } +.main { + width: 100%; + padding: 5%; + background: #303030; + border-radius: 5px; + border: .1rem solid #41474e; + min-height: 186px; + display: flex; + align-items: center; + flex-wrap: wrap; + position: relative; +} + .number-unit-wrap { display: flex; width: 100%; flex-wrap: wrap; justify-content: space-around; + position: relative; } .number-unit { @@ -92,6 +106,20 @@ html.fullscreen { font-size: .85rem; display: block; white-space: nowrap; + margin-top: .3rem; +} + +.number-unit-wrap--accounts { + min-height: 55px; +} + +.number-unit--main { + padding-bottom: 5%; + border-bottom: 1px solid #41474e; +} + +.number-unit--main .number { + font-size: 2.5rem; } @keyframes updated { diff --git a/src/App.jsx b/src/App.jsx index 907b067..a0e0cf8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,9 +1,12 @@ -import './App.css' import React, { PureComponent } from 'react' import { webFrame } from 'electron' import AppProvider from './store/AppProvider' +import { Consumer } from './store/createContext' import Titlebar from './components/Titlebar' -import Accounts from './components/Accounts' +import Total from './components/Total' +import Account from './components/Account' +import Actions from './components/Actions' +import './App.css' // // Disable zooming @@ -11,17 +14,28 @@ import Accounts from './components/Accounts' webFrame.setVisualZoomLevelLimits(1, 1) webFrame.setLayoutZoomLevelLimits(0, 0) -class App extends PureComponent { +export default class App extends PureComponent { render() { return (
- +
+ + + +
+ + {({ accounts }) => + accounts.map((account, i) => ( + + )) + } + +
+
) } } - -export default App diff --git a/src/components/Account.jsx b/src/components/Account.jsx index 6f3c7df..061fef3 100644 --- a/src/components/Account.jsx +++ b/src/components/Account.jsx @@ -1,33 +1,27 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import { fiatFormatter, numberFormatter } from '../util/moneyFormatter' +import Balance from './Balance' -class Account extends PureComponent { +export default class Account extends PureComponent { static propTypes = { - isNativeShown: PropTypes.bool.isRequired, account: PropTypes.shape({ address: PropTypes.string.isRequired, balance: PropTypes.shape({ ocean: PropTypes.number.isRequired, - eur: PropTypes.number.isRequired + eur: PropTypes.number.isRequired, + usd: PropTypes.number.isRequired }).isRequired }) } render() { - const { balance, address } = this.props.account - const { ocean, eur } = balance + const { account } = this.props + const { balance, address } = account return (

- {this.props.isNativeShown ? ( - {fiatFormatter('EUR', eur)} - ) : ( - - {numberFormatter(ocean) || 0} Ọ - - )} +

{address.substring(0, 12)}... @@ -36,5 +30,3 @@ class Account extends PureComponent { ) } } - -export default Account diff --git a/src/components/Accounts.css b/src/components/Accounts.css deleted file mode 100644 index 97dbdf9..0000000 --- a/src/components/Accounts.css +++ /dev/null @@ -1,29 +0,0 @@ -.main { - width: 100%; - padding: 5%; - background: #303030; - border-radius: 5px; - border: .1rem solid #41474e; - min-height: 186px; - display: flex; - align-items: center; - flex-wrap: wrap; - position: relative; -} - -.number-unit-wrap--accounts { - min-height: 55px; -} - -.number-unit--main { - padding-bottom: 5%; - border-bottom: 1px solid #41474e; -} - -.number-unit--main .number { - font-size: 2.5rem; -} - -.number-unit--main .label { - font-size: .95rem; -} diff --git a/src/components/Accounts.jsx b/src/components/Accounts.jsx deleted file mode 100644 index 95fd0a5..0000000 --- a/src/components/Accounts.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, { PureComponent } from 'react' -import { Consumer } from '../store/createContext' -import Total from './Total' -import Account from './Account' -import './Accounts.css' - -export default class Accounts extends PureComponent { - state = { - isNativeShown: false - } - - toggleBalances = () => { - this.setState({ isNativeShown: !this.state.isNativeShown }) - } - - render() { - return ( -
- - -
- - {({ accounts }) => - accounts.map((account, i) => ( - - )) - } - -
-
- ) - } -} diff --git a/src/components/Actions.css b/src/components/Actions.css new file mode 100644 index 0000000..f7eb065 --- /dev/null +++ b/src/components/Actions.css @@ -0,0 +1,17 @@ +.actions { + width: 100%; + text-align: right; + position: absolute; + top: -2rem; + right: 0; +} + +button { + background: none; + border: 0; + box-shadow: none; + margin: 0; + outline: 0; + color: #f6388a; + font-size: .85rem; +} diff --git a/src/components/Actions.jsx b/src/components/Actions.jsx new file mode 100644 index 0000000..a739942 --- /dev/null +++ b/src/components/Actions.jsx @@ -0,0 +1,21 @@ +import React, { PureComponent } from 'react' +import { Consumer } from '../store/createContext' +import './Actions.css' + +export default class Actions extends PureComponent { + render() { + return ( +
+ + {({ toggleCurrencies }) => ( + <> + + + + + )} + +
+ ) + } +} diff --git a/src/components/Balance.jsx b/src/components/Balance.jsx new file mode 100644 index 0000000..792a920 --- /dev/null +++ b/src/components/Balance.jsx @@ -0,0 +1,34 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Consumer } from '../store/createContext' +import { numberFormatter, fiatFormatter } from '../util/moneyFormatter' + +const Balance = ({ balance }) => { + const { ocean, eur, usd } = balance + + return ( + + {({ currency }) => + currency === 'ocean' ? ( + + Ọ {numberFormatter(ocean) || 0} + + ) : currency === 'eur' ? ( + {fiatFormatter('EUR', eur)} + ) : ( + {fiatFormatter('USD', usd)} + ) + } + + ) +} + +Balance.propTypes = { + balance: PropTypes.shape({ + ocean: PropTypes.number.isRequired, + eur: PropTypes.number.isRequired, + usd: PropTypes.number.isRequired + }) +} + +export default Balance diff --git a/src/components/Total.jsx b/src/components/Total.jsx index 3f9e4da..7fc328f 100644 --- a/src/components/Total.jsx +++ b/src/components/Total.jsx @@ -1,12 +1,12 @@ import React from 'react' import { Consumer } from '../store/createContext' -import { numberFormatter } from '../util/moneyFormatter' +import Balance from './Balance' -const calculateTotalBalance = accounts => { +const calculateTotalBalance = (accounts, currency) => { const balanceTotalArray = [] for (const account of accounts) { - balanceTotalArray.push(account.balance.ocean) + balanceTotalArray.push(account.balance[currency]) } // Convert array to numbers and add numbers together @@ -15,15 +15,28 @@ const calculateTotalBalance = accounts => { 0 ) - return numberFormatter(balanceTotal) + return balanceTotal } const Total = () => (
{({ accounts }) => { - const total = calculateTotalBalance(accounts) - return

{total || 0} Ọ

+ const totalOcean = calculateTotalBalance(accounts, 'ocean') + const totalEur = calculateTotalBalance(accounts, 'eur') + const totalUsd = calculateTotalBalance(accounts, 'usd') + + const balance = { + ocean: totalOcean, + eur: totalEur, + usd: totalUsd + } + + return ( +

+ +

+ ) }}
Total balance diff --git a/src/store/AppProvider.jsx b/src/store/AppProvider.jsx index b6daab7..aac3f3c 100644 --- a/src/store/AppProvider.jsx +++ b/src/store/AppProvider.jsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import ms from 'ms' import { Provider } from './createContext' -import { accounts, refreshInterval, oceanTokenContract } from '../../constants' +import { accounts, refreshInterval, oceanTokenContract } from '../../config' export default class AppProvider extends PureComponent { static propTypes = { @@ -10,7 +10,9 @@ export default class AppProvider extends PureComponent { } state = { - accounts: [] + accounts: [], + currency: 'ocean', + toggleCurrencies: currency => this.setState({ currency }) } componentDidMount() { @@ -26,11 +28,9 @@ export default class AppProvider extends PureComponent { this.setState({ accounts: [] }) } - fetchBalance = async account => { + async fetch(url) { try { - const response = await fetch( - `https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${oceanTokenContract}&address=${account}` - ) + const response = await fetch(url) if (response.status !== 200) { return console.log('Non-200 response: ' + response.status) // eslint-disable-line @@ -39,16 +39,37 @@ export default class AppProvider extends PureComponent { const json = await response.json() if (!json) return - const balance = (json.result /= 1000000000000000000) // Convert from wei 10^18 - return balance + return json } catch (error) { - console.log('Error parsing etherscan.io json:' + error) // eslint-disable-line + console.log('Error parsing json:' + error) // eslint-disable-line } } - setBalances = () => { + fetchBalance = async account => { + const json = await this.fetch( + `https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${oceanTokenContract}&address=${account}&tag=latest` + ) + + const balance = (json.result /= 1000000000000000000) // Convert from wei 10^18 + return balance + } + + fetchPrice = async () => { + const json = await this.fetch( + 'https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=usd,eur' + ) + + const { usd, eur } = json['ocean-protocol'] + return { usd, eur } + } + + setBalances = async () => { + // TODO: make this less lazy and update numbers in place + // when they are changed instead of resetting all to 0 here this.clearAccounts() + const { usd, eur } = await this.fetchPrice() + accounts.map(async account => { const oceanBalance = await this.fetchBalance(account) @@ -56,7 +77,8 @@ export default class AppProvider extends PureComponent { address: account, balance: { ocean: oceanBalance || 0, - eur: 0 + eur: oceanBalance / eur || 0, + usd: oceanBalance / usd || 0 } } diff --git a/src/util/moneyFormatter.js b/src/util/moneyFormatter.js index f24c974..f976e14 100644 --- a/src/util/moneyFormatter.js +++ b/src/util/moneyFormatter.js @@ -4,14 +4,14 @@ const locale = navigator.language const numberFormatter = number => new Intl.NumberFormat(locale, { minimumFractionDigits: 0, - maximumFractionDigits: 2 + maximumFractionDigits: 4 }).format(number) const fiatFormatter = (currency, number) => new Intl.NumberFormat(locale, { style: 'currency', currency, - minimumFractionDigits: 0, + minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)