diff --git a/app/pages/home/HomePage.tsx b/app/pages/home/HomePage.tsx index 8db45d3..d59bcbd 100644 --- a/app/pages/home/HomePage.tsx +++ b/app/pages/home/HomePage.tsx @@ -1,7 +1,46 @@ +import { useQuery } from "@tanstack/react-query"; +import { TokenCard } from "./TokenCard"; +import { fetchApi } from "~/lib/fetch"; +import type { Erc20TokensListResponse } from "~/types/api"; +import { LoadingView } from "~/components/loading/LoadingView"; +import { ErrorView } from "~/components/error/ErrorView"; +import { useNavigate } from "react-router"; +import { useConnection } from "wagmi"; +import { useEffect } from "react"; + export function HomePage() { + const { data, isPending, error } = useQuery({ + queryKey: ['tokens'], + queryFn: () => fetchApi("https://api.next.code-camp.org/erc20-tokens") + }); + + const navigate = useNavigate(); + const { address, isConnected } = useConnection(); + + useEffect(() => { + if (!isConnected) { + navigate('/status'); + } + }, [isConnected, navigate]); + + + if (isPending) { + return ; + } + + if (error) { + return ; + } + + if (!address || !isConnected) { + return null; + } + return ( -
-

Hello Wagmi!

+
+ {data.tokens.map(token => ( + + ))}
); } diff --git a/app/pages/home/TokenCard.tsx b/app/pages/home/TokenCard.tsx new file mode 100644 index 0000000..addbf0e --- /dev/null +++ b/app/pages/home/TokenCard.tsx @@ -0,0 +1,128 @@ +import { useQueryClient } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; +import { toast } from "react-toastify"; +import { erc20Abi, formatUnits, isAddress, parseUnits, type Address } from "viem"; +import { useReadContract, useWaitForTransactionReceipt, useWriteContract } from "wagmi"; +import type { SimpleErc20Token } from "~/types/api"; + + +export function TokenCard({ token, address }: { token: SimpleErc20Token, address: Address }) { + const [isTransferSectionOpen, setIsTransferSectionOpen] = useState(false); + + const [inputAddress, setInputAddress] = useState(""); + const isAddressValid = isAddress(inputAddress); + + const [inputAmount, setInputAmount] = useState(""); + const isAmountValid = inputAmount && !isNaN(Number(inputAmount)) && Number(inputAmount) > 0; + + const { data: balance, error: errorBalance } = useReadContract({ + address: token.address, + abi: erc20Abi, + functionName: 'balanceOf', + args: [address] + }); + + const { data: decimals, error: errorDecimals } = useReadContract({ + address: token.address, + abi: erc20Abi, + functionName: 'decimals' + }); + + const { data: hash, mutate: makeTransfer, isPending: isWaitingForUser, error: userError } = useWriteContract(); + const { data: receipt, isPending, isFetching, error: receiptError } = useWaitForTransactionReceipt({ + hash + }) + + const queryClient = useQueryClient(); + useEffect(() => { + if (hash && receipt?.status === 'success') { + queryClient.invalidateQueries(); + } + }, [receipt, hash, queryClient]); + + function onSubmit(e) { + e.preventDefault(); + + if (!isAddressValid) { + toast.error('Invalid address.'); + return; + } + + if (!isAmountValid) { + toast.error('Invalid amount.'); + return; + } + + makeTransfer({ + address: token.address, + abi: erc20Abi, + functionName: 'transfer', + args: [inputAddress, parseUnits(inputAmount, decimals!)] + }) + } + + if (errorBalance || errorDecimals) { + return

Cannot read balance

+ } + + return ( +
+
+

{token.name}

+
+
+

Identifier

+

{token.id}

+ +

Symbol

+

{token.symbol}

+ +

Contract address

+

{token.address}

+ +

Balance

+

+ {balance !== undefined && decimals !== undefined ? formatUnits(balance, decimals) : '...'} {token.symbol} +

+
+ + {isTransferSectionOpen && ( +
+
+

Transfer funds

setIsTransferSectionOpen(false)}>× +
+
+ setInputAddress(e.target.value)} /> + setInputAmount(e.target.value)} /> + +
+ +
+
+ + {isWaitingForUser &&

Waiting for user...

} + {(userError || receiptError) &&

Failed to execute transfer.

} + {(isPending && isFetching) &&

Waiting for receipt...

} + {receipt &&

Status: {receipt.status}

} +
+ )} + +
+ View contract + + +
+
+ ); +} \ No newline at end of file diff --git a/app/types/api.ts b/app/types/api.ts new file mode 100644 index 0000000..7ed42e3 --- /dev/null +++ b/app/types/api.ts @@ -0,0 +1,13 @@ + +export type SimpleErc20Token = { + id: number; + name: string; + symbol: string; + address: `0x${string}`; +} + + +// https://api.next.code-camp.org/erc20-tokens +export type Erc20TokensListResponse = { + tokens: Array; +}