mirror of
https://github.com/kremalicious/blog.git
synced 2024-12-22 17:23:50 +01:00
new send/preview flow
This commit is contained in:
parent
52dba93d71
commit
498e5a1730
@ -8,7 +8,7 @@ const year = new Date().getFullYear()
|
|||||||
const { name, url, github } = config.author
|
const { name, url, github } = config.author
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer role="contentinfo" class={styles.footer}>
|
<footer role="contentinfo" class={styles.footer} id="footer">
|
||||||
<Vcard />
|
<Vcard />
|
||||||
<section class={styles.copyright}>
|
<section class={styles.copyright}>
|
||||||
<p>
|
<p>
|
||||||
|
@ -6,7 +6,7 @@ import { Logo } from '@images/components'
|
|||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
---
|
---
|
||||||
|
|
||||||
<header class={styles.header} aria-label="Header">
|
<header class={styles.header} aria-label="Header" id="header">
|
||||||
<div class={styles.headerContent}>
|
<div class={styles.headerContent}>
|
||||||
<a href="/" class={styles.title}>
|
<a href="/" class={styles.title}>
|
||||||
<Logo class={styles.logo} viewBox="0 0 191 36" /> kremalicious
|
<Logo class={styles.logo} viewBox="0 0 191 36" /> kremalicious
|
||||||
|
16
src/components/Loader/Loader.module.css
Normal file
16
src/components/Loader/Loader.module.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.loader {
|
||||||
|
will-change: transform;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
width: 18px !important;
|
||||||
|
height: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
8
src/components/Loader/Loader.tsx
Normal file
8
src/components/Loader/Loader.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import styles from './Loader.module.css'
|
||||||
|
import { Loader as LoaderIcon } from '@images/components/react'
|
||||||
|
|
||||||
|
export function Loader() {
|
||||||
|
// TODO: fix React props for generated SVG components for class/className
|
||||||
|
//@ts-expect-error-next-line
|
||||||
|
return <LoaderIcon className={styles.loader} />
|
||||||
|
}
|
1
src/components/Loader/index.tsx
Normal file
1
src/components/Loader/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Loader'
|
@ -1,11 +1,9 @@
|
|||||||
.web3 {
|
.web3 {
|
||||||
margin-left: auto;
|
margin: calc(var(--spacer) / 2) auto calc(var(--spacer) / 4) auto;
|
||||||
margin-right: auto;
|
|
||||||
max-width: 25rem;
|
max-width: 25rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
min-height: 165px;
|
min-height: 165px;
|
||||||
margin-top: calc(var(--spacer) / 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rainbowkit button > div {
|
.rainbowkit button > div {
|
||||||
@ -56,11 +54,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.disclaimer {
|
.disclaimer {
|
||||||
|
color: var(--text-color-light);
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
margin-top: calc(var(--spacer) / 3);
|
margin-top: calc(var(--spacer) / 2);
|
||||||
margin-bottom: calc(var(--spacer) / 6);
|
margin-bottom: calc(var(--spacer) / 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disclaimer code {
|
||||||
|
background: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -5,34 +5,35 @@ import { ConnectButton } from '@rainbow-me/rainbowkit'
|
|||||||
import { InputGroup } from '../Input'
|
import { InputGroup } from '../Input'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $selectedToken } from '@features/Web3/stores/selectedToken'
|
import { $selectedToken, $isInitSend } from '@features/Web3/stores'
|
||||||
import siteConfig from '@config/blog.config'
|
import siteConfig from '@config/blog.config'
|
||||||
import { Send } from '../Send/Send'
|
import { Send } from '../Send'
|
||||||
|
|
||||||
export default function Web3Form(): ReactElement {
|
export default function Web3Form(): ReactElement {
|
||||||
const { address: account } = useAccount()
|
const { address: account } = useAccount()
|
||||||
const selectedToken = useStore($selectedToken)
|
const selectedToken = useStore($selectedToken)
|
||||||
|
const isInitSend = useStore($isInitSend)
|
||||||
|
|
||||||
const [amount, setAmount] = useState('')
|
const [amount, setAmount] = useState('')
|
||||||
const [debouncedAmount] = useDebounce(amount, 500)
|
const [debouncedAmount] = useDebounce(amount, 500)
|
||||||
const [initSend, setInitSend] = useState(false)
|
|
||||||
|
|
||||||
const isDisabled = !account
|
const isDisabled = !account
|
||||||
|
|
||||||
|
// reset amount whenever token changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedToken) return
|
if (!selectedToken) return
|
||||||
setAmount('')
|
setAmount('')
|
||||||
}, [selectedToken])
|
}, [selectedToken])
|
||||||
|
|
||||||
return initSend ? (
|
return isInitSend ? (
|
||||||
<Send amount={debouncedAmount} setInitSend={setInitSend} />
|
<Send amount={debouncedAmount} />
|
||||||
) : (
|
) : (
|
||||||
<form
|
<form
|
||||||
className={styles.web3}
|
className={styles.web3}
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (debouncedAmount !== '' || debouncedAmount === '0') return
|
if (debouncedAmount === '' || debouncedAmount === '0') return
|
||||||
setInitSend(true)
|
$isInitSend.set(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@ -42,7 +43,6 @@ export default function Web3Form(): ReactElement {
|
|||||||
<InputGroup
|
<InputGroup
|
||||||
amount={amount}
|
amount={amount}
|
||||||
setAmount={setAmount}
|
setAmount={setAmount}
|
||||||
setInitSend={setInitSend}
|
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
<div className={styles.disclaimer}>
|
<div className={styles.disclaimer}>
|
||||||
|
@ -64,12 +64,12 @@
|
|||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-color: var(--link-color-hover);
|
border-color: var(--link-color-hover);
|
||||||
padding: 0 calc(var(--spacer) / 4);
|
padding: 0 calc(var(--spacer) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 40rem) {
|
@media (min-width: 40rem) {
|
||||||
.submit {
|
.submit {
|
||||||
width: fit-content;
|
width: 115px;
|
||||||
border-top-right-radius: var(--border-radius);
|
border-top-right-radius: var(--border-radius);
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
|
@ -3,17 +3,16 @@ import Input from '@components/Input'
|
|||||||
import { Conversion } from '../Conversion'
|
import { Conversion } from '../Conversion'
|
||||||
import styles from './InputGroup.module.css'
|
import styles from './InputGroup.module.css'
|
||||||
import { TokenSelect } from '../TokenSelect'
|
import { TokenSelect } from '../TokenSelect'
|
||||||
|
import { $isInitSend } from '@features/Web3/stores'
|
||||||
|
|
||||||
export function InputGroup({
|
export function InputGroup({
|
||||||
amount,
|
amount,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
setAmount,
|
setAmount
|
||||||
setInitSend
|
|
||||||
}: {
|
}: {
|
||||||
amount: string
|
amount: string
|
||||||
isDisabled: boolean
|
isDisabled: boolean
|
||||||
setAmount: React.Dispatch<React.SetStateAction<string>>
|
setAmount: React.Dispatch<React.SetStateAction<string>>
|
||||||
setInitSend: React.Dispatch<React.SetStateAction<boolean>>
|
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -36,9 +35,9 @@ export function InputGroup({
|
|||||||
<button
|
<button
|
||||||
className={`${styles.submit} btn btn-primary`}
|
className={`${styles.submit} btn btn-primary`}
|
||||||
disabled={isDisabled || !amount}
|
disabled={isDisabled || !amount}
|
||||||
onClick={() => setInitSend(true)}
|
onClick={() => $isInitSend.set(true)}
|
||||||
>
|
>
|
||||||
Make it rain
|
Preview
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Conversion amount={amount} />
|
<Conversion amount={amount} />
|
||||||
|
24
src/features/Web3/components/Send/Send.module.css
Normal file
24
src/features/Web3/components/Send/Send.module.css
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
.send {
|
||||||
|
margin-top: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions button {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions button:first-child {
|
||||||
|
margin-right: var(--spacer);
|
||||||
|
width: 115px;
|
||||||
|
height: 50px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
@ -1,27 +1,27 @@
|
|||||||
import { useStore } from '@nanostores/react'
|
|
||||||
import { $selectedToken } from '@features/Web3/stores/selectedToken'
|
|
||||||
import { useNetwork, useEnsAddress } from 'wagmi'
|
|
||||||
import siteConfig from '@config/blog.config'
|
|
||||||
import { useState, type FormEvent, useEffect } from 'react'
|
import { useState, type FormEvent, useEffect } from 'react'
|
||||||
import { prepareTransaction } from './prepareTransaction'
|
import { useStore } from '@nanostores/react'
|
||||||
import { sendTransaction } from './sendTransaction'
|
import { useNetwork, useEnsAddress, useEnsName } from 'wagmi'
|
||||||
import type {
|
import type {
|
||||||
SendTransactionArgs,
|
SendTransactionArgs,
|
||||||
WriteContractPreparedArgs
|
WriteContractPreparedArgs
|
||||||
} from 'wagmi/actions'
|
} from 'wagmi/actions'
|
||||||
import { formatEther } from 'viem'
|
import { $selectedToken, $isInitSend } from '@features/Web3/stores'
|
||||||
|
import siteConfig from '@config/blog.config'
|
||||||
|
import { prepareTransaction, sendTransaction } from './actions'
|
||||||
|
import styles from './Send.module.css'
|
||||||
|
import { SendTable } from './SendTable'
|
||||||
|
import { Loader } from '@components/Loader'
|
||||||
|
|
||||||
export function Send({
|
export function Send({ amount }: { amount: string }) {
|
||||||
amount,
|
const { ens } = siteConfig.author.ether
|
||||||
setInitSend
|
|
||||||
}: {
|
|
||||||
amount: string
|
|
||||||
setInitSend: (initSend: boolean) => void
|
|
||||||
}) {
|
|
||||||
const { chain } = useNetwork()
|
const { chain } = useNetwork()
|
||||||
const selectedToken = useStore($selectedToken)
|
const selectedToken = useStore($selectedToken)
|
||||||
const { data: to } = useEnsAddress({
|
|
||||||
name: siteConfig.author.ether.ens,
|
// Always resolve to address from ENS name and vice versa
|
||||||
|
// so nobody has to trust my config values.
|
||||||
|
const { data: to } = useEnsAddress({ name: ens, chainId: 1 })
|
||||||
|
const { data: ensResolved } = useEnsName({
|
||||||
|
address: to as `0x${string}` | undefined,
|
||||||
chainId: 1
|
chainId: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ export function Send({
|
|||||||
SendTransactionArgs | WriteContractPreparedArgs
|
SendTransactionArgs | WriteContractPreparedArgs
|
||||||
>()
|
>()
|
||||||
const [txHash, setTxHash] = useState<string>()
|
const [txHash, setTxHash] = useState<string>()
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function init() {
|
async function init() {
|
||||||
@ -47,27 +48,47 @@ export function Send({
|
|||||||
|
|
||||||
async function handleSend(event: FormEvent<HTMLButtonElement>) {
|
async function handleSend(event: FormEvent<HTMLButtonElement>) {
|
||||||
event?.preventDefault()
|
event?.preventDefault()
|
||||||
const result = await sendTransaction(selectedToken, txConfig)
|
try {
|
||||||
setTxHash(result?.hash)
|
setIsLoading(true)
|
||||||
|
const result = await sendTransaction(selectedToken, txConfig)
|
||||||
|
setTxHash(result?.hash)
|
||||||
|
setIsLoading(false)
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error((error as Error).message)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const value =
|
|
||||||
(txConfig as SendTransactionArgs)?.value ||
|
|
||||||
(txConfig as WriteContractPreparedArgs)?.request?.args[1] ||
|
|
||||||
'0'
|
|
||||||
const displayAmountFromConfig = formatEther(value)
|
|
||||||
console.log(txHash)
|
console.log(txHash)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div className={styles.send}>
|
||||||
<p>You are about to send</p>
|
{/* <h5 className={styles.title}>You are sending</h5> */}
|
||||||
<p>
|
|
||||||
{displayAmountFromConfig} {selectedToken?.symbol} to <code>{to}</code>{' '}
|
<SendTable
|
||||||
on {chain?.name}
|
to={to}
|
||||||
</p>
|
ensResolved={ensResolved}
|
||||||
<button onClick={(e) => handleSend(e)}>Confirm</button>
|
txConfig={txConfig}
|
||||||
<button onClick={() => setInitSend(false)}>Cancel</button>
|
isDisabled={isLoading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<footer className={styles.actions}>
|
||||||
|
<button
|
||||||
|
onClick={(e) => handleSend(e)}
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{isLoading ? <Loader /> : 'Make it rain'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => $isInitSend.set(false)}
|
||||||
|
className="link"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
40
src/features/Web3/components/Send/SendTable.module.css
Normal file
40
src/features/Web3/components/Send/SendTable.module.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
.amount,
|
||||||
|
.network,
|
||||||
|
.to {
|
||||||
|
/* font-weight: var(--font-weight-bold); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.to {
|
||||||
|
display: block;
|
||||||
|
word-break: break-all;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.to:last-child {
|
||||||
|
padding-left: 14px;
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
}
|
||||||
|
|
||||||
|
.to:last-child::first-letter {
|
||||||
|
margin-left: -14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
/* max-width: 386px; */
|
||||||
|
margin-bottom: calc(var(--spacer) / 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
table[aria-disabled='true'] {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td {
|
||||||
|
padding: calc(var(--spacer) / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--text-color-light);
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
66
src/features/Web3/components/Send/SendTable.tsx
Normal file
66
src/features/Web3/components/Send/SendTable.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { formatEther } from 'viem'
|
||||||
|
import { useNetwork } from 'wagmi'
|
||||||
|
import type {
|
||||||
|
SendTransactionArgs,
|
||||||
|
WriteContractPreparedArgs
|
||||||
|
} from 'wagmi/actions'
|
||||||
|
import styles from './SendTable.module.css'
|
||||||
|
import { useStore } from '@nanostores/react'
|
||||||
|
import { $selectedToken } from '@features/Web3/stores'
|
||||||
|
|
||||||
|
export function SendTable({
|
||||||
|
to,
|
||||||
|
ensResolved,
|
||||||
|
txConfig,
|
||||||
|
isDisabled
|
||||||
|
}: {
|
||||||
|
to: `0x${string}` | null | undefined
|
||||||
|
ensResolved: string | null | undefined
|
||||||
|
txConfig: SendTransactionArgs | WriteContractPreparedArgs | undefined
|
||||||
|
isDisabled: boolean
|
||||||
|
}) {
|
||||||
|
const { chain } = useNetwork()
|
||||||
|
const selectedToken = useStore($selectedToken)
|
||||||
|
|
||||||
|
// Derive display values in preview from actual tx config
|
||||||
|
// instead from our form stores
|
||||||
|
const value =
|
||||||
|
(txConfig as SendTransactionArgs)?.value ||
|
||||||
|
(txConfig as WriteContractPreparedArgs)?.request?.args?.[1] ||
|
||||||
|
'0'
|
||||||
|
const displayAmountFromConfig = formatEther(value as bigint)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className={styles.table} aria-disabled={isDisabled}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className={styles.label}>Sending</td>
|
||||||
|
<td>
|
||||||
|
<span className={styles.amount}>
|
||||||
|
{displayAmountFromConfig} {selectedToken?.symbol}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className={styles.label}>on</td>
|
||||||
|
<td>
|
||||||
|
<span className={styles.network}>{chain?.name}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/* <tr>
|
||||||
|
<td>From</td>
|
||||||
|
<td>
|
||||||
|
<code className={styles.from}>{from}</code>
|
||||||
|
</td>
|
||||||
|
</tr> */}
|
||||||
|
<tr>
|
||||||
|
<td className={styles.label}>to</td>
|
||||||
|
<td title={`${ensResolved} successfully resolved to ${to}`}>
|
||||||
|
<code className={styles.to}>{ensResolved}</code>
|
||||||
|
<code className={styles.to}>{`→ ${to}`}</code>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
2
src/features/Web3/components/Send/actions/index.ts
Normal file
2
src/features/Web3/components/Send/actions/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './prepareTransaction'
|
||||||
|
export * from './sendTransaction'
|
@ -0,0 +1,34 @@
|
|||||||
|
import type { GetToken } from '@features/Web3/stores/tokens'
|
||||||
|
import { parseEther, parseUnits } from 'viem'
|
||||||
|
import {
|
||||||
|
prepareSendTransaction,
|
||||||
|
prepareWriteContract,
|
||||||
|
type SendTransactionArgs,
|
||||||
|
type WriteContractPreparedArgs
|
||||||
|
} from 'wagmi/actions'
|
||||||
|
import { abiErc20Transfer } from '../abiErc20Transfer'
|
||||||
|
|
||||||
|
export async function prepareTransaction(
|
||||||
|
selectedToken: GetToken | undefined,
|
||||||
|
amount: string | undefined,
|
||||||
|
to: `0x${string}` | null | undefined,
|
||||||
|
chainId: number | undefined
|
||||||
|
) {
|
||||||
|
if (!chainId || !to || !amount || !selectedToken || !selectedToken?.address)
|
||||||
|
return
|
||||||
|
|
||||||
|
const isNative = selectedToken.address === '0x0'
|
||||||
|
const setupNative = { chainId, to, value: parseEther(amount) }
|
||||||
|
const setupErc20 = {
|
||||||
|
address: selectedToken.address,
|
||||||
|
abi: abiErc20Transfer,
|
||||||
|
functionName: 'transfer',
|
||||||
|
args: [to, parseUnits(amount, selectedToken.decimals || 18)]
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = isNative
|
||||||
|
? ((await prepareSendTransaction(setupNative)) as SendTransactionArgs)
|
||||||
|
: ((await prepareWriteContract(setupErc20)) as WriteContractPreparedArgs)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
@ -1,35 +0,0 @@
|
|||||||
import type { GetToken } from '@features/Web3/stores/tokens'
|
|
||||||
import { parseEther, parseUnits } from 'viem'
|
|
||||||
import {
|
|
||||||
prepareSendTransaction,
|
|
||||||
prepareWriteContract,
|
|
||||||
type SendTransactionArgs,
|
|
||||||
type WriteContractPreparedArgs
|
|
||||||
} from 'wagmi/actions'
|
|
||||||
import { abiErc20Transfer } from './abiErc20Transfer'
|
|
||||||
|
|
||||||
export async function prepareTransaction(
|
|
||||||
selectedToken: GetToken | undefined,
|
|
||||||
amount: string | undefined,
|
|
||||||
to: `0x${string}` | null | undefined,
|
|
||||||
chainId: number | undefined
|
|
||||||
) {
|
|
||||||
if (!chainId || !to || !amount || !selectedToken) return
|
|
||||||
|
|
||||||
const isNative = selectedToken?.address === '0x0'
|
|
||||||
|
|
||||||
const config = isNative
|
|
||||||
? ((await prepareSendTransaction({
|
|
||||||
chainId,
|
|
||||||
to,
|
|
||||||
value: parseEther(amount)
|
|
||||||
})) as SendTransactionArgs)
|
|
||||||
: ((await prepareWriteContract({
|
|
||||||
address: selectedToken?.address,
|
|
||||||
abi: abiErc20Transfer,
|
|
||||||
functionName: 'transfer',
|
|
||||||
args: [to, parseUnits(amount, selectedToken?.decimals || 18)]
|
|
||||||
})) as WriteContractPreparedArgs)
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
export type SendFormData = {
|
|
||||||
data: { hash: `0x${string}` }
|
|
||||||
send: () => Promise<void>
|
|
||||||
isLoading: boolean
|
|
||||||
isSuccess: boolean
|
|
||||||
isError: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
3
src/features/Web3/stores/index.ts
Normal file
3
src/features/Web3/stores/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './tokens'
|
||||||
|
export * from './selectedToken'
|
||||||
|
export * from './isInitSend'
|
3
src/features/Web3/stores/isInitSend.ts
Normal file
3
src/features/Web3/stores/isInitSend.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { atom } from 'nanostores'
|
||||||
|
|
||||||
|
export const $isInitSend = atom<boolean>(false)
|
@ -67,6 +67,35 @@ import CodeCopy from '@components/CopyCode.astro'
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
html.isInitSend #header,
|
||||||
|
html.isInitSend #footer,
|
||||||
|
html.isInitSend section:not(.isInitSend),
|
||||||
|
html.isInitSend h2,
|
||||||
|
html.isInitSend h3 {
|
||||||
|
opacity: 0.1;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.isInitSend #web3 {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { $isInitSend } from '@features/Web3/stores'
|
||||||
|
|
||||||
|
$isInitSend.subscribe((value) => {
|
||||||
|
const html = document.querySelector('html')
|
||||||
|
|
||||||
|
value
|
||||||
|
? html?.classList.add('isInitSend')
|
||||||
|
: html?.classList.remove('isInitSend')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<LayoutBase title="Say Thanks" pageTitle="Say Thanks">
|
<LayoutBase title="Say Thanks" pageTitle="Say Thanks">
|
||||||
<!-- <BackButton /> -->
|
<!-- <BackButton /> -->
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
@ -78,7 +107,7 @@ import CodeCopy from '@components/CopyCode.astro'
|
|||||||
can 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" id="web3">
|
||||||
<h4 class="titleCoin"><Wallet /> Web3 Wallet</h4>
|
<h4 class="titleCoin"><Wallet /> Web3 Wallet</h4>
|
||||||
<Web3 client:load />
|
<Web3 client:load />
|
||||||
</section>
|
</section>
|
||||||
@ -91,12 +120,27 @@ import CodeCopy from '@components/CopyCode.astro'
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="subTitle">Sponsor</h3>
|
<h3 class="subTitle">Sponsor</h3>
|
||||||
<section class="section">You can also sponsor me on GitHub.</section>
|
<section class="section">
|
||||||
<a href="https://github.com/sponsors/kremalicious/">
|
<p>You can also sponsor me on GitHub.</p>
|
||||||
<img
|
<a href="https://github.com/sponsors/kremalicious/">
|
||||||
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"
|
<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"
|
||||||
</a>
|
/>
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<h3 class="subTitle">Hire Me</h3>
|
||||||
|
<section class="section">
|
||||||
|
<p>
|
||||||
|
Available for contract work to solve your design, front-end, and web3
|
||||||
|
problems.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Get in touch on <a href="https://matthiaskretschmann.com"
|
||||||
|
>my portfolio</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LayoutBase>
|
</LayoutBase>
|
||||||
|
@ -33,9 +33,11 @@ a.btn {
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* // Disabled State */
|
/* Disabled State */
|
||||||
.btn.disabled,
|
.btn.disabled,
|
||||||
.btn[disabled] {
|
.btn[disabled],
|
||||||
|
button:disabled,
|
||||||
|
.btn:disabled {
|
||||||
/* TODO: cursor & pointer values can't be used together */
|
/* TODO: cursor & pointer values can't be used together */
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -319,7 +319,6 @@ table {
|
|||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
th,
|
th,
|
||||||
@ -331,6 +330,10 @@ td {
|
|||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Selection
|
/* Selection
|
||||||
///////////////////////////////////// */
|
///////////////////////////////////// */
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user