mirror of
https://github.com/kremalicious/blog.git
synced 2024-11-15 01:25:28 +01:00
Merge pull request #79 from kremalicious/feature/web3-loader
nicer web3 loading experience
This commit is contained in:
commit
d70fecda3f
@ -80,7 +80,7 @@
|
|||||||
"@babel/node": "^7.0.0",
|
"@babel/node": "^7.0.0",
|
||||||
"@babel/preset-env": "^7.1.0",
|
"@babel/preset-env": "^7.1.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"eslint": "^5.7.0",
|
"eslint": "^5.8.0",
|
||||||
"eslint-config-prettier": "^3.1.0",
|
"eslint-config-prettier": "^3.1.0",
|
||||||
"eslint-loader": "^2.1.1",
|
"eslint-loader": "^2.1.1",
|
||||||
"eslint-plugin-graphql": "^2.1.1",
|
"eslint-plugin-graphql": "^2.1.1",
|
||||||
@ -94,7 +94,7 @@
|
|||||||
"prettier": "^1.14.3",
|
"prettier": "^1.14.3",
|
||||||
"prettier-eslint-cli": "^4.7.1",
|
"prettier-eslint-cli": "^4.7.1",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"stylelint": "^9.6.0",
|
"stylelint": "^9.7.0",
|
||||||
"stylelint-config-css-modules": "^1.3.0",
|
"stylelint-config-css-modules": "^1.3.0",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.2.0",
|
||||||
"stylelint-scss": "^3.3.2"
|
"stylelint-scss": "^3.3.2"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
|
|
||||||
.entryMeta {
|
.entryMeta {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-small;
|
||||||
margin-top: $spacer * 2;
|
margin-top: $spacer * 2;
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
}
|
}
|
||||||
|
@ -1,66 +1,59 @@
|
|||||||
import React, { Fragment, PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import styles from './Alerts.module.scss'
|
import styles from './Alerts.module.scss'
|
||||||
|
|
||||||
const Message = ({ message, ...props }) => (
|
export const alertMessages = (networkName, transactionHash) => ({
|
||||||
<div dangerouslySetInnerHTML={{ __html: message }} {...props} />
|
|
||||||
)
|
|
||||||
|
|
||||||
export default class Alerts extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
hasCorrectNetwork: PropTypes.bool.isRequired,
|
|
||||||
hasAccount: PropTypes.bool.isRequired,
|
|
||||||
networkName: PropTypes.string,
|
|
||||||
error: PropTypes.object,
|
|
||||||
transactionHash: PropTypes.string,
|
|
||||||
confirmationNumber: PropTypes.number,
|
|
||||||
receipt: PropTypes.object,
|
|
||||||
web3Connected: PropTypes.bool.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
alertMessages = (networkName, transactionHash) => ({
|
|
||||||
noAccount:
|
noAccount:
|
||||||
'Web3 detected, but no account. Are you logged into your MetaMask account?',
|
'Web3 detected, but no account. Are you logged into your MetaMask account?',
|
||||||
noCorrectNetwork: `Please connect to <strong>Main</strong> network. You are on <strong>${networkName}</strong> right now.`,
|
noCorrectNetwork: `Please connect to <strong>Main</strong> network. You are on <strong>${networkName}</strong> right now.`,
|
||||||
noWeb3:
|
noWeb3:
|
||||||
'No Web3 detected. Install <a href="https://metamask.io">MetaMask</a>, <a href="https://brave.com">Brave</a>, or <a href="https://github.com/ethereum/mist">Mist</a>.',
|
'No Web3 detected. Install <a href="https://metamask.io">MetaMask</a>, <a href="https://brave.com">Brave</a>, or <a href="https://github.com/ethereum/mist">Mist</a>.',
|
||||||
transaction: `<a href="https://etherscan.io/tx/${transactionHash}" target="_blank">See your transaction on etherscan.io.</a>`
|
transaction: `<a href="https://etherscan.io/tx/${transactionHash}" target="_blank">See your transaction on etherscan.io.</a>`,
|
||||||
})
|
waitingForUser: 'Waiting for your confirmation',
|
||||||
|
waitingConfirmation: 'Waiting for network confirmation, hang on',
|
||||||
|
success: 'Confirmed. You are awesome, thanks!'
|
||||||
|
})
|
||||||
|
|
||||||
|
export default class Alerts extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
message: PropTypes.object,
|
||||||
|
transactionHash: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
constructMessage = () => {
|
||||||
|
const { transactionHash, message } = this.props
|
||||||
|
|
||||||
|
let messageOutput
|
||||||
|
|
||||||
|
if (transactionHash) {
|
||||||
|
messageOutput =
|
||||||
|
message.text +
|
||||||
|
'<br />' +
|
||||||
|
alertMessages(null, transactionHash).transaction
|
||||||
|
} else {
|
||||||
|
messageOutput = message.text
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
classes() {
|
||||||
|
const { status } = this.props.message
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
return styles.success
|
||||||
|
} else if (status === 'error') {
|
||||||
|
return styles.error
|
||||||
|
}
|
||||||
|
return styles.alert
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
|
||||||
hasCorrectNetwork,
|
|
||||||
hasAccount,
|
|
||||||
networkName,
|
|
||||||
error,
|
|
||||||
transactionHash,
|
|
||||||
web3Connected
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.alert}>
|
<div
|
||||||
{!web3Connected ? (
|
className={this.classes()}
|
||||||
<Message message={this.alertMessages().noWeb3} />
|
dangerouslySetInnerHTML={{ __html: this.constructMessage() }}
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
{!hasAccount && (
|
|
||||||
<Message message={this.alertMessages().noAccount} />
|
|
||||||
)}
|
|
||||||
{!hasCorrectNetwork && (
|
|
||||||
<Message
|
|
||||||
message={this.alertMessages(networkName).noCorrectNetwork}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{error && <Message message={error.message} />}
|
|
||||||
|
|
||||||
{transactionHash && (
|
|
||||||
<Message
|
|
||||||
message={this.alertMessages(null, transactionHash).transaction}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,44 @@
|
|||||||
@import 'mixins';
|
@import 'mixins';
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
margin-top: $spacer / 2;
|
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: bottom;
|
||||||
|
animation: ellipsis steps(4, end) 1s infinite;
|
||||||
|
content: '\2026'; // ascii code for the ellipsis character
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
composes: alert;
|
||||||
color: darken($alert-error, 60%);
|
color: darken($alert-error, 60%);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
composes: alert;
|
composes: alert;
|
||||||
color: darken($alert-success, 60%);
|
color: darken($alert-success, 60%);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ellipsis {
|
||||||
|
to {
|
||||||
|
width: .75rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,21 +7,17 @@ import styles from './InputGroup.module.scss'
|
|||||||
|
|
||||||
export default class InputGroup extends PureComponent {
|
export default class InputGroup extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
hasCorrectNetwork: PropTypes.bool.isRequired,
|
|
||||||
hasAccount: PropTypes.bool.isRequired,
|
|
||||||
amount: PropTypes.string.isRequired,
|
amount: PropTypes.string.isRequired,
|
||||||
onAmountChange: PropTypes.func.isRequired,
|
onAmountChange: PropTypes.func.isRequired,
|
||||||
handleButton: PropTypes.func.isRequired,
|
sendTransaction: PropTypes.func.isRequired,
|
||||||
selectedAccount: PropTypes.string
|
selectedAccount: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
hasCorrectNetwork,
|
|
||||||
hasAccount,
|
|
||||||
amount,
|
amount,
|
||||||
onAmountChange,
|
onAmountChange,
|
||||||
handleButton,
|
sendTransaction,
|
||||||
selectedAccount
|
selectedAccount
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
@ -30,7 +26,6 @@ export default class InputGroup extends PureComponent {
|
|||||||
<div className={styles.input}>
|
<div className={styles.input}>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
disabled={!hasCorrectNetwork || !hasAccount}
|
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={onAmountChange}
|
onChange={onAmountChange}
|
||||||
min="0"
|
min="0"
|
||||||
@ -40,20 +35,13 @@ export default class InputGroup extends PureComponent {
|
|||||||
<span>ETH</span>
|
<span>ETH</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button className="btn btn-primary" onClick={sendTransaction}>
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={handleButton}
|
|
||||||
disabled={!hasCorrectNetwork || !hasAccount}
|
|
||||||
>
|
|
||||||
Make it rain
|
Make it rain
|
||||||
</button>
|
</button>
|
||||||
{hasCorrectNetwork &&
|
|
||||||
hasAccount && (
|
|
||||||
<div className={styles.infoline}>
|
<div className={styles.infoline}>
|
||||||
<Conversion amount={amount} />
|
<Conversion amount={amount} />
|
||||||
{selectedAccount && <Account account={selectedAccount} />}
|
{selectedAccount && <Account account={selectedAccount} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
max-width: 18rem;
|
max-width: 18rem;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
animation: fadeIn .8s ease-out backwards;
|
||||||
|
|
||||||
@media (min-width: $screen-sm) {
|
@media (min-width: $screen-sm) {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -79,4 +80,19 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: $spacer / 4;
|
margin-top: $spacer / 4;
|
||||||
|
animation: fadeIn .5s .8s ease-out backwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
composes: message from './index.module.scss';
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: .01;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,25 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Web3 from 'web3'
|
|
||||||
import InputGroup from './InputGroup'
|
import InputGroup from './InputGroup'
|
||||||
import Alerts from './Alerts'
|
import Alerts, { alertMessages } from './Alerts'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
import { getNetworkName, Logger } from './utils'
|
import { getWeb3, getAccounts, getNetwork } from './utils'
|
||||||
|
|
||||||
const ONE_SECOND = 1000
|
const ONE_SECOND = 1000
|
||||||
const ONE_MINUTE = ONE_SECOND * 60
|
const ONE_MINUTE = ONE_SECOND * 60
|
||||||
|
const correctNetwork = 1
|
||||||
|
|
||||||
export default class Web3Donation extends PureComponent {
|
export default class Web3Donation extends PureComponent {
|
||||||
state = {
|
state = {
|
||||||
web3Connected: false,
|
netId: null,
|
||||||
networkId: null,
|
|
||||||
networkName: null,
|
networkName: null,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
amount: '0.01',
|
amount: '0.01',
|
||||||
transactionHash: null,
|
transactionHash: null,
|
||||||
receipt: null,
|
receipt: null,
|
||||||
loading: false,
|
message: null,
|
||||||
error: null,
|
inTransaction: false
|
||||||
message: 'Hang on...'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -40,39 +38,26 @@ export default class Web3Donation extends PureComponent {
|
|||||||
this.resetAllTheThings()
|
this.resetAllTheThings()
|
||||||
}
|
}
|
||||||
|
|
||||||
async initWeb3() {
|
initWeb3 = async () => {
|
||||||
// Modern dapp browsers...
|
this.setState({ message: { text: 'Checking' } })
|
||||||
if (window.ethereum) {
|
|
||||||
this.web3 = new Web3(window.ethereum)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Request account access
|
this.web3 = await getWeb3()
|
||||||
await window.ethereum.enable()
|
|
||||||
this.setState({ web3Connected: true })
|
|
||||||
|
|
||||||
this.initAllTheTings()
|
this.web3
|
||||||
|
? this.initAllTheTings()
|
||||||
|
: this.setState({
|
||||||
|
message: { status: 'error', text: alertMessages().noWeb3 }
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// User denied account access...
|
this.setState({ message: { status: 'error', text: error } })
|
||||||
Logger.error(error)
|
|
||||||
this.setState({ error })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Legacy dapp browsers...
|
|
||||||
else if (window.web3) {
|
|
||||||
this.web3 = new Web3(window.web3.currentProvider)
|
|
||||||
this.setState({ web3Connected: true })
|
|
||||||
|
|
||||||
this.initAllTheTings()
|
|
||||||
}
|
|
||||||
// Non-dapp browsers...
|
|
||||||
else {
|
|
||||||
this.setState({ web3Connected: false })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllTheTings() {
|
async initAllTheTings() {
|
||||||
this.fetchAccounts()
|
this.fetchAccounts()
|
||||||
this.fetchNetwork()
|
this.fetchNetwork()
|
||||||
|
|
||||||
this.initAccountsPoll()
|
this.initAccountsPoll()
|
||||||
this.initNetworkPoll()
|
this.initNetworkPoll()
|
||||||
}
|
}
|
||||||
@ -80,7 +65,6 @@ export default class Web3Donation extends PureComponent {
|
|||||||
resetAllTheThings() {
|
resetAllTheThings() {
|
||||||
clearInterval(this.interval)
|
clearInterval(this.interval)
|
||||||
clearInterval(this.networkInterval)
|
clearInterval(this.networkInterval)
|
||||||
this.setState({ web3Connected: false })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initAccountsPoll() {
|
initAccountsPoll() {
|
||||||
@ -95,46 +79,46 @@ export default class Web3Donation extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNetwork = () => {
|
fetchNetwork = async () => {
|
||||||
const { web3 } = this
|
const { web3 } = this
|
||||||
|
const { netId, networkName } = await getNetwork(web3)
|
||||||
|
|
||||||
web3 &&
|
if (netId === correctNetwork) {
|
||||||
web3.eth &&
|
this.setState({ netId, networkName })
|
||||||
web3.eth.net.getId((err, netId) => {
|
} else {
|
||||||
if (err) this.setState({ error: err })
|
|
||||||
|
|
||||||
if (netId !== this.state.networkId) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
error: null,
|
message: {
|
||||||
networkId: netId
|
status: 'error',
|
||||||
})
|
text: alertMessages(networkName).noCorrectNetwork
|
||||||
|
|
||||||
getNetworkName(netId).then(networkName => {
|
|
||||||
this.setState({ networkName })
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetchAccounts = () => {
|
fetchAccounts = async () => {
|
||||||
const { web3 } = this
|
const { web3 } = this
|
||||||
|
const accounts = await getAccounts(web3)
|
||||||
|
|
||||||
web3 &&
|
if (accounts[0]) {
|
||||||
web3.eth &&
|
|
||||||
web3.eth.getAccounts((err, accounts) => {
|
|
||||||
if (err) this.setState({ error: err })
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
error: null,
|
|
||||||
accounts,
|
accounts,
|
||||||
selectedAccount: accounts[0].toLowerCase()
|
selectedAccount: accounts[0].toLowerCase()
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
message: { status: 'error', text: alertMessages().noAccount }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendTransaction() {
|
sendTransaction = () => {
|
||||||
const { web3 } = this
|
const { web3 } = this
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
inTransaction: true,
|
||||||
|
message: { text: alertMessages().waitingForUser }
|
||||||
|
})
|
||||||
|
|
||||||
web3.eth
|
web3.eth
|
||||||
.sendTransaction({
|
.sendTransaction({
|
||||||
from: this.state.selectedAccount,
|
from: this.state.selectedAccount,
|
||||||
@ -144,22 +128,17 @@ export default class Web3Donation extends PureComponent {
|
|||||||
.once('transactionHash', transactionHash => {
|
.once('transactionHash', transactionHash => {
|
||||||
this.setState({
|
this.setState({
|
||||||
transactionHash,
|
transactionHash,
|
||||||
message: 'Waiting for network confirmation, hang on...'
|
message: { text: alertMessages().waitingConfirmation }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.on('error', error => this.setState({ error, loading: false }))
|
.on('error', error =>
|
||||||
|
this.setState({ message: { status: 'error', text: error } })
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setState({ message: 'Confirmed. You are awesome, thanks!' })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleButton = () => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
message: { status: 'success', text: alertMessages().success }
|
||||||
message: 'Waiting for your confirmation...'
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.sendTransaction()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAmountChange = ({ target }) => {
|
onAmountChange = ({ target }) => {
|
||||||
@ -168,22 +147,13 @@ export default class Web3Donation extends PureComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
networkId,
|
|
||||||
accounts,
|
|
||||||
selectedAccount,
|
selectedAccount,
|
||||||
web3Connected,
|
|
||||||
loading,
|
|
||||||
amount,
|
amount,
|
||||||
networkName,
|
|
||||||
error,
|
|
||||||
transactionHash,
|
transactionHash,
|
||||||
confirmationNumber,
|
message,
|
||||||
message
|
inTransaction
|
||||||
} = this.state
|
} = this.state
|
||||||
|
|
||||||
const hasCorrectNetwork = networkId === 1
|
|
||||||
const hasAccount = accounts.length !== 0
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.web3}>
|
<div className={styles.web3}>
|
||||||
<header>
|
<header>
|
||||||
@ -191,32 +161,22 @@ export default class Web3Donation extends PureComponent {
|
|||||||
<p>Send Ether with MetaMask, Brave, or Mist.</p>
|
<p>Send Ether with MetaMask, Brave, or Mist.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{web3Connected && (
|
|
||||||
<div className={styles.web3Row}>
|
<div className={styles.web3Row}>
|
||||||
{loading ? (
|
{selectedAccount &&
|
||||||
message
|
this.state.netId === correctNetwork &&
|
||||||
) : (
|
!inTransaction ? (
|
||||||
<InputGroup
|
<InputGroup
|
||||||
hasCorrectNetwork={hasCorrectNetwork}
|
|
||||||
hasAccount={hasAccount}
|
|
||||||
selectedAccount={selectedAccount}
|
selectedAccount={selectedAccount}
|
||||||
amount={amount}
|
amount={amount}
|
||||||
onAmountChange={this.onAmountChange}
|
onAmountChange={this.onAmountChange}
|
||||||
handleButton={this.handleButton}
|
sendTransaction={this.sendTransaction}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
message && (
|
||||||
|
<Alerts message={message} transactionHash={transactionHash} />
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<Alerts
|
|
||||||
hasCorrectNetwork={hasCorrectNetwork}
|
|
||||||
hasAccount={hasAccount}
|
|
||||||
networkName={networkName}
|
|
||||||
error={error}
|
|
||||||
transactionHash={transactionHash}
|
|
||||||
web3Connected={web3Connected}
|
|
||||||
confirmationNumber={confirmationNumber}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,44 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.web3Row {
|
.web3Row {
|
||||||
min-height: 58px;
|
min-height: 77px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
font-size: $font-size-small;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: bottom;
|
||||||
|
animation: ellipsis steps(4, end) 1s infinite;
|
||||||
|
content: '\2026'; // ascii code for the ellipsis character
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
composes: message;
|
||||||
|
color: green;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ellipsis {
|
||||||
|
to {
|
||||||
|
width: .75rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,49 @@
|
|||||||
export const getNetworkName = async netId => {
|
import Web3 from 'web3'
|
||||||
|
|
||||||
|
export const getWeb3 = async () => {
|
||||||
|
let web3
|
||||||
|
|
||||||
|
// Modern dapp browsers...
|
||||||
|
if (window.ethereum) {
|
||||||
|
web3 = new Web3(window.ethereum)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Request account access
|
||||||
|
await window.ethereum.enable()
|
||||||
|
|
||||||
|
return web3
|
||||||
|
} catch (error) {
|
||||||
|
// User denied account access...
|
||||||
|
Logger.error(error)
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Legacy dapp browsers...
|
||||||
|
else if (window.web3) {
|
||||||
|
web3 = new Web3(window.web3.currentProvider)
|
||||||
|
|
||||||
|
return web3
|
||||||
|
}
|
||||||
|
// Non-dapp browsers...
|
||||||
|
else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAccounts = async web3 => {
|
||||||
|
const ethAccounts = await web3.eth.getAccounts()
|
||||||
|
|
||||||
|
return ethAccounts
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNetwork = async web3 => {
|
||||||
|
const netId = await web3.eth.net.getId()
|
||||||
|
const networkName = getNetworkName(netId)
|
||||||
|
|
||||||
|
return { netId, networkName }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNetworkName = netId => {
|
||||||
let networkName
|
let networkName
|
||||||
|
|
||||||
switch (netId) {
|
switch (netId) {
|
||||||
@ -29,9 +74,7 @@ export const getFiat = async amount => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
if (!response.ok) {
|
if (!response.ok) Logger.error(response.statusText)
|
||||||
throw Error(response.statusText)
|
|
||||||
}
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
const { price_usd, price_eur } = data[0]
|
const { price_usd, price_eur } = data[0]
|
||||||
const dollar = (amount * price_usd).toFixed(2)
|
const dollar = (amount * price_usd).toFixed(2)
|
||||||
|
@ -33,9 +33,8 @@ export default class CoinHiveClient extends PureComponent {
|
|||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
loadScript(config.script, error => {
|
loadScript(config.script, error => {
|
||||||
if (error) {
|
if (error) return
|
||||||
return
|
|
||||||
}
|
|
||||||
resolve(
|
resolve(
|
||||||
window.CoinHive.Anonymous(config.siteKey, {
|
window.CoinHive.Anonymous(config.siteKey, {
|
||||||
throttle: config.throttle,
|
throttle: config.throttle,
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 10;
|
z-index: 9;
|
||||||
background: rgba($body-background-color, .9);
|
background: rgba($body-background-color, .9);
|
||||||
// backdrop-filter: blur(5px);
|
// backdrop-filter: blur(5px);
|
||||||
animation: fadein .3s;
|
animation: fadein .3s;
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import React, { Fragment } from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { QRCode } from 'react-qr-svg'
|
import { QRCode } from 'react-qr-svg'
|
||||||
import Clipboard from 'react-clipboard.js'
|
import Clipboard from 'react-clipboard.js'
|
||||||
import { ReactComponent as IconClipboard } from '../../images/clipboard.svg'
|
import { ReactComponent as IconClipboard } from '../../images/clipboard.svg'
|
||||||
|
|
||||||
|
import styles from './Qr.module.scss'
|
||||||
|
|
||||||
|
const onCopySuccess = e => {
|
||||||
|
e.trigger.classList.add(styles.copied)
|
||||||
|
}
|
||||||
|
|
||||||
const Qr = ({ address, title }) => (
|
const Qr = ({ address, title }) => (
|
||||||
<Fragment>
|
<>
|
||||||
{title && <h4>{title}</h4>}
|
{title && <h4>{title}</h4>}
|
||||||
<QRCode
|
<QRCode
|
||||||
bgColor="transparent"
|
bgColor="transparent"
|
||||||
@ -13,14 +19,21 @@ const Qr = ({ address, title }) => (
|
|||||||
level="Q"
|
level="Q"
|
||||||
style={{ width: 120 }}
|
style={{ width: 120 }}
|
||||||
value={address}
|
value={address}
|
||||||
|
className={styles.qr}
|
||||||
/>
|
/>
|
||||||
<pre>
|
|
||||||
|
<pre className={styles.code}>
|
||||||
<code>{address}</code>
|
<code>{address}</code>
|
||||||
<Clipboard data-clipboard-text={address} button-title="Copy to clipboard">
|
<Clipboard
|
||||||
|
data-clipboard-text={address}
|
||||||
|
button-title="Copy to clipboard"
|
||||||
|
onSuccess={e => onCopySuccess(e)}
|
||||||
|
className={styles.button}
|
||||||
|
>
|
||||||
<IconClipboard />
|
<IconClipboard />
|
||||||
</Clipboard>
|
</Clipboard>
|
||||||
</pre>
|
</pre>
|
||||||
</Fragment>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
Qr.propTypes = {
|
Qr.propTypes = {
|
||||||
|
54
src/components/atoms/Qr.module.scss
Normal file
54
src/components/atoms/Qr.module.scss
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
@import 'variables';
|
||||||
|
|
||||||
|
.qr {
|
||||||
|
margin-bottom: $spacer / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
padding-right: 2rem;
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: $spacer / 2;
|
||||||
|
font-size: .65rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
background: rgba($brand-grey, .3);
|
||||||
|
padding: $spacer / 3;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
fill: $brand-grey-light;
|
||||||
|
transition: .15s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
svg {
|
||||||
|
fill: $brand-grey-dimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copied {
|
||||||
|
background: green;
|
||||||
|
|
||||||
|
// stylelint-disable-next-line no-descending-specificity
|
||||||
|
svg {
|
||||||
|
fill: $brand-grey-dimmed;
|
||||||
|
}
|
||||||
|
}
|
@ -37,7 +37,7 @@ class ModalThanks extends PureComponent {
|
|||||||
<Web3Donation address={author.ether} />
|
<Web3Donation address={author.ether} />
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h4>Other wallets</h4>
|
<h4>Any other wallets</h4>
|
||||||
<p>Send Bitcoin or Ether from any wallet.</p>
|
<p>Send Bitcoin or Ether from any wallet.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -39,48 +39,4 @@
|
|||||||
width: 48%;
|
width: 48%;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
> svg {
|
|
||||||
margin-bottom: $spacer / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
position: relative;
|
|
||||||
padding: 0;
|
|
||||||
padding-right: 2rem;
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: $spacer / 2;
|
|
||||||
font-size: .65rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
border: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
background: rgba($brand-grey, .3);
|
|
||||||
padding: $spacer / 3;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
fill: $brand-grey;
|
|
||||||
transition: .15s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
svg {
|
|
||||||
fill: $brand-grey-dimmed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user