Skip to content

Commit

Permalink
Update transaction confirmation and fee option UIs
Browse files Browse the repository at this point in the history
  • Loading branch information
tolgahan-arikan committed Dec 17, 2024
1 parent b9e58b3 commit 46a4ad0
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 129 deletions.
76 changes: 40 additions & 36 deletions packages/wallet/src/shared/FeeOptionSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { Box, Button, Text } from '@0xsequence/design-system'
import { Box, Text, TokenImage } from '@0xsequence/design-system'
import { ZeroAddress, formatUnits, parseUnits } from 'ethers'
import React from 'react'

import { Alert, AlertProps } from './Alert'

interface FeeOptionToken {
export interface FeeOption {
token: FeeToken
to: string
value: string
gasLimit: number
}
export interface FeeToken {
chainId: number
name: string
symbol: string
decimals?: number
contractAddress: string | null
logoURL: string
contractAddress?: string
tokenID?: string
}

interface FeeOption {
token: FeeOptionToken
value: string
balance?: string
export interface FeeOptionBalance {
tokenName: string
decimals: number
balance: string
}

interface FeeOptionSelectorProps {
export interface FeeOptionSelectorProps {
txnFeeOptions: FeeOption[]
feeOptionBalances: { tokenName: string; decimals: number; balance: string }[]
feeOptionBalances: FeeOptionBalance[]
selectedFeeOptionAddress: string | undefined
setSelectedFeeOptionAddress: (address: string) => void
checkTokenBalancesForFeeOptions: () => void
isRefreshingBalance: boolean
}

const isBalanceSufficient = (balance: string, fee: string, decimals: number) => {
Expand All @@ -35,9 +43,7 @@ export const FeeOptionSelector: React.FC<FeeOptionSelectorProps> = ({
txnFeeOptions,
feeOptionBalances,
selectedFeeOptionAddress,
setSelectedFeeOptionAddress,
checkTokenBalancesForFeeOptions,
isRefreshingBalance
setSelectedFeeOptionAddress
}) => {
const [feeOptionAlert, setFeeOptionAlert] = React.useState<AlertProps | undefined>()

Expand All @@ -56,18 +62,19 @@ export const FeeOptionSelector: React.FC<FeeOptionSelectorProps> = ({
</Text>
<Box flexDirection="column" marginTop="2" gap="2">
{sortedOptions.map((option, index) => {
const isSelected = selectedFeeOptionAddress === (option.token.contractAddress ?? ZeroAddress)
const balance = feeOptionBalances.find(b => b.tokenName === option.token.name)
const isSufficient = isBalanceSufficient(balance?.balance || '0', option.value, option.token.decimals || 0)
return (
<Box
key={index}
padding="3"
paddingX="3"
paddingY="2"
borderRadius="md"
background={
selectedFeeOptionAddress === (option.token.contractAddress ?? ZeroAddress)
? 'gradientBackdrop'
: 'backgroundRaised'
}
borderColor={isSelected ? 'borderFocus' : 'transparent'}
borderWidth="thick"
borderStyle="solid"
background="backgroundRaised"
onClick={() => {
if (isSufficient) {
setSelectedFeeOptionAddress(option.token.contractAddress ?? ZeroAddress)
Expand All @@ -84,16 +91,19 @@ export const FeeOptionSelector: React.FC<FeeOptionSelectorProps> = ({
opacity={isSufficient ? '100' : '50'}
>
<Box flexDirection="row" justifyContent="space-between" alignItems="center">
<Box flexDirection="column">
<Text variant="small" color="text100" fontWeight="bold">
{option.token.name}
</Text>
<Text variant="xsmall" color="text80">
Fee:{' '}
{parseFloat(formatUnits(BigInt(option.value), option.token.decimals || 0)).toLocaleString(undefined, {
maximumFractionDigits: 6
})}
</Text>
<Box flexDirection="row" alignItems="center" gap="2">
<TokenImage src={option.token.logoURL} symbol={option.token.name} />
<Box flexDirection="column">
<Text variant="small" color="text100" fontWeight="bold">
{option.token.name}
</Text>
<Text variant="xsmall" color="text80">
Fee:{' '}
{parseFloat(formatUnits(BigInt(option.value), option.token.decimals || 0)).toLocaleString(undefined, {
maximumFractionDigits: 6
})}
</Text>
</Box>
</Box>
<Box flexDirection="column" alignItems="flex-end">
<Text variant="xsmall" color="text80">
Expand All @@ -112,12 +122,6 @@ export const FeeOptionSelector: React.FC<FeeOptionSelectorProps> = ({
})}
</Box>
<Box marginTop="3" alignItems="flex-end" justifyContent="center" flexDirection="column">
<Button
size="sm"
onClick={checkTokenBalancesForFeeOptions}
label={isRefreshingBalance ? 'Refreshing...' : 'Refresh Balance'}
disabled={isRefreshingBalance}
/>
{feeOptionAlert && (
<Box marginTop="3">
<Alert
Expand Down
132 changes: 39 additions & 93 deletions packages/wallet/src/shared/TransactionConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
import { Box, Button, ChevronRightIcon, Text, vars, Card, GradientAvatar, Spinner } from '@0xsequence/design-system'
import { useIndexerClient } from '@0xsequence/kit'
import React, { useState, useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import React, { useState } from 'react'
import { useAccount } from 'wagmi'

import { truncateAtMiddle } from '../utils'

import { FeeOptionSelector } from './FeeOptionSelector'
import { FeeOption, FeeOptionSelector, type FeeOptionBalance } from './FeeOptionSelector'
import { SendItemInfo } from './SendItemInfo'

interface FeeOptionToken {
name: string
decimals?: number
contractAddress: string | null
}

interface FeeOption {
token: FeeOptionToken
value: string
balance?: string
}

interface TransactionConfirmationProps {
// Display data
name: string
Expand All @@ -44,78 +33,14 @@ interface TransactionConfirmationProps {
onCancel: () => void
}

export const TransactionConfirmation = ({
name,
symbol,
imageUrl,
amount,
toAddress,
showSquareImage,
fiatValue,
chainId,
balance,
decimals,
feeOptions,
onSelectFeeOption,
isLoading,
onConfirm,
onCancel
}: TransactionConfirmationProps) => {
const [selectedFeeOptionAddress, setSelectedFeeOptionAddress] = useState<string>()
const [isRefreshingBalance, setIsRefreshingBalance] = useState(false)
const [feeOptionBalances, setFeeOptionBalances] = useState<{ tokenName: string; decimals: number; balance: string }[]>([])
const useFeeOptionBalances = (feeOptions: TransactionConfirmationProps['feeOptions'], chainId: number) => {
const { address: accountAddress } = useAccount()
const indexerClient = useIndexerClient(chainId)

useEffect(() => {
const fetchBalances = async () => {
if (!feeOptions?.options || !accountAddress || !indexerClient) return

try {
const nativeTokenBalance = await indexerClient.getEtherBalance({
accountAddress
})

const tokenBalances = await indexerClient.getTokenBalances({
accountAddress
})

const balances = feeOptions.options.map(option => {
if (option.token.contractAddress === null) {
return {
tokenName: option.token.name,
decimals: option.token.decimals || 0,
balance: nativeTokenBalance.balance.balanceWei
}
} else {
return {
tokenName: option.token.name,
decimals: option.token.decimals || 0,
balance:
tokenBalances.balances.find(b => b.contractAddress.toLowerCase() === option.token.contractAddress?.toLowerCase())
?.balance || '0'
}
}
})

setFeeOptionBalances(balances)
} catch (error) {
console.error('Error fetching fee option balances:', error)
}
}

fetchBalances()
}, [feeOptions, accountAddress, chainId, indexerClient])

const handleFeeOptionSelect = (address: string) => {
setSelectedFeeOptionAddress(address)
onSelectFeeOption?.(address)
}

const checkTokenBalancesForFeeOptions = async () => {
setIsRefreshingBalance(true)
try {
if (!feeOptions?.options || !accountAddress || !indexerClient) return
return useQuery({
queryKey: ['feeOptionBalances', chainId, accountAddress, feeOptions?.options?.length],
queryFn: async () => {
if (!feeOptions?.options || !accountAddress || !indexerClient) return []

const nativeTokenBalance = await indexerClient.getEtherBalance({
accountAddress
Expand All @@ -125,7 +50,7 @@ export const TransactionConfirmation = ({
accountAddress
})

const balances = feeOptions.options.map(option => {
return feeOptions.options.map(option => {
if (option.token.contractAddress === null) {
return {
tokenName: option.token.name,
Expand All @@ -142,13 +67,36 @@ export const TransactionConfirmation = ({
}
}
})
},
enabled: Boolean(feeOptions?.options && accountAddress && indexerClient),
refetchInterval: 10000,
staleTime: 10000
})
}

setFeeOptionBalances(balances)
} catch (error) {
console.error('Error refreshing fee option balances:', error)
} finally {
setIsRefreshingBalance(false)
}
export const TransactionConfirmation = ({
name,
symbol,
imageUrl,
amount,
toAddress,
showSquareImage,
fiatValue,
chainId,
balance,
decimals,
feeOptions,
onSelectFeeOption,
isLoading,
onConfirm,
onCancel
}: TransactionConfirmationProps) => {
const [selectedFeeOptionAddress, setSelectedFeeOptionAddress] = useState<string>()
const { data: feeOptionBalances = [] } = useFeeOptionBalances(feeOptions, chainId)

const handleFeeOptionSelect = (address: string) => {
setSelectedFeeOptionAddress(address)
onSelectFeeOption?.(address)
}

// If feeOptions exist and have options, a selection is required
Expand All @@ -159,7 +107,7 @@ export const TransactionConfirmation = ({
return (
<Box width="full" height="full" display="flex" alignItems="center" justifyContent="center" background="backgroundPrimary">
<Box gap="2" flexDirection="column" background="backgroundPrimary" width="full">
<Box background="backgroundSecondary" borderRadius="md" padding="4" gap="2" flexDirection="column">
<Box background="backgroundSecondary" borderRadius="md" padding="4" paddingBottom="3" gap="2" flexDirection="column">
<SendItemInfo
imageUrl={imageUrl}
showSquareImage={showSquareImage}
Expand Down Expand Up @@ -204,8 +152,6 @@ export const TransactionConfirmation = ({
feeOptionBalances={feeOptionBalances}
selectedFeeOptionAddress={selectedFeeOptionAddress}
setSelectedFeeOptionAddress={handleFeeOptionSelect}
checkTokenBalancesForFeeOptions={checkTokenBalancesForFeeOptions}
isRefreshingBalance={isRefreshingBalance}
/>
)}
</Box>
Expand Down

0 comments on commit 46a4ad0

Please sign in to comment.