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,6 +562,9 @@ export default function Swap({ className }: { className?: string }) { ...@@ -559,6 +562,9 @@ export default function Swap({ className }: { className?: string }) {
showCancel={true} showCancel={true}
/> />
<PageWrapper> <PageWrapper>
{swapWidgetEnabled ? (
<Widget token={loadedInputCurrency ?? undefined} width="100%" defaultField={Field.INPUT} />
) : (
<SwapWrapper className={className} id="swap-page"> <SwapWrapper className={className} id="swap-page">
<SwapHeader allowedSlippage={allowedSlippage} /> <SwapHeader allowedSlippage={allowedSlippage} />
<ConfirmSwapModal <ConfirmSwapModal
...@@ -634,7 +640,11 @@ export default function Swap({ className }: { className?: string }) { ...@@ -634,7 +640,11 @@ export default function Swap({ className }: { className?: string }) {
value={formattedAmounts[Field.OUTPUT]} value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput} onUserInput={handleTypeOutput}
label={ 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} showMaxButton={false}
hideBalance={false} hideBalance={false}
...@@ -724,7 +734,8 @@ export default function Swap({ className }: { className?: string }) { ...@@ -724,7 +734,8 @@ export default function Swap({ className }: { className?: string }) {
> >
<AutoRow justify="space-between" style={{ flexWrap: 'nowrap' }} height="20px"> <AutoRow justify="space-between" style={{ flexWrap: 'nowrap' }} height="20px">
{/* we need to shorten this string on mobile */} {/* 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"> <ThemedText.SubHeader width="100%" textAlign="center" color="textSecondary">
<Trans>You can now trade {currencies[Field.INPUT]?.symbol}</Trans> <Trans>You can now trade {currencies[Field.INPUT]?.symbol}</Trans>
</ThemedText.SubHeader> </ThemedText.SubHeader>
...@@ -773,7 +784,8 @@ export default function Swap({ className }: { className?: string }) { ...@@ -773,7 +784,8 @@ export default function Swap({ className }: { className?: string }) {
!isValid || !isValid ||
routeIsSyncing || routeIsSyncing ||
routeIsLoading || routeIsLoading ||
(approvalState !== ApprovalState.APPROVED && signatureState !== UseERC20PermitState.SIGNED) || (approvalState !== ApprovalState.APPROVED &&
signatureState !== UseERC20PermitState.SIGNED) ||
priceImpactTooHigh priceImpactTooHigh
} }
error={isValid && priceImpactSeverity > 2} error={isValid && priceImpactSeverity > 2}
...@@ -812,8 +824,8 @@ export default function Swap({ className }: { className?: string }) { ...@@ -812,8 +824,8 @@ export default function Swap({ className }: { className?: string }) {
<MouseoverTooltip <MouseoverTooltip
text={ text={
<Trans> <Trans>
Permission is required for Uniswap to swap each token. This will expire after one month Permission is required for Uniswap to swap each token. This will expire after one
for your security. month for your security.
</Trans> </Trans>
} }
> >
...@@ -872,6 +884,7 @@ export default function Swap({ className }: { className?: string }) { ...@@ -872,6 +884,7 @@ export default function Swap({ className }: { className?: string }) {
</div> </div>
</AutoColumn> </AutoColumn>
</SwapWrapper> </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