mirror of https://github.com/kremalicious/blog.git
handle sending
This commit is contained in:
parent
15130e8d92
commit
d8da825649
|
@ -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 (
|
||||
<WagmiConfig config={wagmiConfig}>
|
||||
<RainbowKitProvider chains={chains} theme={theme}>
|
||||
<Web3Donation address={config.author.ether} />
|
||||
<Web3Donation />
|
||||
</RainbowKitProvider>
|
||||
</WagmiConfig>
|
||||
)
|
||||
|
|
|
@ -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 }
|
||||
}
|
|
@ -16,6 +16,8 @@ export async function getTokens(
|
|||
address: `0x${string}`,
|
||||
chainId: number
|
||||
): Promise<GetToken[]> {
|
||||
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)
|
||||
|
|
|
@ -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 (
|
||||
<div className={styles.conversion}>
|
||||
|
|
|
@ -74,6 +74,10 @@
|
|||
color: var(--text-color-light);
|
||||
}
|
||||
|
||||
.disclaimer code {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0.01;
|
||||
|
|
|
@ -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<React.SetStateAction<string>>
|
||||
setTokenSelected: React.Dispatch<React.SetStateAction<GetToken>>
|
||||
}): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.inputGroup}>
|
||||
<div className={styles.token}>
|
||||
<TokenSelect setToken={setToken} />
|
||||
<TokenSelect setTokenSelected={setTokenSelected} />
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
|
@ -41,7 +42,7 @@ export function InputGroup({
|
|||
Make it rain
|
||||
</button>
|
||||
</div>
|
||||
<Conversion amount={amount} symbol={symbol} />
|
||||
<Conversion amount={amount} token={token} />
|
||||
<div className={styles.disclaimer}>
|
||||
This form sends tokens to my account <code>{config.author.ether}</code>
|
||||
</div>
|
||||
|
|
|
@ -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 <></>
|
||||
}
|
|
@ -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 <></>
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
]
|
|
@ -0,0 +1,2 @@
|
|||
export * from './SendErc20'
|
||||
export * from './SendNative'
|
|
@ -6,13 +6,13 @@ import { Check } from '@images/components/react'
|
|||
import type { GetToken } from '../../api/getTokens'
|
||||
|
||||
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
token: GetToken
|
||||
token: GetToken | undefined
|
||||
}
|
||||
|
||||
export const Token = forwardRef<HTMLDivElement, SelectItemProps>(
|
||||
({ 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<HTMLDivElement, SelectItemProps>(
|
|||
: 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 ? (
|
||||
<Select.Item
|
||||
className={`${className ? className : ''} Token`}
|
||||
{...props}
|
||||
value={token.address}
|
||||
title={token.address}
|
||||
value={token?.address || ''}
|
||||
title={token?.address}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<Select.ItemText>
|
||||
<span className="TokenLogo">
|
||||
{token.logo ? (
|
||||
{token?.logo ? (
|
||||
<img src={token.logo} width="32" height="32" />
|
||||
) : (
|
||||
token.symbol?.substring(0, 3)
|
||||
token?.symbol?.substring(0, 3)
|
||||
)}
|
||||
</span>
|
||||
</Select.ItemText>
|
||||
<div>
|
||||
<h3 className="TokenName">{token.name}</h3>
|
||||
<h3 className="TokenName">{token?.name}</h3>
|
||||
<p className="TokenBalance">{balance}</p>
|
||||
</div>
|
||||
<div className="TokenValue">{valueInUsdFormatted}</div>
|
||||
|
|
|
@ -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<React.SetStateAction<GetToken>>
|
||||
}) {
|
||||
const { data: tokens, isLoading } = useTokens()
|
||||
|
||||
|
@ -16,10 +18,19 @@ export function TokenSelect({
|
|||
<Token key={token.address} token={token} />
|
||||
))
|
||||
|
||||
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 ? (
|
||||
<Select.Root
|
||||
defaultValue={tokens?.[0].address}
|
||||
onValueChange={(value) => setToken(value)}
|
||||
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
|
||||
disabled={!tokens || isLoading}
|
||||
>
|
||||
<Select.Trigger
|
||||
|
|
|
@ -1,98 +1,102 @@
|
|||
import { type ReactElement, useState } from 'react'
|
||||
import { type ReactElement, useState, useEffect } from 'react'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import { parseEther } from 'viem'
|
||||
import {
|
||||
useAccount,
|
||||
useNetwork,
|
||||
usePrepareSendTransaction,
|
||||
useSendTransaction
|
||||
} from 'wagmi'
|
||||
import { useAccount } from 'wagmi'
|
||||
import { ConnectButton } from '@rainbow-me/rainbowkit'
|
||||
|
||||
import Alert, { getTransactionMessage } from './components/Alert/Alert'
|
||||
import { InputGroup } from './components/Input'
|
||||
import styles from './index.module.css'
|
||||
import { SendNative, SendErc20 } from './components/Send'
|
||||
import type { GetToken } from './api/getTokens'
|
||||
|
||||
export default function Web3Donation({
|
||||
address
|
||||
}: {
|
||||
address: string
|
||||
}): ReactElement {
|
||||
export default function Web3Donation(): ReactElement {
|
||||
const { address: account } = useAccount()
|
||||
const { chain } = useNetwork()
|
||||
|
||||
const [amount, setAmount] = useState('')
|
||||
const [debouncedAmount] = useDebounce(amount, 500)
|
||||
const [token, setToken] = useState<string>()
|
||||
const [tokenSelected, setTokenSelected] = useState<GetToken>({
|
||||
address: '0x0'
|
||||
} as any)
|
||||
const [message, setMessage] = useState<{ status: string; text: string }>()
|
||||
const [transactionHash, setTransactionHash] = useState<string>()
|
||||
const [sendFormData, setSendFormData] = useState<{
|
||||
data: { hash: `0x${string}` }
|
||||
send: () => Promise<void>
|
||||
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 (
|
||||
<form
|
||||
className={styles.web3}
|
||||
onSubmit={(e) => {
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault()
|
||||
handleSendTransaction()
|
||||
if (!send || amount === '' || amount === '0') return
|
||||
await send()
|
||||
}}
|
||||
>
|
||||
<ConnectButton chainStatus="icon" showBalance={false} />
|
||||
|
||||
{message ? (
|
||||
<Alert message={message} transactionHash={transactionHash} />
|
||||
<Alert message={message} transactionHash={data?.hash} />
|
||||
) : (
|
||||
<InputGroup
|
||||
amount={amount}
|
||||
symbol={chain?.nativeCurrency?.symbol || 'ETH'}
|
||||
token={tokenSelected}
|
||||
setAmount={setAmount}
|
||||
setToken={setToken}
|
||||
setTokenSelected={setTokenSelected}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
{tokenSelected?.address === '0x0' ? (
|
||||
<SendNative
|
||||
amount={debouncedAmount}
|
||||
setSendFormData={setSendFormData}
|
||||
/>
|
||||
) : (
|
||||
<SendErc20
|
||||
amount={debouncedAmount}
|
||||
tokenAddress={tokenSelected?.address}
|
||||
setSendFormData={setSendFormData}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -37,12 +37,12 @@ const coins = Object.entries(config.author).filter(
|
|||
<BackButton />
|
||||
|
||||
<div class="content">
|
||||
<h3 class="subTitle">Send from your browser wallet.</h3>
|
||||
<h3 class="subTitle">Send from your browser wallet</h3>
|
||||
<Web3 client:only="react" />
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h3 class="subTitle">Send from any wallet.</h3>
|
||||
<h3 class="subTitle">Send from any wallet</h3>
|
||||
|
||||
{
|
||||
coins.map(([key, value]: [key: string, value: string]) => (
|
||||
|
|
Loading…
Reference in New Issue