mirror of
https://github.com/kremalicious/blog.git
synced 2025-02-14 21:10:25 +01:00
use nanostores
This commit is contained in:
parent
ecde440144
commit
21dfb2dd2a
@ -3,7 +3,7 @@ import { describe, it } from 'vitest'
|
|||||||
import Alert from './Alert'
|
import Alert from './Alert'
|
||||||
|
|
||||||
describe('Alert', () => {
|
describe('Alert', () => {
|
||||||
it('renders without crashing', async () => {
|
it('renders without crashing', () => {
|
||||||
render(
|
render(
|
||||||
<Alert
|
<Alert
|
||||||
message={{ status: 'loading', text: 'Loading' }}
|
message={{ status: 'loading', text: 'Loading' }}
|
||||||
|
@ -3,7 +3,7 @@ import { describe, it } from 'vitest'
|
|||||||
import { Conversion } from './Conversion'
|
import { Conversion } from './Conversion'
|
||||||
|
|
||||||
describe('Conversion', () => {
|
describe('Conversion', () => {
|
||||||
it('renders without crashing', async () => {
|
it('renders without crashing', () => {
|
||||||
render(<Conversion amount="1" symbol="ETH" />)
|
render(<Conversion amount="1" />)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, type ReactElement, useState } from 'react'
|
import { useEffect, type ReactElement, useState } from 'react'
|
||||||
import styles from './Conversion.module.css'
|
import styles from './Conversion.module.css'
|
||||||
import { $selectedToken } from '../../stores/selectedToken'
|
import { $selectedToken } from '../../stores/tokens/selectedToken'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
|
|
||||||
export function Conversion({ amount }: { amount: string }): ReactElement {
|
export function Conversion({ amount }: { amount: string }): ReactElement {
|
||||||
|
@ -8,7 +8,7 @@ import styles from './index.module.css'
|
|||||||
import { SendNative, SendErc20 } from '../Send'
|
import { SendNative, SendErc20 } from '../Send'
|
||||||
import { useSend } from '../../hooks/useSend'
|
import { useSend } from '../../hooks/useSend'
|
||||||
import type { SendFormData } from './types'
|
import type { SendFormData } from './types'
|
||||||
import { $selectedToken } from '../../stores/selectedToken'
|
import { $selectedToken } from '../../stores/tokens/selectedToken'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
|
|
||||||
export default function Web3Form(): ReactElement {
|
export default function Web3Form(): ReactElement {
|
||||||
|
@ -6,14 +6,7 @@ const setAmount = vi.fn()
|
|||||||
|
|
||||||
describe('InputGroup', () => {
|
describe('InputGroup', () => {
|
||||||
it('renders without crashing', async () => {
|
it('renders without crashing', async () => {
|
||||||
render(
|
render(<InputGroup amount="1" setAmount={setAmount} isDisabled={false} />)
|
||||||
<InputGroup
|
|
||||||
amount="1"
|
|
||||||
setAmount={setAmount}
|
|
||||||
isDisabled={false}
|
|
||||||
symbol="ETH"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const input = await screen.findByRole('textbox')
|
const input = await screen.findByRole('textbox')
|
||||||
const button = await screen.findByRole('button')
|
const button = await screen.findByRole('button')
|
||||||
@ -23,14 +16,7 @@ describe('InputGroup', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('renders disabled', async () => {
|
it('renders disabled', async () => {
|
||||||
render(
|
render(<InputGroup amount="1" setAmount={setAmount} isDisabled={true} />)
|
||||||
<InputGroup
|
|
||||||
amount="1"
|
|
||||||
setAmount={setAmount}
|
|
||||||
isDisabled={true}
|
|
||||||
symbol="ETH"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const input = await screen.findByRole('textbox')
|
const input = await screen.findByRole('textbox')
|
||||||
expect(input).toBeDefined()
|
expect(input).toBeDefined()
|
||||||
|
@ -4,7 +4,7 @@ import siteConfig from '@config/blog.config'
|
|||||||
import { abiErc20Transfer } from './abiErc20Transfer'
|
import { abiErc20Transfer } from './abiErc20Transfer'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $selectedToken } from '@features/Web3/stores/selectedToken'
|
import { $selectedToken } from '@features/Web3/stores/tokens/selectedToken'
|
||||||
|
|
||||||
export function SendErc20({
|
export function SendErc20({
|
||||||
amount,
|
amount,
|
||||||
@ -16,7 +16,7 @@ export function SendErc20({
|
|||||||
const selectedToken = useStore($selectedToken)
|
const selectedToken = useStore($selectedToken)
|
||||||
|
|
||||||
const { config } = usePrepareContractWrite({
|
const { config } = usePrepareContractWrite({
|
||||||
address: selectedToken.address,
|
address: selectedToken?.address,
|
||||||
abi: abiErc20Transfer,
|
abi: abiErc20Transfer,
|
||||||
functionName: 'transfer',
|
functionName: 'transfer',
|
||||||
args: [
|
args: [
|
||||||
|
@ -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 '../../hooks/useTokens'
|
import type { GetToken } from '@features/Web3/stores/tokens'
|
||||||
|
|
||||||
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
|
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
token: GetToken | undefined
|
token: GetToken | undefined
|
||||||
|
@ -4,17 +4,21 @@ import { Token } from './Token'
|
|||||||
import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react'
|
import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react'
|
||||||
import { TokenLoading } from './TokenLoading'
|
import { TokenLoading } from './TokenLoading'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useTokens } from '../../hooks/useTokens'
|
|
||||||
import { useAccount, useNetwork } from 'wagmi'
|
import { useAccount, useNetwork } from 'wagmi'
|
||||||
import { $selectedToken, $setSelectedToken } from '../../stores/selectedToken'
|
import { useTokensStore } from '@features/Web3/hooks/useTokensStore'
|
||||||
|
import { $selectedToken, $setSelectedToken } from '@features/Web3/stores/tokens'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
|
|
||||||
export function TokenSelect() {
|
export function TokenSelect() {
|
||||||
const { data: tokens, isLoading: tokensIsLoading } = useTokens()
|
|
||||||
const selectedToken = useStore($selectedToken)
|
|
||||||
const { chain } = useNetwork()
|
const { chain } = useNetwork()
|
||||||
const { address } = useAccount()
|
const { address } = useAccount()
|
||||||
|
|
||||||
|
const { data: tokens, loading } = useTokensStore({
|
||||||
|
chainId: chain?.id,
|
||||||
|
address
|
||||||
|
})
|
||||||
|
const selectedToken = useStore($selectedToken)
|
||||||
|
|
||||||
const items = tokens?.map((token) => (
|
const items = tokens?.map((token) => (
|
||||||
<Token key={token.address} token={token} />
|
<Token key={token.address} token={token} />
|
||||||
))
|
))
|
||||||
@ -28,7 +32,7 @@ export function TokenSelect() {
|
|||||||
// Set default token data to first item,
|
// Set default token data to first item,
|
||||||
// which most of time is native token
|
// which most of time is native token
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chain?.id || !address || !tokens) return
|
if (!tokens) return
|
||||||
|
|
||||||
if (!selectedToken || !selectedToken?.address)
|
if (!selectedToken || !selectedToken?.address)
|
||||||
handleValueChange(tokens[0].address)
|
handleValueChange(tokens[0].address)
|
||||||
@ -38,15 +42,15 @@ export function TokenSelect() {
|
|||||||
<Select.Root
|
<Select.Root
|
||||||
defaultValue={selectedToken?.address}
|
defaultValue={selectedToken?.address}
|
||||||
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
|
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
|
||||||
disabled={!tokens || tokensIsLoading}
|
disabled={!tokens || loading}
|
||||||
value={selectedToken?.address}
|
value={selectedToken?.address}
|
||||||
>
|
>
|
||||||
<Select.Trigger
|
<Select.Trigger
|
||||||
className="SelectTrigger"
|
className="SelectTrigger"
|
||||||
disabled={!tokens || tokensIsLoading}
|
disabled={!tokens || loading}
|
||||||
aria-label="Token"
|
aria-label="Token"
|
||||||
>
|
>
|
||||||
{tokensIsLoading ? <TokenLoading /> : <Select.Value />}
|
{loading ? <TokenLoading /> : <Select.Value />}
|
||||||
<Select.Icon>
|
<Select.Icon>
|
||||||
<ChevronDown />
|
<ChevronDown />
|
||||||
</Select.Icon>
|
</Select.Icon>
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import type { GetToken } from './types'
|
|
||||||
|
|
||||||
export async function getTokens(
|
|
||||||
address: `0x${string}`,
|
|
||||||
chainId: number,
|
|
||||||
signal?: AbortSignal
|
|
||||||
): Promise<GetToken[]> {
|
|
||||||
if (!address || !chainId) return []
|
|
||||||
|
|
||||||
// const url = `http://localhost:3000/api/balance?address=${address}&chainId=${chainId}`
|
|
||||||
const url = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chainId}`
|
|
||||||
const response = await fetch(url, { signal })
|
|
||||||
const json: GetToken[] = await response.json()
|
|
||||||
|
|
||||||
if (!json) console.error(response.statusText)
|
|
||||||
return json
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from './useTokens'
|
|
||||||
export * from './types'
|
|
@ -1,46 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useAccount, useNetwork } from 'wagmi'
|
|
||||||
import { getTokens } from './getTokens'
|
|
||||||
import type { GetToken } from './types'
|
|
||||||
|
|
||||||
export function useTokens() {
|
|
||||||
const { address } = useAccount()
|
|
||||||
const { chain } = useNetwork()
|
|
||||||
|
|
||||||
const [data, setData] = useState<GetToken[]>()
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>()
|
|
||||||
const [isError, setIsError] = useState<boolean>()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const abortController = new AbortController()
|
|
||||||
const { signal } = abortController
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
if (!address || !chain?.id) return
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tokens = await getTokens(address, chain.id, signal)
|
|
||||||
setData(tokens)
|
|
||||||
setIsLoading(false)
|
|
||||||
} catch (error: any) {
|
|
||||||
setIsError(true)
|
|
||||||
setIsLoading(false)
|
|
||||||
if ((error as Error).name !== 'AbortError') {
|
|
||||||
console.error((error as Error).message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
init()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
abortController.abort()
|
|
||||||
setData(undefined)
|
|
||||||
setIsLoading(undefined)
|
|
||||||
setIsError(undefined)
|
|
||||||
}
|
|
||||||
}, [address, chain?.id])
|
|
||||||
|
|
||||||
return { data, isLoading, isError }
|
|
||||||
}
|
|
34
src/features/Web3/hooks/useTokensStore.tsx
Normal file
34
src/features/Web3/hooks/useTokensStore.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { useStore } from '@nanostores/react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { createTokensStore } from '../stores/tokens'
|
||||||
|
|
||||||
|
//
|
||||||
|
// Wrapper around $tokens store.
|
||||||
|
//
|
||||||
|
// Workaround to dynamically create the store based on
|
||||||
|
// user address and chainId.
|
||||||
|
//
|
||||||
|
export function useTokensStore({
|
||||||
|
address,
|
||||||
|
chainId
|
||||||
|
}: {
|
||||||
|
address: `0x${string}` | undefined
|
||||||
|
chainId: number | undefined
|
||||||
|
}) {
|
||||||
|
const store = createTokensStore(address, chainId)
|
||||||
|
|
||||||
|
const [tokensFetchStore] = useState(store)
|
||||||
|
const $tokens = useStore(tokensFetchStore)
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (!chainId || !address) return
|
||||||
|
|
||||||
|
// console.log(tokensFetchStore.key)
|
||||||
|
|
||||||
|
// const newUrl = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chainId}`
|
||||||
|
// tokensFetchStore.setKey(tokensFetchStore.key, newUrl)
|
||||||
|
// console.log(tokensFetchStore.key)
|
||||||
|
// }, [chainId, address])
|
||||||
|
|
||||||
|
return $tokens
|
||||||
|
}
|
12
src/features/Web3/stores/tokens/fetcher.ts
Normal file
12
src/features/Web3/stores/tokens/fetcher.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { nanoquery } from '@nanostores/query'
|
||||||
|
|
||||||
|
export const [
|
||||||
|
createFetcherStore,
|
||||||
|
createMutatorStore,
|
||||||
|
{ invalidateKeys, mutateCache }
|
||||||
|
] = nanoquery({
|
||||||
|
fetcher: async (...keys: (string | number)[]) => {
|
||||||
|
const response = await fetch(keys.join(''))
|
||||||
|
return await response.json()
|
||||||
|
}
|
||||||
|
})
|
3
src/features/Web3/stores/tokens/index.ts
Normal file
3
src/features/Web3/stores/tokens/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './types'
|
||||||
|
export * from './tokens'
|
||||||
|
export * from './selectedToken'
|
@ -1,10 +1,10 @@
|
|||||||
import { action } from 'nanostores'
|
import { action } from 'nanostores'
|
||||||
import { persistentAtom } from '@nanostores/persistent'
|
import { persistentAtom } from '@nanostores/persistent'
|
||||||
import type { GetToken } from '../hooks/useTokens'
|
import type { GetToken } from './types'
|
||||||
|
|
||||||
export const $selectedToken = persistentAtom<GetToken>(
|
export const $selectedToken = persistentAtom<GetToken | undefined>(
|
||||||
'@kremalicious/selectedToken',
|
'@kremalicious/selectedToken',
|
||||||
{ address: '0x0' } as any,
|
undefined,
|
||||||
{
|
{
|
||||||
encode: JSON.stringify,
|
encode: JSON.stringify,
|
||||||
decode: JSON.parse
|
decode: JSON.parse
|
12
src/features/Web3/stores/tokens/tokens.ts
Normal file
12
src/features/Web3/stores/tokens/tokens.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createFetcherStore } from './fetcher'
|
||||||
|
import type { GetToken } from './types'
|
||||||
|
|
||||||
|
export const createTokensStore = (
|
||||||
|
address: `0x${string}` | undefined,
|
||||||
|
chainId: number | undefined
|
||||||
|
) => {
|
||||||
|
const url = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chainId}`
|
||||||
|
const fetcherStore = createFetcherStore<GetToken[]>(url)
|
||||||
|
|
||||||
|
return fetcherStore
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user