1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-12-22 09:13:35 +01:00

Web3 cleanup (#678)

* rainbowkit setup and web3 cleanup

* finalize setup

* make transaction work

* switch to @kremalicious/react-feather

* update tests

* remove @loadable/component

* update test
This commit is contained in:
Matthias Kretschmann 2022-05-08 13:49:18 +01:00 committed by GitHub
parent c2429ac05a
commit fd30a31c4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 14050 additions and 8592 deletions

View File

@ -1,3 +1,4 @@
GATSBY_GITHUB_TOKEN=your_token
GATSBY_MAPBOX_ACCESS_TOKEN=
GATSBY_TYPEKIT_ID=xxx
INFURA_ID=xxx

View File

@ -12,7 +12,6 @@ module.exports = {
uri: 'https://matthiaskretschmann.com',
twitter: 'https://twitter.com/kremalicious',
github: 'https://github.com/kremalicious',
facebook: 'https://facebook.com/matthiaskretschmann',
bitcoin: '171qDmKEXm9YBgBLXyGjjPvopP5o9htQ1V',
ether: '0xf50F267b5689b005FE107cfdb34619f24c014457'
},

View File

@ -146,3 +146,13 @@ exports.onPostBuild = async ({ graphql }) => {
return Promise.resolve()
}
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
resolve: {
fallback: {
util: false
}
}
})
}

View File

@ -0,0 +1,13 @@
export function configureChains() {
return { chains: [{}], provider: {} }
}
export const apiProvider = {
infura: jest.fn(),
alchemy: jest.fn(),
fallback: jest.fn()
}
export function getDefaultWallets() {
return { connectors: [{}] }
}

72
jest/__mocks__/wagmi.js Normal file
View File

@ -0,0 +1,72 @@
import { chain as chainOrig } from 'wagmi'
export function useNetwork() {
return {
activeChain: {
nativeCurrency: {
symbol: 'ETH'
}
}
}
}
export function useAccount() {
return {
data: {
address: '0x0000000000000000000000000000000000000000'
}
}
}
export function useSendTransaction() {
return {
data: {
address: '0x0000000000000000000000000000000000000000'
}
}
}
export function useEnsAvatar() {
return {
data: 'xxx.jpg'
}
}
export function useEnsName() {
return {
data: 'fguhifgvewtyifgwyufew.eth'
}
}
export function useBalance() {
return {
data: { formatted: '0.22', symbol: 'ETH' }
}
}
export function useConnect() {
return {
connect: jest.fn()
}
}
export function useDisconnect() {
return {
disconnect: jest.fn()
}
}
export function useProvider() {
return {}
}
export const chain = chainOrig
export function createClient() {
return {
queryClient: {
mount: jest.fn(),
unmount: jest.fn()
}
}
}

View File

@ -21,7 +21,8 @@ const esModules = [
'ccount',
'escape-string-regexp',
'markdown-table',
'web-namespaces'
'web-namespaces',
'@rainbow-me/rainbowkit'
].join('|')
module.exports = {
@ -34,8 +35,7 @@ module.exports = {
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/jest/__mocks__/file-mock.js',
'\\.svg': '<rootDir>/jest/__mocks__/svgr-mock.js',
'^@reach/router(.*)': '<rootDir>/node_modules/@gatsbyjs/reach-router$1',
'^gatsby-page-utils/(.*)$': `gatsby-page-utils/dist/$1` // Workaround for https://github.com/facebook/jest/issues/9771
'^@reach/router(.*)': '<rootDir>/node_modules/@gatsbyjs/reach-router$1'
},
testPathIgnorePatterns: ['node_modules', '.cache', 'public', 'coverage'],
transformIgnorePatterns: [`node_modules/(?!(gatsby|${esModules})/)`],

21717
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,16 +28,13 @@
"not op_mini all"
],
"dependencies": {
"@ethersproject/providers": "^5.6.5",
"@ethersproject/units": "^5.6.0",
"@loadable/component": "^5.15.2",
"@web3-react/core": "^6.1.9",
"@web3-react/injected-connector": "^6.0.7",
"@kremalicious/react-feather": "^2.1.0",
"@rainbow-me/rainbowkit": "^0.1.0",
"axios": "^0.27.2",
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
"dms2dec": "^1.1.0",
"ethereum-blockies": "github:MyEtherWallet/blockies",
"ethers": "^5.6.5",
"fast-exif": "^1.0.1",
"feather-icons": "^4.29.0",
"fraction.js": "^4.2.0",
@ -71,15 +68,14 @@
"react": "^18.1.0",
"react-clipboard.js": "^2.0.16",
"react-dom": "^18.1.0",
"react-feather": "^2.0.9",
"react-helmet": "^6.1.0",
"react-qr-svg": "^2.4.0",
"react-transition-group": "^4.4.2",
"rehype-react": "^7.1.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"slugify": "^1.6.5",
"unified": "^10.1.2"
"unified": "^10.1.2",
"wagmi": "^0.3.4"
},
"devDependencies": {
"@svgr/webpack": "^5.5.0",
@ -108,7 +104,8 @@
"eslint-plugin-testing-library": "^5.4.0",
"fs-extra": "^10.1.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.5.1",
"jest": "^28.1.0",
"jest-environment-jsdom": "^28.1.0",
"markdownlint-cli": "^0.31.1",
"node-iptc": "^1.0.5",
"npm-run-all": "^4.1.5",

View File

@ -9,7 +9,6 @@ export interface Author {
uri: string
twitter: string
github: string
facebook: string
bitcoin: string
ether: string
}

View File

@ -1,11 +1,5 @@
declare module 'pigeon-maps'
declare module 'pigeon-marker'
declare module 'react-blockies'
declare module 'remark-react'
declare module 'unified'
declare module 'fast-exif'
declare module 'node-iptc'
declare module 'ethereum-blockies' {
export function toDataUrl(address: string): string
}

View File

@ -0,0 +1,11 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import Copy from './Copy'
describe('Copy', () => {
it('renders without crashing', () => {
render(<Copy text="hello" />)
fireEvent.click(screen.getByTitle('Copy to clipboard'))
})
})

View File

@ -1,9 +1,7 @@
import React, { ReactElement } from 'react'
import loadable from '@loadable/component'
import { copied, button } from './Copy.module.css'
import Icon from './Icon'
const Clipboard = loadable(() => import('react-clipboard.js'))
import Clipboard from 'react-clipboard.js'
const onCopySuccess = (e: any) => {
e.trigger.classList.add(copied)

View File

@ -1,7 +1,7 @@
import React, { FunctionComponent, ReactElement } from 'react'
// https://featherstyles.com
// import * as Feather from 'react-feather'
// import * as Feather from '@kremalicious/react-feather'
import {
ArrowDownCircle,
Edit,
@ -22,7 +22,7 @@ import {
Aperture,
Maximize,
Crosshair
} from 'react-feather'
} from '@kremalicious/react-feather'
// custom icons
import { ReactComponent as Jsonfeed } from '../../images/jsonfeed.svg'
import { ReactComponent as Bitcoin } from '../../images/bitcoin.svg'

View File

@ -1,11 +1,9 @@
import React, { ReactElement } from 'react'
import React, { ReactElement, InputHTMLAttributes } from 'react'
import { input } from './Input.module.css'
export default function Input({
className,
...props
}: {
className: string
}): ReactElement {
}: InputHTMLAttributes<HTMLInputElement>): ReactElement {
return <input className={`${input} ${className || ''}`} {...props} />
}

View File

@ -1,21 +0,0 @@
.qr {
margin-bottom: calc(var(--spacer) / 2);
}
.code {
margin: 0 auto;
position: relative;
padding: 0;
padding-right: 2rem;
width: fit-content;
}
.code code {
padding: calc(var(--spacer) / 2);
font-size: 0.65rem;
text-align: center;
}
.title {
margin-bottom: calc(var(--spacer) / 4);
}

View File

@ -1,16 +0,0 @@
import React, { Suspense } from 'react'
import { render, waitFor } from '@testing-library/react'
import Qr from './Qr'
describe('Qr', () => {
test('renders lazy', async () => {
const { container } = render(
<Suspense fallback="test loading">
<Qr address="xxx" />
</Suspense>
)
expect(container.firstChild).toBeInTheDocument()
await waitFor(() => container.querySelector('button'))
})
})

View File

@ -1,31 +0,0 @@
import React, { ReactElement } from 'react'
// import { QRCode } from 'react-qr-svg'
import { title as styleTitle, code } from './Qr.module.css'
import Copy from './Copy'
export default function Qr({
address,
title
}: {
address: string
title?: string
}): ReactElement {
return (
<>
{title && <h4 className={styleTitle}>{title}</h4>}
{/* <QRCode
bgColor="transparent"
fgColor="#6b7f88"
level="Q"
style={{ width: 120 }}
value={address}
className={qr}
/> */}
<pre className={code}>
<code>{address}</code>
<Copy text={address} />
</pre>
</>
)
}

View File

@ -1,41 +1,13 @@
import React, { ReactElement } from 'react'
import loadable from '@loadable/component'
const LazyDate = loadable.lib(() => import('date-fns'))
function TimeMarkup({
date,
format,
formatDistance
}: {
date: Date
format?: any
formatDistance?: any
}) {
const dateIso = date.toISOString()
return (
<time
title={format ? format(date, 'yyyy/MM/dd HH:mm') : dateIso}
dateTime={dateIso}
>
{format ? formatDistance(date, Date.now(), { addSuffix: true }) : dateIso}
</time>
)
}
import { format, formatDistance } from 'date-fns'
export default function Time({ date }: { date: string }): ReactElement {
const dateNew = new Date(date)
const dateIso = dateNew.toISOString()
return (
<LazyDate fallback={<TimeMarkup date={dateNew} />}>
{({ format, formatDistance }) => (
<TimeMarkup
date={dateNew}
format={format}
formatDistance={formatDistance}
/>
)}
</LazyDate>
<time title={format(dateNew, 'yyyy/MM/dd HH:mm')} dateTime={dateIso}>
{formatDistance(dateNew, Date.now(), { addSuffix: true })}
</time>
)
}

View File

@ -1,26 +0,0 @@
.link {
margin-bottom: calc(var(--spacer) / 2);
display: inline-block;
}
.accountWrap {
font-size: var(--font-size-small);
color: var(--text-color-light);
margin-bottom: calc(var(--spacer) / 2);
display: flex;
justify-content: space-between;
}
.blockies {
width: 1.2rem;
height: 1.2rem;
border-radius: 50%;
overflow: hidden;
display: inline-block;
vertical-align: middle;
margin-right: calc(var(--spacer) / 4);
}
.balance {
margin-left: var(--spacer);
}

View File

@ -1,11 +0,0 @@
import React from 'react'
import { render } from '@testing-library/react'
import Account from './Account'
describe('Account', () => {
it('renders without crashing', () => {
const { container } = render(<Account />)
expect(container.firstChild).toBeInTheDocument()
})
})

View File

@ -1,49 +0,0 @@
import React, { ReactElement, useState, useEffect } from 'react'
import { toDataUrl } from 'ethereum-blockies'
import { formatEther } from '@ethersproject/units'
import useWeb3, { connectors } from '../../../hooks/useWeb3'
import {
accountWrap,
blockies as styleBlockies,
balance,
link
} from './Account.module.css'
export default function Account(): ReactElement {
const { library, account, activate } = useWeb3()
const [ethBalance, setEthBalance] = useState('0')
const blockies = account && toDataUrl(account)
useEffect(() => {
if (!library || !account) return
async function init() {
const balance = await library.getBalance(account)
setEthBalance(balance.toString())
}
init()
}, [library, account])
const accountDisplay =
account &&
`${account.substring(0, 8)}...${account.substring(account.length - 4)}`
const balanceDisplay =
ethBalance && `Ξ${parseFloat(formatEther(ethBalance)).toPrecision(4)}`
return account ? (
<div className={accountWrap} title={account}>
<span>
<img className={styleBlockies} src={blockies} alt="Blockies" />
{accountDisplay}
</span>
<span className={balance}>{balanceDisplay}</span>
</div>
) : (
<button
className={`link ${link}`}
onClick={() => activate(connectors.MetaMask)}
>
Connect MetaMask
</button>
)
}

View File

@ -0,0 +1,15 @@
import React from 'react'
import { render } from '@testing-library/react'
import Alert from './Alert'
describe('Alert', () => {
it('renders without crashing', async () => {
render(
<Alert
message={{ status: 'loading', text: 'Loading' }}
transactionHash="0xxxx"
/>
)
})
})

View File

@ -6,7 +6,5 @@ import Conversion from './Conversion'
describe('Conversion', () => {
it('renders without crashing', async () => {
render(<Conversion amount="1" />)
// const lazyElement = await findByText(/= €/)
// expect(lazyElement).toBeInTheDocument()
})
})

View File

@ -1,16 +1,17 @@
import React, { useState, useEffect, ReactElement } from 'react'
import axios from 'axios'
import { conversion as styleConversion } from './Conversion.module.css'
import { useNetwork } from 'wagmi'
export async function getFiat(
amount: number
amount: number,
tokenId = 'ethereum'
): Promise<{ [key: string]: string }> {
const url =
'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=eur%2Cusd'
const url = `https://api.coingecko.com/api/v3/simple/price?ids=${tokenId}&vs_currencies=eur%2Cusd`
const response = await axios(url)
if (!response) console.error(response.statusText)
const { usd, eur } = response.data.ethereum
const { usd, eur } = response.data[tokenId]
const dollar = (amount * usd).toFixed(2)
const euro = (amount * eur).toFixed(2)
@ -22,24 +23,32 @@ export default function Conversion({
}: {
amount: string
}): ReactElement {
const { activeChain } = useNetwork()
const [conversion, setConversion] = useState({
euro: '0.00',
dollar: '0.00'
})
const { dollar, euro } = conversion
async function getFiatResponse() {
try {
const { dollar, euro } = await getFiat(Number(amount))
setConversion({ euro, dollar })
} catch (error) {
console.error(error.message)
}
}
useEffect(() => {
if (!activeChain?.nativeCurrency?.symbol) return
async function getFiatResponse() {
try {
const tokenId =
activeChain?.nativeCurrency?.symbol === 'MATIC'
? 'matic-network'
: 'ethereum'
const { dollar, euro } = await getFiat(Number(amount), tokenId)
setConversion({ euro, dollar })
} catch (error) {
console.error(error.message)
}
}
getFiatResponse()
}, [amount])
}, [amount, activeChain?.nativeCurrency?.symbol])
return (
<div className={styleConversion}>

View File

@ -1,8 +1,8 @@
.inputGroup {
max-width: 20rem;
margin: auto;
position: relative;
animation: fadeIn 0.8s ease-out backwards;
margin-top: var(--spacer);
}
@media (min-width: 40rem) {
@ -16,7 +16,7 @@
width: 100%;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-color: var(--text-color-light);
border-color: var(--border-color);
}
@media (min-width: 40rem) {
@ -29,10 +29,6 @@
}
}
:global(.dark) .inputGroup button {
border-color: #000;
}
.input {
position: relative;
}
@ -43,9 +39,9 @@
}
}
.input input {
.inputInput {
text-align: center;
border: 1px solid var(--text-color-light);
border: 1px solid var(--border-color);
font-size: var(--font-size-large);
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 3)
calc(var(--spacer) / 3) calc(var(--spacer) * 1.7);
@ -55,21 +51,21 @@
}
@media (min-width: 40rem) {
.input input {
.inputInput {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: var(--border-radius);
border-bottom: 1px solid var(--text-color-light);
border-bottom: 1px solid var(--border-color);
border-right: 0;
}
}
.input input::-webkit-inner-spin-button {
.inputInput::-webkit-inner-spin-button {
margin-left: -1rem;
}
:global(.dark) .input input {
border-color: #000;
:global(.dark) .inputInput {
border-color: var(--border-color);
}
.currency {
@ -80,7 +76,7 @@
font-size: var(--font-size-small);
padding: calc(var(--spacer) / 3);
background: var(--box-background-color);
border-right: 1px solid var(--text-color-light);
border-right: 1px solid var(--text-color-dimmed);
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
display: flex;

View File

@ -13,9 +13,9 @@ describe('InputGroup', () => {
expect(container.firstChild).toBeInTheDocument()
const input = container.querySelector('input')
// const button = container.querySelector('button')
const button = container.querySelector('button')
fireEvent.change(input, { target: { value: '3' } })
// fireEvent.click(button)
// expect(sendTransaction).toHaveBeenCalled()
fireEvent.click(button)
expect(sendTransaction).toHaveBeenCalled()
})
})

View File

@ -1,15 +1,21 @@
import React, { ReactElement, useState } from 'react'
import useWeb3 from '../../../hooks/useWeb3'
import { useAccount, useNetwork } from 'wagmi'
import Input from '../../atoms/Input'
import Conversion from './Conversion'
import { inputGroup, input, currency } from './InputGroup.module.css'
import {
inputGroup,
input,
inputInput,
currency
} from './InputGroup.module.css'
export default function InputGroup({
sendTransaction
}: {
sendTransaction(amount: string): void
}): ReactElement {
const { account } = useWeb3()
const { data: account } = useAccount()
const { activeChain } = useNetwork()
const [amount, setAmount] = useState('0.01')
const onAmountChange = ({ target }: { target: any }) => {
@ -21,14 +27,16 @@ export default function InputGroup({
<div className={inputGroup}>
<div className={input}>
<Input
type="number"
type="text"
inputMode="decimal"
pattern="[0-9.]*"
value={amount}
onChange={onAmountChange}
min="0"
step="0.01"
className={inputInput}
disabled={!account}
/>
<div className={currency}>
<span>ETH</span>
<span>{activeChain?.nativeCurrency?.symbol || 'ETH'}</span>
</div>
</div>
<button

View File

@ -1,10 +1,16 @@
.web3 {
composes: container from '../../Layout.module.css';
max-width: 20rem;
margin-bottom: calc(var(--spacer) * 2);
text-align: center;
min-height: 165px;
}
.web3:empty {
display: none;
.web3 > div:first-child {
display: flex;
justify-content: space-between;
font-size: var(--font-size-small);
margin-bottom: var(--spacer);
}
.message {

View File

@ -1,24 +0,0 @@
import React from 'react'
import { render, waitFor, fireEvent, screen } from '@testing-library/react'
import { Web3ReactProvider } from '@web3-react/core'
import { getLibrary } from '../../../hooks/useWeb3'
import Web3Donation from '.'
describe('Web3Donation', () => {
it('renders without crashing', async () => {
const { container } = render(
<Web3ReactProvider getLibrary={getLibrary}>
<Web3Donation address="xxx" />
</Web3ReactProvider>
)
const lazyElement = await waitFor(() => container.querySelector('button'))
expect(lazyElement).toBeInTheDocument()
fireEvent.click(lazyElement)
const message = await screen.findByText(
/No Ethereum browser extension detected/
)
expect(message).toBeInTheDocument()
})
})

View File

@ -1,42 +1,33 @@
import React, { ReactElement, useState, useEffect } from 'react'
import React, { ReactElement, useState } from 'react'
import { parseEther } from '@ethersproject/units'
import useWeb3, { getErrorMessage } from '../../../hooks/useWeb3'
import InputGroup from './InputGroup'
import Alert, { getTransactionMessage } from './Alert'
import { web3 as styleWeb3 } from './index.module.css'
import Account from './Account'
import { useSendTransaction } from 'wagmi'
import { ConnectButton } from '@rainbow-me/rainbowkit'
export default function Web3Donation({
address
}: {
address: string
}): ReactElement {
const { connector, library, chainId, account, active, error } = useWeb3()
const { sendTransactionAsync } = useSendTransaction()
const [message, setMessage] = useState<{ status: string; text: string }>()
const [transactionHash, setTransactionHash] = useState<string>()
useEffect(() => {
setMessage(undefined)
error &&
setMessage({
status: 'error',
text: getErrorMessage(error, chainId)
})
}, [connector, account, library, chainId, active, error])
async function sendTransaction(amount: string) {
const signer = library.getSigner()
async function handleSendTransaction(amount: string) {
setMessage({
status: 'loading',
text: getTransactionMessage().waitingForUser
})
try {
const tx = await signer.sendTransaction({
to: address,
value: parseEther(amount) // ETH -> Wei
const tx = await sendTransactionAsync({
request: {
to: address,
value: parseEther(amount) // ETH -> Wei
}
})
setTransactionHash(tx.hash)
setMessage({
@ -57,11 +48,12 @@ export default function Web3Donation({
return (
<div className={styleWeb3}>
<Account />
<ConnectButton chainStatus="icon" showBalance={false} />
{message ? (
<Alert message={message} transactionHash={transactionHash} />
) : (
<InputGroup sendTransaction={sendTransaction} />
<InputGroup sendTransaction={handleSendTransaction} />
)}
</div>
)

View File

@ -124,13 +124,13 @@
--body-background-color: #161a1b;
--box-background-color: rgba(255 255 255 / 3%);
--box-shadow: 0 1.3px 5.4px rgba(0 7 8 / 60%),
0 4.5px 18.1px rgba(0 7 8 / 40%), 0 20px 81px rgba(0 7 8 / 10%);
--box-shadow: 0 1.3px 5.4px rgba(0 7 8 / 50%),
0 4.5px 18.1px rgba(0 7 8 / 30%), 0 20px 81px rgba(0 7 8 / 10%);
--text-color: var(--brand-grey-light);
--text-color-light: var(--brand-grey);
--text-color-dimmed: var(--brand-grey-dark);
--border-color: var(--brand-grey-dark);
--border-color: #111;
--color-headings: var(--brand-main-light);
--input-bg: var(--body-background-color);

View File

@ -2,3 +2,4 @@
@import '_code.css';
@import '_toast.css';
@import '_alerts.css';
@import '@rainbow-me/rainbowkit/styles.css';

74
src/helpers/rainbowkit.ts Normal file
View File

@ -0,0 +1,74 @@
import {
configureChains,
apiProvider,
getDefaultWallets,
Theme
} from '@rainbow-me/rainbowkit'
import { chain, createClient } from 'wagmi'
export const { chains, provider } = configureChains(
[chain.mainnet, chain.polygon, chain.optimism, chain.arbitrum, chain.rinkeby],
[apiProvider.infura(process.env.INFURA_ID), apiProvider.fallback()]
)
export const { connectors } = getDefaultWallets({
appName: 'kremalicious.com',
chains
})
export const wagmiClient = createClient({
autoConnect: true,
connectors,
provider
})
export const theme: Theme = {
colors: {
accentColor: 'var(--brand-cyan)',
accentColorForeground: '#161a1b',
actionButtonBorder: 'var(--body-background-color)',
actionButtonBorderMobile: 'var(--body-background-color)',
actionButtonSecondaryBackground: 'var(--box-background-color)',
closeButton: 'var(--text-color)',
closeButtonBackground: 'var(--box-background-color)',
connectButtonBackground: 'var(--body-background-color)',
connectButtonBackgroundError: 'var(--alert-error)',
connectButtonInnerBackground: 'var(--box-background-color)',
connectButtonText: 'var(--text-color)',
connectButtonTextError: '#161a1b',
connectionIndicator: 'var(--alert-success)',
error: 'var(--alert-error)',
generalBorder: 'var(--border-color)',
generalBorderDim: 'var(--border-color)',
menuItemBackground: 'var(--link-color)',
modalBackdrop: 'rgba(0, 0, 0, 0.5)',
modalBackground: 'var(--body-background-color)',
modalBorder: 'var(--body-background-color)',
modalText: 'var(--text-color)',
modalTextDim: 'var(--text-color-dimmed)',
modalTextSecondary: 'var(--text-color-light)',
profileAction: 'var(--body-background-color)',
profileActionHover: 'var(--box-background-color)',
profileForeground: 'var(--body-background-color)',
selectedOptionBorder: 'var(--boder-color)',
standby: 'var(--text-color-dimmed)'
},
fonts: {
body: 'var(--font-family-headings)'
},
radii: {
actionButton: 'var(--border-radius)',
connectButton: 'var(--border-radius)',
menuButton: 'var(--border-radius)',
modal: 'var(--border-radius)',
modalMobile: 'var(--border-radius)'
},
shadows: {
connectButton: 'var(--box-shadow)',
dialog: 'var(--box-shadow)',
profileDetailsAction: 'none',
selectedOption: 'var(--box-shadow)',
selectedWallet: 'var(--box-shadow)',
walletLogo: 'var(--box-shadow)'
}
}

View File

@ -15,7 +15,6 @@ const query = graphql`
uri
twitter
github
facebook
bitcoin
ether
}

View File

@ -1,75 +0,0 @@
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 })

View File

@ -1,44 +0,0 @@
import { useState, useEffect } from 'react'
import { useWeb3React } from '@web3-react/core'
import { Web3ReactContextInterface } from '@web3-react/core/dist/types'
import { Web3Provider } from '@ethersproject/providers'
import * as connectors from './connectors'
import { getLibrary, getNetworkName, getErrorMessage } from './utils'
function useEagerConnect(): boolean {
const { MetaMask } = connectors
const { activate, active } = useWeb3React<Web3Provider>()
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
}
// Helper hook around useWeb3React to push typings, and connect by default
export default function useWeb3(): Web3ReactContextInterface<Web3Provider> {
const context = useWeb3React<Web3Provider>()
useEagerConnect()
return context
}
export { connectors, getLibrary, getNetworkName, getErrorMessage }

View File

@ -1,52 +0,0 @@
import { UnsupportedChainIdError } from '@web3-react/core'
import {
NoEthereumProviderError,
UserRejectedRequestError
} from '@web3-react/injected-connector'
import { Web3Provider, ExternalProvider } from '@ethersproject/providers'
export function getLibrary(provider: ExternalProvider): Web3Provider {
const library = new Web3Provider(provider)
library.pollingInterval = 10000
return library
}
export function getNetworkName(netId: number): string {
let networkName: string
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): string {
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.'
}
}

View File

@ -1,11 +0,0 @@
import React from 'react'
import { render, waitFor } from '@testing-library/react'
import Thanks from '../thanks'
describe('/thanks', () => {
it('renders without crashing', async () => {
const { container } = render(<Thanks />)
const lazyElement = await waitFor(() => container.querySelector('button'))
expect(lazyElement).toBeInTheDocument()
})
})

View File

@ -19,80 +19,49 @@
min-height: 100vh;
}
.thanks h2 {
text-align: center;
margin: 0;
margin-bottom: calc(var(--spacer) / 8);
color: var(--text-color);
font-size: var(--font-size-h4);
}
.thanks h4 {
color: var(--text-color);
text-align: center;
text-transform: capitalize;
}
.thanks header {
width: 100%;
text-align: center;
margin-bottom: var(--spacer);
}
.thanks header h4 {
font-size: var(--font-size-large);
margin-top: 0;
margin-bottom: calc(var(--spacer) / 6);
}
.thanks header p {
color: var(--text-color-light);
}
.title {
text-align: center;
margin-top: 0;
margin-bottom: calc(var(--spacer) * 2);
font-size: var(--font-size-h2);
}
.web3 {
width: 100%;
.subTitle {
text-align: center;
margin-top: calc(var(--spacer) / 2);
margin-bottom: calc(var(--spacer) * 2);
padding-bottom: var(--spacer);
}
.web3 small {
/* color: darken($alert-info, 60%); */
margin-top: -1rem;
display: block;
font-size: var(--font-size-base);
color: var(--text-color-light);
}
.coins {
composes: container from '../components/Layout.module.css';
max-width: 20rem;
width: 100%;
}
@media (min-width: 40rem) {
.coins {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
border-top: 1px solid var(--border-color);
}
.coin {
margin-top: var(--spacer);
}
@media (min-width: 40rem) {
.coin {
width: 48%;
margin-top: 0;
}
.titleCoin {
margin-bottom: 0;
font-size: var(--font-size-base);
}
.loading {
.code {
position: relative;
padding: 0;
padding-right: 2rem;
width: 100%;
text-align: center;
}
.code code {
padding: calc(var(--spacer) / 2);
font-size: 0.65rem;
}

View File

@ -1,31 +1,34 @@
import React, { ReactElement } from 'react'
import loadable from '@loadable/component'
import { Helmet } from 'react-helmet'
import { Web3ReactProvider } from '@web3-react/core'
import { Author } from '../@types/Site'
import { useSiteMetadata } from '../hooks/use-site-metadata'
import { getLibrary } from '../hooks/useWeb3'
import Qr from '../components/atoms/Qr'
import Icon from '../components/atoms/Icon'
import {
thanks,
title,
web3,
loading,
coins as styleCoins,
coin,
buttonBack
code,
buttonBack,
titleCoin,
subTitle
} from './thanks.module.css'
import Web3Donation from '../components/molecules/Web3Donation'
import Copy from '../components/atoms/Copy'
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { chains, theme, wagmiClient } from '../helpers/rainbowkit'
const LazyWeb3Donation = loadable(
() => import('../components/molecules/Web3Donation')
)
const Coin = ({ address, author }: { address: string; author: Author }) => (
<div className={coin}>
<Qr title={address} address={(author as any)[address]} />
</div>
)
function Coin({ address, title }: { address: string; title: string }) {
return (
<div className={coin}>
<h4 className={titleCoin}>{title}</h4>
<pre className={code}>
<code>{address}</code>
<Copy text={address} />
</pre>
</div>
)
}
const BackButton = () => (
<button
@ -38,8 +41,8 @@ const BackButton = () => (
export default function Thanks(): ReactElement {
const { author } = useSiteMetadata()
const coins = Object.keys(author).filter(
(key) => key === 'bitcoin' || key === 'ether'
const coins = Object.entries(author).filter(
([key]) => key === 'bitcoin' || key === 'ether'
)
return (
@ -55,28 +58,19 @@ export default function Thanks(): ReactElement {
<h1 className={title}>Say Thanks</h1>
</header>
<div className={web3}>
<header>
<h2>With Web3 Wallet</h2>
<p>Send Ether with MetaMask or Brave.</p>
</header>
<Web3ReactProvider getLibrary={getLibrary}>
<LazyWeb3Donation
fallback={<div className={loading}>Loading...</div>}
address={author.ether}
/>
</Web3ReactProvider>
</div>
<WagmiProvider client={wagmiClient}>
<RainbowKitProvider chains={chains} theme={theme}>
<Web3Donation address={author.ether} />
</RainbowKitProvider>
</WagmiProvider>
<div className={styleCoins}>
<header>
<h2>With Any Other Wallet</h2>
<p>Send Bitcoin or Ether from any wallet.</p>
</header>
<h3 className={subTitle}>
Send Bitcoin or ERC-20 tokens from any wallet.
</h3>
{coins.map((address: string) => (
<Coin key={address} address={address} author={author} />
{coins.map(([key, value]) => (
<Coin key={key} title={key} address={value} />
))}
</div>
</article>