forked from Uniswap/interface
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Web 2996 add fiat on ramp buy flow to swap modal on the interfa…
…ce (Uniswap#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: Zach Pomerantz <[email protected]> * remove delay on tooltip * missed one * Update src/components/swap/SwapBuyFiatButton.test.tsx Co-authored-by: Tina <[email protected]> * comments * . * lint error --------- Co-authored-by: Zach Pomerantz <[email protected]> Co-authored-by: Tina <[email protected]>
- Loading branch information
1 parent
972a650
commit 55bd355
Showing
13 changed files
with
506 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
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() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
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> | ||
) | ||
} |
Oops, something went wrong.