mirror of
https://github.com/kremalicious/blog.git
synced 2025-02-14 21:10:25 +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-top: 0;
|
||||||
padding-bottom: 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 { useStore } from '@nanostores/react'
|
||||||
import { useNetwork, useEnsAddress, useEnsName } from 'wagmi'
|
import { useEnsAddress, useEnsName } from 'wagmi'
|
||||||
import type {
|
import { $isInitSend, $txHash } from '@features/Web3/stores'
|
||||||
SendTransactionArgs,
|
|
||||||
WriteContractPreparedArgs
|
|
||||||
} from 'wagmi/actions'
|
|
||||||
import { $selectedToken, $isInitSend, $txHash } from '@features/Web3/stores'
|
|
||||||
import siteConfig from '@config/blog.config'
|
import siteConfig from '@config/blog.config'
|
||||||
import { prepareTransaction, sendTransaction } from './actions'
|
|
||||||
import styles from './Send.module.css'
|
import styles from './Send.module.css'
|
||||||
import { SendTable } from './SendTable'
|
import { SendTable } from './SendTable'
|
||||||
import { Loader } from '@components/Loader'
|
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 }) {
|
export function Send({ amount }: { amount: string }) {
|
||||||
const { ens } = siteConfig.author.ether
|
|
||||||
const { chain } = useNetwork()
|
|
||||||
const selectedToken = useStore($selectedToken)
|
|
||||||
const txHash = useStore($txHash)
|
const txHash = useStore($txHash)
|
||||||
|
|
||||||
// Always resolve to address from ENS name and vice versa
|
// Always resolve to address from ENS name and vice versa
|
||||||
// so nobody has to trust my config values.
|
// so nobody has to trust my config values.
|
||||||
|
const { ens } = siteConfig.author.ether
|
||||||
const { data: to } = useEnsAddress({ name: ens, chainId: 1 })
|
const { data: to } = useEnsAddress({ name: ens, chainId: 1 })
|
||||||
const { data: ensResolved } = useEnsName({
|
const { data: ensResolved } = useEnsName({
|
||||||
address: to as `0x${string}` | undefined,
|
address: to as `0x${string}` | undefined,
|
||||||
chainId: 1
|
chainId: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
const [txConfig, setTxConfig] = useState<
|
const {
|
||||||
SendTransactionArgs | WriteContractPreparedArgs
|
data: txConfig,
|
||||||
>()
|
error: prepareError,
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
isError: isPrepareError
|
||||||
|
} = usePrepareSend({ amount, to })
|
||||||
useEffect(() => {
|
const { handleSend, isLoading, error } = useSend({ txConfig })
|
||||||
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])
|
|
||||||
|
|
||||||
// Cancel send flow if chain changes as this can mess with token selection
|
// Cancel send flow if chain changes as this can mess with token selection
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
@ -52,19 +33,6 @@ export function Send({ amount }: { amount: string }) {
|
|||||||
// $isInitSend.set(false)
|
// $isInitSend.set(false)
|
||||||
// }, [chain?.id])
|
// }, [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)
|
console.log(txHash)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -79,11 +47,13 @@ export function Send({ amount }: { amount: string }) {
|
|||||||
isDisabled={isLoading}
|
isDisabled={isLoading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className={styles.alert}>{error || prepareError}</div>
|
||||||
|
|
||||||
<footer className={styles.actions}>
|
<footer className={styles.actions}>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => handleSend(e)}
|
onClick={(e) => handleSend(e)}
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
disabled={isLoading}
|
disabled={isLoading || !txConfig || isPrepareError}
|
||||||
>
|
>
|
||||||
{isLoading ? <Loader /> : 'Make it rain'}
|
{isLoading ? <Loader /> : 'Make it rain'}
|
||||||
</button>
|
</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 SendTransactionArgs,
|
||||||
type WriteContractPreparedArgs
|
type WriteContractPreparedArgs
|
||||||
} from 'wagmi/actions'
|
} from 'wagmi/actions'
|
||||||
import { abiErc20Transfer } from '../abiErc20Transfer'
|
import { abiErc20Transfer } from './abiErc20Transfer'
|
||||||
|
|
||||||
export async function prepareTransaction(
|
export async function prepare(
|
||||||
selectedToken: GetToken | undefined,
|
selectedToken: GetToken | undefined,
|
||||||
amount: string | undefined,
|
amount: string | undefined,
|
||||||
to: `0x${string}` | null | undefined,
|
to: `0x${string}` | null | undefined,
|
||||||
@ -18,8 +18,9 @@ export async function prepareTransaction(
|
|||||||
return
|
return
|
||||||
|
|
||||||
const isNative = selectedToken.address === '0x0'
|
const isNative = selectedToken.address === '0x0'
|
||||||
const setupNative = { chainId, to, value: parseEther(amount) }
|
const requestNative = { chainId, to, value: parseEther(amount) }
|
||||||
const setupErc20 = {
|
const requestErc20 = {
|
||||||
|
chainId,
|
||||||
address: selectedToken.address,
|
address: selectedToken.address,
|
||||||
abi: abiErc20Transfer,
|
abi: abiErc20Transfer,
|
||||||
functionName: 'transfer',
|
functionName: 'transfer',
|
||||||
@ -27,8 +28,8 @@ export async function prepareTransaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config = isNative
|
const config = isNative
|
||||||
? ((await prepareSendTransaction(setupNative)) as SendTransactionArgs)
|
? ((await prepareSendTransaction(requestNative)) as SendTransactionArgs)
|
||||||
: ((await prepareWriteContract(setupErc20)) as WriteContractPreparedArgs)
|
: ((await prepareWriteContract(requestErc20)) as WriteContractPreparedArgs)
|
||||||
|
|
||||||
return config
|
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
|
type WriteContractPreparedArgs
|
||||||
} from 'wagmi/actions'
|
} from 'wagmi/actions'
|
||||||
|
|
||||||
export async function sendTransaction(
|
export async function send(
|
||||||
selectedToken: GetToken | undefined,
|
selectedToken: GetToken | undefined,
|
||||||
config: SendTransactionArgs | WriteContractPreparedArgs | 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…
x
Reference in New Issue
Block a user