From 2e85f7e24b0041cbd6dd24a0452498cb8f5fe7e1 Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Tue, 9 Oct 2018 23:48:25 +0200 Subject: [PATCH] web3 interaction --- src/components/atoms/PostActions.jsx | 10 +- src/components/atoms/Web3Donation.jsx | 180 ++++++++++++++++++ src/components/atoms/Web3Donation.module.scss | 39 ++++ src/components/molecules/ModalThanks.jsx | 80 +------- .../molecules/ModalThanks.module.scss | 4 - src/components/organisms/Footer.jsx | 10 +- 6 files changed, 234 insertions(+), 89 deletions(-) create mode 100644 src/components/atoms/Web3Donation.jsx create mode 100644 src/components/atoms/Web3Donation.module.scss diff --git a/src/components/atoms/PostActions.jsx b/src/components/atoms/PostActions.jsx index 8c3ff3c3..d663c7a7 100644 --- a/src/components/atoms/PostActions.jsx +++ b/src/components/atoms/PostActions.jsx @@ -48,10 +48,12 @@ export default class PostActions extends PureComponent {

- + {this.state.showModal && ( + + )} ) } diff --git a/src/components/atoms/Web3Donation.jsx b/src/components/atoms/Web3Donation.jsx new file mode 100644 index 00000000..378e670e --- /dev/null +++ b/src/components/atoms/Web3Donation.jsx @@ -0,0 +1,180 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Web3 from 'web3' +import styles from './Web3Donation.module.scss' + +const ONE_SECOND = 1000 +const ONE_MINUTE = ONE_SECOND * 60 + +export default class Web3Donation extends PureComponent { + state = { + web3Connected: false, + networkError: null, + networkId: null, + accounts: [], + selectedAccount: null, + receipt: '', + loading: false, + error: null + } + + static propTypes = { + address: PropTypes.string + } + + web3 = new Web3(Web3.givenProvider || 'ws://localhost:8546') + interval = null + networkInterval = null + + componentDidMount() { + const { web3 } = this + + if (web3 && web3.eth) { + this.setState({ web3Connected: true }) + + this.fetchAccounts() + this.fetchNetwork() + this.initPoll() + this.initNetworkPoll() + } + } + + componentWillUnmount() { + clearInterval(this.interval) + clearInterval(this.networkInterval) + this.setState({ web3Connected: false }) + } + + initPoll() { + if (!this.interval) { + this.interval = setInterval(this.fetchAccounts, ONE_SECOND) + } + } + + initNetworkPoll() { + if (!this.networkInterval) { + this.networkInterval = setInterval(this.fetchNetwork, ONE_MINUTE) + } + } + + fetchNetwork = () => { + const { web3 } = this + + web3 && + web3.eth && + web3.eth.net.getId((err, netId) => { + if (err) { + this.setState({ + networkError: err + }) + } else { + if (netId != this.state.networkId) { + this.setState({ + networkError: null, + networkId: netId + }) + } + } + }) + } + + fetchAccounts = () => { + const { web3 } = this + + web3 && + web3.eth && + web3.eth.getAccounts((err, accounts) => { + if (err) { + this.setState({ + accountsError: err + }) + } else { + this.setState({ + accounts, + selectedAccount: accounts[0] + }) + } + }) + } + + handleWeb3Button = () => { + const { web3 } = this + + this.setState({ loading: true }) + + web3.eth + .sendTransaction({ + from: this.state.selectedAccount, + to: this.props.address, + value: '10000000000000000' + }) + .then(receipt => { + this.setState({ receipt, loading: false }) + }) + .catch(error => { + this.setState({ error, loading: false }) + }) + } + + render() { + if (this.state.web3Connected) { + return ( +
+

web3

+

Send a donation with your MetaMask or Mist account.

+ + {this.state.web3Connected && ( +
+ {this.state.loading ? ( + 'Hang on...' + ) : ( + + )} + + {this.state.accounts.length === 0 && ( +
+ Web3 detected, but no account. Are you logged into your + MetaMask account? +
+ )} + + {this.state.networkId !== 1 && ( +
+ Please connect to Main network +
+ )} + + {this.state.error && ( +
{this.state.error.message}
+ )} + + {this.state.receipt.status && ( +
+ You are awesome, thanks! +
+ + See your transaction on etherscan.io. + +
+ )} +
+ )} +
+ ) + } else { + return null + } + } +} diff --git a/src/components/atoms/Web3Donation.module.scss b/src/components/atoms/Web3Donation.module.scss new file mode 100644 index 00000000..9905ad60 --- /dev/null +++ b/src/components/atoms/Web3Donation.module.scss @@ -0,0 +1,39 @@ +@import 'variables'; +@import 'mixins'; + +.web3 { + @include divider; + + width: 100%; + text-align: center; + margin-top: $spacer / 2; + margin-bottom: $spacer; + padding-bottom: $spacer * 1.5; + + button { + margin: auto; + } + + h4 { + font-size: $font-size-large; + margin-top: 0; + margin-bottom: $spacer / 4; + color: $brand-grey; + text-align: center; + } + + p { + color: $brand-grey-light; + } +} + +.alert { + margin-top: $spacer / 2; + font-size: $font-size-small; + color: darken($alert-error, 60%); +} + +.success { + composes: alert; + color: darken($alert-success, 60%); +} diff --git a/src/components/molecules/ModalThanks.jsx b/src/components/molecules/ModalThanks.jsx index 757411f3..87fbc5ce 100644 --- a/src/components/molecules/ModalThanks.jsx +++ b/src/components/molecules/ModalThanks.jsx @@ -2,8 +2,7 @@ import React, { PureComponent } from 'react' import { StaticQuery, graphql } from 'gatsby' import { QRCode } from 'react-qr-svg' import Clipboard from 'react-clipboard.js' -import Web3 from 'web3' - +import Web3Donation from '../atoms/Web3Donation' import Modal from '../atoms/Modal' import { ReactComponent as IconClipboard } from '../../images/clipboard.svg' import styles from './ModalThanks.module.scss' @@ -22,67 +21,6 @@ const query = graphql` ` class ModalThanks extends PureComponent { - state = { - minimal: false, - web3Connected: false, - balance: '', - network: '', - accounts: [], - receipt: '' - } - - web3 = new Web3(Web3.givenProvider || 'ws://localhost:8546') - - componentDidMount() { - this.getWeb3Account() - } - - getWeb3Account() { - if (this.web3 && this.web3.eth.net.isListening()) { - this.setState({ web3Connected: true }) - - this.web3.eth.net.getId((err, netId) => { - switch (netId) { - case '1': - this.setState({ network: 'Main' }) - break - case '2': - this.setState({ network: 'Morden' }) - break - case '3': - this.setState({ network: 'Ropsten' }) - break - case '4': - this.setState({ network: 'Rinkeby' }) - break - case '42': - this.setState({ network: 'Kovan' }) - break - default: - this.setState({ network: 'unknown' }) - } - }) - - this.web3.eth.getAccounts((error, accounts) => { - this.setState({ accounts }) - this.web3.eth.getBalance(accounts[0]).then(balance => { - this.setState({ balance }) - }) - }) - } - } - - handleWeb3Button = () => { - this.web3.eth - .sendTransaction({ - from: this.state.accounts[0], - to: '0x339dbC44d39bf1961E385ed0Ae88FC6069b87Ea1', - value: '1000000000000000' - }) - .then(receipt => this.setState({ receipt })) - .catch(err => console.error(err)) - } - render() { return (
- {this.state.web3Connected && ( -
- - {this.state.receipt.status && ( -
{this.state.receipt.transactionHash}
- )} -
- )} + {Object.keys(author).map((address, i) => (
@@ -118,7 +44,7 @@ class ModalThanks extends PureComponent { bgColor="transparent" fgColor="#6b7f88" level="Q" - style={{ width: 150 }} + style={{ width: 120 }} value={author[address]} />
diff --git a/src/components/molecules/ModalThanks.module.scss b/src/components/molecules/ModalThanks.module.scss
index 61d62f95..0f499ab2 100644
--- a/src/components/molecules/ModalThanks.module.scss
+++ b/src/components/molecules/ModalThanks.module.scss
@@ -5,10 +5,6 @@
         display: flex;
         justify-content: space-between;
         flex-wrap: wrap;
-
-        > div:first-child {
-            width: 100%;
-        }
     }
 }
 
diff --git a/src/components/organisms/Footer.jsx b/src/components/organisms/Footer.jsx
index 1c0b0099..25b4dc90 100644
--- a/src/components/organisms/Footer.jsx
+++ b/src/components/organisms/Footer.jsx
@@ -74,10 +74,12 @@ export default class Footer extends PureComponent {
                     
                   

- + {this.state.showModal && ( + + )}