1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-12-22 17:23:50 +01:00

move all prepare/send logic into custom hooks

This commit is contained in:
Matthias Kretschmann 2023-11-03 18:57:17 +00:00
parent de14c9905c
commit bad987d68d
Signed by: m
GPG Key ID: 606EEEF3C479A91F
13 changed files with 131 additions and 156 deletions

View File

@ -1,41 +0,0 @@
.alert {
font-size: var(--font-size-small);
display: inline-block;
}
.alert:empty {
display: none;
}
.alert::after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: ellipsis steps(4, end) 1s infinite;
/* ascii code for the ellipsis character */
content: '\2026';
width: 0;
position: absolute;
}
.success {
composes: alert;
color: green;
}
.error {
composes: alert;
color: red;
}
.error::after,
.success::after {
display: none;
}
@keyframes ellipsis {
to {
width: 0.75rem;
}
}

View File

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

View File

@ -1,48 +0,0 @@
import { type ReactElement } from 'react'
import styles from './Alert.module.css'
export function getTransactionMessage(transactionHash?: string): {
[key: string]: 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!'
}
}
function constructMessage(
transactionHash: string,
message?: { text?: string }
): string | undefined {
return transactionHash
? 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 }
}): ReactElement {
return (
<div
className={classes(message.status || '')}
dangerouslySetInnerHTML={{
__html: `${constructMessage(transactionHash as string, message)}`
}}
/>
)
}

View File

@ -22,3 +22,8 @@
padding-top: 0;
padding-bottom: 0;
}
.alert {
font-size: var(--font-size-small);
display: inline-block;
}

View File

@ -1,50 +1,31 @@
import { useState, type FormEvent, useEffect } from 'react'
import { useStore } from '@nanostores/react'
import { useNetwork, useEnsAddress, useEnsName } from 'wagmi'
import type {
SendTransactionArgs,
WriteContractPreparedArgs
} from 'wagmi/actions'
import { $selectedToken, $isInitSend, $txHash } from '@features/Web3/stores'
import { useEnsAddress, useEnsName } from 'wagmi'
import { $isInitSend, $txHash } from '@features/Web3/stores'
import siteConfig from '@config/blog.config'
import { prepareTransaction, sendTransaction } from './actions'
import styles from './Send.module.css'
import { SendTable } from './SendTable'
import { Loader } from '@components/Loader'
import { usePrepareSend } from '@features/Web3/hooks/usePrepareSend'
import { useSend } from '@features/Web3/hooks/useSend'
export function Send({ amount }: { amount: string }) {
const { ens } = siteConfig.author.ether
const { chain } = useNetwork()
const selectedToken = useStore($selectedToken)
const txHash = useStore($txHash)
// Always resolve to address from ENS name and vice versa
// so nobody has to trust my config values.
const { ens } = siteConfig.author.ether
const { data: to } = useEnsAddress({ name: ens, chainId: 1 })
const { data: ensResolved } = useEnsName({
address: to as `0x${string}` | undefined,
chainId: 1
})
const [txConfig, setTxConfig] = useState<
SendTransactionArgs | WriteContractPreparedArgs
>()
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
async function init() {
if (!selectedToken || !amount || !to || !chain?.id) return
const config = await prepareTransaction(
selectedToken,
amount,
to,
chain.id
)
setTxConfig(config)
}
init()
}, [selectedToken || amount || to || chain?.id])
const {
data: txConfig,
error: prepareError,
isError: isPrepareError
} = usePrepareSend({ amount, to })
const { handleSend, isLoading, error } = useSend({ txConfig })
// Cancel send flow if chain changes as this can mess with token selection
// useEffect(() => {
@ -52,19 +33,6 @@ export function Send({ amount }: { amount: string }) {
// $isInitSend.set(false)
// }, [chain?.id])
async function handleSend(event: FormEvent<HTMLButtonElement>) {
event?.preventDefault()
try {
setIsLoading(true)
const result = await sendTransaction(selectedToken, txConfig)
$txHash.set(result?.hash)
setIsLoading(false)
} catch (error: unknown) {
console.error((error as Error).message)
setIsLoading(false)
}
}
console.log(txHash)
return (
@ -79,11 +47,13 @@ export function Send({ amount }: { amount: string }) {
isDisabled={isLoading}
/>
<div className={styles.alert}>{error || prepareError}</div>
<footer className={styles.actions}>
<button
onClick={(e) => handleSend(e)}
className="btn btn-primary"
disabled={isLoading}
disabled={isLoading || !txConfig || isPrepareError}
>
{isLoading ? <Loader /> : 'Make it rain'}
</button>

View File

@ -1,2 +0,0 @@
export * from './prepareTransaction'
export * from './sendTransaction'

View File

@ -0,0 +1 @@
export * from './usePrepareSend'

View File

@ -6,9 +6,9 @@ import {
type SendTransactionArgs,
type WriteContractPreparedArgs
} from 'wagmi/actions'
import { abiErc20Transfer } from '../abiErc20Transfer'
import { abiErc20Transfer } from './abiErc20Transfer'
export async function prepareTransaction(
export async function prepare(
selectedToken: GetToken | undefined,
amount: string | undefined,
to: `0x${string}` | null | undefined,
@ -18,8 +18,9 @@ export async function prepareTransaction(
return
const isNative = selectedToken.address === '0x0'
const setupNative = { chainId, to, value: parseEther(amount) }
const setupErc20 = {
const requestNative = { chainId, to, value: parseEther(amount) }
const requestErc20 = {
chainId,
address: selectedToken.address,
abi: abiErc20Transfer,
functionName: 'transfer',
@ -27,8 +28,8 @@ export async function prepareTransaction(
}
const config = isNative
? ((await prepareSendTransaction(setupNative)) as SendTransactionArgs)
: ((await prepareWriteContract(setupErc20)) as WriteContractPreparedArgs)
? ((await prepareSendTransaction(requestNative)) as SendTransactionArgs)
: ((await prepareWriteContract(requestErc20)) as WriteContractPreparedArgs)
return config
}

View File

@ -0,0 +1,62 @@
import { useState, useEffect } from 'react'
import { useStore } from '@nanostores/react'
import { useNetwork } from 'wagmi'
import type {
SendTransactionArgs,
WriteContractPreparedArgs
} from 'wagmi/actions'
import { $selectedToken } from '@features/Web3/stores'
import { prepare } from './prepare'
export function usePrepareSend({
amount,
to
}: {
amount: string
to: `0x${string}` | null | undefined
}) {
const { chain } = useNetwork()
const selectedToken = useStore($selectedToken)
const [txConfig, setTxConfig] = useState<
SendTransactionArgs | WriteContractPreparedArgs
>()
const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)
const [error, setError] = useState<string | undefined>()
useEffect(() => {
async function init() {
if (!selectedToken || !amount || !to || !chain?.id) return
setError(undefined)
setIsError(false)
setIsLoading(true)
try {
const config = await prepare(selectedToken, amount, to, chain.id)
setTxConfig(config)
} catch (error: unknown) {
console.error((error as Error).message)
setIsError(true)
// only expose useful errors in UI
if (
(error as Error).message.includes(
'this transaction exceeds the balance of the account.'
)
) {
setError('Insufficient funds')
} else {
setError(undefined)
}
} finally {
setIsLoading(false)
}
}
init()
}, [selectedToken || amount || to || chain?.id])
return { data: txConfig, isLoading, isError, error }
}

View File

@ -0,0 +1 @@
export * from './useSend'

View File

@ -6,7 +6,7 @@ import {
type WriteContractPreparedArgs
} from 'wagmi/actions'
export async function sendTransaction(
export async function send(
selectedToken: GetToken | undefined,
config: SendTransactionArgs | WriteContractPreparedArgs | undefined
) {

View File

@ -0,0 +1,40 @@
import { $txHash, $selectedToken } from '@features/Web3/stores'
import { useStore } from '@nanostores/react'
import { useState, type FormEvent } from 'react'
import type {
SendTransactionArgs,
WriteContractPreparedArgs
} from 'wagmi/actions'
import { send } from './send'
export function useSend({
txConfig
}: {
txConfig: SendTransactionArgs | WriteContractPreparedArgs | undefined
}) {
const selectedToken = useStore($selectedToken)
const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)
const [error, setError] = useState<string | undefined>()
async function handleSend(event: FormEvent<HTMLButtonElement>) {
event?.preventDefault()
try {
setIsError(false)
setError(undefined)
setIsLoading(true)
const result = await send(selectedToken, txConfig)
$txHash.set(result?.hash)
} catch (error: unknown) {
console.error((error as Error).message)
setError((error as Error).message)
setIsError(true)
} finally {
setIsLoading(false)
}
}
return { handleSend, isLoading, error, isError }
}