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'
|
||||
|
||||
describe('Alert', () => {
|
||||
it('renders without crashing', async () => {
|
||||
it('renders without crashing', () => {
|
||||
render(
|
||||
<Alert
|
||||
message={{ status: 'loading', text: 'Loading' }}
|
||||
|
@ -3,7 +3,7 @@ import { describe, it } from 'vitest'
|
||||
import { Conversion } from './Conversion'
|
||||
|
||||
describe('Conversion', () => {
|
||||
it('renders without crashing', async () => {
|
||||
render(<Conversion amount="1" symbol="ETH" />)
|
||||
it('renders without crashing', () => {
|
||||
render(<Conversion amount="1" />)
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, type ReactElement, useState } from 'react'
|
||||
import styles from './Conversion.module.css'
|
||||
import { $selectedToken } from '../../stores/selectedToken'
|
||||
import { $selectedToken } from '../../stores/tokens/selectedToken'
|
||||
import { useStore } from '@nanostores/react'
|
||||
|
||||
export function Conversion({ amount }: { amount: string }): ReactElement {
|
||||
|
@ -8,7 +8,7 @@ import styles from './index.module.css'
|
||||
import { SendNative, SendErc20 } from '../Send'
|
||||
import { useSend } from '../../hooks/useSend'
|
||||
import type { SendFormData } from './types'
|
||||
import { $selectedToken } from '../../stores/selectedToken'
|
||||
import { $selectedToken } from '../../stores/tokens/selectedToken'
|
||||
import { useStore } from '@nanostores/react'
|
||||
|
||||
export default function Web3Form(): ReactElement {
|
||||
|
@ -6,14 +6,7 @@ const setAmount = vi.fn()
|
||||
|
||||
describe('InputGroup', () => {
|
||||
it('renders without crashing', async () => {
|
||||
render(
|
||||
<InputGroup
|
||||
amount="1"
|
||||
setAmount={setAmount}
|
||||
isDisabled={false}
|
||||
symbol="ETH"
|
||||
/>
|
||||
)
|
||||
render(<InputGroup amount="1" setAmount={setAmount} isDisabled={false} />)
|
||||
|
||||
const input = await screen.findByRole('textbox')
|
||||
const button = await screen.findByRole('button')
|
||||
@ -23,14 +16,7 @@ describe('InputGroup', () => {
|
||||
})
|
||||
|
||||
it('renders disabled', async () => {
|
||||
render(
|
||||
<InputGroup
|
||||
amount="1"
|
||||
setAmount={setAmount}
|
||||
isDisabled={true}
|
||||
symbol="ETH"
|
||||
/>
|
||||
)
|
||||
render(<InputGroup amount="1" setAmount={setAmount} isDisabled={true} />)
|
||||
|
||||
const input = await screen.findByRole('textbox')
|
||||
expect(input).toBeDefined()
|
||||
|
@ -4,7 +4,7 @@ import siteConfig from '@config/blog.config'
|
||||
import { abiErc20Transfer } from './abiErc20Transfer'
|
||||
import { useEffect } from 'react'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { $selectedToken } from '@features/Web3/stores/selectedToken'
|
||||
import { $selectedToken } from '@features/Web3/stores/tokens/selectedToken'
|
||||
|
||||
export function SendErc20({
|
||||
amount,
|
||||
@ -16,7 +16,7 @@ export function SendErc20({
|
||||
const selectedToken = useStore($selectedToken)
|
||||
|
||||
const { config } = usePrepareContractWrite({
|
||||
address: selectedToken.address,
|
||||
address: selectedToken?.address,
|
||||
abi: abiErc20Transfer,
|
||||
functionName: 'transfer',
|
||||
args: [
|
||||
|
@ -3,7 +3,7 @@ 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 '../../hooks/useTokens'
|
||||
import type { GetToken } from '@features/Web3/stores/tokens'
|
||||
|
||||
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
token: GetToken | undefined
|
||||
|
@ -4,17 +4,21 @@ import { Token } from './Token'
|
||||
import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react'
|
||||
import { TokenLoading } from './TokenLoading'
|
||||
import { useEffect } from 'react'
|
||||
import { useTokens } from '../../hooks/useTokens'
|
||||
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'
|
||||
|
||||
export function TokenSelect() {
|
||||
const { data: tokens, isLoading: tokensIsLoading } = useTokens()
|
||||
const selectedToken = useStore($selectedToken)
|
||||
const { chain } = useNetwork()
|
||||
const { address } = useAccount()
|
||||
|
||||
const { data: tokens, loading } = useTokensStore({
|
||||
chainId: chain?.id,
|
||||
address
|
||||
})
|
||||
const selectedToken = useStore($selectedToken)
|
||||
|
||||
const items = tokens?.map((token) => (
|
||||
<Token key={token.address} token={token} />
|
||||
))
|
||||
@ -28,7 +32,7 @@ export function TokenSelect() {
|
||||
// Set default token data to first item,
|
||||
// which most of time is native token
|
||||
useEffect(() => {
|
||||
if (!chain?.id || !address || !tokens) return
|
||||
if (!tokens) return
|
||||
|
||||
if (!selectedToken || !selectedToken?.address)
|
||||
handleValueChange(tokens[0].address)
|
||||
@ -38,15 +42,15 @@ export function TokenSelect() {
|
||||
<Select.Root
|
||||
defaultValue={selectedToken?.address}
|
||||
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
|
||||
disabled={!tokens || tokensIsLoading}
|
||||
disabled={!tokens || loading}
|
||||
value={selectedToken?.address}
|
||||
>
|
||||
<Select.Trigger
|
||||
className="SelectTrigger"
|
||||
disabled={!tokens || tokensIsLoading}
|
||||
disabled={!tokens || loading}
|
||||
aria-label="Token"
|
||||
>
|
||||
{tokensIsLoading ? <TokenLoading /> : <Select.Value />}
|
||||
{loading ? <TokenLoading /> : <Select.Value />}
|
||||
<Select.Icon>
|
||||
<ChevronDown />
|
||||
</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 { 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',
|
||||
{ address: '0x0' } as any,
|
||||
undefined,
|
||||
{
|
||||
encode: JSON.stringify,
|
||||
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…
Reference in New Issue
Block a user