diff --git a/src/components/Sponsor/Web3.tsx b/src/components/Sponsor/Web3.tsx index ce8e1ca5..36c0b921 100644 --- a/src/components/Sponsor/Web3.tsx +++ b/src/components/Sponsor/Web3.tsx @@ -1,5 +1,4 @@ import Web3Donation from './Web3Donation' -import config from '@config/blog.config' import { RainbowKitProvider } from '@rainbow-me/rainbowkit' import { WagmiConfig } from 'wagmi' import { wagmiConfig, chains, theme } from './Web3Donation/lib/rainbowkit' @@ -9,7 +8,7 @@ export default function Web3(): ReactElement { return ( - + ) diff --git a/src/components/Sponsor/Web3Donation/api/getFiat.ts b/src/components/Sponsor/Web3Donation/api/getFiat.ts deleted file mode 100644 index ff5987f5..00000000 --- a/src/components/Sponsor/Web3Donation/api/getFiat.ts +++ /dev/null @@ -1,18 +0,0 @@ -export async function getFiat({ - amount, - tokenId = 'ethereum' -}: { - amount: number - tokenId?: string -}): Promise<{ [key: string]: string }> { - const url = `https://api.coingecko.com/api/v3/simple/price?ids=${tokenId}&vs_currencies=eur%2Cusd` - const response = await fetch(url) - const json = await response.json() - - if (!json) console.error(response.statusText) - const { usd, eur } = json[tokenId] - const dollar = (amount * usd).toFixed(2) - const euro = (amount * eur).toFixed(2) - - return { dollar, euro } -} diff --git a/src/components/Sponsor/Web3Donation/api/getTokens.ts b/src/components/Sponsor/Web3Donation/api/getTokens.ts index dc1ac35c..6ad87bfa 100644 --- a/src/components/Sponsor/Web3Donation/api/getTokens.ts +++ b/src/components/Sponsor/Web3Donation/api/getTokens.ts @@ -16,6 +16,8 @@ export async function getTokens( address: `0x${string}`, chainId: number ): Promise { + if (!address || !chainId) return [] + // const url = `http://localhost:3000/api/balance?address=${address}&chainId=${chainId}` const url = `https://web3-api-kremalicious.vercel.app/api/balance?address=${address}&chainId=${chainId}` const response = await fetch(url) diff --git a/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.tsx b/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.tsx index 9371c1ea..5b948192 100644 --- a/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.tsx +++ b/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.tsx @@ -1,36 +1,20 @@ -import { type ReactElement, useEffect, useState } from 'react' +import { type ReactElement } from 'react' import styles from './Conversion.module.css' -import { getFiat } from '../../api/getFiat' +import type { GetToken } from '../../api/getTokens' export function Conversion({ amount, - symbol + token }: { amount: string - symbol: string + token: GetToken | undefined }): ReactElement { - const [conversion, setConversion] = useState({ - euro: '0.00', - dollar: '0.00' - }) - const { dollar, euro } = conversion - - useEffect(() => { - async function getFiatResponse() { - try { - const tokenId = symbol === 'MATIC' ? 'matic-network' : 'ethereum' - const { dollar, euro } = await getFiat({ - amount: Number(amount), - tokenId - }) - setConversion({ euro, dollar }) - } catch (error) { - console.error((error as Error).message) - } - } - - getFiatResponse() - }, [amount, symbol]) + 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' return (
diff --git a/src/components/Sponsor/Web3Donation/components/Input/InputGroup.module.css b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.module.css index 6fb0c48f..645fc329 100644 --- a/src/components/Sponsor/Web3Donation/components/Input/InputGroup.module.css +++ b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.module.css @@ -74,6 +74,10 @@ color: var(--text-color-light); } +.disclaimer code { + color: var(--text-color); +} + @keyframes fadeIn { from { opacity: 0.01; diff --git a/src/components/Sponsor/Web3Donation/components/Input/InputGroup.tsx b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.tsx index 3fe4f3ab..df706729 100644 --- a/src/components/Sponsor/Web3Donation/components/Input/InputGroup.tsx +++ b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.tsx @@ -4,25 +4,26 @@ import { Conversion } from '../Conversion' import styles from './InputGroup.module.css' import { TokenSelect } from '../Tokens' import config from '@config/blog.config' +import type { GetToken } from '../../api/getTokens' export function InputGroup({ amount, + token, isDisabled, - symbol, setAmount, - setToken + setTokenSelected }: { amount: string + token: GetToken | undefined isDisabled: boolean - symbol: string - setAmount(amount: string): void - setToken(token: string): void + setAmount: React.Dispatch> + setTokenSelected: React.Dispatch> }): ReactElement { return ( <>
- +
- +
This form sends tokens to my account {config.author.ether}
diff --git a/src/components/Sponsor/Web3Donation/components/Send/SendErc20.tsx b/src/components/Sponsor/Web3Donation/components/Send/SendErc20.tsx new file mode 100644 index 00000000..681e0040 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Send/SendErc20.tsx @@ -0,0 +1,44 @@ +import { parseEther } from 'viem' +import { useContractWrite, usePrepareContractWrite } from 'wagmi' +import siteConfig from '@config/blog.config' +import { abi } from './abi' +import { useEffect } from 'react' + +export function SendErc20({ + amount, + tokenAddress, + setSendFormData +}: { + amount: string + tokenAddress: `0x${string}` | undefined + setSendFormData: any +}) { + const { config } = usePrepareContractWrite({ + address: tokenAddress, + abi, + functionName: 'transfer', + args: [siteConfig.author.ether, parseEther(amount)] + }) + + const { + data, + writeAsync: send, + isError, + isSuccess, + isLoading, + error + } = useContractWrite(config) + + useEffect(() => { + setSendFormData({ + data, + send, + isError, + isSuccess, + isLoading, + error + }) + }, [data, send, isError, isSuccess, isLoading, error]) + + return <> +} diff --git a/src/components/Sponsor/Web3Donation/components/Send/SendNative.tsx b/src/components/Sponsor/Web3Donation/components/Send/SendNative.tsx new file mode 100644 index 00000000..c2f5c881 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Send/SendNative.tsx @@ -0,0 +1,44 @@ +import { parseEther } from 'viem' +import { + useNetwork, + usePrepareSendTransaction, + useSendTransaction +} from 'wagmi' +import siteConfig from '@config/blog.config' +import { useEffect } from 'react' + +export function SendNative({ + amount, + setSendFormData +}: { + amount: string + setSendFormData: any +}) { + const { chain } = useNetwork() + const { config } = usePrepareSendTransaction({ + chainId: chain?.id, + to: siteConfig.author.ether, + value: parseEther(amount) + }) + const { + data, + sendTransactionAsync: send, + isError, + isSuccess, + isLoading, + error + } = useSendTransaction(config) + + useEffect(() => { + setSendFormData({ + data, + send, + isError, + isSuccess, + isLoading, + error + }) + }, [data, send, isError, isSuccess, isLoading, error]) + + return <> +} diff --git a/src/components/Sponsor/Web3Donation/components/Send/abi.ts b/src/components/Sponsor/Web3Donation/components/Send/abi.ts new file mode 100644 index 00000000..60f186d5 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Send/abi.ts @@ -0,0 +1,13 @@ +export const abi = [ + { + constant: false, + inputs: [ + { name: '_to', type: 'address' }, + { name: '_value', type: 'uint256' } + ], + name: 'transfer', + outputs: [{ name: 'success', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function' + } +] diff --git a/src/components/Sponsor/Web3Donation/components/Send/index.tsx b/src/components/Sponsor/Web3Donation/components/Send/index.tsx new file mode 100644 index 00000000..a286ddb3 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Send/index.tsx @@ -0,0 +1,2 @@ +export * from './SendErc20' +export * from './SendNative' diff --git a/src/components/Sponsor/Web3Donation/components/Tokens/Token.tsx b/src/components/Sponsor/Web3Donation/components/Tokens/Token.tsx index 8987f930..f7607df4 100644 --- a/src/components/Sponsor/Web3Donation/components/Tokens/Token.tsx +++ b/src/components/Sponsor/Web3Donation/components/Tokens/Token.tsx @@ -6,13 +6,13 @@ import { Check } from '@images/components/react' import type { GetToken } from '../../api/getTokens' interface SelectItemProps extends HTMLAttributes { - token: GetToken + token: GetToken | undefined } export const Token = forwardRef( ({ className, token, ...props }, forwardedRef) => { const balance = - token.balance && token.symbol + token?.balance && token?.symbol ? formatCurrency(token.balance, token.symbol, 'en', false, { decimalPlaces: 3, significantFigures: 3 @@ -20,28 +20,30 @@ export const Token = forwardRef( : 0 const valueInUsd = - token.balance && token.price?.usd ? token.balance * token.price.usd : 0 + token?.balance && token?.price?.usd + ? token?.balance * token?.price.usd + : 0 const valueInUsdFormatted = formatCurrency(valueInUsd, 'USD', 'en') return balance && parseInt(balance) !== 0 && valueInUsd >= 1 ? ( - {token.logo ? ( + {token?.logo ? ( ) : ( - token.symbol?.substring(0, 3) + token?.symbol?.substring(0, 3) )}
-

{token.name}

+

{token?.name}

{balance}

{valueInUsdFormatted}
diff --git a/src/components/Sponsor/Web3Donation/components/Tokens/TokenSelect.tsx b/src/components/Sponsor/Web3Donation/components/Tokens/TokenSelect.tsx index 562a301d..aff0c99f 100644 --- a/src/components/Sponsor/Web3Donation/components/Tokens/TokenSelect.tsx +++ b/src/components/Sponsor/Web3Donation/components/Tokens/TokenSelect.tsx @@ -4,11 +4,13 @@ import { Token } from './Token' import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react' import { useTokens } from '../../hooks/useTokens' import { TokenLoading } from './TokenLoading' +import type { GetToken } from '../../api/getTokens' +import { useEffect } from 'react' export function TokenSelect({ - setToken + setTokenSelected }: { - setToken: (token: string) => void + setTokenSelected: React.Dispatch> }) { const { data: tokens, isLoading } = useTokens() @@ -16,10 +18,19 @@ export function TokenSelect({ )) + function handleValueChange(value: `0x${string}`) { + const token = tokens?.find((token) => token.address === value) + if (!token) return + setTokenSelected(token) + } + + // set default token data + useEffect(() => handleValueChange('0x0'), []) + return tokens ? ( setToken(value)} + onValueChange={(value: `0x${string}`) => handleValueChange(value)} disabled={!tokens || isLoading} > () + const [tokenSelected, setTokenSelected] = useState({ + address: '0x0' + } as any) const [message, setMessage] = useState<{ status: string; text: string }>() - const [transactionHash, setTransactionHash] = useState() + const [sendFormData, setSendFormData] = useState<{ + data: { hash: `0x${string}` } + send: () => Promise + isLoading: boolean + isSuccess: boolean + isError: boolean + error: Error | null + }>() - // dummy - if (token) { - console.log(token) - } + const { data, send, isLoading, isSuccess, isError, error } = + sendFormData || {} - const { config } = usePrepareSendTransaction({ - chainId: chain?.id, - to: address, - value: parseEther(debouncedAmount) - }) - const { sendTransactionAsync, isError, isSuccess } = - useSendTransaction(config) + useEffect(() => { + if (!isError || !error) return + + setMessage( + error.message.includes('User rejected the request.') + ? undefined + : { + status: 'error', + text: error?.message as string + } + ) + }, [isError]) + + useEffect(() => { + if (!isLoading) return - async function handleSendTransaction() { setMessage({ status: 'loading', - text: getTransactionMessage().waitingForUser + text: getTransactionMessage().waitingConfirmation }) + }, [isLoading]) - try { - const result = sendTransactionAsync && (await sendTransactionAsync()) + useEffect(() => { + if (!isSuccess) return - if (isError) { - throw new Error(undefined) - } - - setTransactionHash(result?.hash) - setMessage({ - status: 'loading', - text: getTransactionMessage().waitingConfirmation - }) - - if (isSuccess) { - setMessage({ - status: 'success', - text: getTransactionMessage().success - }) - } - } catch (error) { - setMessage(undefined) - } - } + setMessage({ + status: 'success', + text: getTransactionMessage().success + }) + }, [isSuccess]) const isDisabled = !account return (
{ + onSubmit={async (e) => { e.preventDefault() - handleSendTransaction() + if (!send || amount === '' || amount === '0') return + await send() }} > {message ? ( - + ) : ( )} + + {tokenSelected?.address === '0x0' ? ( + + ) : ( + + )} ) } diff --git a/src/pages/thanks.astro b/src/pages/thanks.astro index 74ee3ce8..8a4d7a22 100644 --- a/src/pages/thanks.astro +++ b/src/pages/thanks.astro @@ -37,12 +37,12 @@ const coins = Object.entries(config.author).filter(
-

Send from your browser wallet.

+

Send from your browser wallet

-

Send from any wallet.

+

Send from any wallet

{ coins.map(([key, value]: [key: string, value: string]) => (