Add in-app network switch (#203)

* add network select

* new network select component

* improve network switch

* Fixes and refactoring

* Refactoring
This commit is contained in:
Max Alekseenko 2019-10-29 09:43:19 +03:00 committed by varasev
parent 8acf0348db
commit 1cecd5d169
27 changed files with 532 additions and 212 deletions

41
package-lock.json generated
View File

@ -4266,7 +4266,8 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -4284,11 +4285,13 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true
"bundled": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -4301,15 +4304,18 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -4412,7 +4418,8 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true
"bundled": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -4422,6 +4429,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -4434,17 +4442,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true
"bundled": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -4461,6 +4472,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -4533,7 +4545,8 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -4543,6 +4556,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -4618,7 +4632,8 @@
},
"safe-buffer": {
"version": "5.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -4648,6 +4663,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -4665,6 +4681,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -4703,11 +4720,13 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true
"bundled": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true
"bundled": true,
"optional": true
}
}
},

View File

@ -7,7 +7,7 @@
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicons/favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Nunito:300,400,700" rel="stylesheet">
<title>POA Network Governance DApp</title>
<title>POA Governance DApp</title>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD97qDOBYZ2fH86Wq1vzhDOiSUsZGVqbVQ&libraries=places"></script>
</head>
<body>

View File

@ -5,7 +5,7 @@ import { Route, Redirect } from 'react-router-dom'
import { constants } from './utils/constants'
import { getNetworkBranch } from './utils/utils'
import { inject, observer } from 'mobx-react'
import { messages } from './utils/messages'
import messages from './utils/messages'
import './assets/stylesheets/index.css'
@ -41,16 +41,29 @@ class App extends Component {
onNewBallotRender = () => {
const { commonStore, contractsStore } = this.props
if (!contractsStore.web3Instance) {
if (!commonStore.loading) {
if (!commonStore.loading) {
if (!contractsStore.injectedWeb3) {
commonStore.hideLoading()
swal({
title: 'Error',
html: messages.NO_METAMASK_MSG,
icon: 'error',
type: 'error'
})
} else if (!contractsStore.networkMatch) {
commonStore.hideLoading()
swal({
title: 'Warning!',
html: messages.networkMatchError(contractsStore.netId),
type: 'warning'
})
} else if (contractsStore.votingKey && !contractsStore.isValidVotingKey) {
commonStore.hideLoading()
swal({
title: 'Warning!',
html: messages.invalidVotingKeyMsg(contractsStore.votingKey),
type: 'warning'
})
}
return null
}
return <NewBallot networkBranch={this.getVotingNetworkBranch()} />
}
@ -60,8 +73,7 @@ class App extends Component {
}
onSearch = e => {
const { commonStore } = this.props
commonStore.setSearchTerm(e.target.value.toLowerCase())
this.setSearchTerm(e.target.value)
}
hideSearch = () => {
@ -85,13 +97,28 @@ class App extends Component {
return 'All'
}
setSearchTerm = term => {
const { commonStore } = this.props
commonStore.setSearchTerm(term.toLowerCase())
if (this.refs.searchBar) {
this.refs.searchBar.setSearchTerm(term)
}
}
onNetworkChange = e => {
this.setSearchTerm('')
this.props.onNetworkChange(e)
}
isNewBallotPage() {
return `${constants.rootPath}/new` === this.props.location.pathname
}
render() {
const { commonStore, contractsStore } = this.props
const networkBranch = this.getVotingNetworkBranch()
const networkBranch = commonStore.loadingNetworkBranch
? commonStore.loadingNetworkBranch
: this.getVotingNetworkBranch()
return networkBranch ? (
<div
@ -104,10 +131,18 @@ class App extends Component {
baseRootPath={commonStore.rootPath}
netId={contractsStore.netId}
networkBranch={networkBranch}
onChange={this.onNetworkChange}
onMenuToggle={this.toggleMobileMenu}
showMobileMenu={this.state.showMobileMenu}
/>
{this.hideSearch() ? null : <SearchBar networkBranch={networkBranch} onSearch={this.onSearch} />}
{this.hideSearch() ? null : (
<SearchBar
networkBranch={networkBranch}
onSearch={this.onSearch}
searchTerm={commonStore.searchTerm}
ref="searchBar"
/>
)}
<MainTitle text={this.getTitle()} />
<section
className={`lo-App_Content lo-App_Content-${networkBranch} ${

View File

@ -7,7 +7,6 @@
height: $header-height;
justify-content: center;
left: 0;
overflow: hidden;
position: fixed;
right: 0;
top: 0;
@ -58,4 +57,4 @@
@media (min-width: $breakpoint-md) {
display: flex;
}
}
}

View File

@ -0,0 +1,3 @@
.nl-IconNetwork_Path {
@include menu-icon-colors();
}

View File

@ -0,0 +1,101 @@
.NetworkSelect {
display: flex;
@media (min-width: $breakpoint-md) {
position: relative;
margin-left: 34px;
&:hover {
.NetworkSelect_List {
display: block;
}
}
}
}
.sw-Header_Content .NetworkSelect {
display: none;
@media (min-width: $breakpoint-md) {
display: flex;
}
}
.NetworkSelect_Top {
display: flex;
align-items: center;
opacity: .7;
transition: .15s ease-in;
height: 50px;
width: 100%;
justify-content: center;
cursor: pointer;
&:hover {
opacity: 1;
@media (min-width: $breakpoint-md) {
.nl-IconNetwork_Arrow {
transform: rotate(180deg);
}
}
}
}
.nl-NavigationLinks_Link.opacityFull {
opacity: 1;
flex-direction: column;
height: auto !important;
}
.nl-IconNetwork_Arrow {
margin-left: 10px;
transition: .15s ease-in;
path {
fill: #fff;
opacity: .52;
}
}
.NetworkSelect_List {
font-size: 14px;
font-weight: 600;
list-style: none;
padding-left: 0;
width: 100%;
margin: 0;
@media (min-width: $breakpoint-md) {
display: none;
position: absolute;
top: 100%;
right: 0;
background-color: #fff;
width: 180px;
border-radius: 4px 0 4px 4px;
box-shadow: 0 5px 10px rgba(#000, .05);
padding-top: 8px;
padding-bottom: 8px;
}
li.currentNetwork {
@media (max-width: $breakpoint-md) {
display: none;
}
}
button {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
text-decoration: none;
color: #fff;
transition: .15s ease-in;
height: 50px;
background-color: transparent;
border: none;
cursor: pointer;
@media (min-width: $breakpoint-md) {
padding: 8px 18px;
height: auto;
color: $base-text-color;
justify-content: flex-start;
&:hover {
background-color: #f7f7f7;
}
}
}
}

View File

@ -0,0 +1,8 @@
.top-Select {
flex-grow: 1;
max-width: 140px;
@media (min-width: $breakpoint-xl) {
margin-left: 20px;
}
}

View File

@ -30,6 +30,7 @@
@import "IconGithub";
@import "IconLoadMore";
@import "IconMobileMenu";
@import "IconNetwork";
@import "IconPOA";
@import "IconTelegram";
@import "IconToFinalize";
@ -47,9 +48,11 @@
@import "NewBallot";
@import "NewBallotMenu";
@import "NewBallotMenuInfo";
@import "NetworkSelect";
@import "SearchBar";
@import "Select";
@import "Separator";
@import "SocialIcons";
@import "Validator";
@import "VoteProgressBar";
@import "Votes";
@import "Votes";

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@ import { BallotInfoContainer } from '../BallotInfoContainer'
import { Votes } from '../Votes'
import { getNetworkBranch } from '../../utils/utils'
import { inject, observer } from 'mobx-react'
import { messages } from '../../utils/messages'
import messages from '../../utils/messages'
import { observable, action, computed } from 'mobx'
import { sendTransactionByVotingKey } from '../../utils/helpers'
@ -374,6 +374,9 @@ export class BallotCard extends React.Component {
if (!contractsStore.votingKey) {
swal('Warning!', messages.NO_METAMASK_MSG, 'warning')
return
} else if (!contractsStore.networkMatch) {
swal('Warning!', messages.networkMatchError(contractsStore.netId), 'warning')
return
} else if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
return
@ -511,6 +514,9 @@ export class BallotCard extends React.Component {
if (!contractsStore.votingKey) {
swal('Warning!', messages.NO_METAMASK_MSG, 'warning')
return
} else if (!contractsStore.networkMatch) {
swal('Warning!', messages.networkMatchError(contractsStore.netId), 'warning')
return
} else if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
return

View File

@ -2,14 +2,12 @@ import React from 'react'
import { FormInput } from '../FormInput'
import { FormSelect } from '../FormSelect'
import { inject, observer } from 'mobx-react'
import { constants } from '../../utils/constants'
import { getNetworkName } from '../../utils/utils'
@inject('ballotStore', 'contractsStore')
@inject('ballotStore')
@observer
export class BallotProxyMetadata extends React.Component {
render() {
const { ballotStore, contractsStore, networkBranch } = this.props
const { ballotStore, networkBranch } = this.props
let options = [
/*0*/ { value: '', label: '' },
/*1*/ { value: '1', label: ballotStore.ProxyBallotType[1] }, // KeysManager

View File

@ -1,20 +1,31 @@
import React from 'react'
import { ButtonNewBallot } from '../ButtonNewBallot'
import { IconMobileMenu } from '../IconMobileMenu'
import { Logo } from '../Logo'
import { MobileMenuLinks } from '../MobileMenuLinks'
import { NavigationLinks } from '../NavigationLinks'
import NetworkSelect from '../NetworkSelect'
export const Header = ({ baseRootPath = '', networkBranch = undefined, onMenuToggle, showMobileMenu = false }) => {
export const Header = ({
baseRootPath = '',
networkBranch = undefined,
onChange,
onMenuToggle,
showMobileMenu = false
}) => {
return (
<header className={`sw-Header sw-Header-${networkBranch} ${showMobileMenu ? 'sw-Header-menu-open' : ''}`}>
{showMobileMenu ? <MobileMenuLinks networkBranch={networkBranch} onClick={onMenuToggle} /> : null}
{showMobileMenu ? (
<MobileMenuLinks networkBranch={networkBranch} onClick={onMenuToggle} onNetworkChange={onChange} />
) : null}
<div className="sw-Header_Content">
<Logo networkBranch={networkBranch} href={baseRootPath} />
<div className="sw-Header_Links">
<NavigationLinks networkBranch={networkBranch} />
<ButtonNewBallot networkBranch={networkBranch} />
</div>
<NetworkSelect networkBranch={networkBranch} onChange={onChange} />
<IconMobileMenu networkBranch={networkBranch} isOpen={showMobileMenu} onClick={onMenuToggle} />
</div>
</header>

View File

@ -1,12 +1,14 @@
import React from 'react'
import { ButtonNewBallot } from '../ButtonNewBallot'
import { NavigationLinks } from '../NavigationLinks'
import NetworkSelect from '../NetworkSelect'
export const MobileMenuLinks = ({ onClick, networkBranch }) => {
export const MobileMenuLinks = ({ onClick, networkBranch, onNetworkChange }) => {
return (
<div className={`hd-MobileMenuLinks hd-MobileMenuLinks-${networkBranch}`} onClick={onClick}>
<NavigationLinks networkBranch={networkBranch} />
<ButtonNewBallot networkBranch={networkBranch} />
<NetworkSelect networkBranch={networkBranch} onChange={onNetworkChange} />
</div>
)
}

View File

@ -0,0 +1,72 @@
import React, { Component } from 'react'
import { constants } from '../../utils/constants'
export default class NetworkSelect extends Component {
changeNetworkRPC(e) {
e.preventDefault()
let getCurrentClickedLink = e.target.innerHTML
let getCurrentClickedLinkId = ''
for (const _netId in constants.NETWORKS) {
if (constants.NETWORKS[_netId].FULLNAME === getCurrentClickedLink) {
getCurrentClickedLinkId = _netId
}
}
this.props.onChange({ value: getCurrentClickedLinkId })
}
render() {
let networkFullNames = []
let currentNetworkFullName = ''
const networks = constants.NETWORKS
let netIds = []
Object.keys(networks)
.sort((a, b) => (networks[a].SORTORDER > networks[b].SORTORDER ? 1 : -1))
.forEach(function(_netId) {
netIds.push(_netId)
})
let selectedNetworkIndex = -1
netIds.forEach(_netId => {
networkFullNames.push(networks[_netId].FULLNAME)
if (networks[_netId].BRANCH === this.props.networkBranch) {
currentNetworkFullName = networks[_netId].FULLNAME
selectedNetworkIndex = networkFullNames.length - 1
}
})
const listItems = networkFullNames.map((name, index) => {
let className = ''
if (index === selectedNetworkIndex) {
className = 'currentNetwork'
}
return (
<li key={name.toString()} className={className}>
<button onClick={e => this.changeNetworkRPC(e)}>{name}</button>
</li>
)
})
return (
<div className={`NetworkSelect nl-NavigationLinks_Link opacityFull`}>
<div className={`NetworkSelect_Top`}>
<svg className={`nl-IconNetwork`} xmlns="http://www.w3.org/2000/svg" width="18" height="18">
<path
className={`nl-IconNetwork_Path nl-IconNetwork_Path-${this.props.networkBranch}`}
d="M9 18a9 9 0 0 1-9-9 9 9 0 0 1 9-9 9 9 0 0 1 9 9 9 9 0 0 1-9 9zm6.923-8h-1.974c-.116 1.85-.525 3.539-1.167 4.876A6.993 6.993 0 0 0 15.923 10zM9 16c1.51 0 2.747-2.612 2.957-6H6.043c.21 3.388 1.447 6 2.957 6zm-3.782-1.124C4.576 13.539 4.167 11.85 4.051 10H2.077a6.993 6.993 0 0 0 3.141 4.876zM2.077 8h1.974c.116-1.85.525-3.538 1.167-4.876A6.993 6.993 0 0 0 2.077 8zM9 2C7.49 2 6.253 4.612 6.043 8h5.914C11.747 4.612 10.51 2 9 2zm3.782 1.124C13.424 4.462 13.833 6.15 13.949 8h1.974a6.993 6.993 0 0 0-3.141-4.876z"
/>
</svg>
<span className={`nl-NavigationLinks_Text nl-NavigationLinks_Text-${this.props.networkBranch}`}>
{currentNetworkFullName}
</span>
<svg className={`nl-IconNetwork_Arrow`} xmlns="http://www.w3.org/2000/svg" width="8" height="4">
<path d="M0 0h8L4 4 0 0z" />
</svg>
</div>
<ul className={`NetworkSelect_List`}>{listItems}</ul>
</div>
)
}
}

View File

@ -15,7 +15,7 @@ import { Validator } from '../Validator'
import { constants } from '../../utils/constants'
import { getNetworkBranch } from '../../utils/utils'
import { inject, observer } from 'mobx-react'
import { messages } from '../../utils/messages'
import messages from '../../utils/messages'
import { sendTransactionByVotingKey } from '../../utils/helpers'
@inject('commonStore', 'ballotStore', 'validatorStore', 'contractsStore', 'routing', 'ballotsStore')
@ -40,9 +40,10 @@ export class NewBallot extends React.Component {
}
checkValidation() {
const { commonStore, contractsStore, ballotStore, validatorStore } = this.props
const { commonStore, contractsStore, ballotStore } = this.props
// Temporarily commented (until we implement https://github.com/poanetwork/poa-dapps-voting/issues/120)
// const { validatorStore } = this.props
// if (ballotStore.isNewValidatorPersonalData) {
// for (let validatorProp in validatorStore) {
// if (validatorStore[validatorProp].length === 0) {
@ -255,6 +256,9 @@ export class NewBallot extends React.Component {
if (!contractsStore.votingKey) {
swal('Warning!', messages.NO_METAMASK_MSG, 'warning')
return
} else if (!contractsStore.networkMatch) {
swal('Warning!', messages.networkMatchError(contractsStore.netId), 'warning')
return
} else if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
return

View File

@ -1,16 +1,36 @@
import React from 'react'
export const SearchBar = ({ extraClassName = '', networkBranch = false, onSearch }) => {
return (
<div className={`sw-SearchBar sw-SearchBar-${networkBranch} ${extraClassName}`}>
<div className="sw-SearchBar_Content">
<input
className={`sw-SearchBar_Input sw-SearchBar_Input-${networkBranch}`}
onChange={onSearch}
placeholder="Search..."
type="search"
/>
export class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = { searchTerm: '' }
}
setSearchTerm(searchTerm) {
this.setState({ searchTerm })
}
componentDidMount() {
const { searchTerm } = this.props
if (searchTerm !== undefined) {
this.setSearchTerm(searchTerm)
}
}
render() {
const { networkBranch, onSearch } = this.props
return (
<div className={`sw-SearchBar sw-SearchBar-${networkBranch}`}>
<div className="sw-SearchBar_Content">
<input
className={`sw-SearchBar_Input sw-SearchBar_Input-${networkBranch}`}
onChange={onSearch}
placeholder="Search..."
type="search"
value={this.state.searchTerm}
/>
</div>
</div>
</div>
)
)
}
}

View File

@ -1,7 +1,7 @@
import React from 'react'
import { FormAutocomplete } from '../FormAutocomplete'
import { FormInput } from '../FormInput'
import { FormSelect } from '../FormSelect'
//import { FormAutocomplete } from '../FormAutocomplete'
//import { FormInput } from '../FormInput'
//import { FormSelect } from '../FormSelect'
import { constants } from '../../utils/constants'
import { geocodeByAddress } from 'react-places-autocomplete'
import { inject, observer } from 'mobx-react'
@ -64,6 +64,7 @@ export class Validator extends React.Component {
render() {
return null // Temporarily empty (until we implement https://github.com/poanetwork/poa-dapps-voting/issues/120)
/*
const { validatorStore, networkBranch } = this.props
const inputProps = {
value: validatorStore.address,
@ -190,5 +191,6 @@ export class Validator extends React.Component {
</div>
</div>
)
*/
}
}

View File

@ -1,5 +1,5 @@
import { constants } from '../utils/constants'
import { messages } from '../utils/messages'
import messages from '../utils/messages'
import swal from 'sweetalert2'
function addressesURL(branch) {

View File

@ -16,6 +16,7 @@ import { Router, Route } from 'react-router-dom'
import { RouterStore, syncHistoryWithStore } from 'mobx-react-router'
import { constants } from './utils/constants'
import { getContractsAddresses } from './contracts/addresses'
import { getNetworkBranch } from './utils/utils'
const browserHistory = createBrowserHistory()
const routingStore = new RouterStore()
@ -32,56 +33,14 @@ class AppMainRouter extends Component {
super(props)
commonStore.showLoading()
getWeb3()
window.addEventListener('load', () => this.initChain())
}
initChain = () => {
const netId = window.localStorage.netId
getWeb3(netId, this.onAccountChange)
.then(async web3Config => {
await getContractsAddresses(constants.NETWORKS[web3Config.netId].BRANCH)
contractsStore.setWeb3Instance(web3Config)
const setPoaConsensus = contractsStore.setPoaConsensus(web3Config)
const setBallotsStorage = contractsStore.setBallotsStorage(web3Config)
const setKeysManager = contractsStore.setKeysManager(web3Config)
const setProxyStorage = contractsStore.setProxyStorage(web3Config)
const setVotingToChangeKeys = contractsStore.setVotingToChangeKeys(web3Config)
const setVotingToChangeMinThreshold = contractsStore.setVotingToChangeMinThreshold(web3Config)
const setVotingToChangeProxy = contractsStore.setVotingToChangeProxy(web3Config)
const setValidatorMetadata = contractsStore.setValidatorMetadata(web3Config)
let promises = [
setPoaConsensus,
setBallotsStorage,
setKeysManager,
setProxyStorage,
setVotingToChangeKeys,
setVotingToChangeMinThreshold,
setVotingToChangeProxy,
setValidatorMetadata
]
const networkName = constants.NETWORKS[web3Config.netId].NAME.toLowerCase()
if (networkName === constants.CORE || networkName === constants.SOKOL) {
// if we're in Core or Sokol
promises.push(contractsStore.setEmissionFunds(web3Config))
promises.push(contractsStore.setVotingToManageEmissionFunds(web3Config))
}
await Promise.all(promises)
await contractsStore.setMiningKey(web3Config)
await contractsStore.setVotingKey(web3Config)
contractsStore.getKeysBallotThreshold()
contractsStore.getProxyBallotThreshold()
contractsStore.getBallotCancelingThreshold()
await contractsStore.getBallotsLimits()
await contractsStore.getAllValidatorMetadata()
await contractsStore.getAllBallots()
console.log('votingKey', contractsStore.votingKey)
console.log('miningKey', contractsStore.miningKey)
await this.initialize(web3Config)
commonStore.hideLoading()
})
.catch(error => {
@ -90,17 +49,82 @@ class AppMainRouter extends Component {
swal({
title: 'Error',
html: generateElement(error.message),
icon: 'error',
type: 'error'
})
})
}
initialize = async web3Config => {
await getContractsAddresses(constants.NETWORKS[web3Config.netId].BRANCH)
contractsStore.setWeb3Instance(web3Config)
const setPoaConsensus = contractsStore.setPoaConsensus(web3Config)
const setBallotsStorage = contractsStore.setBallotsStorage(web3Config)
const setKeysManager = contractsStore.setKeysManager(web3Config)
const setProxyStorage = contractsStore.setProxyStorage(web3Config)
const setVotingToChangeKeys = contractsStore.setVotingToChangeKeys(web3Config)
const setVotingToChangeMinThreshold = contractsStore.setVotingToChangeMinThreshold(web3Config)
const setVotingToChangeProxy = contractsStore.setVotingToChangeProxy(web3Config)
const setValidatorMetadata = contractsStore.setValidatorMetadata(web3Config)
let promises = [
setPoaConsensus,
setBallotsStorage,
setKeysManager,
setProxyStorage,
setVotingToChangeKeys,
setVotingToChangeMinThreshold,
setVotingToChangeProxy,
setValidatorMetadata
]
const networkName = constants.NETWORKS[web3Config.netId].NAME.toLowerCase()
if (networkName === constants.CORE || networkName === constants.SOKOL) {
// if we're in Core or Sokol
promises.push(contractsStore.setEmissionFunds(web3Config))
promises.push(contractsStore.setVotingToManageEmissionFunds(web3Config))
}
await Promise.all(promises)
await this.setKeys(web3Config.defaultAccount)
contractsStore.getKeysBallotThreshold()
contractsStore.getProxyBallotThreshold()
contractsStore.getBallotCancelingThreshold()
await contractsStore.getBallotsLimits()
await contractsStore.getAllValidatorMetadata()
await contractsStore.getAllBallots()
}
onNetworkChange = e => {
commonStore.showLoading(getNetworkBranch(e.value))
window.localStorage.netId = e.value
contractsStore.resetContracts()
ballotsStore.reset()
this.initChain()
}
onAccountChange = account => {
this.setKeys(account)
}
setKeys = async account => {
await contractsStore.setMiningKey(account)
await contractsStore.setVotingKey(account)
console.log('votingKey', contractsStore.votingKey)
console.log('miningKey', contractsStore.miningKey)
}
render() {
return (
<Provider {...stores}>
<Router history={history}>
<Route component={App} />
<Route component={props => <App onNetworkChange={this.onNetworkChange} {...props} />} />
</Router>
</Provider>
)

View File

@ -4,6 +4,10 @@ class BallotsStore {
@observable ballotCards
constructor() {
this.reset()
}
reset() {
this.ballotCards = []
}
}

View File

@ -2,6 +2,7 @@ import { observable, action } from 'mobx'
class CommonStore {
@observable loading
@observable loadingNetworkBranch
@observable rootPath
@observable isActiveFilter
@observable isToFinalizeFilter
@ -10,6 +11,7 @@ class CommonStore {
constructor() {
this.loading = false
this.loadingNetworkBranch = null
this.isActiveFilter = false
this.isToFinalizeFilter = false
this.rootPath = '/poa-dapps-voting'
@ -17,13 +19,15 @@ class CommonStore {
}
@action('show loading')
showLoading() {
showLoading(loadingNetworkBranch) {
this.loading = true
this.loadingNetworkBranch = loadingNetworkBranch
}
@action('hide loading')
hideLoading() {
this.loading = false
this.loadingNetworkBranch = null
}
@action('set search term')

View File

@ -46,6 +46,7 @@ class ContractsStore {
@observable minBallotDuration
@observable validatorsMetadata
@observable netId
@observable injectedWeb3
constructor() {
this.votingKey = null
@ -53,6 +54,7 @@ class ContractsStore {
this.validatorsMetadata = {}
this.validatorLimits = { keys: null, minThreshold: null, proxy: null }
this.minBallotDuration = { keys: 0, minThreshold: 0, proxy: 0 }
this.injectedWeb3 = false
}
@computed
@ -84,6 +86,22 @@ class ContractsStore {
setWeb3Instance = web3Config => {
this.web3Instance = web3Config.web3Instance
this.netId = web3Config.netId
this.injectedWeb3 = web3Config.injectedWeb3
this.networkMatch = web3Config.networkMatch
}
@action('Reset contracts')
resetContracts = () => {
this.poaConsensus = null
this.ballotsStorage = null
this.emissionFunds = null
this.keysManager = null
this.proxyStorage = null
this.votingToChangeKeys = null
this.votingToChangeMinThreshold = null
this.votingToChangeProxy = null
this.votingToManageEmissionFunds = null
this.validatorMetadata = null
}
@action('Set PoA Consensus contract')
@ -182,14 +200,14 @@ class ContractsStore {
}
@action('Set voting key')
setVotingKey = web3Config => {
this.votingKey = web3Config.defaultAccount
setVotingKey = account => {
this.votingKey = account
}
@action('Set mining key')
setMiningKey = async web3Config => {
setMiningKey = async account => {
try {
this.miningKey = await this.keysManager.instance.methods.miningKeyByVoting(web3Config.defaultAccount).call()
this.miningKey = await this.keysManager.instance.methods.miningKeyByVoting(account).call()
} catch (e) {
console.log(e)
this.miningKey = '0x0000000000000000000000000000000000000000'

View File

@ -59,27 +59,31 @@ constants.KOVAN = 'kovan'
constants.NETWORKS = {
'42': {
NAME: 'Kovan',
FULLNAME: 'Kovan Testnet',
RPC: 'https://kovan.infura.io/v3/1125fe73d87c4e5396678f4e3089b3dd',
BRANCH: constants.KOVAN,
TESTNET: true
SORTORDER: 3
},
'77': {
NAME: 'Sokol',
FULLNAME: 'Sokol Testnet',
RPC: 'https://sokol.poa.network',
BRANCH: constants.SOKOL,
TESTNET: true
SORTORDER: 4
},
'99': {
NAME: 'Core',
FULLNAME: 'POA Core',
RPC: 'https://core.poa.network',
BRANCH: constants.CORE,
TESTNET: false
SORTORDER: 1
},
'100': {
NAME: 'Dai',
NAME: 'xDai',
FULLNAME: 'xDai Stable Chain',
RPC: 'https://dai.poa.network',
BRANCH: constants.DAI,
TESTNET: false
SORTORDER: 2
}
}

View File

@ -1,80 +1,71 @@
import Web3 from 'web3'
import { messages } from './messages'
import helpers from './helpers'
import { constants } from './constants'
import { netIdByName } from './helpers'
let getWeb3 = () => {
return new Promise((resolve, reject) => {
// Wait for loading completion to avoid race conditions with web3 injection timing.
window.addEventListener('load', async () => {
let web3 = null
const defaultNetId = helpers.netIdByBranch(constants.CORE)
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (window.ethereum) {
web3 = new Web3(window.ethereum)
console.log('Injected web3 detected.')
try {
await window.ethereum.enable()
} catch (e) {
console.error('User denied account access')
reject({ message: messages.USER_DENIED_ACCOUNT_ACCESS })
return
}
} else if (typeof window.web3 !== 'undefined') {
web3 = new Web3(window.web3.currentProvider)
console.log('Injected web3 detected.')
export default async function getWeb3(netId = defaultNetId, onAccountChange) {
let web3 = null
netId = Number(netId)
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (window.ethereum) {
web3 = new Web3(window.ethereum)
console.log('Injected web3 detected.')
try {
await window.ethereum.enable()
} catch (e) {
throw Error('You have denied access to your accounts')
}
window.ethereum.autoRefreshOnNetworkChange = true
} else if (window.web3) {
web3 = new Web3(window.web3.currentProvider)
console.log('Injected web3 detected.')
}
const network = constants.NETWORKS[netId]
const injectedWeb3 = web3 !== null
let netIdName = network.NAME
let defaultAccount = null
let networkMatch = false
if (web3) {
const accounts = await web3.eth.getAccounts()
defaultAccount = accounts[0] || null
if (!defaultAccount) {
console.error('Unlock your wallet')
}
let currentAccount = defaultAccount ? defaultAccount.toLowerCase() : ''
web3.currentProvider.publicConfigStore.on('update', function(obj) {
const account = obj.selectedAddress
if (account && account !== currentAccount) {
currentAccount = account
onAccountChange(account)
}
let errorMsg = null
let netIdName
let netId
let defaultAccount = null
if (web3) {
netId = await web3.eth.net.getId()
console.log('netId', netId)
if (!(netId in constants.NETWORKS)) {
netIdName = 'ERROR'
errorMsg = messages.WRONG_NETWORK_MSG
console.log('This is an unknown network.')
} else {
netIdName = constants.NETWORKS[netId].NAME
console.log(`This is ${netIdName}`)
}
const accounts = await web3.eth.getAccounts()
defaultAccount = accounts[0] || null
} else {
// Fallback to local if no web3 injection.
console.log('No web3 instance injected, using Local web3.')
console.error('Metamask not found')
netId = netIdByName(constants.CORE)
const network = constants.NETWORKS[netId]
web3 = new Web3(new Web3.providers.HttpProvider(network.RPC))
netIdName = network.NAME
}
document.title = `${netIdName} - POA Network Governance DApp`
if (errorMsg !== null) {
reject({ message: errorMsg })
return
}
resolve({
web3Instance: web3,
netIdName,
netId,
defaultAccount
})
})
})
}
export default getWeb3
const web3NetId = await web3.eth.net.getId()
if (web3NetId === netId) {
networkMatch = true
} else {
web3 = null
}
}
if (!web3) {
web3 = new Web3(new Web3.providers.HttpProvider(network.RPC))
}
document.title = `${netIdName} - POA Governance DApp`
return {
web3Instance: web3,
netId,
netIdName,
injectedWeb3,
defaultAccount,
networkMatch
}
}

View File

@ -58,10 +58,10 @@ function sendTransactionByVotingKey(props, to, data, cb, warning) {
)
}
function netIdByName(netName) {
const netNameLowerCase = netName.toLowerCase()
function netIdByBranch(branch) {
const branchLowerCase = branch.toLowerCase()
for (let netId in constants.NETWORKS) {
if (constants.NETWORKS[netId].NAME.toLowerCase() === netNameLowerCase) {
if (constants.NETWORKS[netId].BRANCH.toLowerCase() === branchLowerCase) {
return netId
}
}
@ -71,5 +71,5 @@ function netIdByName(netName) {
module.exports = {
toAscii,
sendTransactionByVotingKey,
netIdByName
netIdByBranch
}

View File

@ -1,6 +1,8 @@
import { getNetworkFullName } from './utils'
let messages = {}
messages.invalidVotingKeyMsg = key => {
return `The key ${key} is not valid voting Key! Please make sure you have loaded correct voting key in Metamask`
return `The key ${key} is not valid Voting Key! Please make sure you have loaded correct Voting Key in MetaMask.`
}
messages.VOTED_SUCCESS_MSG = 'You successfully voted'
messages.BALLOT_CREATED_SUCCESS_MSG = 'You successfully created a new ballot'
@ -16,12 +18,7 @@ messages.PROPOSED_ADDRESS_IS_NOT_ADDRESS_MSG = "Proposed address isn't address"
messages.END_TIME_SHOULD_BE_GREATER_THAN_NOW_MSG = 'Ballot end time should be greater than now'
messages.BALLOT_TYPE_IS_EMPTY_MSG = 'Ballot type is empty'
messages.USER_DENIED_ACCOUNT_ACCESS = 'You have denied access to your accounts'
messages.NO_METAMASK_MSG = `You haven't chosen any account in MetaMask.
Please, choose your voting key in MetaMask and reload the page.
Check POA Network <a href='https://github.com/poanetwork/wiki' target='blank'>wiki</a> for more info.`
messages.WRONG_NETWORK_MSG = `You aren't connected to POA Network.
Please, switch on POA plugin and refresh the page.
Check POA Network <a href='https://github.com/poanetwork/wiki' target='blank'>wiki</a> for more info.`
messages.NO_METAMASK_MSG = 'Your MetaMask is locked or not installed.'
messages.ballotIsNotActiveMsg = timeToStart => {
return `The ballot is not active yet. Time to start: ${timeToStart}`
}
@ -38,17 +35,20 @@ messages.EMISSION_RELEASE_TIME_IN_FUTURE = emissionReleaseTime => {
}
messages.PREVIOUS_BALLOT_NOT_FINALIZED = 'Previous ballot should be finalized first.'
messages.BALLOT_CREATE_FAILED_TX = `Your transaction was failed. Please make sure you set correct parameters for ballot creation.
Make sure you don't have Transaction Error. Exception thrown in contract code message in Metamask before you sign it.`
Make sure you don't have Transaction Error. Exception thrown in contract code message in MetaMask before you sign it.`
messages.VOTE_FAILED_TX = `Your transaction was failed. Please make sure you haven't already voted for this ballot.
Make sure you don't have Transaction Error. Exception thrown in contract code message in Metamask before you sign it.`
Make sure you don't have Transaction Error. Exception thrown in contract code message in MetaMask before you sign it.`
messages.FINALIZE_FAILED_TX = `Your transaction was failed. Make sure you don't have Transaction Error.
Exception thrown in contract code message in Metamask before you sign it.`
Exception thrown in contract code message in MetaMask before you sign it.`
messages.CANCEL_BALLOT_FAILED_TX = `Your transaction was failed. Make sure you don't have Transaction Error.
Exception thrown in contract code message in Metamask before you sign it.`
Exception thrown in contract code message in MetaMask before you sign it.`
messages.DESCRIPTION_IS_EMPTY = 'Description cannot be empty'
messages.wrongRepo = repo => {
return `There is no contracts.json in configured repo ${repo}`
}
module.exports = {
messages
messages.networkMatchError = function(netId) {
const networkName = getNetworkFullName(Number(netId))
return `Networks in DApp and MetaMask do not match. Switch MetaMask to <b>${networkName}</b> or change the network in DApp.`
}
export default messages

View File

@ -1,19 +1,11 @@
import { constants } from './constants'
export const isTestnet = netId => {
return netId in constants.NETWORKS && constants.NETWORKS[netId].TESTNET
}
export const isValidNetwork = netId => {
return netId in constants.NETWORKS
}
export const getNetworkBranch = netId => {
return constants.NETWORKS[netId].BRANCH
}
export const getNetworkName = netId => {
return constants.NETWORKS[netId].NAME
export const getNetworkFullName = netId => {
return constants.NETWORKS[netId].FULLNAME
}
export const scrollToTop = () => {