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:
parent
de14c9905c
commit
bad987d68d
@ -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;
|
||||
}
|
||||
}
|
@ -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"
|
||||
/>
|
||||
)
|
||||
})
|
||||
})
|
@ -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)}`
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
@ -22,3 +22,8 @@
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.alert {
|
||||
font-size: var(--font-size-small);
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -1,2 +0,0 @@
|
||||
export * from './prepareTransaction'
|
||||
export * from './sendTransaction'
|
1
src/features/Web3/hooks/usePrepareSend/index.tsx
Normal file
1
src/features/Web3/hooks/usePrepareSend/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './usePrepareSend'
|
@ -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
|
||||
}
|
62
src/features/Web3/hooks/usePrepareSend/usePrepareSend.tsx
Normal file
62
src/features/Web3/hooks/usePrepareSend/usePrepareSend.tsx
Normal 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 }
|
||||
}
|
1
src/features/Web3/hooks/useSend/index.tsx
Normal file
1
src/features/Web3/hooks/useSend/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './useSend'
|
@ -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
|
||||
) {
|
40
src/features/Web3/hooks/useSend/useSend.tsx
Normal file
40
src/features/Web3/hooks/useSend/useSend.tsx
Normal 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 }
|
||||
}
|
Loading…
Reference in New Issue
Block a user