feat: swap component refactor limits (#7588)

* feat: add limits tab, flag

* feat: add unit test

* fix: update snapshot
This commit is contained in:
eddie 2023-11-15 09:00:03 -08:00 committed by GitHub
parent ff6d1cc510
commit 52dc441e31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 3422 additions and 2876 deletions

@ -10,6 +10,7 @@ import { useInfoExploreFlag } from 'featureFlags/flags/infoExplore'
import { useInfoLiveViewsFlag } from 'featureFlags/flags/infoLiveViews' import { useInfoLiveViewsFlag } from 'featureFlags/flags/infoLiveViews'
import { useInfoPoolPageFlag } from 'featureFlags/flags/infoPoolPage' import { useInfoPoolPageFlag } from 'featureFlags/flags/infoPoolPage'
import { useInfoTDPFlag } from 'featureFlags/flags/infoTDP' import { useInfoTDPFlag } from 'featureFlags/flags/infoTDP'
import { useLimitsEnabledFlag } from 'featureFlags/flags/limits'
import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx' import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2' import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2'
import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet' import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet'
@ -274,6 +275,12 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.feesEnabled} featureFlag={FeatureFlag.feesEnabled}
label="Enable Swap Fees" label="Enable Swap Fees"
/> />
<FeatureFlagOption
variant={BaseVariant}
value={useLimitsEnabledFlag()}
featureFlag={FeatureFlag.limitsEnabled}
label="Enable Limits"
/>
<FeatureFlagOption <FeatureFlagOption
variant={BaseVariant} variant={BaseVariant}
value={useFallbackProviderEnabledFlag()} value={useFallbackProviderEnabledFlag()}

@ -29,12 +29,18 @@ const StyledTextButton = styled(ButtonText)`
color: ${({ theme }) => theme.neutral2}; color: ${({ theme }) => theme.neutral2};
gap: 4px; gap: 4px;
font-weight: 485; font-weight: 485;
transition-duration: ${({ theme }) => theme.transition.duration.fast};
transition-timing-function: ease-in-out;
transition-property: opacity, color, background-color;
&:focus { &:focus {
text-decoration: none; text-decoration: none;
} }
&:active { &:active {
text-decoration: none; text-decoration: none;
} }
:hover {
opacity: ${({ theme }) => theme.opacity.hover};
}
` `
export default function SwapBuyFiatButton() { export default function SwapBuyFiatButton() {

@ -0,0 +1,37 @@
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
import { render, screen } from 'test-utils/render'
import SwapHeader, { SwapTab } from './SwapHeader'
jest.mock('../../featureFlags/flags/limits', () => ({ useLimitsEnabled: () => true }))
describe('SwapHeader.tsx', () => {
it('matches base snapshot', () => {
const { asFragment } = render(
<SwapHeader
trade={TEST_TRADE_EXACT_INPUT}
selectedTab={SwapTab.Swap}
autoSlippage={TEST_ALLOWED_SLIPPAGE}
onClickTab={jest.fn()}
/>
)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByText('Swap')).toBeInTheDocument()
expect(screen.getByText('Buy')).toBeInTheDocument()
expect(screen.getByText('Limit')).toBeInTheDocument()
})
it('calls callback for switching tabs', () => {
const onClickTab = jest.fn()
render(
<SwapHeader
trade={TEST_TRADE_EXACT_INPUT}
selectedTab={SwapTab.Swap}
autoSlippage={TEST_ALLOWED_SLIPPAGE}
onClickTab={onClickTab}
/>
)
screen.getByText('Limit').click()
expect(onClickTab).toHaveBeenCalledWith(SwapTab.Limit)
})
})

@ -1,8 +1,9 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core' import { Percent } from '@uniswap/sdk-core'
import { useLimitsEnabled } from 'featureFlags/flags/limits'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
import styled from 'styled-components' import styled from 'styled-components'
import { ThemedText } from 'theme/components' import { ButtonText } from 'theme/components'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import SettingsTab from '../Settings' import SettingsTab from '../Settings'
@ -18,22 +19,49 @@ const HeaderButtonContainer = styled(RowFixed)`
gap: 16px; gap: 16px;
` `
const StyledTextButton = styled(ButtonText)<{ $isActive: boolean }>`
color: ${({ theme, $isActive }) => ($isActive ? theme.neutral1 : theme.neutral2)};
gap: 4px;
font-weight: 485;
&:focus {
text-decoration: none;
}
&:active {
text-decoration: none;
}
`
export enum SwapTab {
Swap = 'swap',
Limit = 'limit',
}
export default function SwapHeader({ export default function SwapHeader({
autoSlippage, autoSlippage,
chainId, chainId,
trade, trade,
selectedTab,
onClickTab,
}: { }: {
autoSlippage: Percent autoSlippage: Percent
chainId?: number chainId?: number
trade?: InterfaceTrade trade?: InterfaceTrade
selectedTab: SwapTab
onClickTab: (tab: SwapTab) => void
}) { }) {
const limitsEnabled = useLimitsEnabled()
return ( return (
<StyledSwapHeader> <StyledSwapHeader>
<HeaderButtonContainer> <HeaderButtonContainer>
<ThemedText.SubHeader> <StyledTextButton $isActive={selectedTab === SwapTab.Swap} onClick={() => onClickTab?.(SwapTab.Swap)}>
<Trans>Swap</Trans> <Trans>Swap</Trans>
</ThemedText.SubHeader> </StyledTextButton>
<SwapBuyFiatButton /> <SwapBuyFiatButton />
{limitsEnabled && (
<StyledTextButton $isActive={selectedTab === SwapTab.Limit} onClick={() => onClickTab?.(SwapTab.Limit)}>
<Trans>Limit</Trans>
</StyledTextButton>
)}
</HeaderButtonContainer> </HeaderButtonContainer>
<RowFixed> <RowFixed>
<SettingsTab autoSlippage={autoSlippage} chainId={chainId} trade={trade} /> <SettingsTab autoSlippage={autoSlippage} chainId={chainId} trade={trade} />

@ -120,6 +120,12 @@ exports[`SwapBuyFiatButton.tsx matches base snapshot 1`] = `
color: #7D7D7D; color: #7D7D7D;
gap: 4px; gap: 4px;
font-weight: 485; font-weight: 485;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
-webkit-transition-timing-function: ease-in-out;
transition-timing-function: ease-in-out;
-webkit-transition-property: opacity,color,background-color;
transition-property: opacity,color,background-color;
} }
.c4:focus { .c4:focus {
@ -132,6 +138,10 @@ exports[`SwapBuyFiatButton.tsx matches base snapshot 1`] = `
text-decoration: none; text-decoration: none;
} }
.c4:hover {
opacity: 0.6;
}
<div <div
class="c0" class="c0"
> >

@ -0,0 +1,343 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SwapHeader.tsx matches base snapshot 1`] = `
<DocumentFragment>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c9 {
box-sizing: border-box;
margin: 0;
min-width: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
display: inline-block;
text-align: center;
line-height: inherit;
-webkit-text-decoration: none;
text-decoration: none;
font-size: inherit;
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
color: white;
background-color: primary;
border: 0;
border-radius: 4px;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c4 {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
}
.c6 {
outline: none;
border: none;
font-size: inherit;
padding: 0;
margin: 0;
background: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
-webkit-transition-timing-function: ease-in-out;
transition-timing-function: ease-in-out;
-webkit-transition-property: opacity,color,background-color;
transition-property: opacity,color,background-color;
}
.c6:hover {
opacity: 0.6;
}
.c6:focus {
-webkit-text-decoration: underline;
text-decoration: underline;
}
.c10 {
padding: 16px;
width: 100%;
line-height: 24px;
font-weight: 535;
text-align: center;
border-radius: 16px;
outline: none;
border: 1px solid transparent;
color: #222222;
-webkit-text-decoration: none;
text-decoration: none;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
cursor: pointer;
position: relative;
z-index: 1;
will-change: transform;
-webkit-transition: -webkit-transform 450ms ease;
-webkit-transition: transform 450ms ease;
transition: transform 450ms ease;
-webkit-transform: perspective(1px) translateZ(0);
-ms-transform: perspective(1px) translateZ(0);
transform: perspective(1px) translateZ(0);
}
.c10:disabled {
opacity: 50%;
cursor: auto;
pointer-events: none;
}
.c10 > * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.c10 > a {
-webkit-text-decoration: none;
text-decoration: none;
}
.c11 {
padding: 0;
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
background: none;
-webkit-text-decoration: none;
text-decoration: none;
}
.c11:focus {
-webkit-text-decoration: underline;
text-decoration: underline;
}
.c11:hover {
opacity: 0.9;
}
.c11:active {
-webkit-text-decoration: underline;
text-decoration: underline;
}
.c11:disabled {
opacity: 50%;
cursor: auto;
}
.c8 {
display: inline-block;
height: inherit;
}
.c17 {
height: 24px;
width: 24px;
}
.c17 > * {
fill: #7D7D7D;
}
.c15 {
border: none;
background-color: transparent;
margin: 0;
padding: 0;
cursor: pointer;
outline: none;
}
.c15:not([disabled]):hover {
opacity: 0.7;
}
.c16 {
padding: 6px 12px;
border-radius: 16px;
}
.c14 {
position: relative;
}
.c12 {
color: #7D7D7D;
gap: 4px;
font-weight: 485;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
-webkit-transition-timing-function: ease-in-out;
transition-timing-function: ease-in-out;
-webkit-transition-property: opacity,color,background-color;
transition-property: opacity,color,background-color;
}
.c12:focus {
-webkit-text-decoration: none;
text-decoration: none;
}
.c12:active {
-webkit-text-decoration: none;
text-decoration: none;
}
.c12:hover {
opacity: 0.6;
}
.c3 {
margin-bottom: 10px;
color: #7D7D7D;
}
.c5 {
padding: 0 12px;
gap: 16px;
}
.c7 {
color: #222222;
gap: 4px;
font-weight: 485;
}
.c7:focus {
-webkit-text-decoration: none;
text-decoration: none;
}
.c7:active {
-webkit-text-decoration: none;
text-decoration: none;
}
.c13 {
color: #7D7D7D;
gap: 4px;
font-weight: 485;
}
.c13:focus {
-webkit-text-decoration: none;
text-decoration: none;
}
.c13:active {
-webkit-text-decoration: none;
text-decoration: none;
}
<div
class="c0 c1 c2 c3"
>
<div
class="c0 c1 c4 c5"
>
<button
class="c6 c7"
>
Swap
</button>
<div
class="c8"
>
<div>
<button
class="c9 c10 c11 c12"
data-testid="buy-fiat-button"
>
Buy
</button>
</div>
</div>
<button
class="c6 c13"
>
Limit
</button>
</div>
<div
class="c0 c1 c4"
>
<div
class="c14"
>
<button
aria-label="Transaction Settings"
class="c15"
data-testid="open-settings-dialog-button"
disabled=""
id="open-settings-dialog-button"
>
<div
class="c0 c1 c16"
>
<svg
class="c17"
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20.83 14.6C19.9 14.06 19.33 13.07 19.33 12C19.33 10.93 19.9 9.93999 20.83 9.39999C20.99 9.29999 21.05 9.1 20.95 8.94L19.28 6.06C19.22 5.95 19.11 5.89001 19 5.89001C18.94 5.89001 18.88 5.91 18.83 5.94C18.37 6.2 17.85 6.34 17.33 6.34C16.8 6.34 16.28 6.19999 15.81 5.92999C14.88 5.38999 14.31 4.41 14.31 3.34C14.31 3.15 14.16 3 13.98 3H10.02C9.83999 3 9.69 3.15 9.69 3.34C9.69 4.41 9.12 5.38999 8.19 5.92999C7.72 6.19999 7.20001 6.34 6.67001 6.34C6.15001 6.34 5.63001 6.2 5.17001 5.94C5.01001 5.84 4.81 5.9 4.72 6.06L3.04001 8.94C3.01001 8.99 3 9.05001 3 9.10001C3 9.22001 3.06001 9.32999 3.17001 9.39999C4.10001 9.93999 4.67001 10.92 4.67001 11.99C4.67001 13.07 4.09999 14.06 3.17999 14.6H3.17001C3.01001 14.7 2.94999 14.9 3.04999 15.06L4.72 17.94C4.78 18.05 4.89 18.11 5 18.11C5.06 18.11 5.12001 18.09 5.17001 18.06C6.11001 17.53 7.26 17.53 8.19 18.07C9.11 18.61 9.67999 19.59 9.67999 20.66C9.67999 20.85 9.82999 21 10.02 21H13.98C14.16 21 14.31 20.85 14.31 20.66C14.31 19.59 14.88 18.61 15.81 18.07C16.28 17.8 16.8 17.66 17.33 17.66C17.85 17.66 18.37 17.8 18.83 18.06C18.99 18.16 19.19 18.1 19.28 17.94L20.96 15.06C20.99 15.01 21 14.95 21 14.9C21 14.78 20.94 14.67 20.83 14.6ZM12 15C10.34 15 9 13.66 9 12C9 10.34 10.34 9 12 9C13.66 9 15 10.34 15 12C15 13.66 13.66 15 12 15Z"
fill="currentColor"
/>
</svg>
</div>
</button>
</div>
</div>
</div>
</DocumentFragment>
`;

@ -0,0 +1,9 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useLimitsEnabledFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.limitsEnabled)
}
export function useLimitsEnabled(): boolean {
return useLimitsEnabledFlag() === BaseVariant.Enabled
}

@ -23,6 +23,7 @@ export enum FeatureFlag {
progressIndicatorV2 = 'progress_indicator_v2', progressIndicatorV2 = 'progress_indicator_v2',
feesEnabled = 'fees_enabled', feesEnabled = 'fees_enabled',
androidGALaunch = 'android_ga_launch', androidGALaunch = 'android_ga_launch',
limitsEnabled = 'limits_enabled',
} }
interface FeatureFlagsContextType { interface FeatureFlagsContextType {

File diff suppressed because it is too large Load Diff

@ -26,7 +26,7 @@ import PriceImpactModal from 'components/swap/PriceImpactModal'
import PriceImpactWarning from 'components/swap/PriceImpactWarning' import PriceImpactWarning from 'components/swap/PriceImpactWarning'
import { ArrowWrapper, PageWrapper, SwapWrapper } from 'components/swap/styled' import { ArrowWrapper, PageWrapper, SwapWrapper } from 'components/swap/styled'
import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown' import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown'
import SwapHeader from 'components/swap/SwapHeader' import SwapHeader, { SwapTab } from 'components/swap/SwapHeader'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink' import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { useConnectionReady } from 'connection/eagerlyConnect' import { useConnectionReady } from 'connection/eagerlyConnect'
@ -601,8 +601,10 @@ export function Swap({
const isDark = useIsDarkMode() const isDark = useIsDarkMode()
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
const [currentTab, setCurrentTab] = useState<SwapTab>(SwapTab.Swap)
const swapElement = ( const swapElement = (
<SwapWrapper isDark={isDark} className={className} id="swap-page"> <>
<TokenSafetyModal <TokenSafetyModal
isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning} isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning}
tokenAddress={importTokensNotInDefault[0]?.address} tokenAddress={importTokensNotInDefault[0]?.address}
@ -611,7 +613,6 @@ export function Swap({
onCancel={handleDismissTokenWarning} onCancel={handleDismissTokenWarning}
showCancel={true} showCancel={true}
/> />
<SwapHeader trade={trade} autoSlippage={autoSlippage} chainId={chainId} />
{trade && showConfirm && ( {trade && showConfirm && (
<ConfirmSwapModal <ConfirmSwapModal
trade={trade} trade={trade}
@ -832,13 +833,23 @@ export function Swap({
</div> </div>
</AutoColumn> </AutoColumn>
{!showOptInSmall && !isUniswapXDefaultEnabled && <UniswapXOptIn isSmall={false} swapInfo={swapInfo} />} {!showOptInSmall && !isUniswapXDefaultEnabled && <UniswapXOptIn isSmall={false} swapInfo={swapInfo} />}
</SwapWrapper> </>
) )
return ( return (
<> <SwapWrapper isDark={isDark} className={className} id="swap-page">
{swapElement} <SwapHeader
selectedTab={currentTab}
onClickTab={(tab) => {
setCurrentTab(tab)
}}
trade={trade}
autoSlippage={autoSlippage}
chainId={chainId}
/>
{/* todo: build Limit UI */}
{currentTab === SwapTab.Swap ? swapElement : undefined}
{showOptInSmall && !isUniswapXDefaultEnabled && <UniswapXOptIn isSmall swapInfo={swapInfo} />} {showOptInSmall && !isUniswapXDefaultEnabled && <UniswapXOptIn isSmall swapInfo={swapInfo} />}
</> </SwapWrapper>
) )
} }