mirror of
https://github.com/kremalicious/blog.git
synced 2024-12-23 01:30:01 +01:00
new token select ui
This commit is contained in:
parent
2f6e4f0b3c
commit
ea9ed0382e
9
package-lock.json
generated
9
package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"@astrojs/react": "^3.0.4",
|
||||
"@astrojs/rss": "^3.0.0",
|
||||
"@astrojs/sitemap": "^3.0.2",
|
||||
"@coingecko/cryptoformat": "^0.6.0",
|
||||
"@nanostores/query": "^0.2.4",
|
||||
"@nanostores/react": "^0.7.1",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
@ -851,6 +852,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@coingecko/cryptoformat": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@coingecko/cryptoformat/-/cryptoformat-0.6.0.tgz",
|
||||
"integrity": "sha512-XWi9gsUDrJh27NzMRJo1Ll2OzG6A9PCf+74edgPUuyyZ3VMmcVCaQppS4hK4D/ZKIPDGtgaR7AIubcfnoPwbrg==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
|
@ -46,6 +46,7 @@
|
||||
"@astrojs/react": "^3.0.4",
|
||||
"@astrojs/rss": "^3.0.0",
|
||||
"@astrojs/sitemap": "^3.0.2",
|
||||
"@coingecko/cryptoformat": "^0.6.0",
|
||||
"@nanostores/query": "^0.2.4",
|
||||
"@nanostores/react": "^0.7.1",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
|
@ -1,9 +0,0 @@
|
||||
export async function getBalance(address: `0x${string}` | undefined) {
|
||||
const url = `http://localhost:3000/api/balance?address=${address}`
|
||||
// const url = `https://web3-api-kremalicious.vercel.app/api/balance?address=${address}`
|
||||
const response = await fetch(url)
|
||||
const json = await response.json()
|
||||
|
||||
if (!json) console.error(response.statusText)
|
||||
return json
|
||||
}
|
22
src/components/Sponsor/Web3Donation/api/getTokens.ts
Normal file
22
src/components/Sponsor/Web3Donation/api/getTokens.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export type GetToken = {
|
||||
address: `0x${string}`
|
||||
balance: number | undefined
|
||||
chainId: number
|
||||
name: string | null
|
||||
symbol: string | null
|
||||
decimals: number | null
|
||||
logo: string | null
|
||||
}
|
||||
|
||||
export async function getTokens(
|
||||
address: `0x${string}`,
|
||||
chainId: number
|
||||
): Promise<GetToken[]> {
|
||||
// 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)
|
||||
const json: GetToken[] = await response.json()
|
||||
|
||||
if (!json) console.error(response.statusText)
|
||||
return json
|
||||
}
|
@ -13,11 +13,13 @@
|
||||
}
|
||||
} */
|
||||
|
||||
/* .currency {
|
||||
} */
|
||||
|
||||
:global([data-theme='dark']) .currency {
|
||||
border-right-color: #000;
|
||||
.token {
|
||||
width: 80px;
|
||||
background: var(--box-background-color);
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.inputInput {
|
||||
@ -32,9 +34,7 @@
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.inputInput {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-right: 0;
|
||||
}
|
||||
@ -52,7 +52,7 @@
|
||||
width: 100%;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-color: var(--border-color);
|
||||
border-color: var(--link-color);
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
|
@ -8,18 +8,20 @@ export function InputGroup({
|
||||
amount,
|
||||
isDisabled,
|
||||
symbol,
|
||||
setAmount
|
||||
setAmount,
|
||||
setToken
|
||||
}: {
|
||||
amount: string
|
||||
isDisabled: boolean
|
||||
symbol: string
|
||||
setAmount(amount: string): void
|
||||
setToken(token: string): void
|
||||
}): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.inputGroup}>
|
||||
<div className={styles.currency}>
|
||||
<TokenSelect />
|
||||
<div className={styles.token}>
|
||||
<TokenSelect setToken={setToken} />
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { forwardRef, type HTMLAttributes } from 'react'
|
||||
import * as Select from '@radix-ui/react-select'
|
||||
import classnames from 'classnames'
|
||||
import './SelectItem.css'
|
||||
import { Check } from '@images/components/react'
|
||||
|
||||
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
value: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
||||
({ children, className, value, icon, ...props }, forwardedRef) => {
|
||||
return (
|
||||
<Select.Item
|
||||
className={classnames('SelectItem', className)}
|
||||
{...props}
|
||||
value={value}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<div className="Token">
|
||||
<Select.ItemText>
|
||||
<img src={icon} width="32" height="32" />
|
||||
</Select.ItemText>
|
||||
<span>{children}</span>
|
||||
</div>
|
||||
<Select.ItemIndicator className="SelectItemIndicator">
|
||||
<Check />
|
||||
</Select.ItemIndicator>
|
||||
</Select.Item>
|
||||
)
|
||||
}
|
||||
)
|
@ -4,8 +4,8 @@
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 4)
|
||||
calc(var(--spacer) / 4) 25px;
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
|
||||
calc(var(--spacer) / 3) 25px;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
@ -18,6 +18,10 @@
|
||||
.SelectItem[data-highlighted] {
|
||||
outline: none;
|
||||
background-color: var(--text-color);
|
||||
}
|
||||
|
||||
.SelectItem[data-highlighted],
|
||||
.SelectItem[data-highlighted] * {
|
||||
color: var(--body-background-color);
|
||||
}
|
||||
|
||||
@ -39,7 +43,22 @@
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--border-color);
|
||||
background: var(--brand-light);
|
||||
}
|
||||
|
||||
.TokenName,
|
||||
.TokenBalance {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.TokenName {
|
||||
font-size: var(--font-size-base);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.TokenBalance {
|
||||
font-size: var(--font-size-small);
|
||||
font-variant: tabular-nums;
|
||||
}
|
||||
|
||||
.SelectItemIndicator {
|
@ -0,0 +1,45 @@
|
||||
import { forwardRef, type HTMLAttributes } from 'react'
|
||||
import * as Select from '@radix-ui/react-select'
|
||||
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||
import './Token.css'
|
||||
import { Check } from '@images/components/react'
|
||||
import type { GetToken } from '../../api/getTokens'
|
||||
|
||||
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
token: GetToken
|
||||
}
|
||||
|
||||
export const Token = forwardRef<HTMLDivElement, SelectItemProps>(
|
||||
({ className, token, ...props }, forwardedRef) => {
|
||||
const balance =
|
||||
token.balance && token.symbol
|
||||
? formatCurrency(token.balance, token.symbol, 'en', false, {
|
||||
decimalPlaces: 3,
|
||||
significantFigures: 3
|
||||
})
|
||||
: 0
|
||||
|
||||
return balance && parseInt(balance) !== 0 ? (
|
||||
<Select.Item
|
||||
className={`${className} SelectItem`}
|
||||
{...props}
|
||||
value={token.address}
|
||||
title={token.address}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<div className="Token">
|
||||
<Select.ItemText>
|
||||
<img src={token.logo || ''} width="32" height="32" />
|
||||
</Select.ItemText>
|
||||
<div>
|
||||
<h3 className="TokenName">{token.name}</h3>
|
||||
<p className="TokenBalance">{balance}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Select.ItemIndicator className="SelectItemIndicator">
|
||||
<Check />
|
||||
</Select.ItemIndicator>
|
||||
</Select.Item>
|
||||
) : null
|
||||
}
|
||||
)
|
@ -7,30 +7,37 @@ button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 70px;
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
font-size: var(--font-size-small);
|
||||
line-height: 1;
|
||||
padding: 0 calc(var(--spacer) / 4);
|
||||
background: var(--box-background-color);
|
||||
border-right: 1px solid var(--text-color-dimmed);
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.SelectTrigger:hover {
|
||||
background-color: whitesmoke;
|
||||
background-color: var(--text-color);
|
||||
color: var(--body-background-color);
|
||||
}
|
||||
|
||||
.SelectTrigger:focus {
|
||||
/* .SelectTrigger:focus {
|
||||
box-shadow: 0 0 0 2px blue;
|
||||
}
|
||||
} */
|
||||
|
||||
.SelectTrigger[data-disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.SelectTrigger img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--brand-light);
|
||||
}
|
||||
|
||||
.SelectContent {
|
||||
overflow: hidden;
|
||||
background-color: var(--body-background-color);
|
||||
@ -43,9 +50,9 @@ button {
|
||||
}
|
||||
|
||||
.SelectLabel {
|
||||
padding: 0 25px;
|
||||
padding: calc(var(--spacer) / 4);
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--text-color-light);
|
||||
color: var(--text-color);
|
||||
text-transform: capitalize;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
@ -1,14 +1,25 @@
|
||||
import * as Select from '@radix-ui/react-select'
|
||||
import './Select.css'
|
||||
import { SelectItem } from './SelectItem'
|
||||
import './TokenSelect.css'
|
||||
import { Token } from './Token'
|
||||
import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react'
|
||||
import { useTokens } from '../../hooks/useTokens'
|
||||
|
||||
export function TokenSelect() {
|
||||
export function TokenSelect({
|
||||
setToken
|
||||
}: {
|
||||
setToken: (token: string) => void
|
||||
}) {
|
||||
const { data: tokens } = useTokens()
|
||||
|
||||
return (
|
||||
<Select.Root disabled={!tokens}>
|
||||
const items = tokens?.map((token) => (
|
||||
<Token key={token.address} token={token} />
|
||||
))
|
||||
|
||||
return tokens ? (
|
||||
<Select.Root
|
||||
defaultValue={tokens[0].address}
|
||||
onValueChange={(value) => setToken(value)}
|
||||
>
|
||||
<Select.Trigger className="SelectTrigger" aria-label="Token">
|
||||
<Select.Value placeholder="…" />
|
||||
<Select.Icon>
|
||||
@ -26,16 +37,7 @@ export function TokenSelect() {
|
||||
<Select.Label className="SelectLabel">
|
||||
In Your Wallet
|
||||
</Select.Label>
|
||||
|
||||
{tokens?.map((token: any) => (
|
||||
<SelectItem
|
||||
key={token.token_address}
|
||||
value={token.token_address}
|
||||
icon={token.logo}
|
||||
>
|
||||
{token.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
{items}
|
||||
</Select.Group>
|
||||
</Select.Viewport>
|
||||
<Select.ScrollDownButton className="SelectScrollButton">
|
||||
@ -44,5 +46,5 @@ export function TokenSelect() {
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
)
|
||||
) : null
|
||||
}
|
@ -1 +1 @@
|
||||
export * from './Select'
|
||||
export * from './TokenSelect'
|
||||
|
@ -1,26 +1,23 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useAccount, useNetwork } from 'wagmi'
|
||||
import { getBalance } from '../api/getBalance'
|
||||
import { getTokens, type GetTokens } from '../api/getTokens'
|
||||
|
||||
export function useTokens() {
|
||||
const { address } = useAccount()
|
||||
const { chain } = useNetwork()
|
||||
|
||||
const [data, setData] = useState()
|
||||
const [data, setData] = useState<GetTokens[]>()
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
const [isError, setIsError] = useState<boolean>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!address || !chain) return
|
||||
async function init() {
|
||||
if (!address || !chain) return
|
||||
|
||||
async function getTokens() {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const response = await getBalance(address)
|
||||
const tokens = response.filter(
|
||||
(token: any) => parseInt(token.chainId) === chain?.id
|
||||
)
|
||||
const tokens = await getTokens(address, chain.id)
|
||||
setData(tokens)
|
||||
setIsLoading(false)
|
||||
} catch (error) {
|
||||
@ -29,7 +26,7 @@ export function useTokens() {
|
||||
console.error((error as Error).message)
|
||||
}
|
||||
}
|
||||
getTokens()
|
||||
init()
|
||||
}, [address, chain])
|
||||
|
||||
return { data, isLoading, isError }
|
||||
|
@ -23,17 +23,23 @@ export default function Web3Donation({
|
||||
|
||||
const [amount, setAmount] = useState('0.005')
|
||||
const [debouncedAmount] = useDebounce(amount, 500)
|
||||
const [token, setToken] = useState<string>()
|
||||
const [message, setMessage] = useState<{ status: string; text: string }>()
|
||||
const [transactionHash, setTransactionHash] = useState<string>()
|
||||
|
||||
// dummy
|
||||
if (token) {
|
||||
console.log(token)
|
||||
}
|
||||
|
||||
const { config } = usePrepareSendTransaction({
|
||||
chainId: chain?.id,
|
||||
to: address,
|
||||
value: debouncedAmount ? parseEther(debouncedAmount) : undefined
|
||||
value: parseEther(debouncedAmount)
|
||||
})
|
||||
const { sendTransactionAsync, isError, isSuccess } =
|
||||
useSendTransaction(config)
|
||||
|
||||
const [message, setMessage] = useState<{ status: string; text: string }>()
|
||||
const [transactionHash, setTransactionHash] = useState<string>()
|
||||
|
||||
async function handleSendTransaction() {
|
||||
setMessage({
|
||||
status: 'loading',
|
||||
@ -83,6 +89,7 @@ export default function Web3Donation({
|
||||
amount={amount}
|
||||
symbol={chain?.nativeCurrency?.symbol || 'ETH'}
|
||||
setAmount={setAmount}
|
||||
setToken={setToken}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { type Theme, getDefaultWallets } from '@rainbow-me/rainbowkit'
|
||||
import { configureChains, createConfig } from 'wagmi'
|
||||
import { mainnet, polygon, base, bsc } from 'wagmi/chains'
|
||||
import { mainnet, polygon, base, optimism } from 'wagmi/chains'
|
||||
import { infuraProvider } from 'wagmi/providers/infura'
|
||||
import { publicProvider } from 'wagmi/providers/public'
|
||||
|
||||
@ -13,7 +13,7 @@ if (isProduction && (!PUBLIC_INFURA_ID || !PUBLIC_WALLETCONNECT_ID)) {
|
||||
}
|
||||
|
||||
export const { chains, publicClient } = configureChains(
|
||||
[mainnet, polygon, base, bsc],
|
||||
[mainnet, polygon, base, optimism],
|
||||
[infuraProvider({ apiKey: PUBLIC_INFURA_ID }), publicProvider()]
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user