Commit c560b943 authored by eddie's avatar eddie Committed by GitHub

feat: create feature flag for swap widget (#5909)

* feat: create feature flag for swap widget

* feat: add new flag to modal

* fix: missing defaultField usage
parent 93a4f002
...@@ -3,6 +3,7 @@ import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp' ...@@ -3,6 +3,7 @@ import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2' import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken' import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2' import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc' import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { useAtomValue, useUpdateAtom } from 'jotai/utils' import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react' import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
...@@ -229,6 +230,12 @@ export default function FeatureFlagModal() { ...@@ -229,6 +230,12 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.payWithAnyToken} featureFlag={FeatureFlag.payWithAnyToken}
label="Pay With Any Token" label="Pay With Any Token"
/> />
<FeatureFlagOption
variant={SwapWidgetVariant}
value={useSwapWidgetFlag()}
featureFlag={FeatureFlag.swapWidget}
label="Swap Widget"
/>
<FeatureFlagGroup name="Debug"> <FeatureFlagGroup name="Debug">
<FeatureFlagOption <FeatureFlagOption
variant={TraceJsonRpcVariant} variant={TraceJsonRpcVariant}
......
import { WidgetSkeleton } from 'components/Widget' import { WidgetSkeleton } from 'components/Widget'
import { WIDGET_WIDTH } from 'components/Widget' import { DEFAULT_WIDGET_WIDTH } from 'components/Widget'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
...@@ -44,7 +44,7 @@ export const RightPanel = styled.div` ...@@ -44,7 +44,7 @@ export const RightPanel = styled.div`
display: none; display: none;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
width: ${WIDGET_WIDTH}px; width: ${DEFAULT_WIDGET_WIDTH}px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) { @media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
display: flex; display: flex;
......
...@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro' ...@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import { Trace } from '@uniswap/analytics' import { Trace } from '@uniswap/analytics'
import { InterfacePageName } from '@uniswap/analytics-events' import { InterfacePageName } from '@uniswap/analytics-events'
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { Field } from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import CurrencyLogo from 'components/Logo/CurrencyLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo'
import { AboutSection } from 'components/Tokens/TokenDetails/About' import { AboutSection } from 'components/Tokens/TokenDetails/About'
...@@ -223,6 +224,7 @@ export default function TokenDetails({ ...@@ -223,6 +224,7 @@ export default function TokenDetails({
<RightPanel> <RightPanel>
<Widget <Widget
token={token ?? undefined} token={token ?? undefined}
defaultField={Field.OUTPUT}
onTokenChange={navigateToWidgetSelectedToken} onTokenChange={navigateToWidgetSelectedToken}
onReviewSwapClick={onReviewSwapClick} onReviewSwapClick={onReviewSwapClick}
/> />
......
...@@ -10,6 +10,7 @@ import { Currency, TradeType } from '@uniswap/sdk-core' ...@@ -10,6 +10,7 @@ import { Currency, TradeType } from '@uniswap/sdk-core'
import { import {
AddEthereumChainParameter, AddEthereumChainParameter,
EMPTY_TOKEN_LIST, EMPTY_TOKEN_LIST,
Field,
OnReviewSwapClick, OnReviewSwapClick,
SwapWidget, SwapWidget,
SwapWidgetSkeleton, SwapWidgetSkeleton,
...@@ -36,7 +37,7 @@ import { useSyncWidgetSettings } from './settings' ...@@ -36,7 +37,7 @@ import { useSyncWidgetSettings } from './settings'
import { DARK_THEME, LIGHT_THEME } from './theme' import { DARK_THEME, LIGHT_THEME } from './theme'
import { useSyncWidgetTransactions } from './transactions' import { useSyncWidgetTransactions } from './transactions'
export const WIDGET_WIDTH = 360 export const DEFAULT_WIDGET_WIDTH = 360
const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/' const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
...@@ -46,15 +47,23 @@ function useWidgetTheme() { ...@@ -46,15 +47,23 @@ function useWidgetTheme() {
interface WidgetProps { interface WidgetProps {
token?: Currency token?: Currency
width?: number | string
defaultField: Field
onTokenChange?: (token: Currency) => void onTokenChange?: (token: Currency) => void
onReviewSwapClick?: OnReviewSwapClick onReviewSwapClick?: OnReviewSwapClick
} }
export default function Widget({ token, onTokenChange, onReviewSwapClick }: WidgetProps) { export default function Widget({
token,
width = DEFAULT_WIDGET_WIDTH,
defaultField,
onTokenChange,
onReviewSwapClick,
}: WidgetProps) {
const { connector, provider } = useWeb3React() const { connector, provider } = useWeb3React()
const locale = useActiveLocale() const locale = useActiveLocale()
const theme = useWidgetTheme() const theme = useWidgetTheme()
const { inputs, tokenSelector } = useSyncWidgetInputs({ token, onTokenChange }) const { inputs, tokenSelector } = useSyncWidgetInputs({ token, onTokenChange, defaultField })
const { settings } = useSyncWidgetSettings() const { settings } = useSyncWidgetSettings()
const { transactions } = useSyncWidgetTransactions() const { transactions } = useSyncWidgetTransactions()
...@@ -159,7 +168,7 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg ...@@ -159,7 +168,7 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg
routerUrl={WIDGET_ROUTER_URL} routerUrl={WIDGET_ROUTER_URL}
locale={locale} locale={locale}
theme={theme} theme={theme}
width={WIDGET_WIDTH} width={width}
// defaultChainId is excluded - it is always inferred from the passed provider // defaultChainId is excluded - it is always inferred from the passed provider
onConnectWalletClick={onConnectWalletClick} onConnectWalletClick={onConnectWalletClick}
provider={provider} provider={provider}
...@@ -182,5 +191,5 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg ...@@ -182,5 +191,5 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg
export function WidgetSkeleton() { export function WidgetSkeleton() {
const theme = useWidgetTheme() const theme = useWidgetTheme()
return <SwapWidgetSkeleton theme={theme} width={WIDGET_WIDTH} /> return <SwapWidgetSkeleton theme={theme} width={DEFAULT_WIDGET_WIDTH} />
} }
...@@ -21,26 +21,29 @@ function includesDefaultToken(tokens: SwapTokens) { ...@@ -21,26 +21,29 @@ function includesDefaultToken(tokens: SwapTokens) {
*/ */
export function useSyncWidgetInputs({ export function useSyncWidgetInputs({
token, token,
defaultField,
onTokenChange, onTokenChange,
}: { }: {
token?: Currency token?: Currency
defaultField: Field
onTokenChange?: (token: Currency) => void onTokenChange?: (token: Currency) => void
}) { }) {
const trace = useTrace({ section: InterfaceSectionName.WIDGET }) const trace = useTrace({ section: InterfaceSectionName.WIDGET })
const [type, setType] = useState<SwapValue['type']>(TradeType.EXACT_INPUT) const [type, setType] = useState<SwapValue['type']>(TradeType.EXACT_INPUT)
const [amount, setAmount] = useState<SwapValue['amount']>(EMPTY_AMOUNT) const [amount, setAmount] = useState<SwapValue['amount']>(EMPTY_AMOUNT)
const [tokens, setTokens] = useState<SwapTokens>({ [Field.OUTPUT]: token, default: token }) const [tokens, setTokens] = useState<SwapTokens>({ [defaultField]: token, default: token })
useEffect(() => { useEffect(() => {
setTokens((tokens) => { setTokens((tokens) => {
const update = { ...tokens, default: token } const update = { ...tokens, default: token }
if (!includesDefaultToken(update)) { if (!includesDefaultToken(update)) {
return { [Field.OUTPUT]: update.default, default: update.default } return { [defaultField]: update.default, default: update.default }
} }
return update return update
}) })
}, [token]) }, [defaultField, token])
const onAmountChange = useCallback( const onAmountChange = useCallback(
(field: Field, amount: string, origin?: 'max') => { (field: Field, amount: string, origin?: 'max') => {
......
...@@ -4,4 +4,5 @@ export enum FeatureFlag { ...@@ -4,4 +4,5 @@ export enum FeatureFlag {
permit2 = 'permit2', permit2 = 'permit2',
nftListV2 = 'nftListV2', nftListV2 = 'nftListV2',
payWithAnyToken = 'payWithAnyToken', payWithAnyToken = 'payWithAnyToken',
swapWidget = 'swapWidget',
} }
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useSwapWidgetFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.swapWidget, BaseVariant.Control)
}
export function useSwapWidgetEnabled(): boolean {
return useSwapWidgetFlag() === BaseVariant.Enabled
}
export { BaseVariant as SwapWidgetVariant }
...@@ -19,8 +19,10 @@ import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown' ...@@ -19,8 +19,10 @@ import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import Widget from 'components/Widget'
import { isSupportedChain } from 'constants/chains' import { isSupportedChain } from 'constants/chains'
import { usePermit2Enabled } from 'featureFlags/flags/permit2' import { usePermit2Enabled } from 'featureFlags/flags/permit2'
import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance' import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import { useSwapCallback } from 'hooks/useSwapCallback' import { useSwapCallback } from 'hooks/useSwapCallback'
import useTransactionDeadline from 'hooks/useTransactionDeadline' import useTransactionDeadline from 'hooks/useTransactionDeadline'
...@@ -157,6 +159,7 @@ export default function Swap({ className }: { className?: string }) { ...@@ -157,6 +159,7 @@ export default function Swap({ className }: { className?: string }) {
const loadedUrlParams = useDefaultsFromURLSearch() const loadedUrlParams = useDefaultsFromURLSearch()
const [newSwapQuoteNeedsLogging, setNewSwapQuoteNeedsLogging] = useState(true) const [newSwapQuoteNeedsLogging, setNewSwapQuoteNeedsLogging] = useState(true)
const [fetchingSwapQuoteStartTime, setFetchingSwapQuoteStartTime] = useState<Date | undefined>() const [fetchingSwapQuoteStartTime, setFetchingSwapQuoteStartTime] = useState<Date | undefined>()
const swapWidgetEnabled = useSwapWidgetEnabled()
// token warning stuff // token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [ const [loadedInputCurrency, loadedOutputCurrency] = [
...@@ -559,319 +562,329 @@ export default function Swap({ className }: { className?: string }) { ...@@ -559,319 +562,329 @@ export default function Swap({ className }: { className?: string }) {
showCancel={true} showCancel={true}
/> />
<PageWrapper> <PageWrapper>
<SwapWrapper className={className} id="swap-page"> {swapWidgetEnabled ? (
<SwapHeader allowedSlippage={allowedSlippage} /> <Widget token={loadedInputCurrency ?? undefined} width="100%" defaultField={Field.INPUT} />
<ConfirmSwapModal ) : (
isOpen={showConfirm} <SwapWrapper className={className} id="swap-page">
trade={trade} <SwapHeader allowedSlippage={allowedSlippage} />
originalTrade={tradeToConfirm} <ConfirmSwapModal
onAcceptChanges={handleAcceptChanges} isOpen={showConfirm}
attemptingTxn={attemptingTxn} trade={trade}
txHash={txHash} originalTrade={tradeToConfirm}
recipient={recipient} onAcceptChanges={handleAcceptChanges}
allowedSlippage={allowedSlippage} attemptingTxn={attemptingTxn}
onConfirm={handleSwap} txHash={txHash}
swapErrorMessage={swapErrorMessage} recipient={recipient}
onDismiss={handleConfirmDismiss} allowedSlippage={allowedSlippage}
swapQuoteReceivedDate={swapQuoteReceivedDate} onConfirm={handleSwap}
fiatValueInput={fiatValueTradeInput} swapErrorMessage={swapErrorMessage}
fiatValueOutput={fiatValueTradeOutput} onDismiss={handleConfirmDismiss}
/> swapQuoteReceivedDate={swapQuoteReceivedDate}
fiatValueInput={fiatValueTradeInput}
<div style={{ display: 'relative' }}> fiatValueOutput={fiatValueTradeOutput}
<SwapSection> />
<Trace section={InterfaceSectionName.CURRENCY_INPUT_PANEL}>
<SwapCurrencyInputPanel <div style={{ display: 'relative' }}>
label={ <SwapSection>
independentField === Field.OUTPUT && !showWrap ? ( <Trace section={InterfaceSectionName.CURRENCY_INPUT_PANEL}>
<Trans>From (at most)</Trans>
) : (
<Trans>From</Trans>
)
}
value={formattedAmounts[Field.INPUT]}
showMaxButton={showMaxButton}
currency={currencies[Field.INPUT] ?? null}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
fiatValue={fiatValueInput ?? undefined}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
showCommonBases={true}
id={InterfaceSectionName.CURRENCY_INPUT_PANEL}
loading={independentField === Field.OUTPUT && routeIsSyncing}
/>
</Trace>
</SwapSection>
<ArrowWrapper clickable={isSupportedChain(chainId)}>
<TraceEvent
events={[BrowserEvent.onClick]}
name={SwapEventName.SWAP_TOKENS_REVERSED}
element={InterfaceElementName.SWAP_TOKENS_REVERSE_ARROW_BUTTON}
>
<ArrowContainer
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
color={theme.textPrimary}
>
<ArrowDown
size="16"
color={
currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.textPrimary : theme.textTertiary
}
/>
</ArrowContainer>
</TraceEvent>
</ArrowWrapper>
</div>
<AutoColumn gap="md">
<div>
<OutputSwapSection showDetailsDropdown={showDetailsDropdown}>
<Trace section={InterfaceSectionName.CURRENCY_OUTPUT_PANEL}>
<SwapCurrencyInputPanel <SwapCurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={ label={
independentField === Field.INPUT && !showWrap ? <Trans>To (at least)</Trans> : <Trans>To</Trans> independentField === Field.OUTPUT && !showWrap ? (
<Trans>From (at most)</Trans>
) : (
<Trans>From</Trans>
)
} }
showMaxButton={false} value={formattedAmounts[Field.INPUT]}
hideBalance={false} showMaxButton={showMaxButton}
fiatValue={fiatValueOutput ?? undefined} currency={currencies[Field.INPUT] ?? null}
priceImpact={stablecoinPriceImpact} onUserInput={handleTypeInput}
currency={currencies[Field.OUTPUT] ?? null} onMax={handleMaxInput}
onCurrencySelect={handleOutputSelect} fiatValue={fiatValueInput ?? undefined}
otherCurrency={currencies[Field.INPUT]} onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
showCommonBases={true} showCommonBases={true}
id={InterfaceSectionName.CURRENCY_OUTPUT_PANEL} id={InterfaceSectionName.CURRENCY_INPUT_PANEL}
loading={independentField === Field.INPUT && routeIsSyncing} loading={independentField === Field.OUTPUT && routeIsSyncing}
/> />
</Trace> </Trace>
</SwapSection>
{recipient !== null && !showWrap ? ( <ArrowWrapper clickable={isSupportedChain(chainId)}>
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.textSecondary} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
<Trans>- Remove recipient</Trans>
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
</OutputSwapSection>
{showDetailsDropdown && (
<DetailsSwapSection>
<SwapDetailsDropdown
trade={trade}
syncing={routeIsSyncing}
loading={routeIsLoading}
allowedSlippage={allowedSlippage}
/>
</DetailsSwapSection>
)}
</div>
{showPriceImpactWarning && <PriceImpactWarning priceImpact={largerPriceImpact} />}
<div>
{swapIsUnsupported ? (
<ButtonPrimary disabled={true}>
<ThemedText.DeprecatedMain mb="4px">
<Trans>Unsupported Asset</Trans>
</ThemedText.DeprecatedMain>
</ButtonPrimary>
) : !account ? (
<TraceEvent <TraceEvent
events={[BrowserEvent.onClick]} events={[BrowserEvent.onClick]}
name={InterfaceEventName.CONNECT_WALLET_BUTTON_CLICKED} name={SwapEventName.SWAP_TOKENS_REVERSED}
properties={{ received_swap_quote: getIsValidSwapQuote(trade, tradeState, swapInputError) }} element={InterfaceElementName.SWAP_TOKENS_REVERSE_ARROW_BUTTON}
element={InterfaceElementName.CONNECT_WALLET_BUTTON}
> >
<ButtonLight onClick={toggleWalletModal} fontWeight={600}> <ArrowContainer
<Trans>Connect Wallet</Trans> onClick={() => {
</ButtonLight> setApprovalSubmitted(false) // reset 2 step UI for approvals
</TraceEvent> onSwitchTokens()
) : showWrap ? ( }}
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap} fontWeight={600}> color={theme.textPrimary}
{wrapInputError ? ( >
<WrapErrorText wrapInputError={wrapInputError} /> <ArrowDown
) : wrapType === WrapType.WRAP ? ( size="16"
<Trans>Wrap</Trans> color={
) : wrapType === WrapType.UNWRAP ? ( currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.textPrimary : theme.textTertiary
<Trans>Unwrap</Trans>
) : null}
</ButtonPrimary>
) : routeNotFound && userHasSpecifiedInputOutput && !routeIsLoading && !routeIsSyncing ? (
<GrayCard style={{ textAlign: 'center' }}>
<ThemedText.DeprecatedMain mb="4px">
<Trans>Insufficient liquidity for this trade.</Trans>
</ThemedText.DeprecatedMain>
</GrayCard>
) : showApproveFlow ? (
<AutoRow style={{ flexWrap: 'nowrap', width: '100%' }}>
<AutoColumn style={{ width: '100%' }} gap="12px">
<ButtonConfirmed
fontWeight={600}
onClick={handleApprove}
disabled={approveTokenButtonDisabled}
width="100%"
altDisabledStyle={approvalState === ApprovalState.PENDING} // show solid button while waiting
confirmed={
approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED
} }
> />
<AutoRow justify="space-between" style={{ flexWrap: 'nowrap' }} height="20px"> </ArrowContainer>
{/* we need to shorten this string on mobile */} </TraceEvent>
{approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED ? ( </ArrowWrapper>
<ThemedText.SubHeader width="100%" textAlign="center" color="textSecondary"> </div>
<Trans>You can now trade {currencies[Field.INPUT]?.symbol}</Trans> <AutoColumn gap="md">
</ThemedText.SubHeader> <div>
<OutputSwapSection showDetailsDropdown={showDetailsDropdown}>
<Trace section={InterfaceSectionName.CURRENCY_OUTPUT_PANEL}>
<SwapCurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap ? (
<Trans>To (at least)</Trans>
) : ( ) : (
<ThemedText.SubHeader width="100%" textAlign="center" color="white"> <Trans>To</Trans>
<Trans>Allow the Uniswap Protocol to use your {currencies[Field.INPUT]?.symbol}</Trans> )
</ThemedText.SubHeader> }
)} showMaxButton={false}
hideBalance={false}
{approvalPending || approvalState === ApprovalState.PENDING ? ( fiatValue={fiatValueOutput ?? undefined}
<Loader stroke={theme.white} /> priceImpact={stablecoinPriceImpact}
) : (approvalSubmitted && approvalState === ApprovalState.APPROVED) || currency={currencies[Field.OUTPUT] ?? null}
onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]}
showCommonBases={true}
id={InterfaceSectionName.CURRENCY_OUTPUT_PANEL}
loading={independentField === Field.INPUT && routeIsSyncing}
/>
</Trace>
{recipient !== null && !showWrap ? (
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.textSecondary} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
<Trans>- Remove recipient</Trans>
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
</OutputSwapSection>
{showDetailsDropdown && (
<DetailsSwapSection>
<SwapDetailsDropdown
trade={trade}
syncing={routeIsSyncing}
loading={routeIsLoading}
allowedSlippage={allowedSlippage}
/>
</DetailsSwapSection>
)}
</div>
{showPriceImpactWarning && <PriceImpactWarning priceImpact={largerPriceImpact} />}
<div>
{swapIsUnsupported ? (
<ButtonPrimary disabled={true}>
<ThemedText.DeprecatedMain mb="4px">
<Trans>Unsupported Asset</Trans>
</ThemedText.DeprecatedMain>
</ButtonPrimary>
) : !account ? (
<TraceEvent
events={[BrowserEvent.onClick]}
name={InterfaceEventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: getIsValidSwapQuote(trade, tradeState, swapInputError) }}
element={InterfaceElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={toggleWalletModal} fontWeight={600}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
</TraceEvent>
) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap} fontWeight={600}>
{wrapInputError ? (
<WrapErrorText wrapInputError={wrapInputError} />
) : wrapType === WrapType.WRAP ? (
<Trans>Wrap</Trans>
) : wrapType === WrapType.UNWRAP ? (
<Trans>Unwrap</Trans>
) : null}
</ButtonPrimary>
) : routeNotFound && userHasSpecifiedInputOutput && !routeIsLoading && !routeIsSyncing ? (
<GrayCard style={{ textAlign: 'center' }}>
<ThemedText.DeprecatedMain mb="4px">
<Trans>Insufficient liquidity for this trade.</Trans>
</ThemedText.DeprecatedMain>
</GrayCard>
) : showApproveFlow ? (
<AutoRow style={{ flexWrap: 'nowrap', width: '100%' }}>
<AutoColumn style={{ width: '100%' }} gap="12px">
<ButtonConfirmed
fontWeight={600}
onClick={handleApprove}
disabled={approveTokenButtonDisabled}
width="100%"
altDisabledStyle={approvalState === ApprovalState.PENDING} // show solid button while waiting
confirmed={
approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED
}
>
<AutoRow justify="space-between" style={{ flexWrap: 'nowrap' }} height="20px">
{/* we need to shorten this string on mobile */}
{approvalState === ApprovalState.APPROVED ||
signatureState === UseERC20PermitState.SIGNED ? ( signatureState === UseERC20PermitState.SIGNED ? (
<CheckCircle size="20" color={theme.accentSuccess} /> <ThemedText.SubHeader width="100%" textAlign="center" color="textSecondary">
) : ( <Trans>You can now trade {currencies[Field.INPUT]?.symbol}</Trans>
</ThemedText.SubHeader>
) : (
<ThemedText.SubHeader width="100%" textAlign="center" color="white">
<Trans>Allow the Uniswap Protocol to use your {currencies[Field.INPUT]?.symbol}</Trans>
</ThemedText.SubHeader>
)}
{approvalPending || approvalState === ApprovalState.PENDING ? (
<Loader stroke={theme.white} />
) : (approvalSubmitted && approvalState === ApprovalState.APPROVED) ||
signatureState === UseERC20PermitState.SIGNED ? (
<CheckCircle size="20" color={theme.accentSuccess} />
) : (
<MouseoverTooltip
text={
<Trans>
You must give the Uniswap smart contracts permission to use your{' '}
{currencies[Field.INPUT]?.symbol}. You only have to do this once per token.
</Trans>
}
>
<HelpCircle size="20" color={theme.white} style={{ marginLeft: '8px' }} />
</MouseoverTooltip>
)}
</AutoRow>
</ButtonConfirmed>
<ButtonError
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
width="100%"
id="swap-button"
disabled={
!isValid ||
routeIsSyncing ||
routeIsLoading ||
(approvalState !== ApprovalState.APPROVED &&
signatureState !== UseERC20PermitState.SIGNED) ||
priceImpactTooHigh
}
error={isValid && priceImpactSeverity > 2}
>
<Text fontSize={16} fontWeight={600}>
{priceImpactTooHigh ? (
<Trans>High Price Impact</Trans>
) : trade && priceImpactSeverity > 2 ? (
<Trans>Swap Anyway</Trans>
) : (
<Trans>Swap</Trans>
)}
</Text>
</ButtonError>
</AutoColumn>
</AutoRow>
) : isValid && allowance.state === AllowanceState.REQUIRED ? (
<ButtonPrimary
onClick={updateAllowance}
disabled={isAllowancePending || isApprovalLoading}
style={{ gap: 14 }}
>
{isAllowancePending ? (
<>
<Loader size="20px" />
<Trans>Approve in your wallet</Trans>
</>
) : isApprovalLoading ? (
<>
<Loader size="20px" />
<Trans>Approval pending</Trans>
</>
) : (
<>
<div style={{ height: 20 }}>
<MouseoverTooltip <MouseoverTooltip
text={ text={
<Trans> <Trans>
You must give the Uniswap smart contracts permission to use your{' '} Permission is required for Uniswap to swap each token. This will expire after one
{currencies[Field.INPUT]?.symbol}. You only have to do this once per token. month for your security.
</Trans> </Trans>
} }
> >
<HelpCircle size="20" color={theme.white} style={{ marginLeft: '8px' }} /> <Info size={20} />
</MouseoverTooltip> </MouseoverTooltip>
)} </div>
</AutoRow> <Trans>Approve use of {currencies[Field.INPUT]?.symbol}</Trans>
</ButtonConfirmed> </>
<ButtonError )}
onClick={() => { </ButtonPrimary>
if (isExpertMode) { ) : (
handleSwap() <ButtonError
} else { onClick={() => {
setSwapState({ if (isExpertMode) {
tradeToConfirm: trade, handleSwap()
attemptingTxn: false, } else {
swapErrorMessage: undefined, setSwapState({
showConfirm: true, tradeToConfirm: trade,
txHash: undefined, attemptingTxn: false,
}) swapErrorMessage: undefined,
} showConfirm: true,
}} txHash: undefined,
width="100%" })
id="swap-button"
disabled={
!isValid ||
routeIsSyncing ||
routeIsLoading ||
(approvalState !== ApprovalState.APPROVED && signatureState !== UseERC20PermitState.SIGNED) ||
priceImpactTooHigh
} }
error={isValid && priceImpactSeverity > 2} }}
> id="swap-button"
<Text fontSize={16} fontWeight={600}> disabled={
{priceImpactTooHigh ? ( !isValid ||
<Trans>High Price Impact</Trans> routeIsSyncing ||
) : trade && priceImpactSeverity > 2 ? ( routeIsLoading ||
<Trans>Swap Anyway</Trans> priceImpactTooHigh ||
) : ( (permit2Enabled ? allowance.state !== AllowanceState.ALLOWED : Boolean(swapCallbackError))
<Trans>Swap</Trans>
)}
</Text>
</ButtonError>
</AutoColumn>
</AutoRow>
) : isValid && allowance.state === AllowanceState.REQUIRED ? (
<ButtonPrimary
onClick={updateAllowance}
disabled={isAllowancePending || isApprovalLoading}
style={{ gap: 14 }}
>
{isAllowancePending ? (
<>
<Loader size="20px" />
<Trans>Approve in your wallet</Trans>
</>
) : isApprovalLoading ? (
<>
<Loader size="20px" />
<Trans>Approval pending</Trans>
</>
) : (
<>
<div style={{ height: 20 }}>
<MouseoverTooltip
text={
<Trans>
Permission is required for Uniswap to swap each token. This will expire after one month
for your security.
</Trans>
}
>
<Info size={20} />
</MouseoverTooltip>
</div>
<Trans>Approve use of {currencies[Field.INPUT]?.symbol}</Trans>
</>
)}
</ButtonPrimary>
) : (
<ButtonError
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
} }
}} error={
id="swap-button" isValid &&
disabled={ priceImpactSeverity > 2 &&
!isValid || (permit2Enabled ? allowance.state === AllowanceState.ALLOWED : !swapCallbackError)
routeIsSyncing || }
routeIsLoading || >
priceImpactTooHigh || <Text fontSize={20} fontWeight={600}>
(permit2Enabled ? allowance.state !== AllowanceState.ALLOWED : Boolean(swapCallbackError)) {swapInputError ? (
} swapInputError
error={ ) : routeIsSyncing || routeIsLoading ? (
isValid && <Trans>Swap</Trans>
priceImpactSeverity > 2 && ) : priceImpactTooHigh ? (
(permit2Enabled ? allowance.state === AllowanceState.ALLOWED : !swapCallbackError) <Trans>Price Impact Too High</Trans>
} ) : priceImpactSeverity > 2 ? (
> <Trans>Swap Anyway</Trans>
<Text fontSize={20} fontWeight={600}> ) : (
{swapInputError ? ( <Trans>Swap</Trans>
swapInputError )}
) : routeIsSyncing || routeIsLoading ? ( </Text>
<Trans>Swap</Trans> </ButtonError>
) : priceImpactTooHigh ? ( )}
<Trans>Price Impact Too High</Trans> {isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
) : priceImpactSeverity > 2 ? ( </div>
<Trans>Swap Anyway</Trans> </AutoColumn>
) : ( </SwapWrapper>
<Trans>Swap</Trans> )}
)}
</Text>
</ButtonError>
)}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</div>
</AutoColumn>
</SwapWrapper>
<NetworkAlert /> <NetworkAlert />
</PageWrapper> </PageWrapper>
<SwitchLocaleLink /> <SwitchLocaleLink />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment