mirror of
https://github.com/kremalicious/blog.git
synced 2024-11-26 11:49:04 +01:00
make all the little interactions to work reliably
This commit is contained in:
parent
e74c1d7cb5
commit
15b4cbd8ac
@ -9,8 +9,6 @@ export function Conversion({ amount }: { amount: string }): ReactElement {
|
|||||||
const [dollar, setDollar] = useState('0.00')
|
const [dollar, setDollar] = useState('0.00')
|
||||||
const [euro, setEuro] = useState('0.00')
|
const [euro, setEuro] = useState('0.00')
|
||||||
|
|
||||||
console.log(selectedToken?.price)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedToken?.price || !amount) {
|
if (!selectedToken?.price || !amount) {
|
||||||
setDollar('0.00')
|
setDollar('0.00')
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
max-width: 25rem;
|
max-width: 25rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
min-height: 165px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rainbowkit button > div {
|
.rainbowkit button > div {
|
||||||
@ -37,8 +39,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.disclaimer {
|
.disclaimer {
|
||||||
font-size: var(--font-size-mini);
|
font-size: var(--font-size-small);
|
||||||
margin-top: calc(var(--spacer) / 1.5);
|
margin-top: calc(var(--spacer) / 3);
|
||||||
margin-bottom: calc(var(--spacer) / 6);
|
margin-bottom: calc(var(--spacer) / 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import { useSend } from '../../hooks/useSend'
|
|||||||
import type { SendFormData } from './types'
|
import type { SendFormData } from './types'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $selectedToken } from '@features/Web3/stores/selectedToken'
|
import { $selectedToken } from '@features/Web3/stores/selectedToken'
|
||||||
|
import siteConfig from '@config/blog.config'
|
||||||
|
|
||||||
export default function Web3Form(): ReactElement {
|
export default function Web3Form(): ReactElement {
|
||||||
const { address: account } = useAccount()
|
const { address: account } = useAccount()
|
||||||
@ -38,24 +39,25 @@ export default function Web3Form(): ReactElement {
|
|||||||
await send()
|
await send()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles.rainbowkit}>
|
|
||||||
<ConnectButton chainStatus="full" showBalance={false} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{message && message.status !== 'error' ? (
|
{message && message.status !== 'error' ? (
|
||||||
<Alert message={message} transactionHash={data?.hash} />
|
<Alert message={message} transactionHash={data?.hash} />
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
|
<div className={styles.rainbowkit}>
|
||||||
|
<ConnectButton chainStatus="full" showBalance={false} />
|
||||||
|
</div>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
amount={amount}
|
amount={amount}
|
||||||
setAmount={setAmount}
|
setAmount={setAmount}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
<div className={styles.disclaimer}>
|
||||||
|
Sends tokens to my account{' '}
|
||||||
|
<code>{siteConfig.author.ether.ens}</code>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{message && message?.status === 'error' ? (
|
|
||||||
<Alert message={message} />
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{selectedToken?.address === '0x0' ? (
|
{selectedToken?.address === '0x0' ? (
|
||||||
<SendPrepareNative
|
<SendPrepareNative
|
||||||
amount={debouncedAmount}
|
amount={debouncedAmount}
|
||||||
@ -67,10 +69,6 @@ export default function Web3Form(): ReactElement {
|
|||||||
setSendFormData={setSendFormData}
|
setSendFormData={setSendFormData}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.disclaimer}>
|
|
||||||
Sends tokens to my account, suitable for any ERC-20 token.
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
animation: fadeIn 0.8s ease-out backwards;
|
animation: fadeIn 0.8s ease-out backwards;
|
||||||
margin-top: calc(var(--spacer) / 3);
|
margin-top: calc(var(--spacer) / 3);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
min-height: 54px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.token {
|
.token {
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
margin-right: calc(var(--spacer) / 4);
|
margin-right: calc(var(--spacer) / 4);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
background: var(--brand-light);
|
background: var(--brand-light);
|
||||||
overflow: hidden;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -36,6 +35,9 @@
|
|||||||
|
|
||||||
.TokenLogo img {
|
.TokenLogo img {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TokenName,
|
.TokenName,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
--gap: 5deg;
|
--gap: 5deg;
|
||||||
--color: var(--text-color);
|
--color: var(--text-color);
|
||||||
|
|
||||||
|
display: block;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
@ -3,15 +3,17 @@ import './TokenSelect.css'
|
|||||||
import { Token } from './Token'
|
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 { useTokens } from '@features/Web3/hooks/useTokens'
|
import { useFetchTokens } from '@features/Web3/hooks/useFetchTokens'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
|
import { $tokens } from '@features/Web3/stores/tokens'
|
||||||
import {
|
import {
|
||||||
$selectedToken,
|
$selectedToken,
|
||||||
$setSelectedToken
|
$setSelectedToken
|
||||||
} from '@features/Web3/stores/selectedToken'
|
} from '@features/Web3/stores/selectedToken'
|
||||||
|
|
||||||
export function TokenSelect() {
|
export function TokenSelect() {
|
||||||
const { data: tokens, isLoading } = useTokens()
|
const { isLoading } = useFetchTokens()
|
||||||
|
const tokens = useStore($tokens)
|
||||||
const selectedToken = useStore($selectedToken)
|
const selectedToken = useStore($selectedToken)
|
||||||
|
|
||||||
const items = tokens?.map((token) => (
|
const items = tokens?.map((token) => (
|
||||||
@ -24,16 +26,15 @@ export function TokenSelect() {
|
|||||||
$setSelectedToken(token)
|
$setSelectedToken(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return tokens ? (
|
||||||
<Select.Root
|
<Select.Root
|
||||||
defaultValue={selectedToken?.address}
|
defaultValue={selectedToken?.address}
|
||||||
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
|
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
|
||||||
disabled={!tokens || isLoading}
|
disabled={isLoading}
|
||||||
value={selectedToken?.address}
|
|
||||||
>
|
>
|
||||||
<Select.Trigger
|
<Select.Trigger
|
||||||
className="SelectTrigger"
|
className="SelectTrigger"
|
||||||
disabled={!tokens || isLoading}
|
disabled={isLoading}
|
||||||
aria-label="Token"
|
aria-label="Token"
|
||||||
>
|
>
|
||||||
{isLoading ? <TokenLoading /> : <Select.Value />}
|
{isLoading ? <TokenLoading /> : <Select.Value />}
|
||||||
@ -61,5 +62,11 @@ export function TokenSelect() {
|
|||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Portal>
|
</Select.Portal>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
)
|
) : isLoading ? (
|
||||||
|
<>
|
||||||
|
<div className="Token">
|
||||||
|
<TokenLoading />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null
|
||||||
}
|
}
|
||||||
|
1
src/features/Web3/hooks/useFetchTokens/index.ts
Normal file
1
src/features/Web3/hooks/useFetchTokens/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './useFetchTokens'
|
@ -1,21 +1,22 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import type { GetToken } from './types'
|
import type { GetToken } from '@features/Web3/stores/tokens'
|
||||||
import { useNetwork, useAccount } from 'wagmi'
|
import { useNetwork, useAccount } from 'wagmi'
|
||||||
import { fetcher } from '@stores/fetcher'
|
import { fetcher } from '@stores/fetcher'
|
||||||
|
import { $setTokens } from '@features/Web3/stores/tokens'
|
||||||
import { $setSelectedToken } from '@features/Web3/stores/selectedToken'
|
import { $setSelectedToken } from '@features/Web3/stores/selectedToken'
|
||||||
|
|
||||||
//
|
//
|
||||||
// Wrapper for fetching user tokens with swr.
|
// Wrapper for fetching user tokens with swr.
|
||||||
//
|
//
|
||||||
export function useTokens() {
|
export function useFetchTokens() {
|
||||||
const { chain } = useNetwork()
|
const { chain } = useNetwork()
|
||||||
const { address } = useAccount()
|
const { address } = useAccount()
|
||||||
|
|
||||||
const [url, setUrl] = useState<string | undefined>(undefined)
|
const [url, setUrl] = useState<string | undefined>()
|
||||||
|
|
||||||
const fetchResults = useSWR<GetToken[] | undefined>(url, fetcher)
|
const fetchResults = useSWR<GetToken[] | undefined>(url, fetcher)
|
||||||
const { data: tokens } = fetchResults
|
const { data } = fetchResults
|
||||||
|
|
||||||
// Set url only after we have all data loaded on client,
|
// Set url only after we have all data loaded on client,
|
||||||
// preventing initial fetch.
|
// preventing initial fetch.
|
||||||
@ -26,13 +27,18 @@ export function useTokens() {
|
|||||||
setUrl(url)
|
setUrl(url)
|
||||||
}, [address, chain?.id])
|
}, [address, chain?.id])
|
||||||
|
|
||||||
|
// Sync with $tokens store
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return
|
||||||
|
$setTokens(data)
|
||||||
|
}, [data])
|
||||||
|
|
||||||
// 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 (!tokens?.[0]?.chainId) return
|
if (!data?.[0]?.chainId) return
|
||||||
|
$setSelectedToken(data?.[0])
|
||||||
$setSelectedToken(tokens?.[0])
|
}, [data?.[0]?.chainId])
|
||||||
}, [tokens?.[0]?.chainId])
|
|
||||||
|
|
||||||
return fetchResults
|
return fetchResults
|
||||||
}
|
}
|
@ -1,2 +0,0 @@
|
|||||||
export * from './useTokens'
|
|
||||||
export * from './types'
|
|
@ -1,6 +1,6 @@
|
|||||||
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 './tokens'
|
||||||
|
|
||||||
export const $selectedToken = persistentAtom<GetToken | undefined>(
|
export const $selectedToken = persistentAtom<GetToken | undefined>(
|
||||||
'@kremalicious/selectedToken',
|
'@kremalicious/selectedToken',
|
||||||
|
2
src/features/Web3/stores/tokens/index.ts
Normal file
2
src/features/Web3/stores/tokens/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './tokens'
|
||||||
|
export * from './types'
|
13
src/features/Web3/stores/tokens/tokens.ts
Normal file
13
src/features/Web3/stores/tokens/tokens.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { action, atom } from 'nanostores'
|
||||||
|
import type { GetToken } from './types'
|
||||||
|
|
||||||
|
export const $tokens = atom<GetToken[] | undefined>()
|
||||||
|
|
||||||
|
export const $setTokens = action(
|
||||||
|
$tokens,
|
||||||
|
'setTokens',
|
||||||
|
(store, tokens: GetToken[]) => {
|
||||||
|
store.set(tokens)
|
||||||
|
return store.get()
|
||||||
|
}
|
||||||
|
)
|
@ -67,21 +67,20 @@ import CodeCopy from '@components/CopyCode.astro'
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<LayoutBase title="Say Thanks">
|
<LayoutBase title="Say Thanks" pageTitle="Say Thanks">
|
||||||
<!-- <BackButton /> -->
|
<!-- <BackButton /> -->
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h3 class="subTitle">Send Magic Internet Money</h3>
|
<h3 class="subTitle">Magic Internet Money</h3>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
If you like what I do and want to support me in a decentralized way,
|
If you like what I do and want to support me in a decentralized way, you
|
||||||
send me some Ether, ERC-20 token, or Bitcoin.
|
can send me some Ether, ERC-20 token, or Bitcoin.
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section highlight">
|
<section class="section highlight">
|
||||||
<h4 class="titleCoin"><Wallet /> Web3 Wallet</h4>
|
<h4 class="titleCoin"><Wallet /> Web3 Wallet</h4>
|
||||||
<Web3 client:load />
|
<Web3 client:load />
|
||||||
<CodeCopy address={config.author.ether.ens} />
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
@ -92,9 +91,7 @@ import CodeCopy from '@components/CopyCode.astro'
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="subTitle">Sponsor</h3>
|
<h3 class="subTitle">Sponsor</h3>
|
||||||
<section class="section">
|
<section class="section">You can also sponsor me on GitHub.</section>
|
||||||
If you want to support me the old way, you can sponsor me on GitHub.
|
|
||||||
</section>
|
|
||||||
<a href="https://github.com/sponsors/kremalicious/">
|
<a href="https://github.com/sponsors/kremalicious/">
|
||||||
<img
|
<img
|
||||||
src="https://img.shields.io/static/v1?label=Sponsor%20On%20GitHub&labelColor=%2343a699&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86&style=for-the-badge"
|
src="https://img.shields.io/static/v1?label=Sponsor%20On%20GitHub&labelColor=%2343a699&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86&style=for-the-badge"
|
||||||
|
Loading…
Reference in New Issue
Block a user