Commit 55bd3555 authored by lynn's avatar lynn Committed by GitHub

feat: Web 2996 add fiat on ramp buy flow to swap modal on the interface (#6240)

* init

* testing if it works

* wip

* tooltip still not working correctly

* modal still not triggered after initial buy click

* remove invalid import

* region check fixed

* add disabled buy button treatment

* simplify and fix toggle twice bug

* no more state mgmt bugs finally

* rename vars for clarity and add todos

* add feature flag, remove toast

* keep wallet drawer open upon repeated buy clicks

* remove from feature flag modal for now

* unused vars

* first round respond to tina comments

* respond to tina padding comments, fix padding in response to cal feedback

* last round tina comments

* add tooltip delay requested by fred and cal

* middle of revisions, fiat buy flow readability wip

* hook logic refactor done + added basic unit test

* rename enum and add todo for unit tests

* mouseover tooltip disable properly

* fix mouseover tooltip not working, ensure dot working as expected, rename buyFiatClicked to buyFiatFlowCompleted

* change developer doc comment

* respond comments

* update snapshot test

* comments

* small changes + unit tests

* dedup

* remove enzyme

* Remove unecessary line

* simplify

* more cleanup

* add missing await

* more comments

* more comment responses

* more comment responses

* delay show fixes and respond to comments

* fix logic for show

* remove tooltip delay, unit test changes

* Update src/components/Popover/index.tsx
Co-authored-by: default avatarZach Pomerantz <zzmp@uniswap.org>

* remove delay on tooltip

* missed one

* Update src/components/swap/SwapBuyFiatButton.test.tsx
Co-authored-by: default avatarTina <59578595+tinaszheng@users.noreply.github.com>

* comments

* .

* lint error

---------
Co-authored-by: default avatarZach Pomerantz <zzmp@uniswap.org>
Co-authored-by: default avatarTina <59578595+tinaszheng@users.noreply.github.com>
parent 972a6506
...@@ -25,11 +25,11 @@ jest.mock( ...@@ -25,11 +25,11 @@ jest.mock(
jest.mock('@web3-react/core', () => { jest.mock('@web3-react/core', () => {
const web3React = jest.requireActual('@web3-react/core') const web3React = jest.requireActual('@web3-react/core')
return { return {
...web3React,
useWeb3React: () => ({ useWeb3React: () => ({
account: '123', account: '123',
isActive: true, isActive: true,
}), }),
...web3React,
} }
}) })
......
import { transparentize } from 'polished' import { transparentize } from 'polished'
import { ReactNode, useCallback, useEffect, useState } from 'react' import { ReactNode, useEffect, useState } from 'react'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import Popover, { PopoverProps } from '../Popover' import Popover, { PopoverProps } from '../Popover'
// TODO(WEB-3163): migrate noops throughout web to a shared util file.
const noop = () => null
export const TooltipContainer = styled.div` export const TooltipContainer = styled.div`
max-width: 256px; max-width: 256px;
cursor: default; cursor: default;
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
pointer-events: auto;
color: ${({ theme }) => theme.textPrimary}; color: ${({ theme }) => theme.textPrimary};
font-weight: 400; font-weight: 400;
...@@ -25,7 +29,6 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> { ...@@ -25,7 +29,6 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> {
text: ReactNode text: ReactNode
open?: () => void open?: () => void
close?: () => void close?: () => void
noOp?: () => void
disableHover?: boolean // disable the hover and content display disableHover?: boolean // disable the hover and content display
timeout?: number timeout?: number
} }
...@@ -33,17 +36,19 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> { ...@@ -33,17 +36,19 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> {
interface TooltipContentProps extends Omit<PopoverProps, 'content'> { interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
content: ReactNode content: ReactNode
onOpen?: () => void onOpen?: () => void
open?: () => void
close?: () => void
// whether to wrap the content in a `TooltipContainer` // whether to wrap the content in a `TooltipContainer`
wrap?: boolean wrap?: boolean
disableHover?: boolean // disable the hover and content display disableHover?: boolean // disable the hover and content display
} }
export default function Tooltip({ text, open, close, noOp, disableHover, ...rest }: TooltipProps) { export default function Tooltip({ text, open, close, disableHover, ...rest }: TooltipProps) {
return ( return (
<Popover <Popover
content={ content={
text && ( text && (
<TooltipContainer onMouseEnter={disableHover ? noOp : open} onMouseLeave={disableHover ? noOp : close}> <TooltipContainer onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover ? noop : close}>
{text} {text}
</TooltipContainer> </TooltipContainer>
) )
...@@ -53,15 +58,28 @@ export default function Tooltip({ text, open, close, noOp, disableHover, ...rest ...@@ -53,15 +58,28 @@ export default function Tooltip({ text, open, close, noOp, disableHover, ...rest
) )
} }
function TooltipContent({ content, wrap = false, ...rest }: TooltipContentProps) { function TooltipContent({ content, wrap = false, open, close, disableHover, ...rest }: TooltipContentProps) {
return <Popover content={wrap ? <TooltipContainer>{content}</TooltipContainer> : content} {...rest} /> return (
<Popover
content={
wrap ? (
<TooltipContainer onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover ? noop : close}>
{content}
</TooltipContainer>
) : (
content
)
}
{...rest}
/>
)
} }
/** Standard text tooltip. */ /** Standard text tooltip. */
export function MouseoverTooltip({ text, disableHover, children, timeout, ...rest }: Omit<TooltipProps, 'show'>) { export function MouseoverTooltip({ text, disableHover, children, timeout, ...rest }: Omit<TooltipProps, 'show'>) {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const open = useCallback(() => text && setShow(true), [text, setShow]) const open = () => text && setShow(true)
const close = useCallback(() => setShow(false), [setShow]) const close = () => setShow(false)
useEffect(() => { useEffect(() => {
if (show && timeout) { if (show && timeout) {
...@@ -76,18 +94,16 @@ export function MouseoverTooltip({ text, disableHover, children, timeout, ...res ...@@ -76,18 +94,16 @@ export function MouseoverTooltip({ text, disableHover, children, timeout, ...res
return return
}, [timeout, show]) }, [timeout, show])
const noOp = () => null
return ( return (
<Tooltip <Tooltip
{...rest} {...rest}
open={open} open={open}
close={close} close={close}
noOp={noOp}
disableHover={disableHover} disableHover={disableHover}
show={show} show={show}
text={disableHover ? null : text} text={disableHover ? null : text}
> >
<div onMouseEnter={disableHover ? noOp : open} onMouseLeave={disableHover || timeout ? noOp : close}> <div onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover || timeout ? noop : close}>
{children} {children}
</div> </div>
</Tooltip> </Tooltip>
...@@ -103,18 +119,23 @@ export function MouseoverTooltipContent({ ...@@ -103,18 +119,23 @@ export function MouseoverTooltipContent({
...rest ...rest
}: Omit<TooltipContentProps, 'show'>) { }: Omit<TooltipContentProps, 'show'>) {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const open = useCallback(() => { const open = () => {
setShow(true) setShow(true)
openCallback?.() openCallback?.()
}, [openCallback]) }
const close = useCallback(() => setShow(false), [setShow]) const close = () => {
setShow(false)
}
return ( return (
<TooltipContent {...rest} show={!disableHover && show} content={disableHover ? null : content}> <TooltipContent
<div {...rest}
style={{ display: 'inline-block', lineHeight: 0, padding: '0.25rem' }} open={open}
onMouseEnter={open} close={close}
onMouseLeave={close} show={!disableHover && show}
> content={disableHover ? null : content}
>
<div onMouseEnter={open} onMouseLeave={close}>
{children} {children}
</div> </div>
</TooltipContent> </TooltipContent>
......
import userEvent from '@testing-library/user-event'
import { useWeb3React } from '@web3-react/core'
import { useWalletDrawer } from 'components/WalletDropdown'
import { fireEvent, render, screen } from 'test-utils'
import { useFiatOnrampAvailability, useOpenModal } from '../../state/application/hooks'
import SwapBuyFiatButton, { MOONPAY_REGION_AVAILABILITY_ARTICLE } from './SwapBuyFiatButton'
jest.mock('@web3-react/core', () => {
const web3React = jest.requireActual('@web3-react/core')
return {
...web3React,
useWeb3React: jest.fn(),
}
})
jest.mock('../../state/application/hooks')
const mockUseFiatOnrampAvailability = useFiatOnrampAvailability as jest.MockedFunction<typeof useFiatOnrampAvailability>
const mockUseOpenModal = useOpenModal as jest.MockedFunction<typeof useOpenModal>
jest.mock('components/WalletDropdown')
const mockUseWalletDrawer = useWalletDrawer as jest.MockedFunction<typeof useWalletDrawer>
const mockUseFiatOnRampsUnavailable = (shouldCheck: boolean) => {
return {
available: false,
availabilityChecked: shouldCheck,
error: null,
loading: false,
}
}
const mockUseFiatOnRampsAvailable = (shouldCheck: boolean) => {
if (shouldCheck) {
return {
available: true,
availabilityChecked: true,
error: null,
loading: false,
}
}
return {
available: false,
availabilityChecked: false,
error: null,
loading: false,
}
}
describe('SwapBuyFiatButton.tsx', () => {
let toggleWalletDrawer: jest.Mock<any, any>
let useOpenModal: jest.Mock<any, any>
beforeAll(() => {
toggleWalletDrawer = jest.fn()
useOpenModal = jest.fn()
})
beforeEach(() => {
jest.resetAllMocks()
;(useWeb3React as jest.Mock).mockReturnValue({
account: undefined,
isActive: false,
})
})
it('matches base snapshot', () => {
mockUseFiatOnrampAvailability.mockImplementation(mockUseFiatOnRampsUnavailable)
mockUseWalletDrawer.mockImplementation(() => [false, toggleWalletDrawer])
const { asFragment } = render(<SwapBuyFiatButton />)
expect(asFragment()).toMatchSnapshot()
})
it('fiat on ramps available in region, account unconnected', async () => {
mockUseFiatOnrampAvailability.mockImplementation(mockUseFiatOnRampsAvailable)
mockUseWalletDrawer.mockImplementation(() => [false, toggleWalletDrawer])
mockUseOpenModal.mockImplementation(() => useOpenModal)
render(<SwapBuyFiatButton />)
await userEvent.click(screen.getByTestId('buy-fiat-button'))
expect(toggleWalletDrawer).toHaveBeenCalledTimes(1)
expect(screen.queryByTestId('fiat-on-ramp-unavailable-tooltip')).not.toBeInTheDocument()
})
it('fiat on ramps available in region, account connected', async () => {
;(useWeb3React as jest.Mock).mockReturnValue({
account: '0x52270d8234b864dcAC9947f510CE9275A8a116Db',
isActive: true,
})
mockUseFiatOnrampAvailability.mockImplementation(mockUseFiatOnRampsAvailable)
mockUseWalletDrawer.mockImplementation(() => [false, toggleWalletDrawer])
mockUseOpenModal.mockImplementation(() => useOpenModal)
render(<SwapBuyFiatButton />)
expect(screen.getByTestId('buy-fiat-flow-incomplete-indicator')).toBeInTheDocument()
await userEvent.click(screen.getByTestId('buy-fiat-button'))
expect(toggleWalletDrawer).toHaveBeenCalledTimes(0)
expect(useOpenModal).toHaveBeenCalledTimes(1)
expect(screen.queryByTestId('fiat-on-ramp-unavailable-tooltip')).not.toBeInTheDocument()
expect(screen.queryByTestId('buy-fiat-flow-incomplete-indicator')).not.toBeInTheDocument()
})
it('fiat on ramps unavailable in region', async () => {
mockUseFiatOnrampAvailability.mockImplementation(mockUseFiatOnRampsUnavailable)
mockUseWalletDrawer.mockImplementation(() => [false, toggleWalletDrawer])
render(<SwapBuyFiatButton />)
await userEvent.click(screen.getByTestId('buy-fiat-button'))
fireEvent.mouseOver(screen.getByTestId('buy-fiat-button'))
expect(await screen.findByTestId('fiat-on-ramp-unavailable-tooltip')).toBeInTheDocument()
expect(await screen.findByText(/Learn more/i)).toHaveAttribute('href', MOONPAY_REGION_AVAILABILITY_ARTICLE)
expect(await screen.findByTestId('buy-fiat-button')).toBeDisabled()
})
})
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { ButtonText } from 'components/Button'
import { MouseoverTooltipContent } from 'components/Tooltip'
import { useWalletDrawer } from 'components/WalletDropdown'
import { useCallback, useEffect, useState } from 'react'
import { useBuyFiatFlowCompleted } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ExternalLink } from 'theme'
import { useFiatOnrampAvailability, useOpenModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
const Dot = styled.div`
height: 8px;
width: 8px;
background-color: ${({ theme }) => theme.accentActive};
border-radius: 50%;
`
export const MOONPAY_REGION_AVAILABILITY_ARTICLE =
'https://support.uniswap.org/hc/en-us/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-'
enum BuyFiatFlowState {
// Default initial state. User is not actively trying to buy fiat.
INACTIVE,
// Buy fiat flow is active and region availability has been checked.
ACTIVE_CHECKING_REGION,
// Buy fiat flow is active, feature is available in user's region & needs wallet connection.
ACTIVE_NEEDS_ACCOUNT,
}
const StyledTextButton = styled(ButtonText)`
color: ${({ theme }) => theme.textSecondary};
gap: 4px;
&:focus {
text-decoration: none;
}
&:active {
text-decoration: none;
}
`
export default function SwapBuyFiatButton() {
const { account } = useWeb3React()
const openFiatOnRampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP)
const [buyFiatFlowCompleted, setBuyFiatFlowCompleted] = useBuyFiatFlowCompleted()
const [checkFiatRegionAvailability, setCheckFiatRegionAvailability] = useState(false)
const {
available: fiatOnrampAvailable,
availabilityChecked: fiatOnrampAvailabilityChecked,
loading: fiatOnrampAvailabilityLoading,
} = useFiatOnrampAvailability(checkFiatRegionAvailability)
const [buyFiatFlowState, setBuyFiatFlowState] = useState(BuyFiatFlowState.INACTIVE)
const [walletDrawerOpen, toggleWalletDrawer] = useWalletDrawer()
/*
* Depending on the current state of the buy fiat flow the user is in (buyFiatFlowState),
* the desired behavior of clicking the 'Buy' button is different.
* 1) Initially upon first click, need to check the availability of the feature in the user's
* region, and continue the flow.
* 2) If the feature is available in the user's region, need to connect a wallet, and continue
* the flow.
* 3) If the feature is available and a wallet account is connected, show fiat on ramp modal.
* 4) If the feature is unavailable, show feature unavailable tooltip.
*/
const handleBuyCrypto = useCallback(() => {
if (!fiatOnrampAvailabilityChecked) {
setCheckFiatRegionAvailability(true)
setBuyFiatFlowState(BuyFiatFlowState.ACTIVE_CHECKING_REGION)
} else if (fiatOnrampAvailable && !account && !walletDrawerOpen) {
toggleWalletDrawer()
setBuyFiatFlowState(BuyFiatFlowState.ACTIVE_NEEDS_ACCOUNT)
} else if (fiatOnrampAvailable && account) {
openFiatOnRampModal()
setBuyFiatFlowCompleted(true)
setBuyFiatFlowState(BuyFiatFlowState.INACTIVE)
} else if (!fiatOnrampAvailable) {
setBuyFiatFlowCompleted(true)
setBuyFiatFlowState(BuyFiatFlowState.INACTIVE)
}
}, [
fiatOnrampAvailabilityChecked,
fiatOnrampAvailable,
account,
walletDrawerOpen,
toggleWalletDrawer,
openFiatOnRampModal,
setBuyFiatFlowCompleted,
])
// Continue buy fiat flow automatically when requisite state changes have occured.
useEffect(() => {
if (
(buyFiatFlowState === BuyFiatFlowState.ACTIVE_CHECKING_REGION && fiatOnrampAvailabilityChecked) ||
(account && buyFiatFlowState === BuyFiatFlowState.ACTIVE_NEEDS_ACCOUNT)
) {
handleBuyCrypto()
}
}, [account, handleBuyCrypto, buyFiatFlowState, fiatOnrampAvailabilityChecked])
const buyCryptoButtonDisabled =
(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) ||
fiatOnrampAvailabilityLoading ||
// When wallet drawer is open AND user is in the connect wallet step of the buy fiat flow, disable buy fiat button.
(walletDrawerOpen && buyFiatFlowState === BuyFiatFlowState.ACTIVE_NEEDS_ACCOUNT)
const fiatOnRampsUnavailableTooltipDisabled =
!fiatOnrampAvailabilityChecked || (fiatOnrampAvailabilityChecked && fiatOnrampAvailable)
return (
<MouseoverTooltipContent
wrap
content={
<div data-testid="fiat-on-ramp-unavailable-tooltip">
<Trans>Crypto purchases are not available in your region. </Trans>
<ExternalLink href={MOONPAY_REGION_AVAILABILITY_ARTICLE} style={{ paddingLeft: '4px' }}>
<Trans>Learn more</Trans>
</ExternalLink>
</div>
}
placement="bottom"
disableHover={fiatOnRampsUnavailableTooltipDisabled}
>
<StyledTextButton onClick={handleBuyCrypto} disabled={buyCryptoButtonDisabled} data-testid="buy-fiat-button">
<Trans>Buy</Trans>
{!buyFiatFlowCompleted && <Dot data-testid="buy-fiat-flow-incomplete-indicator" />}
</StyledTextButton>
</MouseoverTooltipContent>
)
}
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core' import { Percent } from '@uniswap/sdk-core'
import { useFiatOnRampButtonEnabled } from 'featureFlags/flags/fiatOnRampButton'
import { subhead } from 'nft/css/common.css'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from '../../theme'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import SettingsTab from '../Settings' import SettingsTab from '../Settings'
import SwapBuyFiatButton from './SwapBuyFiatButton'
const StyledSwapHeader = styled.div` const StyledSwapHeader = styled.div`
padding: 8px 12px; padding: 8px 12px;
...@@ -13,14 +15,27 @@ const StyledSwapHeader = styled.div` ...@@ -13,14 +15,27 @@ const StyledSwapHeader = styled.div`
color: ${({ theme }) => theme.textSecondary}; color: ${({ theme }) => theme.textSecondary};
` `
const TextHeader = styled.div`
color: ${({ theme }) => theme.textPrimary};
margin-right: 8px;
display: flex;
line-height: 20px;
flex-direction: row;
justify-content: center;
align-items: center;
`
export default function SwapHeader({ allowedSlippage }: { allowedSlippage: Percent }) { export default function SwapHeader({ allowedSlippage }: { allowedSlippage: Percent }) {
const fiatOnRampButtonEnabled = useFiatOnRampButtonEnabled()
return ( return (
<StyledSwapHeader> <StyledSwapHeader>
<RowBetween> <RowBetween>
<RowFixed> <RowFixed style={{ gap: '8px' }}>
<ThemedText.DeprecatedBlack fontWeight={500} fontSize={16} style={{ marginRight: '8px' }}> <TextHeader className={subhead}>
<Trans>Swap</Trans> <Trans>Swap</Trans>
</ThemedText.DeprecatedBlack> </TextHeader>
{fiatOnRampButtonEnabled && <SwapBuyFiatButton />}
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
<SettingsTab placeholderSlippage={allowedSlippage} /> <SettingsTab placeholderSlippage={allowedSlippage} />
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SwapBuyFiatButton.tsx matches base snapshot 1`] = `
<DocumentFragment>
.c1 {
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;
}
.c2 {
padding: 16px;
width: 100%;
font-weight: 500;
text-align: center;
border-radius: 20px;
outline: none;
border: 1px solid transparent;
color: #0D111C;
-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);
}
.c2:disabled {
opacity: 50%;
cursor: auto;
pointer-events: none;
}
.c2 > * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.c2 > a {
-webkit-text-decoration: none;
text-decoration: none;
}
.c3 {
padding: 0;
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
background: none;
-webkit-text-decoration: none;
text-decoration: none;
}
.c3:focus {
-webkit-text-decoration: underline;
text-decoration: underline;
}
.c3:hover {
opacity: 0.9;
}
.c3:active {
-webkit-text-decoration: underline;
text-decoration: underline;
}
.c3:disabled {
opacity: 50%;
cursor: auto;
}
.c0 {
display: inline-block;
height: inherit;
}
.c5 {
height: 8px;
width: 8px;
background-color: #4C82FB;
border-radius: 50%;
}
.c4 {
color: #7780A0;
gap: 4px;
}
.c4:focus {
-webkit-text-decoration: none;
text-decoration: none;
}
.c4:active {
-webkit-text-decoration: none;
text-decoration: none;
}
<div
class="c0"
>
<div>
<button
class="c1 c2 c3 c4"
data-testid="buy-fiat-button"
>
Buy
<div
class="c5"
data-testid="buy-fiat-flow-incomplete-indicator"
/>
</button>
</div>
</div>
</DocumentFragment>
`;
...@@ -5,6 +5,7 @@ export enum FeatureFlag { ...@@ -5,6 +5,7 @@ export enum FeatureFlag {
traceJsonRpc = 'traceJsonRpc', traceJsonRpc = 'traceJsonRpc',
permit2 = 'permit2', permit2 = 'permit2',
payWithAnyToken = 'payWithAnyToken', payWithAnyToken = 'payWithAnyToken',
fiatOnRampButtonOnSwap = 'fiat_on_ramp_button_on_swap_page',
swapWidget = 'swap_widget_replacement_enabled', swapWidget = 'swap_widget_replacement_enabled',
statsigDummy = 'web_dummy_gate_amplitude_id', statsigDummy = 'web_dummy_gate_amplitude_id',
nftGraphql = 'nft_graphql_migration', nftGraphql = 'nft_graphql_migration',
......
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
function useFiatOnRampButtonFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.fiatOnRampButtonOnSwap)
}
export function useFiatOnRampButtonEnabled(): boolean {
return useFiatOnRampButtonFlag() === BaseVariant.Enabled
}
...@@ -5,11 +5,11 @@ import Bag from './Bag' ...@@ -5,11 +5,11 @@ import Bag from './Bag'
jest.mock('@web3-react/core', () => { jest.mock('@web3-react/core', () => {
const web3React = jest.requireActual('@web3-react/core') const web3React = jest.requireActual('@web3-react/core')
return { return {
...web3React,
useWeb3React: () => ({ useWeb3React: () => ({
account: '0x52270d8234b864dcAC9947f510CE9275A8a116Db', account: '0x52270d8234b864dcAC9947f510CE9275A8a116Db',
isActive: true, isActive: true,
}), }),
...web3React,
} }
}) })
......
...@@ -18,6 +18,7 @@ import { ...@@ -18,6 +18,7 @@ import {
addSerializedToken, addSerializedToken,
updateHideClosedPositions, updateHideClosedPositions,
updateHideUniswapWalletBanner, updateHideUniswapWalletBanner,
updateUserBuyFiatFlowCompleted,
updateUserClientSideRouter, updateUserClientSideRouter,
updateUserDeadline, updateUserDeadline,
updateUserExpertMode, updateUserExpertMode,
...@@ -68,6 +69,18 @@ export function useIsExpertMode(): boolean { ...@@ -68,6 +69,18 @@ export function useIsExpertMode(): boolean {
return useAppSelector((state) => state.user.userExpertMode) return useAppSelector((state) => state.user.userExpertMode)
} }
export function useBuyFiatFlowCompleted(): [boolean | undefined, (buyFiatFlowCompleted: boolean) => void] {
const dispatch = useAppDispatch()
const buyFiatFlowCompleted = useAppSelector((state) => state.user.buyFiatFlowCompleted)
const setBuyFiatFlowCompleted = useCallback(
(buyFiatFlowCompleted: boolean) => {
dispatch(updateUserBuyFiatFlowCompleted(buyFiatFlowCompleted))
},
[dispatch]
)
return [buyFiatFlowCompleted, setBuyFiatFlowCompleted]
}
export function useExpertModeManager(): [boolean, () => void] { export function useExpertModeManager(): [boolean, () => void] {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const expertMode = useIsExpertMode() const expertMode = useIsExpertMode()
......
...@@ -9,6 +9,8 @@ import { SerializedPair, SerializedToken } from './types' ...@@ -9,6 +9,8 @@ import { SerializedPair, SerializedToken } from './types'
const currentTimestamp = () => new Date().getTime() const currentTimestamp = () => new Date().getTime()
export interface UserState { export interface UserState {
buyFiatFlowCompleted: boolean | undefined
selectedWallet?: ConnectionType selectedWallet?: ConnectionType
// the timestamp of the last updateVersion action // the timestamp of the last updateVersion action
...@@ -55,6 +57,7 @@ function pairKey(token0Address: string, token1Address: string) { ...@@ -55,6 +57,7 @@ function pairKey(token0Address: string, token1Address: string) {
} }
export const initialState: UserState = { export const initialState: UserState = {
buyFiatFlowCompleted: undefined,
selectedWallet: undefined, selectedWallet: undefined,
userExpertMode: false, userExpertMode: false,
userLocale: null, userLocale: null,
...@@ -75,6 +78,9 @@ const userSlice = createSlice({ ...@@ -75,6 +78,9 @@ const userSlice = createSlice({
name: 'user', name: 'user',
initialState, initialState,
reducers: { reducers: {
updateUserBuyFiatFlowCompleted(state, action) {
state.buyFiatFlowCompleted = action.payload
},
updateSelectedWallet(state, { payload: { wallet } }) { updateSelectedWallet(state, { payload: { wallet } }) {
state.selectedWallet = wallet state.selectedWallet = wallet
}, },
...@@ -163,6 +169,7 @@ const userSlice = createSlice({ ...@@ -163,6 +169,7 @@ const userSlice = createSlice({
export const { export const {
addSerializedPair, addSerializedPair,
addSerializedToken, addSerializedToken,
updateUserBuyFiatFlowCompleted,
updateSelectedWallet, updateSelectedWallet,
updateHideClosedPositions, updateHideClosedPositions,
updateUserClientSideRouter, updateUserClientSideRouter,
......
...@@ -3566,6 +3566,11 @@ ...@@ -3566,6 +3566,11 @@
"@testing-library/dom" "^8.5.0" "@testing-library/dom" "^8.5.0"
"@types/react-dom" "^18.0.0" "@types/react-dom" "^18.0.0"
"@testing-library/user-event@^14.4.3":
version "14.4.3"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591"
integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==
"@tootallnate/once@1": "@tootallnate/once@1":
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
...@@ -9065,9 +9070,9 @@ domelementtype@1: ...@@ -9065,9 +9070,9 @@ domelementtype@1:
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
domelementtype@^2.0.1, domelementtype@^2.2.0: domelementtype@^2.0.1, domelementtype@^2.2.0:
version "2.2.0" version "2.3.0"
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domexception@^2.0.1: domexception@^2.0.1:
version "2.0.1" version "2.0.1"
...@@ -14135,9 +14140,9 @@ nth-check@^1.0.2: ...@@ -14135,9 +14140,9 @@ nth-check@^1.0.2:
boolbase "~1.0.0" boolbase "~1.0.0"
nth-check@^2.0.0: nth-check@^2.0.0:
version "2.0.0" version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
dependencies: dependencies:
boolbase "^1.0.0" boolbase "^1.0.0"
...@@ -14219,9 +14224,9 @@ object-copy@^0.1.0: ...@@ -14219,9 +14224,9 @@ object-copy@^0.1.0:
kind-of "^3.0.3" kind-of "^3.0.3"
object-inspect@^1.12.2, object-inspect@^1.9.0: object-inspect@^1.12.2, object-inspect@^1.9.0:
version "1.12.2" version "1.12.3"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
object-is@^1.0.1: object-is@^1.0.1:
version "1.1.5" version "1.1.5"
......
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