Skip to content

Commit

Permalink
feat: desaturate unapproved input token (#289)
Browse files Browse the repository at this point in the history
* refactor: clearer quote var

* feat: add RouterPreference.SKIP

* refactor: clearer input/output convention

* fix: immediately update ux for new trades

* Revert "refactor: clearer input/output convention"

This reverts commit 0c9cf78.

* feat: rm unused auto-switcher

* chore: rm unused approved

* feat: desaturate unapproved token img

Co-authored-by: Kristie Huang <kristiehuang01@gmail.com>

* refactor: move swap approval to info state

* feat: desaturate unapproved input token

Co-authored-by: Kristie Huang <kristiehuang01@gmail.com>
  • Loading branch information
zzmp and kristiehuang authored Oct 24, 2022
1 parent c379661 commit be77a8d
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 140 deletions.
24 changes: 18 additions & 6 deletions src/components/Swap/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { TextButton } from 'components/Button'
import { loadingTransitionCss } from 'css/loading'
import { useIsSwapFieldIndependent, useSwapAmount, useSwapCurrency, useSwapInfo } from 'hooks/swap'
import { SwapApprovalState } from 'hooks/swap/useSwapApproval'
import { useIsWrap } from 'hooks/swap/useWrapCallback'
import { usePrefetchCurrencyColor } from 'hooks/useCurrencyColor'
import { PriceImpact } from 'hooks/usePriceImpact'
Expand All @@ -26,7 +27,7 @@ export const Balance = styled(ThemedText.Body2)`
transition: color 0.25s ease-in-out;
`

const InputColumn = styled(Column)<{ approved?: boolean; disableHover?: boolean }>`
const InputColumn = styled(Column)<{ disableHover?: boolean }>`
background-color: ${({ theme }) => theme.module};
border-radius: ${({ theme }) => theme.borderRadius - 0.25}em;
margin-bottom: 4px;
Expand Down Expand Up @@ -81,16 +82,18 @@ export function useFormattedFieldAmount({

interface FieldWrapperProps {
field: Field
impact?: PriceImpact
maxAmount?: string
isSufficientBalance?: boolean
approved?: boolean
impact?: PriceImpact
}

export function FieldWrapper({
field,
impact,
maxAmount,
isSufficientBalance,
approved,
impact,
className,
}: FieldWrapperProps & { className?: string }) {
const {
Expand Down Expand Up @@ -138,13 +141,14 @@ export function FieldWrapper({
<InputColumn disableHover={isDisabled || !currency} ref={wrapper} onClick={onClick} className={className}>
<TokenInput
ref={setInput}
field={field}
amount={formattedAmount}
currency={currency}
loading={isLoading}
approved={approved}
disabled={isDisabled}
field={field}
onChangeInput={updateAmount}
onChangeCurrency={updateCurrency}
loading={isLoading}
>
<ThemedText.Body2 color="secondary" userSelect>
<Row>
Expand Down Expand Up @@ -176,6 +180,7 @@ export function FieldWrapper({
export default function Input() {
const {
[Field.INPUT]: { balance, amount: currencyAmount },
approval: { state: approvalState },
} = useSwapInfo()

const isSufficientBalance = useMemo(() => {
Expand All @@ -192,5 +197,12 @@ export default function Input() {
return max.toExact()
}, [balance, currencyAmount])

return <FieldWrapper field={Field.INPUT} isSufficientBalance={isSufficientBalance} maxAmount={maxAmount} />
return (
<FieldWrapper
field={Field.INPUT}
maxAmount={maxAmount}
isSufficientBalance={isSufficientBalance}
approved={approvalState === SwapApprovalState.APPROVED}
/>
)
}
31 changes: 13 additions & 18 deletions src/components/Swap/SwapActionButton/ApproveButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core'
import ActionButton from 'components/ActionButton'
import EtherscanLink from 'components/EtherscanLink'
import { ApproveOrPermitState } from 'hooks/swap/useSwapApproval'
import { SwapApprovalState } from 'hooks/swap/useSwapApproval'
import { usePendingApproval } from 'hooks/transactions'
import { Spinner } from 'icons'
import { useCallback, useEffect, useMemo, useState } from 'react'
Expand All @@ -12,25 +11,21 @@ import { ApprovalTransactionInfo, TransactionType } from 'state/transactions'
import { Colors } from 'theme'
import { ExplorerDataType } from 'utils/getExplorerLink'

export function useIsPendingApproval(token?: Token): boolean {
return Boolean(usePendingApproval(token))
}

/**
* An approving ActionButton.
* Should only be rendered if a valid trade exists that is not yet approved.
*/
export default function ApproveButton({
color,
trade,
approvalState,
handleApproveOrPermit,
state,
approve,
onSubmit,
}: {
color: keyof Colors
trade?: InterfaceTrade
approvalState: ApproveOrPermitState
handleApproveOrPermit: () => Promise<{
state: SwapApprovalState
approve?: () => Promise<{
response: TransactionResponse
tokenAddress: string
spenderAddress: string
Expand All @@ -41,13 +36,13 @@ export default function ApproveButton({
const onApprove = useCallback(async () => {
setIsPending(true)
await onSubmit(async () => {
const info = await handleApproveOrPermit()
const info = await approve?.()
if (!info) return

return { type: TransactionType.APPROVAL, ...info }
})
setIsPending(false)
}, [handleApproveOrPermit, onSubmit])
}, [approve, onSubmit])

const currency = trade?.inputAmount?.currency
const symbol = currency?.symbol || ''
Expand All @@ -58,8 +53,8 @@ export default function ApproveButton({
const pendingApprovalHash = usePendingApproval(currency?.isToken ? currency : undefined)

const actionProps = useMemo(() => {
switch (approvalState) {
case ApproveOrPermitState.REQUIRES_APPROVAL:
switch (state) {
case SwapApprovalState.REQUIRES_APPROVAL:
if (isPending) {
return { message: <Trans>Approve in your wallet</Trans>, icon: Spinner }
}
Expand All @@ -68,7 +63,7 @@ export default function ApproveButton({
onClick: onApprove,
children: <Trans>Approve</Trans>,
}
case ApproveOrPermitState.REQUIRES_SIGNATURE:
case SwapApprovalState.REQUIRES_SIGNATURE:
if (isPending) {
return { message: <Trans>Allow in your wallet</Trans>, icon: Spinner }
}
Expand All @@ -77,7 +72,7 @@ export default function ApproveButton({
onClick: onApprove,
children: <Trans>Allow</Trans>,
}
case ApproveOrPermitState.PENDING_APPROVAL:
case SwapApprovalState.PENDING_APPROVAL:
return {
message: (
<EtherscanLink type={ExplorerDataType.TRANSACTION} data={pendingApprovalHash}>
Expand All @@ -86,12 +81,12 @@ export default function ApproveButton({
),
icon: Spinner,
}
case ApproveOrPermitState.PENDING_SIGNATURE:
case SwapApprovalState.PENDING_SIGNATURE:
return { message: <Trans>Allowance pending</Trans>, icon: Spinner }
default:
return
}
}, [approvalState, symbol, isPending, onApprove, pendingApprovalHash])
}, [isPending, onApprove, pendingApprovalHash, state, symbol])

return <ActionButton color={color} action={actionProps} />
}
12 changes: 6 additions & 6 deletions src/components/Swap/SwapActionButton/SwapButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { useSwapInfo } from 'hooks/swap'
import { ApproveOrPermitState, useApproveOrPermit } from 'hooks/swap/useSwapApproval'
import { SwapApprovalState } from 'hooks/swap/useSwapApproval'
import { useSwapCallback } from 'hooks/swap/useSwapCallback'
import { useConditionalHandler } from 'hooks/useConditionalHandler'
import { useSetOldestValidBlock } from 'hooks/useIsValidBlock'
Expand All @@ -16,7 +16,7 @@ import invariant from 'tiny-invariant'
import ActionButton from '../../ActionButton'
import Dialog from '../../Dialog'
import { SummaryDialog } from '../Summary'
import ApproveButton, { useIsPendingApproval } from './ApproveButton'
import ApproveButton from './ApproveButton'

/**
* A swapping ActionButton.
Expand All @@ -33,21 +33,21 @@ export default function SwapButton({
}) {
const { account, chainId } = useWeb3React()
const {
[Field.INPUT]: { usdc: inputUSDC, amount: inputCurrencyAmount },
[Field.INPUT]: { usdc: inputUSDC },
[Field.OUTPUT]: { usdc: outputUSDC },
trade: { trade, gasUseEstimateUSD },
slippage,
impact,
approval,
} = useSwapInfo()
const approval = useApproveOrPermit(trade, slippage.allowed, useIsPendingApproval, inputCurrencyAmount)
const deadline = useTransactionDeadline()
const feeOptions = useAtomValue(feeOptionsAtom)

const { callback: swapCallback } = useSwapCallback({
trade,
allowedSlippage: slippage.allowed,
recipientAddressOrName: account ?? null,
signatureData: approval.signatureData,
signatureData: approval?.signatureData,
deadline,
feeOptions,
})
Expand Down Expand Up @@ -91,7 +91,7 @@ export default function SwapButton({
setOpen(await onReviewSwapClick())
}, [onReviewSwapClick])

if (approval.approvalState !== ApproveOrPermitState.APPROVED && !disabled) {
if (approval.state !== SwapApprovalState.APPROVED && !disabled) {
return <ApproveButton color={color} onSubmit={onSubmit} trade={trade} {...approval} />
}

Expand Down
10 changes: 5 additions & 5 deletions src/components/Swap/TokenInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ export interface TokenInputHandle {
}

interface TokenInputProps {
field: Field
amount: string
currency?: Currency
approved?: boolean
loading?: boolean
disabled?: boolean
field: Field
max?: string
onChangeInput: (input: string) => void
onChangeCurrency: (currency: Currency) => void
loading?: boolean
}

export const TokenInput = forwardRef<TokenInputHandle, PropsWithChildren<TokenInputProps>>(function TokenInput(
{ amount, currency, disabled, field, onChangeInput, onChangeCurrency, loading, children, ...rest },
{ field, amount, currency, approved, loading, disabled, onChangeInput, onChangeCurrency, children, ...rest },
ref
) {
const input = useRef<HTMLInputElement>(null)
Expand Down Expand Up @@ -78,7 +78,7 @@ export const TokenInput = forwardRef<TokenInputHandle, PropsWithChildren<TokenIn
ref={input}
/>
</ThemedText.H2>
<TokenSelect value={currency} disabled={disabled} onSelect={onSelect} field={field} />
<TokenSelect field={field} value={currency} approved={approved} disabled={disabled} onSelect={onSelect} />
</TokenInputRow>
{children}
</TokenInputColumn>
Expand Down
10 changes: 8 additions & 2 deletions src/components/TokenSelect/TokenButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ const transitionCss = css`
transition: background-color 0.125s linear, border-color 0.125s linear, filter 0.125s linear, width 0.125s ease-out;
`

const StyledTokenButton = styled(Button)`
const StyledTokenButton = styled(Button)<{ approved?: boolean }>`
border-radius: ${({ theme }) => theme.borderRadius}em;
padding: 0.25em;
:enabled {
${({ transition }) => transition && transitionCss};
}
${TokenImg} {
filter: ${({ approved }) => approved === false && 'grayscale(1)'};
}
`

const TokenButtonRow = styled(Row)<{ empty: boolean }>`
Expand All @@ -37,11 +41,12 @@ const TokenButtonRow = styled(Row)<{ empty: boolean }>`

interface TokenButtonProps {
value?: Currency
approved?: boolean
disabled?: boolean
onClick: () => void
}

export default function TokenButton({ value, disabled, onClick }: TokenButtonProps) {
export default function TokenButton({ value, approved, disabled, onClick }: TokenButtonProps) {
const buttonBackgroundColor = value ? 'interactive' : 'accent'
const contentColor = buttonBackgroundColor === 'accent' ? 'onAccent' : 'currentColor'

Expand All @@ -65,6 +70,7 @@ export default function TokenButton({ value, disabled, onClick }: TokenButtonPro
<StyledTokenButton
onClick={onClick}
color={buttonBackgroundColor}
approved={approved}
disabled={disabled}
style={style}
transition={shouldTransition}
Expand Down
9 changes: 5 additions & 4 deletions src/components/TokenSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,14 @@ export function TokenSelectDialog({ value, onSelect, onClose }: TokenSelectDialo
}

interface TokenSelectProps {
disabled?: boolean
field: Field
onSelect: (value: Currency) => void
value?: Currency
approved?: boolean
disabled?: boolean
onSelect: (value: Currency) => void
}

export default memo(function TokenSelect({ disabled, field, onSelect, value }: TokenSelectProps) {
export default memo(function TokenSelect({ field, value, approved, disabled, onSelect }: TokenSelectProps) {
usePrefetchBalances()

const [open, setOpen] = useState(false)
Expand All @@ -144,7 +145,7 @@ export default memo(function TokenSelect({ disabled, field, onSelect, value }: T
)
return (
<>
<TokenButton value={value} disabled={disabled} onClick={onOpen} />
<TokenButton value={value} approved={approved} disabled={disabled} onClick={onOpen} />
{open && <TokenSelectDialog value={value} onSelect={selectAndClose} onClose={() => setOpen(false)} />}
</>
)
Expand Down
Loading

1 comment on commit be77a8d

@vercel
Copy link

@vercel vercel bot commented on be77a8d Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

widgets – ./

widgets-uniswap.vercel.app
widgets-seven-tau.vercel.app
widgets-git-main-uniswap.vercel.app

Please sign in to comment.