Fixes to the Registry dapp (#4984)

* Don't show fee warning when there is none

* Hide Warning in Registry onclick

* Use the default account in the Registry

* Fix Etherscan links in Regsitry
This commit is contained in:
Nicolas Gotchac 2017-03-22 11:56:52 +01:00 committed by Jaco Greeff
parent b725829bfd
commit a028e445fe
11 changed files with 118 additions and 102 deletions

View File

@ -42,7 +42,7 @@ export default class Personal {
// FIXME: Because of the different API instances, the "wait for valid changes" approach // FIXME: Because of the different API instances, the "wait for valid changes" approach
// doesn't work. Since the defaultAccount is critical to operation, we poll in exactly // doesn't work. Since the defaultAccount is critical to operation, we poll in exactly
// same way we do in ../eth (ala same as eth_blockNumber) and update. This should be moved // same way we do in ../eth (ala eth_blockNumber) and update. This should be moved
// to pub-sub as it becomes available // to pub-sub as it becomes available
_defaultAccount = (timerDisabled = false) => { _defaultAccount = (timerDisabled = false) => {
const nextTimeout = (timeout = 1000) => { const nextTimeout = (timeout = 1000) => {

View File

@ -15,24 +15,10 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.button {
/* TODO remove !important once material design lite is used */
padding: 0 !important;
}
.icon { .icon {
/* TODO remove !important once material design lite is used */ /* TODO remove !important once material design lite is used */
margin: 0 !important;
width: 30px !important;
height: 30px !important; height: 30px !important;
} margin: 0 !important;
padding: 0 !important;
.menuIcon { width: 30px !important;
display: inline-block;
vertical-align: middle;
}
.menuText {
display: inline-block;
line-height: 24px;
vertical-align: top;
} }

View File

@ -17,83 +17,50 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import IconMenu from 'material-ui/IconMenu';
import IconButton from 'material-ui/IconButton/IconButton';
import AccountIcon from 'material-ui/svg-icons/action/account-circle'; import AccountIcon from 'material-ui/svg-icons/action/account-circle';
import MenuItem from 'material-ui/MenuItem';
import { init } from './actions';
import IdentityIcon from '../IdentityIcon'; import IdentityIcon from '../IdentityIcon';
import Address from '../ui/address';
import { select } from './actions';
import styles from './accounts.css'; import styles from './accounts.css';
class Accounts extends Component { class Accounts extends Component {
static propTypes = { static propTypes = {
all: PropTypes.object.isRequired, selected: PropTypes.oneOfType([
selected: PropTypes.object, PropTypes.oneOf([ null ]),
PropTypes.string
]),
onInit: PropTypes.func.isRequired
};
select: PropTypes.func.isRequired componentWillMount () {
this.props.onInit();
} }
render () { render () {
const { all, selected } = this.props; const { selected } = this.props;
const origin = { horizontal: 'right', vertical: 'top' }; if (!selected) {
return (
const accountsButton = (
<IconButton className={ styles.button }>
{ selected
? (
<IdentityIcon
className={ styles.icon }
address={ selected.address }
/>
) : (
<AccountIcon <AccountIcon
className={ styles.icon } className={ styles.icon }
color='white' color='white'
/> />
)
}
</IconButton>);
return (
<IconMenu
value={ selected ? this.renderAccount(selected) : null }
onChange={ this.onAccountSelect }
iconButtonElement={ accountsButton }
anchorOrigin={ origin }
targetOrigin={ origin }
>
{ Object.values(all).map(this.renderAccount) }
</IconMenu>
); );
} }
renderAccount = (account) => {
const { selected } = this.props;
const isSelected = selected && selected.address === account.address;
return ( return (
<MenuItem <IdentityIcon
key={ account.address } className={ styles.icon }
value={ account.address } address={ selected }
checked={ isSelected } />
insetChildren={ !isSelected }
>
<Address address={ account.address } />
</MenuItem>
); );
}; }
onAccountSelect = (e, address) => {
this.props.select(address);
};
} }
const mapStateToProps = (state) => state.accounts; const mapStateToProps = (state) => state.accounts;
const mapDispatchToProps = (dispatch) => bindActionCreators({ select }, dispatch); const mapDispatchToProps = (dispatch) => bindActionCreators({
onInit: init
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Accounts); export default connect(mapStateToProps, mapDispatchToProps)(Accounts);

View File

@ -14,4 +14,27 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { api } from '../parity';
export const select = (address) => ({ type: 'accounts select', address }); export const select = (address) => ({ type: 'accounts select', address });
export const init = () => (dispatch) => {
api.subscribe('parity_defaultAccount', (error, accountAddress) => {
if (error) {
return console.error(error);
}
if (accountAddress) {
dispatch(select(accountAddress));
}
});
return api.parity
.defaultAccount()
.then((accountAddress) => {
dispatch(select(accountAddress));
})
.catch((error) => {
console.error(error);
});
};

View File

@ -16,9 +16,11 @@
*/ */
.header { .header {
align-items: center;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin: 0; padding: .3em 1em; margin: 0;
padding: 0.3em 1em;
color: #fff; color: #fff;
background-color: #333; background-color: #333;
} }
@ -54,6 +56,7 @@
background: #f80; background: #f80;
bottom: 0; bottom: 0;
color: #fff; color: #fff;
cursor: pointer;
left: 0; left: 0;
opacity: 1; opacity: 1;
padding: 1.5em; padding: 1.5em;

View File

@ -49,6 +49,10 @@ export default class Application extends Component {
fee: nullableProptype(PropTypes.object.isRequired) fee: nullableProptype(PropTypes.object.isRequired)
}; };
state = {
showWarning: true
};
render () { render () {
const { contract, fee } = this.props; const { contract, fee } = this.props;
let warning = null; let warning = null;
@ -65,9 +69,7 @@ export default class Application extends Component {
<Lookup /> <Lookup />
{ this.renderActions() } { this.renderActions() }
<Events /> <Events />
<div className={ styles.warning }> { this.renderWarning() }
WARNING: The name registry is experimental. Please ensure that you understand the risks, benefits & consequences of registering a name before doing so. A non-refundable fee of { api.util.fromWei(fee).toFormat(3) }<small>ETH</small> is required for all registrations.
</div>
</div> </div>
) : ( ) : (
<CircularProgress size={ 60 } /> <CircularProgress size={ 60 } />
@ -98,4 +100,39 @@ export default class Application extends Component {
</div> </div>
); );
} }
renderWarning () {
const { showWarning } = this.state;
const { fee } = this.props;
if (!showWarning) {
return null;
}
return (
<div
className={ styles.warning }
onClick={ this.handleHideWarning }
>
<span>
WARNING: The name registry is experimental. Please ensure that you understand the risks,
benefits & consequences of registering a name before doing so.
</span>
{
fee && api.util.fromWei(fee).gt(0)
? (
<span>
&nbsp;A non-refundable fee of { api.util.fromWei(fee).toFormat(3) } <small>ETH</small>
&nbsp;is required for all registrations.
</span>
)
: null
}
</div>
);
}
handleHideWarning = () => {
this.setState({ showWarning: false });
}
} }

View File

@ -33,11 +33,11 @@ export const reserveFail = (name, error) => ({ type: 'names reserve fail', name,
export const reserve = (name) => (dispatch, getState) => { export const reserve = (name) => (dispatch, getState) => {
const state = getState(); const state = getState();
const account = state.accounts.selected; const accountAddress = state.accounts.selected;
const contract = state.contract; const contract = state.contract;
const fee = state.fee; const fee = state.fee;
if (!contract || !account) { if (!contract || !accountAddress) {
return; return;
} }
@ -58,7 +58,7 @@ export const reserve = (name) => (dispatch, getState) => {
const { reserve } = contract.instance; const { reserve } = contract.instance;
const options = { const options = {
from: account.address, from: accountAddress,
value: fee value: fee
}; };
const values = [ const values = [
@ -88,10 +88,10 @@ export const dropFail = (name, error) => ({ type: 'names drop fail', name, error
export const drop = (name) => (dispatch, getState) => { export const drop = (name) => (dispatch, getState) => {
const state = getState(); const state = getState();
const account = state.accounts.selected; const accountAddress = state.accounts.selected;
const contract = state.contract; const contract = state.contract;
if (!contract || !account) { if (!contract || !accountAddress) {
return; return;
} }
@ -105,14 +105,14 @@ export const drop = (name) => (dispatch, getState) => {
return getOwner(contract, name) return getOwner(contract, name)
.then((owner) => { .then((owner) => {
if (owner.toLowerCase() !== account.address.toLowerCase()) { if (owner.toLowerCase() !== accountAddress.toLowerCase()) {
throw new Error(`you are not the owner of "${name}"`); throw new Error(`you are not the owner of "${name}"`);
} }
const { drop } = contract.instance; const { drop } = contract.instance;
const options = { const options = {
from: account.address from: accountAddress
}; };
const values = [ const values = [

View File

@ -30,10 +30,10 @@ export const fail = (error) => ({ type: 'records update fail', error });
export const update = (name, key, value) => (dispatch, getState) => { export const update = (name, key, value) => (dispatch, getState) => {
const state = getState(); const state = getState();
const account = state.accounts.selected; const accountAddress = state.accounts.selected;
const contract = state.contract; const contract = state.contract;
if (!contract || !account) { if (!contract || !accountAddress) {
return; return;
} }
@ -42,7 +42,7 @@ export const update = (name, key, value) => (dispatch, getState) => {
return getOwner(contract, name) return getOwner(contract, name)
.then((owner) => { .then((owner) => {
if (owner.toLowerCase() !== account.address.toLowerCase()) { if (owner.toLowerCase() !== accountAddress.toLowerCase()) {
throw new Error(`you are not the owner of "${name}"`); throw new Error(`you are not the owner of "${name}"`);
} }
@ -51,7 +51,7 @@ export const update = (name, key, value) => (dispatch, getState) => {
: contract.instance.setData || contract.instance.set; : contract.instance.setData || contract.instance.set;
const options = { const options = {
from: account.address from: accountAddress
}; };
const values = [ const values = [

View File

@ -30,10 +30,10 @@ export const fail = (action, error) => ({ type: `reverse ${action} fail`, error
export const propose = (name, address) => (dispatch, getState) => { export const propose = (name, address) => (dispatch, getState) => {
const state = getState(); const state = getState();
const account = state.accounts.selected; const accountAddress = state.accounts.selected;
const contract = state.contract; const contract = state.contract;
if (!contract || !account) { if (!contract || !accountAddress) {
return; return;
} }
@ -42,14 +42,14 @@ export const propose = (name, address) => (dispatch, getState) => {
return getOwner(contract, name) return getOwner(contract, name)
.then((owner) => { .then((owner) => {
if (owner.toLowerCase() !== account.address.toLowerCase()) { if (owner.toLowerCase() !== accountAddress.toLowerCase()) {
throw new Error(`you are not the owner of "${name}"`); throw new Error(`you are not the owner of "${name}"`);
} }
const { proposeReverse } = contract.instance; const { proposeReverse } = contract.instance;
const options = { const options = {
from: account.address from: accountAddress
}; };
const values = [ const values = [
@ -74,10 +74,10 @@ export const propose = (name, address) => (dispatch, getState) => {
export const confirm = (name) => (dispatch, getState) => { export const confirm = (name) => (dispatch, getState) => {
const state = getState(); const state = getState();
const account = state.accounts.selected; const accountAddress = state.accounts.selected;
const contract = state.contract; const contract = state.contract;
if (!contract || !account) { if (!contract || !accountAddress) {
return; return;
} }
@ -86,14 +86,14 @@ export const confirm = (name) => (dispatch, getState) => {
return getOwner(contract, name) return getOwner(contract, name)
.then((owner) => { .then((owner) => {
if (owner.toLowerCase() !== account.address.toLowerCase()) { if (owner.toLowerCase() !== accountAddress.toLowerCase()) {
throw new Error(`you are not the owner of "${name}"`); throw new Error(`you are not the owner of "${name}"`);
} }
const { confirmReverse } = contract.instance; const { confirmReverse } = contract.instance;
const options = { const options = {
from: account.address from: accountAddress
}; };
const values = [ const values = [

View File

@ -31,8 +31,8 @@ export default (state = initialState, action) => {
return { ...state, all: accounts }; return { ...state, all: accounts };
} }
if (action.type === 'accounts select' && state.all[action.address]) { if (action.type === 'accounts select') {
return { ...state, selected: state.all[action.address] }; return { ...state, selected: action.address };
} }
return state; return state;

View File

@ -22,7 +22,7 @@ const etherscanUrl = (hash, isTestnet, netVersion) => {
hash = hash.toLowerCase().replace(leading0x, ''); hash = hash.toLowerCase().replace(leading0x, '');
const type = hash.length === 40 ? 'address' : 'tx'; const type = hash.length === 40 ? 'address' : 'tx';
return `https://${externalUrl(isTestnet, netVersion)}/${type}/0x${hash}`; return `${externalUrl(isTestnet, netVersion)}/${type}/0x${hash}`;
}; };
export default etherscanUrl; export default etherscanUrl;