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'
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
......@@ -229,6 +230,12 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.payWithAnyToken}
label="Pay With Any Token"
/>
<FeatureFlagOption
variant={SwapWidgetVariant}
value={useSwapWidgetFlag()}
featureFlag={FeatureFlag.swapWidget}
label="Swap Widget"
/>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption
variant={TraceJsonRpcVariant}
......
import { WidgetSkeleton } from 'components/Widget'
import { WIDGET_WIDTH } from 'components/Widget'
import { DEFAULT_WIDGET_WIDTH } from 'components/Widget'
import { ArrowLeft } from 'react-feather'
import { useParams } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro'
......@@ -44,7 +44,7 @@ export const RightPanel = styled.div`
display: none;
flex-direction: column;
gap: 20px;
width: ${WIDGET_WIDTH}px;
width: ${DEFAULT_WIDGET_WIDTH}px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
display: flex;
......
......@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import { Trace } from '@uniswap/analytics'
import { InterfacePageName } from '@uniswap/analytics-events'
import { Currency } from '@uniswap/sdk-core'
import { Field } from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core'
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import { AboutSection } from 'components/Tokens/TokenDetails/About'
......@@ -223,6 +224,7 @@ export default function TokenDetails({
<RightPanel>
<Widget
token={token ?? undefined}
defaultField={Field.OUTPUT}
onTokenChange={navigateToWidgetSelectedToken}
onReviewSwapClick={onReviewSwapClick}
/>
......
......@@ -10,6 +10,7 @@ import { Currency, TradeType } from '@uniswap/sdk-core'
import {
AddEthereumChainParameter,
EMPTY_TOKEN_LIST,
Field,
OnReviewSwapClick,
SwapWidget,
SwapWidgetSkeleton,
......@@ -36,7 +37,7 @@ import { useSyncWidgetSettings } from './settings'
import { DARK_THEME, LIGHT_THEME } from './theme'
import { useSyncWidgetTransactions } from './transactions'
export const WIDGET_WIDTH = 360
export const DEFAULT_WIDGET_WIDTH = 360
const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
......@@ -46,15 +47,23 @@ function useWidgetTheme() {
interface WidgetProps {
token?: Currency
width?: number | string
defaultField: Field
onTokenChange?: (token: Currency) => void
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 locale = useActiveLocale()
const theme = useWidgetTheme()
const { inputs, tokenSelector } = useSyncWidgetInputs({ token, onTokenChange })
const { inputs, tokenSelector } = useSyncWidgetInputs({ token, onTokenChange, defaultField })
const { settings } = useSyncWidgetSettings()
const { transactions } = useSyncWidgetTransactions()
......@@ -159,7 +168,7 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg
routerUrl={WIDGET_ROUTER_URL}
locale={locale}
theme={theme}
width={WIDGET_WIDTH}
width={width}
// defaultChainId is excluded - it is always inferred from the passed provider
onConnectWalletClick={onConnectWalletClick}
provider={provider}
......@@ -182,5 +191,5 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg
export function WidgetSkeleton() {
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) {
*/
export function useSyncWidgetInputs({
token,
defaultField,
onTokenChange,
}: {
token?: Currency
defaultField: Field
onTokenChange?: (token: Currency) => void
}) {
const trace = useTrace({ section: InterfaceSectionName.WIDGET })
const [type, setType] = useState<SwapValue['type']>(TradeType.EXACT_INPUT)
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(() => {
setTokens((tokens) => {
const update = { ...tokens, default: token }
if (!includesDefaultToken(update)) {
return { [Field.OUTPUT]: update.default, default: update.default }
return { [defaultField]: update.default, default: update.default }
}
return update
})
}, [token])
}, [defaultField, token])
const onAmountChange = useCallback(
(field: Field, amount: string, origin?: 'max') => {
......
......@@ -4,4 +4,5 @@ export enum FeatureFlag {
permit2 = 'permit2',
nftListV2 = 'nftListV2',
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'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { MouseoverTooltip } from 'components/Tooltip'
import Widget from 'components/Widget'
import { isSupportedChain } from 'constants/chains'
import { usePermit2Enabled } from 'featureFlags/flags/permit2'
import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import { useSwapCallback } from 'hooks/useSwapCallback'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
......@@ -157,6 +159,7 @@ export default function Swap({ className }: { className?: string }) {
const loadedUrlParams = useDefaultsFromURLSearch()
const [newSwapQuoteNeedsLogging, setNewSwapQuoteNeedsLogging] = useState(true)
const [fetchingSwapQuoteStartTime, setFetchingSwapQuoteStartTime] = useState<Date | undefined>()
const swapWidgetEnabled = useSwapWidgetEnabled()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
......@@ -559,6 +562,9 @@ export default function Swap({ className }: { className?: string }) {
showCancel={true}
/>
<PageWrapper>
{swapWidgetEnabled ? (
<Widget token={loadedInputCurrency ?? undefined} width="100%" defaultField={Field.INPUT} />
) : (
<SwapWrapper className={className} id="swap-page">
<SwapHeader allowedSlippage={allowedSlippage} />
<ConfirmSwapModal
......@@ -634,7 +640,11 @@ export default function Swap({ className }: { className?: string }) {
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap ? <Trans>To (at least)</Trans> : <Trans>To</Trans>
independentField === Field.INPUT && !showWrap ? (
<Trans>To (at least)</Trans>
) : (
<Trans>To</Trans>
)
}
showMaxButton={false}
hideBalance={false}
......@@ -724,7 +734,8 @@ export default function Swap({ className }: { className?: string }) {
>
<AutoRow justify="space-between" style={{ flexWrap: 'nowrap' }} height="20px">
{/* we need to shorten this string on mobile */}
{approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED ? (
{approvalState === ApprovalState.APPROVED ||
signatureState === UseERC20PermitState.SIGNED ? (
<ThemedText.SubHeader width="100%" textAlign="center" color="textSecondary">
<Trans>You can now trade {currencies[Field.INPUT]?.symbol}</Trans>
</ThemedText.SubHeader>
......@@ -773,7 +784,8 @@ export default function Swap({ className }: { className?: string }) {
!isValid ||
routeIsSyncing ||
routeIsLoading ||
(approvalState !== ApprovalState.APPROVED && signatureState !== UseERC20PermitState.SIGNED) ||
(approvalState !== ApprovalState.APPROVED &&
signatureState !== UseERC20PermitState.SIGNED) ||
priceImpactTooHigh
}
error={isValid && priceImpactSeverity > 2}
......@@ -812,8 +824,8 @@ export default function Swap({ className }: { className?: string }) {
<MouseoverTooltip
text={
<Trans>
Permission is required for Uniswap to swap each token. This will expire after one month
for your security.
Permission is required for Uniswap to swap each token. This will expire after one
month for your security.
</Trans>
}
>
......@@ -872,6 +884,7 @@ export default function Swap({ className }: { className?: string }) {
</div>
</AutoColumn>
</SwapWrapper>
)}
<NetworkAlert />
</PageWrapper>
<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