Merge pull request #1 from kremalicious/feature/preferences
user preferences & router setup
This commit is contained in:
commit
63f4e1c2e4
|
@ -29,9 +29,6 @@ Clone, add adresses, and run:
|
|||
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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
module.exports = {
|
||||
accounts: ['ETH ADDRESS 1', 'ETH ADDRESS 2'],
|
||||
prices: ['eur', 'usd', 'btc', 'eth'],
|
||||
refreshInterval: '1m',
|
||||
oceanTokenContract: '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41'
|
||||
|
|
|
@ -20,8 +20,10 @@
|
|||
"dependencies": {
|
||||
"@coingecko/cryptoformat": "^0.3.1",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@reach/router": "^1.2.1",
|
||||
"ms": "^2.1.1",
|
||||
"react": "^16.8.6",
|
||||
"react-blockies": "^1.4.1",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -31,6 +33,7 @@
|
|||
"@babel/preset-env": "^7.4.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@svgr/webpack": "^4.2.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.5",
|
||||
"css-loader": "^2.1.1",
|
||||
|
@ -38,6 +41,7 @@
|
|||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-installer-dmg": "^2.0.0",
|
||||
"electron-packager": "^13.1.1",
|
||||
"electron-store": "^3.2.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-prettier": "^4.2.0",
|
||||
"eslint-plugin-react": "^7.13.0",
|
||||
|
|
147
src/App.css
147
src/App.css
|
@ -1,24 +1,26 @@
|
|||
@import '../node_modules/@oceanprotocol/typographies/css/ocean-typo.css';
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
background: #fcfcfc !important;
|
||||
}
|
||||
|
||||
html.dark,
|
||||
.dark body {
|
||||
background: #141414 !important;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fcfcfc !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html.dark,
|
||||
.dark body {
|
||||
background: #141414 !important;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
@ -64,11 +66,18 @@ button {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.app__content {
|
||||
.app {
|
||||
margin-top: 35px;
|
||||
padding: 5% 7%;
|
||||
cursor: default;
|
||||
height: calc(100vh - 35px);
|
||||
height: 100vh;
|
||||
transition: .15s ease-out;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.app,
|
||||
.app > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
@ -76,112 +85,6 @@ button {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.fullscreen .app__content {
|
||||
.fullscreen .app {
|
||||
transform: translate3d(0, -36px, 0);
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 100%;
|
||||
padding: 5%;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
border: .1rem solid #e2e2e2;
|
||||
min-height: 222px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
animation: fadein .5s .5s ease-out;
|
||||
}
|
||||
|
||||
.dark .main {
|
||||
background: #222;
|
||||
border-color: #303030;
|
||||
}
|
||||
|
||||
.number-unit-wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.number-unit {
|
||||
text-align: center;
|
||||
flex: 1 1 20%;
|
||||
margin-top: 5%;
|
||||
padding-left: 2%;
|
||||
padding-right: 2%;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #8b98a9;
|
||||
font-size: .85rem;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
margin-top: .3rem;
|
||||
transition: color .2s ease-out;
|
||||
}
|
||||
|
||||
.number-unit:hover .label {
|
||||
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;
|
||||
}
|
||||
|
||||
.number-unit--main {
|
||||
padding-bottom: 5%;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
}
|
||||
|
||||
.number-unit--main:hover .label {
|
||||
color: #8b98a9;
|
||||
}
|
||||
|
||||
.dark .number-unit--main {
|
||||
border-bottom-color: #303030;
|
||||
}
|
||||
|
||||
.number-unit--main .number {
|
||||
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;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
48
src/App.jsx
48
src/App.jsx
|
@ -1,12 +1,15 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import {
|
||||
Router,
|
||||
createMemorySource,
|
||||
createHistory,
|
||||
LocationProvider
|
||||
} from '@reach/router'
|
||||
import { webFrame } from 'electron'
|
||||
import AppProvider from './store/AppProvider'
|
||||
import { Consumer } from './store/createContext'
|
||||
import Titlebar from './components/Titlebar'
|
||||
import Total from './components/Total'
|
||||
import Account from './components/Account'
|
||||
import Ticker from './components/Ticker'
|
||||
import Spinner from './components/Spinner'
|
||||
import Home from './screens/Home'
|
||||
import Preferences from './screens/Preferences'
|
||||
import './App.css'
|
||||
|
||||
//
|
||||
|
@ -15,35 +18,22 @@ import './App.css'
|
|||
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||
webFrame.setLayoutZoomLevelLimits(0, 0)
|
||||
|
||||
// https://github.com/reach/router/issues/25
|
||||
const source = createMemorySource('/')
|
||||
const history = createHistory(source)
|
||||
|
||||
export default class App extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<AppProvider>
|
||||
<Titlebar />
|
||||
<div className="app__content">
|
||||
<Consumer>
|
||||
{({ isLoading, accounts }) => (
|
||||
<>
|
||||
<main className="main">
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
<Total />
|
||||
|
||||
<div className="number-unit-wrap number-unit-wrap--accounts">
|
||||
{accounts.map((account, i) => (
|
||||
<Account key={i} account={account} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<Ticker style={isLoading ? { opacity: 0 } : null} />
|
||||
</>
|
||||
)}
|
||||
</Consumer>
|
||||
<div className="app">
|
||||
<LocationProvider history={history}>
|
||||
<Router>
|
||||
<Home path="/" default />
|
||||
<Preferences path="preferences" />
|
||||
</Router>
|
||||
</LocationProvider>
|
||||
</div>
|
||||
</AppProvider>
|
||||
)
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Consumer } from '../store/createContext'
|
||||
import { AppContext } from '../store/createContext'
|
||||
import { locale } from '../util/moneyFormatter'
|
||||
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||
|
||||
const Balance = ({ balance }) => (
|
||||
<h1 className="number">
|
||||
<Consumer>
|
||||
<AppContext.Consumer>
|
||||
{({ currency }) =>
|
||||
formatCurrency(balance[currency], currency.toUpperCase(), locale)
|
||||
.replace(/BTC/, 'Ƀ')
|
||||
.replace(/ETH/, 'Ξ')
|
||||
.replace(/OCEAN/, 'Ọ')
|
||||
}
|
||||
</Consumer>
|
||||
</AppContext.Consumer>
|
||||
</h1>
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import { Consumer } from '../store/createContext'
|
||||
import { AppContext } from '../store/createContext'
|
||||
import { locale } from '../util/moneyFormatter'
|
||||
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||
import './Ticker.css'
|
||||
|
@ -8,7 +8,7 @@ export default class Ticker extends PureComponent {
|
|||
render() {
|
||||
return (
|
||||
<footer className="number-unit-wrap ticker" {...this.props}>
|
||||
<Consumer>
|
||||
<AppContext.Consumer>
|
||||
{({ toggleCurrencies, currency, prices }) => (
|
||||
<>
|
||||
{Object.keys(prices).map((key, i) => (
|
||||
|
@ -27,7 +27,7 @@ export default class Ticker extends PureComponent {
|
|||
))}
|
||||
</>
|
||||
)}
|
||||
</Consumer>
|
||||
</AppContext.Consumer>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.titlebar {
|
||||
align-self: flex-start;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import { Consumer } from '../store/createContext'
|
||||
import { AppContext } from '../store/createContext'
|
||||
import Balance from './Balance'
|
||||
import { prices } from '../../config'
|
||||
|
||||
|
@ -21,7 +21,7 @@ const calculateTotalBalance = (accounts, currency) => {
|
|||
|
||||
const Total = () => (
|
||||
<div className="number-unit number-unit--main">
|
||||
<Consumer>
|
||||
<AppContext.Consumer>
|
||||
{({ accounts }) => {
|
||||
const conversions = Object.assign(
|
||||
...prices.map(key => ({
|
||||
|
@ -36,7 +36,7 @@ const Total = () => (
|
|||
|
||||
return <Balance balance={balanceNew} />
|
||||
}}
|
||||
</Consumer>
|
||||
</AppContext.Consumer>
|
||||
<span className="label">Total Balance</span>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path d="M32,17.969 L32,13.969 L27.219,11.977 C27.086,11.602 26.946,11.24 26.774,10.883 L28.704,6.078 L25.875,3.25 L21.112,5.211 C20.75,5.036 20.378,4.888 19.995,4.75 L17.969,0 L13.969,0 L11.992,4.734 C11.594,4.875 11.211,5.023 10.831,5.203 L6.078,3.294 L3.25,6.122 L5.188,10.833 C5,11.219 4.847,11.614 4.703,12.021 L0,14.031 L0,18.031 L4.706,19.992 C4.852,20.398 5.008,20.794 5.195,21.18 L3.292,25.922 L6.12,28.75 L10.844,26.805 C11.222,26.985 11.61,27.13 12.008,27.266 L14.031,32 L18.031,32 L20.01,27.242 C20.39,27.101 20.765,26.953 21.124,26.781 L25.921,28.703 L28.749,25.875 L26.78,21.102 C26.947,20.743 27.085,20.38 27.218,20.008 L32,17.969 Z M15.969,22 C12.657,22 9.969,19.312 9.969,16 C9.969,12.688 12.657,10 15.969,10 C19.281,10 21.969,12.688 21.969,16 C21.969,19.312 19.281,22 15.969,22 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 871 B |
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
|
||||
import App from './App'
|
||||
|
||||
document.body.style.backgroundColor = '#141414'
|
||||
|
@ -9,5 +10,4 @@ let root = document.createElement('div')
|
|||
root.id = 'root'
|
||||
document.body.appendChild(root)
|
||||
|
||||
// Now we can render our application into it
|
||||
render(<App />, document.getElementById('root'))
|
||||
|
|
|
@ -31,9 +31,9 @@ const createWindow = async () => {
|
|||
frame: false,
|
||||
show: false,
|
||||
title: 'Ocean',
|
||||
scrollBounce: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
nodeIntegration: true,
|
||||
scrollBounce: true
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -102,6 +102,8 @@ const createWindow = async () => {
|
|||
mainWindow.setSize(width, height, true)
|
||||
})
|
||||
|
||||
switchTheme()
|
||||
|
||||
// Load menubar menu items
|
||||
require('./menu.js')
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
.main {
|
||||
width: 100%;
|
||||
padding: 5%;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
border: .1rem solid #e2e2e2;
|
||||
min-height: 222px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
animation: fadein .5s .5s ease-out;
|
||||
}
|
||||
|
||||
.dark .main {
|
||||
background: #222;
|
||||
border-color: #303030;
|
||||
}
|
||||
|
||||
.preferences-link {
|
||||
position: absolute;
|
||||
right: 5%;
|
||||
top: 1.5rem;
|
||||
}
|
||||
|
||||
.preferences-link svg {
|
||||
fill: #8b98a9;
|
||||
transition: fill .2s ease-out;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.preferences-link:hover svg {
|
||||
fill: #f6388a;
|
||||
}
|
||||
|
||||
.number-unit-wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.number-unit {
|
||||
text-align: center;
|
||||
flex: 1 1 20%;
|
||||
margin-top: 5%;
|
||||
padding-left: 2%;
|
||||
padding-right: 2%;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #8b98a9;
|
||||
font-size: .85rem;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
margin-top: .3rem;
|
||||
transition: color .2s ease-out;
|
||||
}
|
||||
|
||||
.number-unit:hover .label {
|
||||
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;
|
||||
}
|
||||
|
||||
.number-unit--main {
|
||||
padding-bottom: 5%;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
}
|
||||
|
||||
.number-unit--main:hover .label {
|
||||
color: #8b98a9;
|
||||
}
|
||||
|
||||
.dark .number-unit--main {
|
||||
border-bottom-color: #303030;
|
||||
}
|
||||
|
||||
.number-unit--main .number {
|
||||
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;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import { Link } from '@reach/router'
|
||||
import { AppContext } from '../store/createContext'
|
||||
import Total from '../components/Total'
|
||||
import Account from '../components/Account'
|
||||
import Ticker from '../components/Ticker'
|
||||
import Spinner from '../components/Spinner'
|
||||
import IconCog from '../images/cog.svg'
|
||||
import './Home.css'
|
||||
|
||||
export default class Home extends PureComponent {
|
||||
static contextType = AppContext
|
||||
|
||||
render() {
|
||||
const { isLoading, accounts, needsConfig } = this.context
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="main">
|
||||
<Link className="preferences-link" to="preferences">
|
||||
<IconCog />
|
||||
</Link>
|
||||
|
||||
{needsConfig ? (
|
||||
'Needs config'
|
||||
) : isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
<Total />
|
||||
|
||||
<div className="number-unit-wrap number-unit-wrap--accounts">
|
||||
{accounts.map((account, i) => (
|
||||
<Account key={i} account={account} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<Ticker style={isLoading ? { opacity: 0 } : null} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
.preferences {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
margin: 5%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preferences__title {
|
||||
font-size: 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;
|
||||
position: absolute;
|
||||
top: -1.5rem;
|
||||
right: 0;
|
||||
color: #8b98a9;
|
||||
}
|
||||
|
||||
.preferences__close:hover {
|
||||
color: #f6388a;
|
||||
}
|
||||
|
||||
.preference__list {
|
||||
padding: 0;
|
||||
border-top: 1px solid #e2e2e2;
|
||||
}
|
||||
|
||||
.dark .preference__list {
|
||||
border-top-color: #303030;
|
||||
}
|
||||
|
||||
.preference__list li {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
padding-top: .3rem;
|
||||
padding-bottom: .25rem;
|
||||
}
|
||||
|
||||
.dark .preference__list li {
|
||||
border-bottom-color: #303030;
|
||||
}
|
||||
|
||||
.preferences button {
|
||||
background: none;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
color: #f6388a;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
button.delete {
|
||||
font-size: 2rem;
|
||||
color: #41474e;
|
||||
transition: color .5s ease-out;
|
||||
}
|
||||
|
||||
button.delete:hover {
|
||||
color: #f6388a;
|
||||
}
|
||||
|
||||
.preference {
|
||||
-webkit-app-region: none;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
.preference__title {
|
||||
font-size: 1rem;
|
||||
color: #8b98a9;
|
||||
}
|
||||
|
||||
.preference .identicon {
|
||||
width: 1.5rem !important;
|
||||
height: 1.5rem !important;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
margin-top: -.2rem;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.preference__input {
|
||||
font-size: 1rem;
|
||||
outline: 0;
|
||||
background: none;
|
||||
border: 0;
|
||||
width: 80%;
|
||||
color: #303030;
|
||||
margin-top: .75rem;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
.dark .preference__input {
|
||||
color: #fff;
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import { Link } from '@reach/router'
|
||||
import Store from 'electron-store'
|
||||
import Blockies from 'react-blockies'
|
||||
import './Preferences.css'
|
||||
import { AppContext } from '../store/createContext'
|
||||
|
||||
export default class Preferences extends PureComponent {
|
||||
static contextType = AppContext
|
||||
|
||||
store = new Store()
|
||||
|
||||
state = { accounts: [], input: '' }
|
||||
|
||||
componentDidMount() {
|
||||
if (this.store.has('accounts')) {
|
||||
this.setState({ accounts: this.store.get('accounts') })
|
||||
}
|
||||
}
|
||||
|
||||
handleInputChange = e => {
|
||||
this.setState({ input: e.target.value })
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
this.store.set('accounts', joined)
|
||||
this.setState({ accounts: joined, input: '' })
|
||||
this.context.setBalances(joined)
|
||||
}
|
||||
}
|
||||
|
||||
handleDelete = (e, account) => {
|
||||
e.preventDefault()
|
||||
|
||||
let array = this.state.accounts
|
||||
array = array.filter(item => account !== item)
|
||||
|
||||
const index = array.indexOf(account)
|
||||
if (index > -1) {
|
||||
array.splice(index, 1)
|
||||
}
|
||||
|
||||
this.store.set('accounts', array)
|
||||
this.setState({ accounts: array })
|
||||
this.context.setBalances(array)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="preferences">
|
||||
<h1 className="preferences__title">Preferences</h1>{' '}
|
||||
<Link className="preferences__close" title="Close Preferences" to="/">
|
||||
×
|
||||
</Link>
|
||||
<div className="preference">
|
||||
<h2 className="preference__title">Accounts</h2>
|
||||
<ul className="preference__list">
|
||||
{this.state.accounts &&
|
||||
this.state.accounts.map(account => (
|
||||
<li key={account}>
|
||||
<div>
|
||||
<Blockies seed={account} size={10} scale={3} />
|
||||
{account}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="delete"
|
||||
onClick={e => this.handleDelete(e, account)}
|
||||
title="Remove account"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="0xxxxxxxx"
|
||||
value={this.state.input}
|
||||
onChange={this.handleInputChange}
|
||||
className="preference__input"
|
||||
/>
|
||||
<button
|
||||
className="preference__input__add"
|
||||
onClick={e => this.handleSave(e)}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,30 +1,31 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ms from 'ms'
|
||||
import { Provider } from './createContext'
|
||||
import {
|
||||
accounts,
|
||||
refreshInterval,
|
||||
oceanTokenContract,
|
||||
prices
|
||||
} from '../../config'
|
||||
import Store from 'electron-store'
|
||||
import { AppContext } from './createContext'
|
||||
import { refreshInterval, prices, oceanTokenContract } from '../../config'
|
||||
|
||||
export default class AppProvider extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.any.isRequired
|
||||
}
|
||||
|
||||
store = new Store()
|
||||
|
||||
state = {
|
||||
isLoading: true,
|
||||
accounts: [],
|
||||
currency: 'ocean',
|
||||
needsConfig: false,
|
||||
prices: Object.assign(...prices.map(key => ({ [key]: 0 }))),
|
||||
toggleCurrencies: currency => this.setState({ currency })
|
||||
toggleCurrencies: currency => this.setState({ currency }),
|
||||
setBalances: account => this.setBalances(account)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { accountsPref } = await this.getAccounts()
|
||||
await this.fetchAndSetPrices()
|
||||
await this.setBalances()
|
||||
await this.setBalances(accountsPref)
|
||||
|
||||
await setInterval(this.fetchAndSetPrices, ms(refreshInterval))
|
||||
await setInterval(this.setBalances, ms(refreshInterval))
|
||||
|
@ -36,6 +37,22 @@ export default class AppProvider extends PureComponent {
|
|||
this.clearAccounts()
|
||||
}
|
||||
|
||||
getAccounts() {
|
||||
let accountsPref
|
||||
|
||||
if (this.store.has('accounts')) {
|
||||
accountsPref = this.store.get('accounts')
|
||||
|
||||
!accountsPref.length
|
||||
? this.setState({ needsConfig: true })
|
||||
: this.setState({ needsConfig: false })
|
||||
} else {
|
||||
accountsPref = []
|
||||
}
|
||||
|
||||
return { accountsPref }
|
||||
}
|
||||
|
||||
clearAccounts() {
|
||||
this.setState({ accounts: [] })
|
||||
}
|
||||
|
@ -57,7 +74,7 @@ export default class AppProvider extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
fetchBalance = async account => {
|
||||
async fetchBalance(account) {
|
||||
const json = await this.fetch(
|
||||
`https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${oceanTokenContract}&address=${account}&tag=latest`
|
||||
)
|
||||
|
@ -66,7 +83,7 @@ export default class AppProvider extends PureComponent {
|
|||
return balance
|
||||
}
|
||||
|
||||
fetchAndSetPrices = async () => {
|
||||
async fetchAndSetPrices() {
|
||||
const currencies = prices.join(',')
|
||||
const json = await this.fetch(
|
||||
`https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}`
|
||||
|
@ -82,12 +99,14 @@ export default class AppProvider extends PureComponent {
|
|||
})
|
||||
}
|
||||
|
||||
setBalances = async () => {
|
||||
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()
|
||||
|
||||
accounts.map(async account => {
|
||||
const accountsArray = accounts ? accounts : this.state.accounts
|
||||
|
||||
accountsArray.map(async account => {
|
||||
const oceanBalance = await this.fetchBalance(account)
|
||||
|
||||
const conversions = Object.assign(
|
||||
|
@ -111,6 +130,10 @@ export default class AppProvider extends PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
return <Provider value={this.state}>{this.props.children}</Provider>
|
||||
return (
|
||||
<AppContext.Provider value={this.state}>
|
||||
{this.props.children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createContext } from 'react'
|
||||
|
||||
const { Provider, Consumer } = createContext()
|
||||
const AppContext = createContext({})
|
||||
|
||||
export { Provider, Consumer }
|
||||
export { AppContext }
|
||||
|
|
|
@ -28,7 +28,12 @@ module.exports = {
|
|||
include: defaultInclude
|
||||
},
|
||||
{
|
||||
test: /\.(eot|svg|ttf|woff|woff2)$/,
|
||||
test: /\.svg$/,
|
||||
use: ['@svgr/webpack'],
|
||||
include: defaultInclude
|
||||
},
|
||||
{
|
||||
test: /\.(eot|ttf|woff|woff2)$/,
|
||||
use: ['file-loader?name=font/[name]__[hash:base64:5].[ext]'],
|
||||
include: defaultInclude
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue