2
0
mirror of synced 2025-02-22 20:08:32 +00:00

first stab at showing multiple accounts dropdown

better error handling for error page (more description)

add config support for embark to autmoatically generate and fund accounts

updated test to use default account
This commit is contained in:
emizzle 2018-07-14 07:50:15 +02:00
parent 5f5f5f47da
commit cd65f6a2b4
9 changed files with 54 additions and 59 deletions

View File

@ -42,6 +42,7 @@ nav .navbar-text a:hover, nav .navbar-text a:focus {
text-decoration:none;
}
nav .error {color:red}
nav.error .navbar-brand {padding-top:10px;}
.tweets .tweet .list-group-item-text {
white-space: pre-line;
}

View File

@ -21,7 +21,8 @@ class App extends Component {
this.state = {
user: {},
account: '',
error: {}
error: {},
accounts: []
}
}
//#endregion
@ -43,7 +44,7 @@ class App extends Component {
const accounts = await web3.eth.getAccounts();
try {
// get the owner associated with the current defaultAccount
const usernameHash = await DTwitter.methods.owners(accounts[0]).call();
const usernameHash = await DTwitter.methods.owners(web3.eth.defaultAccount).call();
// get user details from contract
const user = await DTwitter.methods.users(usernameHash).call();
@ -52,10 +53,10 @@ class App extends Component {
user.picture = user.picture.length > 0 ? EmbarkJS.Storage.getUrl(user.picture) : imgAvatar;
// update state with user details
return this.setState({ user: user, account: accounts[0] });
return this.setState({ user: user, account: web3.eth.defaultAccount, accounts: accounts });
}
catch (err) {
this._onError(err);
this._onError(err, 'App._loadCurrentUser');
}
}
@ -64,7 +65,8 @@ class App extends Component {
*
* @param {Error} err - error encountered
*/
_onError(err) {
_onError(err, source) {
if(source) err.source = source;
this.setState({ error: err });
this.props.history.push('/whoopsie');
}
@ -83,13 +85,16 @@ class App extends Component {
<Header
user={this.state.user}
account={this.state.account}
error={this.state.error} />
accounts={this.state.accounts}
error={this.state.error}
onAfterUserUpdate={(e) => this._loadCurrentUser()} />
<Main
user={this.state.user}
account={this.state.account}
accounts={this.state.accounts}
error={this.state.error}
onAfterUserUpdate={(e) => this._loadCurrentUser()}
onError={(err) => this._onError(err)} />
onError={(err, source) => this._onError(err, source)} />
</div>
);
}

View File

@ -17,9 +17,8 @@ class Error extends Component {
//#region React lifecycle events
render() {
const metaMaskPossible = (this.props.error.message.indexOf('Internal JSON-RPC error') > 0 || this.props.error.code === -32603);
console.log('error = ' + this.props.error.code);
console.log('metaMaskPossible = ' + this.props.error.message.indexOf('Internal JSON-RPC error') > 0);
const metaMaskPossible = (this.props.error.message.indexOf('Internal JSON-RPC error') > 0 || this.props.error.message.indexOf('Failed to fetch') > 0);
return (
<Grid>
<Row>
@ -30,13 +29,21 @@ class Error extends Component {
{metaMaskPossible ?
<React.Fragment>
<h3>Metamask error?</h3>
<p>It appears you are using metamask. Have you signed in and are you on the right network?</p>
<p>It appears you might be using metamask. Have you signed in and are you on the right network?</p>
</React.Fragment>
:
''
}
<h3>Error details</h3>
<pre>{this.props.error.message}<br />{this.props.error.stack}</pre>
<pre>{this.props.error.message}<br />
{this.props.error.stack}
{ this.props.error.source ?
<React.Fragment>
<br/>Source: { this.props.error.source }
</React.Fragment>
:
''
}</pre>
</Col>
</Row>
</Grid>

View File

@ -39,7 +39,6 @@ import { FormGroup, ControlLabel, FormControl, HelpBlock, InputGroup } from 'rea
* @returns {React.Component} The completed component to render.
*/
const FieldGroup = ({ id, label, help, validationState, hasFeedback, inputAddOn, ...props }) => {
return (
<React.Fragment>
<FormGroup controlId={id} validationState={validationState}>
@ -52,7 +51,7 @@ const FieldGroup = ({ id, label, help, validationState, hasFeedback, inputAddOn,
{ inputAddOn.location === 'after' ? <InputGroup.Addon>{ inputAddOn.addOn }</InputGroup.Addon> : '' }
</InputGroup>
:
<FormControl {...props} />
<FormControl {...props}>{ props.children }</FormControl>
}
{hasFeedback ? <FormControl.Feedback /> : ''}
{help && <HelpBlock>{help}</HelpBlock>}

View File

@ -5,6 +5,7 @@ import DoTweet from './DoTweet';
import Search from './Search';
import { limitAddressLength } from '../utils';
import Spinner from 'react-spinkit';
import FieldGroup from './FieldGroup';
/**
* Class representing the header of the page that handles
@ -47,6 +48,11 @@ class Header extends Component {
_handleToggle() {
this.setState({ showTooltip: !this.state.showTooltip });
}
_handleAcctChange(e){
web3.eth.defaultAccount = e.target.value;
this.props.onAfterUserUpdate();
}
//#endregion
//#region React lifecycle events
@ -118,9 +124,12 @@ class Header extends Component {
</Modal.Footer>
</Modal>
</React.Fragment>;
const accts = this.props.accounts.map(function(address, index){
return <option key={ index } value={ address }>{ address }</option>
});
return (
<Navbar collapseOnSelect className={this.props.user.username ? '' : 'logged-out'}>
<Navbar collapseOnSelect className={this.props.user.username ? '' : 'logged-out'} className={ isError ? 'error' : '' }>
<Navbar.Header>
<Navbar.Brand>
<NavLink exact to="/">dTwitter <small>embark by Status</small></NavLink>
@ -132,7 +141,14 @@ class Header extends Component {
<Navbar.Form>
<Search />
</Navbar.Form>
<FieldGroup
id='selectAddr'
label='Select account'
componentClass='select'
placeholder='Select account'
onChange={ (e) => this._handleAcctChange(e) }>
{ accts }
</FieldGroup>
{ isLoading ?
states.isLoading
:

View File

@ -60,7 +60,7 @@ class UserTweets extends Component {
fromBlock: 0
}, (err, event) => {
if (err){
this.props.onError(err);
this.props.onError(err, 'UserTweets._subscribeToNewTweetEvent');
}
})
.on('data', (event) => {
@ -73,7 +73,7 @@ class UserTweets extends Component {
this.setState({tweets: tweets});
})
.on('error', function(error){
this.props.onError(err);
this.props.onError(err, 'UserTweets._subscribeToNewTweetEvent');
});
}

View File

@ -1,38 +1 @@
{
"0x44be6d937485110cdc88ce58bd6b48c0c9f48bb2c9ae62c40c353ac5a8b0f226": {
"contracts": {
"0x35cbd109f501602ed728b03e78faa3df2cc6b5c9db220e139623f9ae41389b02": {
"name": "DTwitter",
"address": "0xce37C47cefd1d40dc058995616953C943C93657d"
},
"0x1797453304e4b69b44fb7d649a72ca80394a8b4e1d14c2cc530f5dc3ae1f0ea3": {
"name": "ENSRegistry",
"address": "0xaaC3765Af434050d29f4b7188044Fa1C619bF614"
},
"0x1c1c5eec1cd80df5fe51d3922a226a9009dd83ab9e65b707d5f20f439dfbb237": {
"name": "FIFSRegistrar",
"address": "0xB1014418a4f4436CDfa4D4D7516Fa5f0B2DF3feB"
},
"0xd129cd954f5ea85193fd4eacbb0ec150e26459405df123408a122481ff1e1f66": {
"name": "FIFSRegistrar",
"address": "0xEa6bfC300214dDb918E24c24Af8A272E9e6EaFDF"
}
}
},
"0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177": {
"contracts": {
"0x35cbd109f501602ed728b03e78faa3df2cc6b5c9db220e139623f9ae41389b02": {
"name": "DTwitter",
"address": "0xafaf19FbCB2E3d1c7DCC926fFC62E368f96326bD"
},
"0x6d1e2e0bd96a493aaab99909e0fd4ed874e93edd9a58020916846e08876c3b01": {
"name": "FIFSRegistrar",
"address": "0x774a72b8509B6eEF6d22ac59159FB85B5be858e7"
},
"0x1797453304e4b69b44fb7d649a72ca80394a8b4e1d14c2cc530f5dc3ae1f0ea3": {
"name": "ENSRegistry",
"address": "0xA5665431C52A0D9b9bC1DD4Da314Cce70d44ED1F"
}
}
}
}
{}

View File

@ -12,15 +12,19 @@ module.exports = {
rpcHost: "localhost",
rpcPort: 8545,
rpcCorsDomain: "auto",
proxy: true,
rpcApi: ['eth', 'web3', 'net', 'debug', 'personal'],
proxy: false,
account: {
password: "config/development/password"
password: "config/development/password",
numAccounts: 3,
balance: "5 ether"
},
targetGasLimit: 10000000,
wsOrigins: "auto",
wsRPC: true,
wsHost: "localhost",
wsPort: 8546,
wsApi: ['eth', 'web3', 'net', 'shh', 'debug', 'personal'],
simulatorMnemonic: "example exile argue silk regular smile grass bomb merge arm assist farm",
simulatorBlocktime: 0
},

View File

@ -54,7 +54,7 @@ contract("DTwitter contract", function () {
it("should have created an owner for our defaultAccount", async function () {
// read from the owners mapping
const usernameHash = await owners(accounts[0]).call();
const usernameHash = await owners(web3.eth.defaultAccount).call();
// check the return value from owners mapping matches
assert.equal(usernameHash, web3.utils.keccak256(username));