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

refactor, fetch abort

This commit is contained in:
Matthias Kretschmann 2023-10-29 14:36:48 +00:00
parent b9b0093a2e
commit 74f89bcb7e
Signed by: m
GPG Key ID: 606EEEF3C479A91F
11 changed files with 85 additions and 43 deletions

View File

@ -1,6 +1,6 @@
import { type ReactElement } from 'react' import { useEffect, type ReactElement, useState } from 'react'
import styles from './Conversion.module.css' import styles from './Conversion.module.css'
import type { GetToken } from '../../api/getTokens' import type { GetToken } from '../../hooks/useTokens'
export function Conversion({ export function Conversion({
amount, amount,
@ -9,12 +9,25 @@ export function Conversion({
amount: string amount: string
token: GetToken | undefined token: GetToken | undefined
}): ReactElement { }): ReactElement {
const dollar = token?.price?.usd const [dollar, setDollar] = useState('0.00')
? (Number(amount) * token?.price?.usd).toFixed(2) const [euro, setEuro] = useState('0.00')
: '0.00'
const euro = token?.price?.eur useEffect(() => {
? (Number(amount) * token?.price?.eur).toFixed(2) if (!token?.price || !amount) {
: '0.00' setDollar('0.00')
setEuro('0.00')
return
}
const dollar = token?.price?.usd
? (Number(amount) * token?.price?.usd).toFixed(2)
: '0.00'
const euro = token?.price?.eur
? (Number(amount) * token?.price?.eur).toFixed(2)
: '0.00'
setDollar(dollar)
setEuro(euro)
}, [token?.price, amount])
return ( return (
<div className={styles.conversion}> <div className={styles.conversion}>

View File

@ -4,7 +4,7 @@ import { Conversion } from '../Conversion'
import styles from './InputGroup.module.css' import styles from './InputGroup.module.css'
import { TokenSelect } from '../Tokens' import { TokenSelect } from '../Tokens'
import config from '@config/blog.config' import config from '@config/blog.config'
import type { GetToken } from '../../api/getTokens' import type { GetToken } from '../../hooks/useTokens'
export function InputGroup({ export function InputGroup({
amount, amount,
@ -23,7 +23,10 @@ export function InputGroup({
<> <>
<div className={styles.inputGroup}> <div className={styles.inputGroup}>
<div className={styles.token}> <div className={styles.token}>
<TokenSelect setTokenSelected={setTokenSelected} /> <TokenSelect
selectedToken={token}
setTokenSelected={setTokenSelected}
/>
</div> </div>
<Input <Input
type="text" type="text"

View File

@ -1,7 +1,7 @@
import { parseEther } from 'viem' import { parseEther } from 'viem'
import { useContractWrite, usePrepareContractWrite } from 'wagmi' import { useContractWrite, usePrepareContractWrite } from 'wagmi'
import siteConfig from '@config/blog.config' import siteConfig from '@config/blog.config'
import { abi } from './abi' import { abiErc20Transfer } from './abiErc20Transfer'
import { useEffect } from 'react' import { useEffect } from 'react'
export function SendErc20({ export function SendErc20({
@ -15,7 +15,7 @@ export function SendErc20({
}) { }) {
const { config } = usePrepareContractWrite({ const { config } = usePrepareContractWrite({
address: tokenAddress, address: tokenAddress,
abi, abi: abiErc20Transfer,
functionName: 'transfer', functionName: 'transfer',
args: [siteConfig.author.ether, parseEther(amount)] args: [siteConfig.author.ether, parseEther(amount)]
}) })

View File

@ -1,4 +1,4 @@
export const abi = [ export const abiErc20Transfer = [
{ {
constant: false, constant: false,
inputs: [ inputs: [

View File

@ -3,7 +3,7 @@ import * as Select from '@radix-ui/react-select'
import { formatCurrency } from '@coingecko/cryptoformat' import { formatCurrency } from '@coingecko/cryptoformat'
import './Token.css' import './Token.css'
import { Check } from '@images/components/react' import { Check } from '@images/components/react'
import type { GetToken } from '../../api/getTokens' import type { GetToken } from '../../hooks/useTokens'
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> { interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
token: GetToken | undefined token: GetToken | undefined

View File

@ -2,17 +2,22 @@ import * as Select from '@radix-ui/react-select'
import './TokenSelect.css' import './TokenSelect.css'
import { Token } from './Token' import { Token } from './Token'
import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react' import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react'
import { useTokens } from '../../hooks/useTokens' import { useTokens } from '../../hooks/useTokens/useTokens'
import { TokenLoading } from './TokenLoading' import { TokenLoading } from './TokenLoading'
import type { GetToken } from '../../api/getTokens'
import { useEffect } from 'react' import { useEffect } from 'react'
import type { GetToken } from '../../hooks/useTokens'
import { useAccount, useNetwork } from 'wagmi'
export function TokenSelect({ export function TokenSelect({
selectedToken,
setTokenSelected setTokenSelected
}: { }: {
selectedToken: GetToken | undefined
setTokenSelected: React.Dispatch<React.SetStateAction<GetToken>> setTokenSelected: React.Dispatch<React.SetStateAction<GetToken>>
}) { }) {
const { data: tokens, isLoading } = useTokens() const { data: tokens, isLoading } = useTokens()
const { chain } = useNetwork()
const { address } = useAccount()
const items = tokens?.map((token) => ( const items = tokens?.map((token) => (
<Token key={token.address} token={token} /> <Token key={token.address} token={token} />
@ -24,12 +29,16 @@ export function TokenSelect({
setTokenSelected(token) setTokenSelected(token)
} }
// set default token data // Set default token data to native token
useEffect(() => handleValueChange('0x0'), []) useEffect(() => {
if (!chain?.id || !address || !tokens) return
return tokens ? ( handleValueChange('0x0')
}, [chain?.id, address, tokens])
return (
<Select.Root <Select.Root
defaultValue={tokens?.[0].address} defaultValue={selectedToken?.address}
onValueChange={(value: `0x${string}`) => handleValueChange(value)} onValueChange={(value: `0x${string}`) => handleValueChange(value)}
disabled={!tokens || isLoading} disabled={!tokens || isLoading}
> >
@ -63,5 +72,5 @@ export function TokenSelect({
</Select.Content> </Select.Content>
</Select.Portal> </Select.Portal>
</Select.Root> </Select.Root>
) : null )
} }

View File

@ -1,26 +1,15 @@
export type GetToken = { import type { GetToken } from './types'
address: `0x${string}`
balance: number | undefined
chainId: number
name: string | null
symbol: string | null
decimals: number | null
logo: string | null
price: {
usd: number | null
eur: number | null
}
}
export async function getTokens( export async function getTokens(
address: `0x${string}`, address: `0x${string}`,
chainId: number chainId: number,
signal?: AbortSignal
): Promise<GetToken[]> { ): Promise<GetToken[]> {
if (!address || !chainId) return [] if (!address || !chainId) return []
// const url = `http://localhost:3000/api/balance?address=${address}&chainId=${chainId}` // const url = `http://localhost:3000/api/balance?address=${address}&chainId=${chainId}`
const url = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chainId}` const url = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chainId}`
const response = await fetch(url) const response = await fetch(url, { signal })
const json: GetToken[] = await response.json() const json: GetToken[] = await response.json()
if (!json) console.error(response.statusText) if (!json) console.error(response.statusText)

View File

@ -0,0 +1,2 @@
export * from './useTokens'
export * from './types'

View File

@ -0,0 +1,13 @@
export type GetToken = {
address: `0x${string}`
balance: number | undefined
chainId: number
name: string | null
symbol: string | null
decimals: number | null
logo: string | null
price: {
usd: number | null
eur: number | null
}
}

View File

@ -1,6 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useAccount, useNetwork } from 'wagmi' import { useAccount, useNetwork } from 'wagmi'
import { getTokens, type GetToken } from '../api/getTokens' import { getTokens } from './getTokens'
import type { GetToken } from './types'
export function useTokens() { export function useTokens() {
const { address } = useAccount() const { address } = useAccount()
@ -11,23 +12,35 @@ export function useTokens() {
const [isError, setIsError] = useState<boolean>() const [isError, setIsError] = useState<boolean>()
useEffect(() => { useEffect(() => {
const abortController = new AbortController()
const { signal } = abortController
async function init() { async function init() {
if (!address || !chain) return if (!address || !chain?.id) return
setIsLoading(true) setIsLoading(true)
try { try {
const tokens = await getTokens(address, chain.id) const tokens = await getTokens(address, chain.id, signal)
setData(tokens) setData(tokens)
setIsLoading(false) setIsLoading(false)
} catch (error) { } catch (error: any) {
setIsError(true) setIsError(true)
setIsLoading(false) setIsLoading(false)
console.error((error as Error).message) if ((error as Error).name !== 'AbortError') {
console.error((error as Error).message)
}
} }
} }
init() init()
}, [address, chain])
return () => {
abortController.abort()
setData(undefined)
setIsLoading(undefined)
setIsError(undefined)
}
}, [address, chain?.id])
return { data, isLoading, isError } return { data, isLoading, isError }
} }

View File

@ -6,7 +6,7 @@ import Alert, { getTransactionMessage } from './components/Alert/Alert'
import { InputGroup } from './components/Input' import { InputGroup } from './components/Input'
import styles from './index.module.css' import styles from './index.module.css'
import { SendNative, SendErc20 } from './components/Send' import { SendNative, SendErc20 } from './components/Send'
import type { GetToken } from './api/getTokens' import type { GetToken } from './hooks/useTokens'
export default function Web3Donation(): ReactElement { export default function Web3Donation(): ReactElement {
const { address: account } = useAccount() const { address: account } = useAccount()