balance data fetching refactor, preferences tweaks
This commit is contained in:
parent
96862db1c1
commit
057f27a970
|
@ -21,6 +21,7 @@
|
|||
"@coingecko/cryptoformat": "^0.3.1",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@reach/router": "^1.2.1",
|
||||
"ethereum-address": "0.0.4",
|
||||
"ms": "^2.1.1",
|
||||
"react": "^16.8.6",
|
||||
"react-blockies": "^1.4.1",
|
||||
|
|
17
src/App.css
17
src/App.css
|
@ -1,5 +1,3 @@
|
|||
@import '../node_modules/@oceanprotocol/typographies/css/ocean-typo.css';
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
|
@ -54,23 +52,16 @@ html.fullscreen {
|
|||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
font-family: 'Sharp Sans Display', -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: 'Sharp Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
Helvetica, Arial, sans-serif;
|
||||
font-weight: 600;
|
||||
h4,
|
||||
h5 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.app {
|
||||
margin-top: 35px;
|
||||
padding: 5% 7%;
|
||||
cursor: default;
|
||||
height: 100vh;
|
||||
height: calc(100vh - 5%);
|
||||
transition: .15s ease-out;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
.number {
|
||||
margin: 0;
|
||||
transition: .15s ease-out;
|
||||
-webkit-app-region: no-drag;
|
||||
-webkit-user-select: text;
|
||||
font-size: 1rem;
|
||||
display: inline-block;
|
||||
padding: 0 .3rem;
|
||||
animation: fadeIn .5s ease-out;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.updated {
|
||||
animation: updated .5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes updated {
|
||||
0% {
|
||||
background: rgba(255, 255, 255, .2);
|
||||
}
|
||||
|
||||
100% {
|
||||
background: rgba(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
|
@ -1,24 +1,28 @@
|
|||
import React from 'react'
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { AppContext } from '../store/createContext'
|
||||
import { locale } from '../util/moneyFormatter'
|
||||
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||
import './Balance.css'
|
||||
|
||||
const Balance = ({ balance }) => (
|
||||
<h1 className="number">
|
||||
<AppContext.Consumer>
|
||||
{({ currency }) =>
|
||||
formatCurrency(balance[currency], currency.toUpperCase(), locale)
|
||||
export default class Balance extends PureComponent {
|
||||
static contextType = AppContext
|
||||
|
||||
static propTypes = {
|
||||
balance: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currency } = this.context
|
||||
const { balance } = this.props
|
||||
|
||||
return (
|
||||
<h1 className="number">
|
||||
{formatCurrency(balance[currency], currency.toUpperCase(), locale)
|
||||
.replace(/BTC/, 'Ƀ')
|
||||
.replace(/ETH/, 'Ξ')
|
||||
.replace(/OCEAN/, 'Ọ')
|
||||
}
|
||||
</AppContext.Consumer>
|
||||
</h1>
|
||||
)
|
||||
|
||||
Balance.propTypes = {
|
||||
balance: PropTypes.object.isRequired
|
||||
.replace(/OCEAN/, 'Ọ')}
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Balance
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, { PureComponent } from 'react'
|
||||
import { AppContext } from '../store/createContext'
|
||||
import Balance from './Balance'
|
||||
import { prices } from '../../config'
|
||||
|
@ -19,26 +19,26 @@ const calculateTotalBalance = (accounts, currency) => {
|
|||
return balanceTotal
|
||||
}
|
||||
|
||||
const Total = () => (
|
||||
<div className="number-unit number-unit--main">
|
||||
<AppContext.Consumer>
|
||||
{({ accounts }) => {
|
||||
const conversions = Object.assign(
|
||||
...prices.map(key => ({
|
||||
[key]: calculateTotalBalance(accounts, key)
|
||||
}))
|
||||
)
|
||||
export default class Total extends PureComponent {
|
||||
static contextType = AppContext
|
||||
|
||||
const balanceNew = {
|
||||
ocean: calculateTotalBalance(accounts, 'ocean'),
|
||||
...conversions
|
||||
}
|
||||
render() {
|
||||
const conversions = Object.assign(
|
||||
...prices.map(key => ({
|
||||
[key]: calculateTotalBalance(this.context.accounts, key)
|
||||
}))
|
||||
)
|
||||
|
||||
return <Balance balance={balanceNew} />
|
||||
}}
|
||||
</AppContext.Consumer>
|
||||
<span className="label">Total Balance</span>
|
||||
</div>
|
||||
)
|
||||
const balanceNew = {
|
||||
ocean: calculateTotalBalance(this.context.accounts, 'ocean'),
|
||||
...conversions
|
||||
}
|
||||
|
||||
export default Total
|
||||
return (
|
||||
<div className="number-unit number-unit--main">
|
||||
<Balance balance={balanceNew} />
|
||||
<span className="label">Total Balance</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,23 +64,6 @@
|
|||
color: #f6388a;
|
||||
}
|
||||
|
||||
.number {
|
||||
margin: 0;
|
||||
transition: .15s ease-out;
|
||||
font-weight: 400;
|
||||
-webkit-app-region: no-drag;
|
||||
-webkit-user-select: text;
|
||||
font-size: 1rem;
|
||||
display: inline-block;
|
||||
padding: 0 .3rem;
|
||||
animation: fadeIn .5s ease-out;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.updated {
|
||||
animation: updated .5s ease-out;
|
||||
}
|
||||
|
||||
.number-unit-wrap--accounts {
|
||||
min-height: 55px;
|
||||
}
|
||||
|
@ -102,16 +85,6 @@
|
|||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
@keyframes updated {
|
||||
0% {
|
||||
background: rgba(255, 255, 255, .2);
|
||||
}
|
||||
|
||||
100% {
|
||||
background: rgba(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
|
|
|
@ -7,17 +7,14 @@
|
|||
}
|
||||
|
||||
.preferences__title {
|
||||
font-size: 2rem;
|
||||
font-size: 2.2rem;
|
||||
margin-top: -1rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.preferences__close {
|
||||
text-decoration: none;
|
||||
font-family: 'Sharp Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
Helvetica, Arial, sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 2.5rem;
|
||||
font-size: 2rem;
|
||||
position: absolute;
|
||||
top: -1.5rem;
|
||||
right: 0;
|
||||
|
@ -37,13 +34,17 @@
|
|||
border-top-color: #303030;
|
||||
}
|
||||
|
||||
.preference__list li {
|
||||
list-style: none;
|
||||
.preference__list li,
|
||||
.preference__list li > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preference__list li {
|
||||
list-style: none;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
padding-top: .3rem;
|
||||
padding-top: .25rem;
|
||||
padding-bottom: .25rem;
|
||||
}
|
||||
|
||||
|
@ -56,14 +57,16 @@
|
|||
border: 0;
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
color: #f6388a;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
button.delete {
|
||||
position: relative;
|
||||
font-size: 2rem;
|
||||
top: -.2rem;
|
||||
color: #41474e;
|
||||
transition: color .5s ease-out;
|
||||
}
|
||||
|
@ -77,18 +80,27 @@ button.delete:hover {
|
|||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
.preference__title,
|
||||
.preference__help {
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.preference__title {
|
||||
font-size: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.preference__help {
|
||||
color: #8b98a9;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.preference .identicon {
|
||||
width: 1.5rem !important;
|
||||
height: 1.5rem !important;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
margin-top: -.2rem;
|
||||
margin-right: .5rem;
|
||||
margin-right: .75rem;
|
||||
}
|
||||
|
||||
.preference__input {
|
||||
|
@ -105,3 +117,7 @@ button.delete:hover {
|
|||
.dark .preference__input {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.preference__error {
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
|
|
@ -2,15 +2,16 @@ import React, { PureComponent } from 'react'
|
|||
import { Link } from '@reach/router'
|
||||
import Store from 'electron-store'
|
||||
import Blockies from 'react-blockies'
|
||||
import './Preferences.css'
|
||||
import ethereum_address from 'ethereum-address'
|
||||
import { AppContext } from '../store/createContext'
|
||||
import './Preferences.css'
|
||||
|
||||
export default class Preferences extends PureComponent {
|
||||
static contextType = AppContext
|
||||
|
||||
store = new Store()
|
||||
|
||||
state = { accounts: [], input: '' }
|
||||
state = { accounts: [], input: '', error: '' }
|
||||
|
||||
componentDidMount() {
|
||||
if (this.store.has('accounts')) {
|
||||
|
@ -25,15 +26,27 @@ export default class Preferences extends PureComponent {
|
|||
handleSave = e => {
|
||||
e.preventDefault()
|
||||
|
||||
if (
|
||||
this.state.input !== '' &&
|
||||
!this.state.accounts.includes(this.state.input) // duplication check
|
||||
) {
|
||||
const joined = [...this.state.accounts, this.state.input]
|
||||
const { accounts, input } = this.state
|
||||
|
||||
const isEmpty = input === ''
|
||||
const isDuplicate = accounts.includes(input)
|
||||
const isAddress = ethereum_address.isAddress(input)
|
||||
|
||||
if (isEmpty) {
|
||||
this.setState({ error: 'Please enter an address.' })
|
||||
return
|
||||
} else if (isDuplicate) {
|
||||
this.setState({ error: 'Address already added. Try another one.' })
|
||||
return
|
||||
} else if (!isAddress) {
|
||||
this.setState({ error: 'Not an Ethereum address. Try another one.' })
|
||||
return
|
||||
} else {
|
||||
const joined = [...accounts, input]
|
||||
|
||||
this.store.set('accounts', joined)
|
||||
this.setState({ accounts: joined, input: '' })
|
||||
this.context.setBalances(joined)
|
||||
this.setState({ accounts: joined, input: '', error: '' })
|
||||
this.context.setBalances()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,10 +63,12 @@ export default class Preferences extends PureComponent {
|
|||
|
||||
this.store.set('accounts', array)
|
||||
this.setState({ accounts: array })
|
||||
this.context.setBalances(array)
|
||||
this.context.setBalances()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { accounts, input, error } = this.state
|
||||
|
||||
return (
|
||||
<div className="preferences">
|
||||
<h1 className="preferences__title">Preferences</h1>{' '}
|
||||
|
@ -62,9 +77,12 @@ export default class Preferences extends PureComponent {
|
|||
</Link>
|
||||
<div className="preference">
|
||||
<h2 className="preference__title">Accounts</h2>
|
||||
<p className="preference__help">
|
||||
Add Ethereum account addresses holding Ocean Tokens.
|
||||
</p>
|
||||
<ul className="preference__list">
|
||||
{this.state.accounts &&
|
||||
this.state.accounts.map(account => (
|
||||
{accounts &&
|
||||
accounts.map(account => (
|
||||
<li key={account}>
|
||||
<div>
|
||||
<Blockies seed={account} size={10} scale={3} />
|
||||
|
@ -84,7 +102,7 @@ export default class Preferences extends PureComponent {
|
|||
<input
|
||||
type="text"
|
||||
placeholder="0xxxxxxxx"
|
||||
value={this.state.input}
|
||||
value={input}
|
||||
onChange={this.handleInputChange}
|
||||
className="preference__input"
|
||||
/>
|
||||
|
@ -96,6 +114,7 @@ export default class Preferences extends PureComponent {
|
|||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
{error !== '' && <div className="preference__error">{error}</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
|
|||
import ms from 'ms'
|
||||
import Store from 'electron-store'
|
||||
import { AppContext } from './createContext'
|
||||
import fetchData from '../util/fetch'
|
||||
import { refreshInterval, prices, oceanTokenContract } from '../../config'
|
||||
|
||||
export default class AppProvider extends PureComponent {
|
||||
|
@ -19,24 +20,19 @@ export default class AppProvider extends PureComponent {
|
|||
needsConfig: false,
|
||||
prices: Object.assign(...prices.map(key => ({ [key]: 0 }))),
|
||||
toggleCurrencies: currency => this.setState({ currency }),
|
||||
setBalances: account => this.setBalances(account)
|
||||
setBalances: () => this.setBalances()
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { accountsPref } = await this.getAccounts()
|
||||
await this.fetchAndSetPrices()
|
||||
await this.setBalances(accountsPref)
|
||||
await this.setBalances()
|
||||
|
||||
await setInterval(this.fetchAndSetPrices, ms(refreshInterval))
|
||||
await setInterval(this.setBalances, ms(refreshInterval))
|
||||
setInterval(this.fetchAndSetPrices, ms(refreshInterval))
|
||||
setInterval(this.setBalances, ms(refreshInterval))
|
||||
|
||||
this.setState({ isLoading: false })
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearAccounts()
|
||||
}
|
||||
|
||||
getAccounts() {
|
||||
let accountsPref
|
||||
|
||||
|
@ -50,64 +46,41 @@ export default class AppProvider extends PureComponent {
|
|||
accountsPref = []
|
||||
}
|
||||
|
||||
return { accountsPref }
|
||||
return accountsPref
|
||||
}
|
||||
|
||||
clearAccounts() {
|
||||
this.setState({ accounts: [] })
|
||||
}
|
||||
|
||||
async fetch(url) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
|
||||
if (response.status !== 200) {
|
||||
return console.log('Non-200 response: ' + response.status) // eslint-disable-line
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
if (!json) return
|
||||
|
||||
return json
|
||||
} catch (error) {
|
||||
console.log('Error parsing json:' + error) // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
async fetchBalance(account) {
|
||||
const json = await this.fetch(
|
||||
async getBalance(account) {
|
||||
const json = await fetchData(
|
||||
`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
|
||||
const balance = (json.result /= 1000000000000000000) // Convert from vodka 10^18
|
||||
return balance
|
||||
}
|
||||
|
||||
async fetchAndSetPrices() {
|
||||
fetchAndSetPrices = async () => {
|
||||
const currencies = prices.join(',')
|
||||
const json = await this.fetch(
|
||||
const json = await fetchData(
|
||||
`https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}`
|
||||
)
|
||||
|
||||
await this.setState({
|
||||
prices: Object.assign(
|
||||
...prices.map(key => ({
|
||||
ocean: 1,
|
||||
[key]: json['ocean-protocol'][key]
|
||||
}))
|
||||
)
|
||||
})
|
||||
const newPrizes = Object.assign(
|
||||
...prices.map(key => ({
|
||||
ocean: 1,
|
||||
[key]: json['ocean-protocol'][key]
|
||||
}))
|
||||
)
|
||||
|
||||
this.setState({ prices: newPrizes })
|
||||
}
|
||||
|
||||
setBalances(accounts) {
|
||||
// TODO: make this less lazy and update numbers in place
|
||||
// when they are changed instead of resetting all to 0 here
|
||||
this.clearAccounts()
|
||||
setBalances = async () => {
|
||||
const accountsPref = await this.getAccounts()
|
||||
|
||||
const accountsArray = accounts ? accounts : this.state.accounts
|
||||
let newAccounts = []
|
||||
|
||||
accountsArray.map(async account => {
|
||||
const oceanBalance = await this.fetchBalance(account)
|
||||
for (const account of accountsPref) {
|
||||
const oceanBalance = await this.getBalance(account)
|
||||
|
||||
const conversions = Object.assign(
|
||||
...prices.map(key => ({
|
||||
|
@ -118,15 +91,17 @@ export default class AppProvider extends PureComponent {
|
|||
const newAccount = {
|
||||
address: account,
|
||||
balance: {
|
||||
ocean: oceanBalance || 0,
|
||||
ocean: oceanBalance,
|
||||
...conversions
|
||||
}
|
||||
}
|
||||
|
||||
await this.setState(prevState => ({
|
||||
accounts: [...prevState.accounts, newAccount]
|
||||
}))
|
||||
})
|
||||
newAccounts.push(newAccount)
|
||||
}
|
||||
|
||||
if (newAccounts !== this.state.accounts) {
|
||||
this.setState({ accounts: newAccounts })
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createContext } from 'react'
|
||||
|
||||
const AppContext = createContext({})
|
||||
const AppContext = createContext()
|
||||
|
||||
export { AppContext }
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
const fetchData = async url => {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
|
||||
if (response.status !== 200) {
|
||||
return console.log('Non-200 response: ' + response.status) // eslint-disable-line
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
if (!json) return
|
||||
|
||||
return json
|
||||
} catch (error) {
|
||||
console.log('Error parsing json:' + error) // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
export default fetchData
|
Loading…
Reference in New Issue