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:
parent
5f5f5f47da
commit
cd65f6a2b4
@ -42,6 +42,7 @@ nav .navbar-text a:hover, nav .navbar-text a:focus {
|
|||||||
text-decoration:none;
|
text-decoration:none;
|
||||||
}
|
}
|
||||||
nav .error {color:red}
|
nav .error {color:red}
|
||||||
|
nav.error .navbar-brand {padding-top:10px;}
|
||||||
.tweets .tweet .list-group-item-text {
|
.tweets .tweet .list-group-item-text {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@ class App extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
user: {},
|
user: {},
|
||||||
account: '',
|
account: '',
|
||||||
error: {}
|
error: {},
|
||||||
|
accounts: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -43,7 +44,7 @@ class App extends Component {
|
|||||||
const accounts = await web3.eth.getAccounts();
|
const accounts = await web3.eth.getAccounts();
|
||||||
try {
|
try {
|
||||||
// get the owner associated with the current defaultAccount
|
// 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
|
// get user details from contract
|
||||||
const user = await DTwitter.methods.users(usernameHash).call();
|
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;
|
user.picture = user.picture.length > 0 ? EmbarkJS.Storage.getUrl(user.picture) : imgAvatar;
|
||||||
|
|
||||||
// update state with user details
|
// 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) {
|
catch (err) {
|
||||||
this._onError(err);
|
this._onError(err, 'App._loadCurrentUser');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +65,8 @@ class App extends Component {
|
|||||||
*
|
*
|
||||||
* @param {Error} err - error encountered
|
* @param {Error} err - error encountered
|
||||||
*/
|
*/
|
||||||
_onError(err) {
|
_onError(err, source) {
|
||||||
|
if(source) err.source = source;
|
||||||
this.setState({ error: err });
|
this.setState({ error: err });
|
||||||
this.props.history.push('/whoopsie');
|
this.props.history.push('/whoopsie');
|
||||||
}
|
}
|
||||||
@ -83,13 +85,16 @@ class App extends Component {
|
|||||||
<Header
|
<Header
|
||||||
user={this.state.user}
|
user={this.state.user}
|
||||||
account={this.state.account}
|
account={this.state.account}
|
||||||
error={this.state.error} />
|
accounts={this.state.accounts}
|
||||||
|
error={this.state.error}
|
||||||
|
onAfterUserUpdate={(e) => this._loadCurrentUser()} />
|
||||||
<Main
|
<Main
|
||||||
user={this.state.user}
|
user={this.state.user}
|
||||||
account={this.state.account}
|
account={this.state.account}
|
||||||
|
accounts={this.state.accounts}
|
||||||
error={this.state.error}
|
error={this.state.error}
|
||||||
onAfterUserUpdate={(e) => this._loadCurrentUser()}
|
onAfterUserUpdate={(e) => this._loadCurrentUser()}
|
||||||
onError={(err) => this._onError(err)} />
|
onError={(err, source) => this._onError(err, source)} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,8 @@ class Error extends Component {
|
|||||||
//#region React lifecycle events
|
//#region React lifecycle events
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const metaMaskPossible = (this.props.error.message.indexOf('Internal JSON-RPC error') > 0 || this.props.error.code === -32603);
|
const metaMaskPossible = (this.props.error.message.indexOf('Internal JSON-RPC error') > 0 || this.props.error.message.indexOf('Failed to fetch') > 0);
|
||||||
console.log('error = ' + this.props.error.code);
|
|
||||||
console.log('metaMaskPossible = ' + this.props.error.message.indexOf('Internal JSON-RPC error') > 0);
|
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Grid>
|
||||||
<Row>
|
<Row>
|
||||||
@ -30,13 +29,21 @@ class Error extends Component {
|
|||||||
{metaMaskPossible ?
|
{metaMaskPossible ?
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<h3>Metamask error?</h3>
|
<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>
|
</React.Fragment>
|
||||||
:
|
:
|
||||||
''
|
''
|
||||||
}
|
}
|
||||||
<h3>Error details</h3>
|
<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>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -39,7 +39,6 @@ import { FormGroup, ControlLabel, FormControl, HelpBlock, InputGroup } from 'rea
|
|||||||
* @returns {React.Component} The completed component to render.
|
* @returns {React.Component} The completed component to render.
|
||||||
*/
|
*/
|
||||||
const FieldGroup = ({ id, label, help, validationState, hasFeedback, inputAddOn, ...props }) => {
|
const FieldGroup = ({ id, label, help, validationState, hasFeedback, inputAddOn, ...props }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<FormGroup controlId={id} validationState={validationState}>
|
<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> : '' }
|
{ inputAddOn.location === 'after' ? <InputGroup.Addon>{ inputAddOn.addOn }</InputGroup.Addon> : '' }
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
:
|
:
|
||||||
<FormControl {...props} />
|
<FormControl {...props}>{ props.children }</FormControl>
|
||||||
}
|
}
|
||||||
{hasFeedback ? <FormControl.Feedback /> : ''}
|
{hasFeedback ? <FormControl.Feedback /> : ''}
|
||||||
{help && <HelpBlock>{help}</HelpBlock>}
|
{help && <HelpBlock>{help}</HelpBlock>}
|
||||||
|
@ -5,6 +5,7 @@ import DoTweet from './DoTweet';
|
|||||||
import Search from './Search';
|
import Search from './Search';
|
||||||
import { limitAddressLength } from '../utils';
|
import { limitAddressLength } from '../utils';
|
||||||
import Spinner from 'react-spinkit';
|
import Spinner from 'react-spinkit';
|
||||||
|
import FieldGroup from './FieldGroup';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the header of the page that handles
|
* Class representing the header of the page that handles
|
||||||
@ -47,6 +48,11 @@ class Header extends Component {
|
|||||||
_handleToggle() {
|
_handleToggle() {
|
||||||
this.setState({ showTooltip: !this.state.showTooltip });
|
this.setState({ showTooltip: !this.state.showTooltip });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleAcctChange(e){
|
||||||
|
web3.eth.defaultAccount = e.target.value;
|
||||||
|
this.props.onAfterUserUpdate();
|
||||||
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region React lifecycle events
|
//#region React lifecycle events
|
||||||
@ -118,9 +124,12 @@ class Header extends Component {
|
|||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</Modal>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
const accts = this.props.accounts.map(function(address, index){
|
||||||
|
return <option key={ index } value={ address }>{ address }</option>
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
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.Header>
|
||||||
<Navbar.Brand>
|
<Navbar.Brand>
|
||||||
<NavLink exact to="/">dTwitter <small>embark by Status</small></NavLink>
|
<NavLink exact to="/">dTwitter <small>embark by Status</small></NavLink>
|
||||||
@ -132,7 +141,14 @@ class Header extends Component {
|
|||||||
<Navbar.Form>
|
<Navbar.Form>
|
||||||
<Search />
|
<Search />
|
||||||
</Navbar.Form>
|
</Navbar.Form>
|
||||||
|
<FieldGroup
|
||||||
|
id='selectAddr'
|
||||||
|
label='Select account'
|
||||||
|
componentClass='select'
|
||||||
|
placeholder='Select account'
|
||||||
|
onChange={ (e) => this._handleAcctChange(e) }>
|
||||||
|
{ accts }
|
||||||
|
</FieldGroup>
|
||||||
{ isLoading ?
|
{ isLoading ?
|
||||||
states.isLoading
|
states.isLoading
|
||||||
:
|
:
|
||||||
|
@ -60,7 +60,7 @@ class UserTweets extends Component {
|
|||||||
fromBlock: 0
|
fromBlock: 0
|
||||||
}, (err, event) => {
|
}, (err, event) => {
|
||||||
if (err){
|
if (err){
|
||||||
this.props.onError(err);
|
this.props.onError(err, 'UserTweets._subscribeToNewTweetEvent');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('data', (event) => {
|
.on('data', (event) => {
|
||||||
@ -73,7 +73,7 @@ class UserTweets extends Component {
|
|||||||
this.setState({tweets: tweets});
|
this.setState({tweets: tweets});
|
||||||
})
|
})
|
||||||
.on('error', function(error){
|
.on('error', function(error){
|
||||||
this.props.onError(err);
|
this.props.onError(err, 'UserTweets._subscribeToNewTweetEvent');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
chains.json
39
chains.json
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -12,15 +12,19 @@ module.exports = {
|
|||||||
rpcHost: "localhost",
|
rpcHost: "localhost",
|
||||||
rpcPort: 8545,
|
rpcPort: 8545,
|
||||||
rpcCorsDomain: "auto",
|
rpcCorsDomain: "auto",
|
||||||
proxy: true,
|
rpcApi: ['eth', 'web3', 'net', 'debug', 'personal'],
|
||||||
|
proxy: false,
|
||||||
account: {
|
account: {
|
||||||
password: "config/development/password"
|
password: "config/development/password",
|
||||||
|
numAccounts: 3,
|
||||||
|
balance: "5 ether"
|
||||||
},
|
},
|
||||||
targetGasLimit: 10000000,
|
targetGasLimit: 10000000,
|
||||||
wsOrigins: "auto",
|
wsOrigins: "auto",
|
||||||
wsRPC: true,
|
wsRPC: true,
|
||||||
wsHost: "localhost",
|
wsHost: "localhost",
|
||||||
wsPort: 8546,
|
wsPort: 8546,
|
||||||
|
wsApi: ['eth', 'web3', 'net', 'shh', 'debug', 'personal'],
|
||||||
simulatorMnemonic: "example exile argue silk regular smile grass bomb merge arm assist farm",
|
simulatorMnemonic: "example exile argue silk regular smile grass bomb merge arm assist farm",
|
||||||
simulatorBlocktime: 0
|
simulatorBlocktime: 0
|
||||||
},
|
},
|
||||||
|
@ -54,7 +54,7 @@ contract("DTwitter contract", function () {
|
|||||||
it("should have created an owner for our defaultAccount", async function () {
|
it("should have created an owner for our defaultAccount", async function () {
|
||||||
|
|
||||||
// read from the owners mapping
|
// 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
|
// check the return value from owners mapping matches
|
||||||
assert.equal(usernameHash, web3.utils.keccak256(username));
|
assert.equal(usernameHash, web3.utils.keccak256(username));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user