mirror of
https://github.com/kremalicious/blog.git
synced 2025-02-14 21:10:25 +01:00
Merge pull request #195 from kremalicious/feature/web3-refactor
web3 refactor
This commit is contained in:
commit
a9c1e30e6e
39
.github/workflows/test.yml
vendored
39
.github/workflows/test.yml
vendored
@ -1,39 +0,0 @@
|
|||||||
name: Test
|
|
||||||
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: ['10', '12']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- name: Cache node modules
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: ${{ runner.os }}-node-${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- name: Cache Gatsby build output
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: public
|
|
||||||
key: ${{ runner.os }}-public
|
|
||||||
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm test
|
|
||||||
- run: npm run build
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
GATSBY_GITHUB_TOKEN: ${{ secrets.GATSBY_GITHUB_TOKEN }}
|
|
@ -121,6 +121,12 @@ exports.onPostBuild = async ({ graphql }) => {
|
|||||||
// https://github.com/ethereum/web3.js/issues/1105#issuecomment-446039296
|
// https://github.com/ethereum/web3.js/issues/1105#issuecomment-446039296
|
||||||
exports.onCreateWebpackConfig = ({ actions }) => {
|
exports.onCreateWebpackConfig = ({ actions }) => {
|
||||||
actions.setWebpackConfig({
|
actions.setWebpackConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
// replace native `scrypt` module with pure js `js-scrypt`
|
||||||
|
scrypt: 'js-scrypt'
|
||||||
|
}
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
// ignore these plugins completely
|
// ignore these plugins completely
|
||||||
new webpack.IgnorePlugin(/^(?:electron|ws)$/)
|
new webpack.IgnorePlugin(/^(?:electron|ws)$/)
|
||||||
|
2989
package-lock.json
generated
2989
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@ -29,10 +29,15 @@
|
|||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ethersproject/providers": "^5.0.0-beta.146",
|
||||||
|
"@ethersproject/units": "^5.0.0-beta.132",
|
||||||
"@loadable/component": "^5.10.3",
|
"@loadable/component": "^5.10.3",
|
||||||
|
"@web3-react/core": "^6.0.0-beta.15",
|
||||||
|
"@web3-react/injected-connector": "^6.0.0-beta.17",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"date-fns": "^2.8.1",
|
"date-fns": "^2.8.1",
|
||||||
"dms2dec": "^1.1.0",
|
"dms2dec": "^1.1.0",
|
||||||
|
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||||
"fast-exif": "^1.0.1",
|
"fast-exif": "^1.0.1",
|
||||||
"feather-icons": "^4.24.1",
|
"feather-icons": "^4.24.1",
|
||||||
"fraction.js": "^4.0.12",
|
"fraction.js": "^4.0.12",
|
||||||
@ -58,36 +63,33 @@
|
|||||||
"gatsby-remark-copy-linked-files": "^2.1.30",
|
"gatsby-remark-copy-linked-files": "^2.1.30",
|
||||||
"gatsby-remark-images": "^3.1.33",
|
"gatsby-remark-images": "^3.1.33",
|
||||||
"gatsby-remark-smartypants": "^2.1.16",
|
"gatsby-remark-smartypants": "^2.1.16",
|
||||||
"gatsby-remark-vscode": "^1.3.0",
|
"gatsby-remark-vscode": "^1.4.0",
|
||||||
"gatsby-source-filesystem": "^2.1.38",
|
"gatsby-source-filesystem": "^2.1.38",
|
||||||
"gatsby-source-graphql": "^2.1.24",
|
"gatsby-source-graphql": "^2.1.24",
|
||||||
"gatsby-transformer-remark": "^2.6.37",
|
"gatsby-transformer-remark": "^2.6.37",
|
||||||
"gatsby-transformer-sharp": "^2.3.5",
|
"gatsby-transformer-sharp": "^2.3.5",
|
||||||
"graphql": "^14.5.8",
|
"graphql": "^14.5.8",
|
||||||
"intersection-observer": "^0.7.0",
|
"intersection-observer": "^0.7.0",
|
||||||
"load-script": "^1.0.0",
|
"node-fetch": "^2.6.0",
|
||||||
"pigeon-maps": "^0.14.0",
|
"pigeon-maps": "^0.14.0",
|
||||||
"pigeon-marker": "^0.3.4",
|
"pigeon-marker": "^0.3.4",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-blockies": "^1.4.1",
|
"react-clipboard.js": "^2.0.16",
|
||||||
"react-clipboard.js": "^2.0.13",
|
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-feather": "^2.0.3",
|
"react-feather": "^2.0.3",
|
||||||
"react-helmet": "^5.2.1",
|
"react-helmet": "^5.2.1",
|
||||||
"react-modal": "^3.11.1",
|
"react-pose": "^4.0.10",
|
||||||
"react-pose": "^4.0.9",
|
|
||||||
"react-qr-svg": "^2.2.1",
|
"react-qr-svg": "^2.2.1",
|
||||||
"react-transition-group": "^4.3.0",
|
"react-transition-group": "^4.3.0",
|
||||||
"remark": "^11.0.1",
|
"remark": "^11.0.2",
|
||||||
"remark-react": "^6.0.0",
|
"remark-react": "^6.0.0",
|
||||||
"slugify": "^1.3.6",
|
"slugify": "^1.3.6",
|
||||||
"use-dark-mode": "^2.3.1",
|
"use-dark-mode": "^2.3.1"
|
||||||
"web3": "^1.2.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/node": "^7.7.0",
|
"@babel/node": "^7.7.4",
|
||||||
"@babel/preset-env": "^7.7.1",
|
"@babel/preset-env": "^7.7.4",
|
||||||
"@babel/preset-typescript": "^7.7.0",
|
"@babel/preset-typescript": "^7.7.4",
|
||||||
"@svgr/webpack": "^4.3.3",
|
"@svgr/webpack": "^4.3.3",
|
||||||
"@testing-library/jest-dom": "^4.2.3",
|
"@testing-library/jest-dom": "^4.2.3",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
@ -95,11 +97,11 @@
|
|||||||
"@types/jest": "^24.0.21",
|
"@types/jest": "^24.0.21",
|
||||||
"@types/loadable__component": "^5.10.0",
|
"@types/loadable__component": "^5.10.0",
|
||||||
"@types/lunr": "^2.3.2",
|
"@types/lunr": "^2.3.2",
|
||||||
"@types/node": "^12.12.11",
|
"@types/node": "^12.12.12",
|
||||||
"@types/react": "^16.9.11",
|
"@types/node-fetch": "^2.5.4",
|
||||||
|
"@types/react": "^16.9.12",
|
||||||
"@types/react-dom": "^16.9.3",
|
"@types/react-dom": "^16.9.3",
|
||||||
"@types/react-helmet": "^5.0.14",
|
"@types/react-helmet": "^5.0.14",
|
||||||
"@types/react-modal": "^3.10.0",
|
|
||||||
"@types/react-transition-group": "^4.2.3",
|
"@types/react-transition-group": "^4.2.3",
|
||||||
"@types/shortid": "0.0.29",
|
"@types/shortid": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.8.0",
|
"@typescript-eslint/eslint-plugin": "^2.8.0",
|
||||||
@ -107,7 +109,7 @@
|
|||||||
"@welldone-software/why-did-you-render": "^3.3.9",
|
"@welldone-software/why-did-you-render": "^3.3.9",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-jest": "^24.9.0",
|
"babel-jest": "^24.9.0",
|
||||||
"eslint": "^6.6.0",
|
"eslint": "^6.7.0",
|
||||||
"eslint-config-prettier": "^6.7.0",
|
"eslint-config-prettier": "^6.7.0",
|
||||||
"eslint-loader": "^3.0.2",
|
"eslint-loader": "^3.0.2",
|
||||||
"eslint-plugin-graphql": "^3.1.0",
|
"eslint-plugin-graphql": "^3.1.0",
|
||||||
|
4
src/@types/node_modules.d.ts
vendored
4
src/@types/node_modules.d.ts
vendored
@ -2,3 +2,7 @@ declare module 'pigeon-maps'
|
|||||||
declare module 'pigeon-marker'
|
declare module 'pigeon-marker'
|
||||||
declare module 'react-blockies'
|
declare module 'react-blockies'
|
||||||
declare module 'remark-react'
|
declare module 'remark-react'
|
||||||
|
|
||||||
|
declare module 'ethereum-blockies' {
|
||||||
|
export function toDataUrl(address: string): string
|
||||||
|
}
|
||||||
|
@ -28,33 +28,33 @@ function FeaturedPure({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Featured() {
|
const query = graphql`
|
||||||
const query = graphql`
|
query {
|
||||||
query {
|
allMarkdownRemark(
|
||||||
allMarkdownRemark(
|
filter: { frontmatter: { featured: { eq: true } } }
|
||||||
filter: { frontmatter: { featured: { eq: true } } }
|
sort: { fields: [fields___date], order: DESC }
|
||||||
sort: { fields: [fields___date], order: DESC }
|
) {
|
||||||
) {
|
edges {
|
||||||
edges {
|
node {
|
||||||
node {
|
id
|
||||||
id
|
frontmatter {
|
||||||
frontmatter {
|
title
|
||||||
title
|
image {
|
||||||
image {
|
childImageSharp {
|
||||||
childImageSharp {
|
...ImageFluidThumb
|
||||||
...ImageFluidThumb
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fields {
|
}
|
||||||
slug
|
fields {
|
||||||
}
|
slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function Featured() {
|
||||||
const data = useStaticQuery(query)
|
const data = useStaticQuery(query)
|
||||||
return <FeaturedPure data={data} />
|
return <FeaturedPure data={data} />
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,24 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
@import 'mixins';
|
||||||
|
|
||||||
.account {
|
.accountWrap {
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-small;
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
max-width: 8rem;
|
margin-bottom: $spacer;
|
||||||
white-space: nowrap;
|
display: flex;
|
||||||
overflow: hidden;
|
justify-content: space-between;
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.identicon {
|
.blockies {
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: $spacer / 8;
|
margin-right: $spacer / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance {
|
||||||
margin-left: $spacer;
|
margin-left: $spacer;
|
||||||
}
|
}
|
||||||
|
14
src/components/molecules/Web3Donation/Account.test.tsx
Normal file
14
src/components/molecules/Web3Donation/Account.test.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, waitForElement } from '@testing-library/react'
|
||||||
|
|
||||||
|
import Account from './Account'
|
||||||
|
|
||||||
|
describe('Account', () => {
|
||||||
|
it('renders without crashing', async () => {
|
||||||
|
const { container } = render(<Account />)
|
||||||
|
const lazyElement = await waitForElement(() =>
|
||||||
|
container.querySelector('.balance')
|
||||||
|
)
|
||||||
|
expect(lazyElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,12 +1,27 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Blockies from 'react-blockies'
|
import { toDataUrl } from 'ethereum-blockies'
|
||||||
|
import { formatEther } from '@ethersproject/units'
|
||||||
import styles from './Account.module.scss'
|
import styles from './Account.module.scss'
|
||||||
|
import useWeb3, { getBalance } from '../../../hooks/use-web3'
|
||||||
|
|
||||||
const Account = ({ account }: { account: string }) => (
|
export default function Account() {
|
||||||
<div className={styles.account} title={account}>
|
const { library, account } = useWeb3()
|
||||||
<Blockies seed={account} scale={2} size={8} className={styles.identicon} />
|
const ethBalance = account && getBalance(account, library)
|
||||||
{account}
|
const blockies = account && toDataUrl(account)
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default Account
|
const accountDisplay =
|
||||||
|
account &&
|
||||||
|
`${account.substring(0, 8)}...${account.substring(account.length - 4)}`
|
||||||
|
const balanceDisplay =
|
||||||
|
ethBalance && `Ξ${parseFloat(formatEther(ethBalance)).toPrecision(4)}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.accountWrap} title={account}>
|
||||||
|
<span className={styles.account}>
|
||||||
|
<img className={styles.blockies} src={blockies} alt="Blockies" />
|
||||||
|
{accountDisplay}
|
||||||
|
</span>
|
||||||
|
<span className={styles.balance}>{balanceDisplay}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
46
src/components/molecules/Web3Donation/Alert.tsx
Normal file
46
src/components/molecules/Web3Donation/Alert.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styles from './Alert.module.scss'
|
||||||
|
|
||||||
|
export function getTransactionMessage(transactionHash?: string) {
|
||||||
|
return {
|
||||||
|
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!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const constructMessage = (
|
||||||
|
transactionHash: string,
|
||||||
|
message?: { text?: string }
|
||||||
|
) =>
|
||||||
|
transactionHash
|
||||||
|
? message &&
|
||||||
|
message.text +
|
||||||
|
'<br /><br />' +
|
||||||
|
getTransactionMessage(transactionHash).transaction
|
||||||
|
: message && message.text
|
||||||
|
|
||||||
|
const classes = (status: string) =>
|
||||||
|
status === 'success'
|
||||||
|
? styles.success
|
||||||
|
: status === 'error'
|
||||||
|
? styles.error
|
||||||
|
: styles.alert
|
||||||
|
|
||||||
|
export default function Alert({
|
||||||
|
transactionHash,
|
||||||
|
message
|
||||||
|
}: {
|
||||||
|
transactionHash: string
|
||||||
|
message?: { text?: string; status?: string }
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classes(message.status)}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `${constructMessage(transactionHash, message)}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styles from './Alerts.module.scss'
|
|
||||||
|
|
||||||
export const alertMessages = (
|
|
||||||
networkName?: string,
|
|
||||||
transactionHash?: string
|
|
||||||
) => ({
|
|
||||||
noAccount:
|
|
||||||
'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.`,
|
|
||||||
noWeb3:
|
|
||||||
'No Web3 detected. Install <a href="https://metamask.io">MetaMask</a> or <a href="https://brave.com">Brave</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!'
|
|
||||||
})
|
|
||||||
|
|
||||||
interface AlertProps {
|
|
||||||
transactionHash: string
|
|
||||||
message?: { text?: string; status?: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
const constructMessage = (
|
|
||||||
transactionHash: string,
|
|
||||||
message?: { text?: string }
|
|
||||||
) =>
|
|
||||||
transactionHash
|
|
||||||
? message &&
|
|
||||||
message.text + '<br />' + alertMessages(null, transactionHash).transaction
|
|
||||||
: message && message.text
|
|
||||||
|
|
||||||
const classes = (status: string) =>
|
|
||||||
status === 'success'
|
|
||||||
? styles.success
|
|
||||||
: status === 'error'
|
|
||||||
? styles.error
|
|
||||||
: styles.alert
|
|
||||||
|
|
||||||
export default function Alerts({ transactionHash, message }: AlertProps) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classes(message.status)}
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: `${constructMessage(transactionHash, message)}`
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
@ -3,7 +3,10 @@
|
|||||||
.conversion {
|
.conversion {
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-mini;
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
text-align: center;
|
text-align: left;
|
||||||
|
margin-top: $spacer / 4;
|
||||||
|
margin-left: $spacer * 1.4;
|
||||||
|
animation: fadeIn 0.5s 0.8s ease-out backwards;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
margin-left: $spacer / 2;
|
margin-left: $spacer / 2;
|
||||||
|
12
src/components/molecules/Web3Donation/Conversion.test.tsx
Normal file
12
src/components/molecules/Web3Donation/Conversion.test.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, waitForElement } from '@testing-library/react'
|
||||||
|
|
||||||
|
import Conversion from './Conversion'
|
||||||
|
|
||||||
|
describe('Conversion', () => {
|
||||||
|
it('renders without crashing', async () => {
|
||||||
|
const { getByText } = render(<Conversion amount={1} />)
|
||||||
|
const lazyElement = await waitForElement(() => getByText(/= €/))
|
||||||
|
expect(lazyElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,45 +1,45 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { getFiat, Logger } from './utils'
|
import fetch from 'node-fetch'
|
||||||
import styles from './Conversion.module.scss'
|
import styles from './Conversion.module.scss'
|
||||||
|
|
||||||
export default class Conversion extends PureComponent<
|
export async function getFiat(amount: number) {
|
||||||
{ amount: number },
|
const url = 'https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=EUR'
|
||||||
{ euro: string; dollar: string }
|
const response = await fetch(url)
|
||||||
> {
|
if (!response.ok) console.error(response.statusText)
|
||||||
state = {
|
const data = await response.json()
|
||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
const { price_usd, price_eur } = data[0]
|
||||||
|
const dollar = (amount * price_usd).toFixed(2)
|
||||||
|
const euro = (amount * price_eur).toFixed(2)
|
||||||
|
/* eslint-enable @typescript-eslint/camelcase */
|
||||||
|
|
||||||
|
return { dollar, euro }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Conversion({ amount }: { amount: number }) {
|
||||||
|
const [conversion, setConversion] = useState({
|
||||||
euro: '0.00',
|
euro: '0.00',
|
||||||
dollar: '0.00'
|
dollar: '0.00'
|
||||||
}
|
})
|
||||||
|
const { dollar, euro } = conversion
|
||||||
|
|
||||||
componentDidMount() {
|
async function getFiatResponse() {
|
||||||
this.getFiatResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: any) {
|
|
||||||
const { amount } = this.props
|
|
||||||
|
|
||||||
if (amount !== prevProps.amount) {
|
|
||||||
this.getFiatResponse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFiatResponse() {
|
|
||||||
try {
|
try {
|
||||||
const { dollar, euro } = await getFiat(this.props.amount)
|
const { dollar, euro } = await getFiat(amount)
|
||||||
this.setState({ euro, dollar })
|
setConversion({ euro, dollar })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error.message)
|
console.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
const { dollar, euro } = this.state
|
getFiatResponse()
|
||||||
|
}, [amount])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.conversion}>
|
<div className={styles.conversion}>
|
||||||
<span>{dollar !== '0.00' && `= $ ${dollar}`}</span>
|
<span>{dollar !== '0.00' && `= $ ${dollar}`}</span>
|
||||||
<span>{euro !== '0.00' && `= € ${euro}`}</span>
|
<span>{euro !== '0.00' && `= € ${euro}`}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -88,15 +88,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoline {
|
|
||||||
flex-basis: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: $spacer / 4;
|
|
||||||
animation: fadeIn 0.5s 0.8s ease-out backwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
composes: message from './index.module.scss';
|
composes: message from './index.module.scss';
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,44 @@
|
|||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import Input from '../../atoms/Input'
|
import Input from '../../atoms/Input'
|
||||||
import Account from './Account'
|
import Account from './Account'
|
||||||
import Conversion from './Conversion'
|
import Conversion from './Conversion'
|
||||||
import styles from './InputGroup.module.scss'
|
import styles from './InputGroup.module.scss'
|
||||||
|
|
||||||
export default function InputGroup({
|
export default function InputGroup({
|
||||||
amount,
|
sendTransaction
|
||||||
onAmountChange,
|
|
||||||
sendTransaction,
|
|
||||||
selectedAccount
|
|
||||||
}: {
|
}: {
|
||||||
amount: number
|
sendTransaction(amount: number): void
|
||||||
onAmountChange(target: any): void
|
|
||||||
sendTransaction(): void
|
|
||||||
selectedAccount?: string | null
|
|
||||||
}) {
|
}) {
|
||||||
|
const [amount, setAmount] = useState(0.03)
|
||||||
|
|
||||||
|
const onAmountChange = ({ target }: { target: any }) => {
|
||||||
|
setAmount(target.value)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.inputGroup}>
|
<div>
|
||||||
<div className={styles.input}>
|
<Account />
|
||||||
<Input
|
<div className={styles.inputGroup}>
|
||||||
type="number"
|
<div className={styles.input}>
|
||||||
value={amount}
|
<Input
|
||||||
onChange={onAmountChange}
|
type="number"
|
||||||
min="0"
|
value={amount}
|
||||||
step="0.01"
|
onChange={onAmountChange}
|
||||||
/>
|
min="0"
|
||||||
<div className={styles.currency}>
|
step="0.01"
|
||||||
<span>ETH</span>
|
/>
|
||||||
|
<div className={styles.currency}>
|
||||||
|
<span>ETH</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => sendTransaction(amount)}
|
||||||
|
>
|
||||||
|
Make it rain
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-primary" onClick={sendTransaction}>
|
<Conversion amount={amount} />
|
||||||
Make it rain
|
|
||||||
</button>
|
|
||||||
<div className={styles.infoline}>
|
|
||||||
<Conversion amount={amount} />
|
|
||||||
{selectedAccount && <Account account={selectedAccount} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,6 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
|
||||||
.web3 {
|
.web3 {
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: $spacer / 2;
|
|
||||||
margin-bottom: $spacer;
|
|
||||||
padding-bottom: $spacer;
|
|
||||||
|
|
||||||
small {
|
|
||||||
color: darken($alert-info, 60%);
|
|
||||||
margin-top: -($spacer / 2);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.web3Row {
|
|
||||||
min-height: 77px;
|
min-height: 77px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
26
src/components/molecules/Web3Donation/index.test.tsx
Normal file
26
src/components/molecules/Web3Donation/index.test.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, waitForElement, fireEvent } from '@testing-library/react'
|
||||||
|
import { Web3ReactProvider } from '@web3-react/core'
|
||||||
|
import { getLibrary } from '../../../hooks/use-web3'
|
||||||
|
|
||||||
|
import Web3Donation from '.'
|
||||||
|
|
||||||
|
describe('Web3Donation', () => {
|
||||||
|
it('renders without crashing', async () => {
|
||||||
|
const { container, getByText } = render(
|
||||||
|
<Web3ReactProvider getLibrary={getLibrary}>
|
||||||
|
<Web3Donation address="xxx" />
|
||||||
|
</Web3ReactProvider>
|
||||||
|
)
|
||||||
|
const lazyElement = await waitForElement(() =>
|
||||||
|
container.querySelector('button')
|
||||||
|
)
|
||||||
|
expect(lazyElement).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(lazyElement)
|
||||||
|
const message = await waitForElement(() =>
|
||||||
|
getByText(/No Ethereum browser extension detected/)
|
||||||
|
)
|
||||||
|
expect(message).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,210 +1,70 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import Web3 from 'web3'
|
import useWeb3, { connectors, getErrorMessage } from '../../../hooks/use-web3'
|
||||||
import InputGroup from './InputGroup'
|
import InputGroup from './InputGroup'
|
||||||
import Alerts, { alertMessages } from './Alerts'
|
import Alert, { getTransactionMessage } from './Alert'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
import { getWeb3, getAccounts, getNetwork } from './utils'
|
|
||||||
|
|
||||||
const ONE_SECOND = 1000
|
export default function Web3Donation({ address }: { address: string }) {
|
||||||
const ONE_MINUTE = ONE_SECOND * 60
|
const {
|
||||||
const correctNetwork = 1
|
connector,
|
||||||
|
library,
|
||||||
|
chainId,
|
||||||
|
account,
|
||||||
|
activate,
|
||||||
|
active,
|
||||||
|
error
|
||||||
|
} = useWeb3()
|
||||||
|
const [message, setMessage] = useState()
|
||||||
|
|
||||||
interface Web3DonationState {
|
useEffect(() => {
|
||||||
netId: number
|
setMessage(undefined)
|
||||||
networkName: string
|
|
||||||
accounts: string[]
|
|
||||||
selectedAccount: string
|
|
||||||
amount: number
|
|
||||||
transactionHash: string
|
|
||||||
receipt: string
|
|
||||||
message: {
|
|
||||||
status?: string
|
|
||||||
text?: string
|
|
||||||
}
|
|
||||||
inTransaction: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Web3Donation extends PureComponent<
|
error &&
|
||||||
{ address: string },
|
setMessage({
|
||||||
Web3DonationState
|
status: 'error',
|
||||||
> {
|
text: getErrorMessage(error, chainId)
|
||||||
state = {
|
|
||||||
netId: 0,
|
|
||||||
networkName: '',
|
|
||||||
accounts: [''],
|
|
||||||
selectedAccount: '',
|
|
||||||
amount: 0.01,
|
|
||||||
transactionHash: '',
|
|
||||||
receipt: '',
|
|
||||||
message: {},
|
|
||||||
inTransaction: false
|
|
||||||
}
|
|
||||||
|
|
||||||
web3: Web3 = null
|
|
||||||
interval: any = null
|
|
||||||
networkInterval: any = null
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.initWeb3()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.resetAllTheThings()
|
|
||||||
}
|
|
||||||
|
|
||||||
initWeb3 = async () => {
|
|
||||||
this.setState({ message: { text: 'Checking' } })
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.web3 = await getWeb3()
|
|
||||||
|
|
||||||
this.web3
|
|
||||||
? this.initAllTheTings()
|
|
||||||
: this.setState({
|
|
||||||
message: {
|
|
||||||
status: 'error',
|
|
||||||
text: alertMessages().noWeb3
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
this.setState({
|
|
||||||
message: { status: 'error', text: error }
|
|
||||||
})
|
})
|
||||||
}
|
}, [connector, account, library, chainId, active, error])
|
||||||
}
|
|
||||||
|
|
||||||
async initAllTheTings() {
|
const [transactionHash, setTransactionHash] = useState(undefined)
|
||||||
this.fetchAccounts()
|
|
||||||
this.fetchNetwork()
|
|
||||||
|
|
||||||
this.initAccountsPoll()
|
async function sendTransaction(amount: number) {
|
||||||
this.initNetworkPoll()
|
const signer = library.getSigner()
|
||||||
}
|
|
||||||
|
|
||||||
resetAllTheThings() {
|
setMessage({
|
||||||
clearInterval(this.interval)
|
status: 'loading',
|
||||||
clearInterval(this.networkInterval)
|
text: getTransactionMessage().waitingForUser
|
||||||
}
|
|
||||||
|
|
||||||
initAccountsPoll() {
|
|
||||||
if (!this.interval) {
|
|
||||||
this.interval = setInterval(this.fetchAccounts, ONE_SECOND * 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initNetworkPoll() {
|
|
||||||
if (!this.networkInterval) {
|
|
||||||
this.networkInterval = setInterval(this.fetchNetwork, ONE_MINUTE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchNetwork = async () => {
|
|
||||||
const { web3 } = this
|
|
||||||
const { netId, networkName } = await getNetwork(web3)
|
|
||||||
|
|
||||||
if (netId === correctNetwork) {
|
|
||||||
this.setState({ netId, networkName })
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
message: {
|
|
||||||
status: 'error',
|
|
||||||
text: alertMessages(networkName).noCorrectNetwork
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchAccounts = async () => {
|
|
||||||
const { web3 } = this
|
|
||||||
const accounts = await getAccounts(web3)
|
|
||||||
|
|
||||||
if (accounts[0]) {
|
|
||||||
this.setState({
|
|
||||||
accounts,
|
|
||||||
selectedAccount: accounts[0].toLowerCase()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
message: {
|
|
||||||
status: 'error',
|
|
||||||
text: alertMessages().noAccount
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTransaction = () => {
|
|
||||||
const { web3 } = this
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
inTransaction: true,
|
|
||||||
message: { text: alertMessages().waitingForUser }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
web3.eth
|
const tx = await signer.sendTransaction({
|
||||||
.sendTransaction({
|
to: address,
|
||||||
from: this.state.selectedAccount,
|
value: amount * 1e18 // ETH -> Wei
|
||||||
to: this.props.address,
|
})
|
||||||
value: this.state.amount * 1e18 // ETH -> Wei
|
setTransactionHash(tx.hash)
|
||||||
})
|
setMessage({
|
||||||
.once('transactionHash', transactionHash => {
|
status: 'loading',
|
||||||
this.setState({
|
text: getTransactionMessage().waitingConfirmation
|
||||||
transactionHash,
|
})
|
||||||
message: { text: alertMessages().waitingConfirmation }
|
|
||||||
})
|
await tx.wait()
|
||||||
})
|
|
||||||
.on('error', error =>
|
setMessage({
|
||||||
this.setState({
|
status: 'success',
|
||||||
message: { status: 'error', text: error.message }
|
text: getTransactionMessage().success
|
||||||
})
|
})
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this.setState({
|
|
||||||
message: {
|
|
||||||
status: 'success',
|
|
||||||
text: alertMessages().success
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAmountChange = ({ target }: { target: any }) => {
|
return (
|
||||||
this.setState({ amount: target.value })
|
<div className={styles.web3}>
|
||||||
}
|
{!active && !message ? (
|
||||||
|
<button className="link" onClick={() => activate(connectors.MetaMask)}>
|
||||||
render() {
|
Activate Web3
|
||||||
const {
|
</button>
|
||||||
selectedAccount,
|
) : library && account && !message ? (
|
||||||
amount,
|
<InputGroup sendTransaction={sendTransaction} />
|
||||||
transactionHash,
|
) : (
|
||||||
message,
|
message && <Alert message={message} transactionHash={transactionHash} />
|
||||||
inTransaction
|
)}
|
||||||
} = this.state
|
</div>
|
||||||
|
)
|
||||||
return (
|
|
||||||
<div className={styles.web3}>
|
|
||||||
<header>
|
|
||||||
<h4>Web3 Wallet</h4>
|
|
||||||
<p>Send Ether with MetaMask or Brave.</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className={styles.web3Row}>
|
|
||||||
{selectedAccount &&
|
|
||||||
this.state.netId === correctNetwork &&
|
|
||||||
!inTransaction ? (
|
|
||||||
<InputGroup
|
|
||||||
selectedAccount={selectedAccount}
|
|
||||||
amount={amount}
|
|
||||||
onAmountChange={this.onAmountChange}
|
|
||||||
sendTransaction={this.sendTransaction}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
message && (
|
|
||||||
<Alerts message={message} transactionHash={transactionHash} />
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
import Web3 from 'web3'
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
ethereum: any
|
|
||||||
web3: Web3
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Console {
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Logger {
|
|
||||||
static dispatch(verb: any, ...args: any) {
|
|
||||||
console[verb](...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
static log(...args: any) {
|
|
||||||
Logger.dispatch('log', ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
static debug(...args: any) {
|
|
||||||
Logger.dispatch('debug', ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
static error(...args: any) {
|
|
||||||
Logger.dispatch('error', ...args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getNetworkName = (netId: number) => {
|
|
||||||
let networkName
|
|
||||||
|
|
||||||
switch (netId) {
|
|
||||||
case 1:
|
|
||||||
networkName = 'Main'
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
networkName = 'Morden'
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
networkName = 'Ropsten'
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
networkName = 'Rinkeby'
|
|
||||||
break
|
|
||||||
case 42:
|
|
||||||
networkName = 'Kovan'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
networkName = 'Private'
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkName
|
|
||||||
}
|
|
||||||
|
|
||||||
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: Web3) => {
|
|
||||||
const ethAccounts = await web3.eth.getAccounts()
|
|
||||||
|
|
||||||
return ethAccounts
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getNetwork = async (web3: Web3) => {
|
|
||||||
const netId = await web3.eth.net.getId()
|
|
||||||
const networkName = getNetworkName(netId)
|
|
||||||
|
|
||||||
return { netId, networkName }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getFiat = async (amount: number) => {
|
|
||||||
const url = 'https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=EUR'
|
|
||||||
const response = await fetch(url)
|
|
||||||
if (!response.ok) Logger.error(response.statusText)
|
|
||||||
const data = await response.json()
|
|
||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
const { price_usd, price_eur } = data[0]
|
|
||||||
const dollar = (amount * price_usd).toFixed(2)
|
|
||||||
const euro = (amount * price_eur).toFixed(2)
|
|
||||||
/* eslint-enable @typescript-eslint/camelcase */
|
|
||||||
|
|
||||||
return { dollar, euro }
|
|
||||||
}
|
|
75
src/hooks/use-web3/connectors.tsx
Normal file
75
src/hooks/use-web3/connectors.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { InjectedConnector } from '@web3-react/injected-connector'
|
||||||
|
// import { NetworkConnector } from '@web3-react/network-connector'
|
||||||
|
// import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
|
||||||
|
// import { WalletLinkConnector } from '@web3-react/walletlink-connector'
|
||||||
|
// import { LedgerConnector } from '@web3-react/ledger-connector'
|
||||||
|
// import { TrezorConnector } from '@web3-react/trezor-connector'
|
||||||
|
// import { FrameConnector } from '@web3-react/frame-connector'
|
||||||
|
// import { AuthereumConnector } from '@web3-react/authereum-connector'
|
||||||
|
// import { FortmaticConnector } from '@web3-react/fortmatic-connector'
|
||||||
|
// import { PortisConnector } from '@web3-react/portis-connector'
|
||||||
|
// import { SquarelinkConnector } from '@web3-react/squarelink-connector'
|
||||||
|
// import { TorusConnector } from '@web3-react/torus-connector'
|
||||||
|
|
||||||
|
// const POLLING_INTERVAL = 8000
|
||||||
|
// const RPC_URLS: { [chainId: number]: string } = {
|
||||||
|
// 1: process.env.RPC_URL_1 as string,
|
||||||
|
// 4: process.env.RPC_URL_4 as string
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const MetaMask = new InjectedConnector({
|
||||||
|
supportedChainIds: [1]
|
||||||
|
})
|
||||||
|
|
||||||
|
// export const network = new NetworkConnector({
|
||||||
|
// urls: { 1: RPC_URLS[1], 4: RPC_URLS[4] },
|
||||||
|
// defaultChainId: 1,
|
||||||
|
// pollingInterval: POLLING_INTERVAL
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const walletconnect = new WalletConnectConnector({
|
||||||
|
// rpc: { 1: RPC_URLS[1] },
|
||||||
|
// bridge: 'https://bridge.walletconnect.org',
|
||||||
|
// qrcode: true,
|
||||||
|
// pollingInterval: POLLING_INTERVAL
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const walletlink = new WalletLinkConnector({
|
||||||
|
// url: RPC_URLS[1],
|
||||||
|
// appName: 'web3-react example'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const ledger = new LedgerConnector({
|
||||||
|
// chainId: 1,
|
||||||
|
// url: RPC_URLS[1],
|
||||||
|
// pollingInterval: POLLING_INTERVAL
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const trezor = new TrezorConnector({
|
||||||
|
// chainId: 1,
|
||||||
|
// url: RPC_URLS[1],
|
||||||
|
// pollingInterval: POLLING_INTERVAL,
|
||||||
|
// manifestEmail: 'dummy@abc.xyz',
|
||||||
|
// manifestAppUrl: 'http://localhost:1234'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const frame = new FrameConnector({ supportedChainIds: [1] })
|
||||||
|
|
||||||
|
// export const authereum = new AuthereumConnector({ chainId: 42 })
|
||||||
|
|
||||||
|
// export const fortmatic = new FortmaticConnector({
|
||||||
|
// apiKey: process.env.FORTMATIC_API_KEY as string,
|
||||||
|
// chainId: 4
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const portis = new PortisConnector({
|
||||||
|
// dAppId: process.env.PORTIS_DAPP_ID as string,
|
||||||
|
// networks: [1, 100]
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const squarelink = new SquarelinkConnector({
|
||||||
|
// clientId: process.env.SQUARELINK_CLIENT_ID as string,
|
||||||
|
// networks: [1, 100]
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const torus = new TorusConnector({ chainId: 1 })
|
99
src/hooks/use-web3/index.tsx
Normal file
99
src/hooks/use-web3/index.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
|
import { Web3ReactContextInterface } from '@web3-react/core/dist/types'
|
||||||
|
import * as connectors from './connectors'
|
||||||
|
import {
|
||||||
|
getLibrary,
|
||||||
|
getNetworkName,
|
||||||
|
getErrorMessage,
|
||||||
|
getBalance
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
export { connectors, getLibrary, getNetworkName, getErrorMessage, getBalance }
|
||||||
|
|
||||||
|
export function useEagerConnect() {
|
||||||
|
const { MetaMask } = connectors
|
||||||
|
const { activate, active } = useWeb3React()
|
||||||
|
const [tried, setTried] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
MetaMask.isAuthorized().then(isAuthorized => {
|
||||||
|
if (isAuthorized) {
|
||||||
|
activate(MetaMask, undefined, true).catch(() => {
|
||||||
|
setTried(true)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setTried(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// if the connection worked, wait until we get confirmation of that to flip the flag
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tried && active) {
|
||||||
|
setTried(true)
|
||||||
|
}
|
||||||
|
}, [tried, active])
|
||||||
|
|
||||||
|
return tried
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInactiveListener(suppress = false) {
|
||||||
|
const { active, error, activate } = useWeb3React()
|
||||||
|
const { MetaMask } = connectors
|
||||||
|
|
||||||
|
useEffect((): any => {
|
||||||
|
const { ethereum } = window as any
|
||||||
|
if (ethereum && !active && !error && !suppress) {
|
||||||
|
const handleConnect = () => {
|
||||||
|
console.log("Handling 'connect' event")
|
||||||
|
activate(MetaMask)
|
||||||
|
}
|
||||||
|
const handleChainChanged = (chainId: string | number) => {
|
||||||
|
console.log("Handling 'chainChanged' event with payload", chainId)
|
||||||
|
activate(MetaMask)
|
||||||
|
}
|
||||||
|
const handleNetworkChanged = (networkId: string | number) => {
|
||||||
|
console.log("Handling 'networkChanged' event with payload", networkId)
|
||||||
|
activate(MetaMask)
|
||||||
|
}
|
||||||
|
const handleAccountsChanged = (accounts: string[]) => {
|
||||||
|
console.log("Handling 'accountsChanged' event with payload", accounts)
|
||||||
|
if (accounts.length > 0) {
|
||||||
|
activate(MetaMask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ethereum.on('connect', handleConnect)
|
||||||
|
ethereum.on('chainChanged', handleChainChanged)
|
||||||
|
ethereum.on('networkChanged', handleNetworkChanged)
|
||||||
|
ethereum.on('accountsChanged', handleAccountsChanged)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ethereum.removeListener('networkChanged', handleNetworkChanged)
|
||||||
|
ethereum.removeListener('accountsChanged', handleAccountsChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [active, error, suppress, activate])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useWeb3(): Web3ReactContextInterface {
|
||||||
|
const context = useWeb3React()
|
||||||
|
|
||||||
|
// handle logic to recognize the connector currently being activated
|
||||||
|
const [activatingConnector, setActivatingConnector] = useState()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activatingConnector && activatingConnector === context.connector) {
|
||||||
|
setActivatingConnector(undefined)
|
||||||
|
}
|
||||||
|
}, [activatingConnector, context.connector])
|
||||||
|
|
||||||
|
// handle logic to eagerly connect to the injected ethereum provider, if it exists and has granted access already
|
||||||
|
const triedEager = useEagerConnect()
|
||||||
|
|
||||||
|
// handle logic to connect in reaction to certain events on the injected ethereum provider, if it exists
|
||||||
|
useInactiveListener(!triedEager || !!activatingConnector)
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
83
src/hooks/use-web3/utils.tsx
Normal file
83
src/hooks/use-web3/utils.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { UnsupportedChainIdError } from '@web3-react/core'
|
||||||
|
import {
|
||||||
|
NoEthereumProviderError,
|
||||||
|
UserRejectedRequestError
|
||||||
|
} from '@web3-react/injected-connector'
|
||||||
|
import { Web3Provider } from '@ethersproject/providers'
|
||||||
|
|
||||||
|
export function getLibrary(provider: any): Web3Provider {
|
||||||
|
const library = new Web3Provider(provider)
|
||||||
|
library.pollingInterval = 10000
|
||||||
|
return library
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNetworkName(netId: number) {
|
||||||
|
let networkName
|
||||||
|
|
||||||
|
switch (netId) {
|
||||||
|
case 1:
|
||||||
|
networkName = 'Main'
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
networkName = 'Morden'
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
networkName = 'Ropsten'
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
networkName = 'Rinkeby'
|
||||||
|
break
|
||||||
|
case 42:
|
||||||
|
networkName = 'Kovan'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
networkName = 'Private'
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkName
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getErrorMessage(error: Error, chainId: number) {
|
||||||
|
if (error instanceof NoEthereumProviderError) {
|
||||||
|
return 'No Ethereum browser extension detected, install <a href="https://metamask.io">MetaMask</a> or <a href="https://brave.com">Brave</a>.'
|
||||||
|
} else if (error instanceof UnsupportedChainIdError) {
|
||||||
|
const networkName = getNetworkName(chainId)
|
||||||
|
return `Please connect to <strong>Main</strong> network. You are on <strong>${networkName}</strong> right now.`
|
||||||
|
} else if (error instanceof UserRejectedRequestError) {
|
||||||
|
return 'Please authorize this website to access your Ethereum account.'
|
||||||
|
} else {
|
||||||
|
console.error(error)
|
||||||
|
return 'An unknown error occurred. Check the console for more details.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBalance(account: string, library: any) {
|
||||||
|
const [ethBalance, setEthBalance] = useState()
|
||||||
|
|
||||||
|
useEffect((): any => {
|
||||||
|
if (library && account) {
|
||||||
|
let stale = false
|
||||||
|
|
||||||
|
library
|
||||||
|
.getBalance(account)
|
||||||
|
.then((balance: any) => {
|
||||||
|
if (!stale) {
|
||||||
|
setEthBalance(balance)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!stale) {
|
||||||
|
setEthBalance(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stale = true
|
||||||
|
setEthBalance(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [library, account])
|
||||||
|
|
||||||
|
return ethBalance
|
||||||
|
}
|
@ -61,6 +61,20 @@
|
|||||||
font-size: $font-size-h2;
|
font-size: $font-size-h2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.web3 {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: $spacer / 2;
|
||||||
|
margin-bottom: $spacer * 2;
|
||||||
|
padding-bottom: $spacer;
|
||||||
|
|
||||||
|
small {
|
||||||
|
color: darken($alert-info, 60%);
|
||||||
|
margin-top: -($spacer / 2);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.coins {
|
.coins {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -2,13 +2,15 @@ import React from 'react'
|
|||||||
import loadable from '@loadable/component'
|
import loadable from '@loadable/component'
|
||||||
import shortid from 'shortid'
|
import shortid from 'shortid'
|
||||||
import Helmet from 'react-helmet'
|
import Helmet from 'react-helmet'
|
||||||
|
import { Web3ReactProvider } from '@web3-react/core'
|
||||||
import { Author } from '../@types/Site'
|
import { Author } from '../@types/Site'
|
||||||
import { useSiteMetadata } from '../hooks/use-site-metadata'
|
import { useSiteMetadata } from '../hooks/use-site-metadata'
|
||||||
|
import { getLibrary } from '../hooks/use-web3'
|
||||||
import Qr from '../components/atoms/Qr'
|
import Qr from '../components/atoms/Qr'
|
||||||
import Icon from '../components/atoms/Icon'
|
import Icon from '../components/atoms/Icon'
|
||||||
import styles from './thanks.module.scss'
|
import styles from './thanks.module.scss'
|
||||||
|
|
||||||
const Web3Donation = loadable(() =>
|
const LazyWeb3Donation = loadable(() =>
|
||||||
import('../components/molecules/Web3Donation')
|
import('../components/molecules/Web3Donation')
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,10 +48,19 @@ export default function Thanks() {
|
|||||||
<h1 className={styles.title}>Say Thanks</h1>
|
<h1 className={styles.title}>Say Thanks</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Web3Donation
|
<div className={styles.web3}>
|
||||||
fallback={<div className={styles.loading}>Loading...</div>}
|
<header>
|
||||||
address={author.ether}
|
<h4>Web3 Wallet</h4>
|
||||||
/>
|
<p>Send Ether with MetaMask or Brave.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<Web3ReactProvider getLibrary={getLibrary}>
|
||||||
|
<LazyWeb3Donation
|
||||||
|
fallback={<div className={styles.loading}>Loading...</div>}
|
||||||
|
address={author.ether}
|
||||||
|
/>
|
||||||
|
</Web3ReactProvider>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.coins}>
|
<div className={styles.coins}>
|
||||||
<header>
|
<header>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user