Skip to content

Commit

Permalink
feat(send): allow entering amount in fiat (#5213)
Browse files Browse the repository at this point in the history
### Description

Allows entering fiat amount with upto 2 decimal places. Automatically
adds grouping separators to input

### Test plan

Unit tests. Tested manually on iOS and android with different separators



https://github.com/valora-inc/wallet/assets/5062591/e4f1f522-3572-4d80-83a5-b4683f17bbb6



### Related issues

- Fixes ACT-1142

### Backwards compatibility

Yes

### Network scalability

N/A
  • Loading branch information
satish-ravi committed Apr 10, 2024
1 parent c3d6657 commit 7e7c5f3
Show file tree
Hide file tree
Showing 11 changed files with 533 additions and 215 deletions.
4 changes: 2 additions & 2 deletions e2e/src/usecases/HandleDeepLinkSend.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export default HandleDeepLinkSend = () => {
await launchDeepLink(PAY_URL)
await waitForElementId('SendEnterAmount/TokenSelect', 10_000)
await expect(element(by.text('cUSD')).atIndex(0)).toBeVisible()
await element(by.id('SendEnterAmount/Input')).replaceText('0.01')
await element(by.id('SendEnterAmount/Input')).tapReturnKey()
await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01')
await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey()
await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000)

await addComment(commentText)
Expand Down
6 changes: 3 additions & 3 deletions e2e/src/usecases/SecureSend.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ export default SecureSend = () => {
await waitForElementByIdAndTap('cUSDSymbol', 30_000)

// Enter the amount and review
await element(by.id('SendEnterAmount/Input')).tap()
await element(by.id('SendEnterAmount/Input')).replaceText(AMOUNT_TO_SEND)
await element(by.id('SendEnterAmount/Input')).tapReturnKey()
await element(by.id('SendEnterAmount/TokenAmountInput')).tap()
await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText(AMOUNT_TO_SEND)
await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey()
await element(by.id('SendEnterAmount/ReviewButton')).tap()

// Write a comment.
Expand Down
32 changes: 16 additions & 16 deletions e2e/src/usecases/Send.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default Send = () => {

it('Then tapping send button should navigate to Send Enter Amount screen', async () => {
await element(by.id('SendOrInviteButton')).tap()
await waitForElementId('SendEnterAmount/Input', 30_000)
await waitForElementId('SendEnterAmount/TokenAmountInput', 30_000)
})

it('Then should be able to change token', async () => {
Expand All @@ -66,9 +66,9 @@ export default Send = () => {
})

it('Then should be able to enter amount and navigate to review screen', async () => {
await waitForElementByIdAndTap('SendEnterAmount/Input', 30_000)
await element(by.id('SendEnterAmount/Input')).replaceText('0.01')
await element(by.id('SendEnterAmount/Input')).tapReturnKey()
await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000)
await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01')
await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey()
await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000)
await isElementVisible('ConfirmButton')
})
Expand All @@ -86,10 +86,10 @@ export default Send = () => {
it('Then should be able to edit amount', async () => {
await element(by.id('BackChevron')).tap()
await isElementVisible('SendEnterAmount/ReviewButton')
await element(by.id('SendEnterAmount/Input')).tap()
await waitForElementByIdAndTap('SendEnterAmount/Input', 30_000)
await element(by.id('SendEnterAmount/Input')).replaceText('0.01')
await element(by.id('SendEnterAmount/Input')).tapReturnKey()
await element(by.id('SendEnterAmount/TokenAmountInput')).tap()
await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000)
await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01')
await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey()
await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000)
let amount = await element(by.id('SendAmount')).getAttributes()
jestExpect(amount.text).toEqual('0.01 cEUR')
Expand Down Expand Up @@ -128,7 +128,7 @@ export default Send = () => {

it('Then should be able to click on recent recipient', async () => {
await element(by.text('0xe5f5...8846')).atIndex(0).tap()
await waitForElementId('SendEnterAmount/Input', 30_000)
await waitForElementId('SendEnterAmount/TokenAmountInput', 30_000)
})

it('Then should be able to choose token', async () => {
Expand All @@ -138,9 +138,9 @@ export default Send = () => {
})

it('Then should be able to enter amount and navigate to review screen', async () => {
await waitForElementByIdAndTap('SendEnterAmount/Input', 30_000)
await element(by.id('SendEnterAmount/Input')).replaceText('0.01')
await element(by.id('SendEnterAmount/Input')).tapReturnKey()
await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000)
await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01')
await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey()
await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000)
await isElementVisible('ConfirmButton')
})
Expand Down Expand Up @@ -195,7 +195,7 @@ export default Send = () => {

it('Then tapping send button should navigate to Send Enter Amount screen', async () => {
await element(by.id('SendOrInviteButton')).tap()
await waitForElementId('SendEnterAmount/Input', 30_000)
await waitForElementId('SendEnterAmount/TokenAmountInput', 30_000)
})

it('Then should be able to select token', async () => {
Expand All @@ -205,9 +205,9 @@ export default Send = () => {
})

it('Then should be able to enter amount and navigate to review screen', async () => {
await waitForElementByIdAndTap('SendEnterAmount/Input', 30_000)
await element(by.id('SendEnterAmount/Input')).replaceText('0.01')
await element(by.id('SendEnterAmount/Input')).tapReturnKey()
await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000)
await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01')
await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey()
await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000)
await isElementVisible('ConfirmButton')
})
Expand Down
46 changes: 19 additions & 27 deletions src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,10 @@ import { NotificationReceiveState } from 'src/notifications/types'
import { AdventureCardName } from 'src/onboarding/types'
import { PointsActivity } from 'src/points/types'
import { RecipientType } from 'src/recipients/recipient'
import { QrCode } from 'src/send/types'
import { AmountEnteredIn, QrCode } from 'src/send/types'
import { Field } from 'src/swap/types'
import { TokenDetailsActionName } from 'src/tokens/types'
import { NetworkId, TokenTransactionTypeV2, TransactionStatus } from 'src/transactions/types'
import { Currency } from 'src/utils/currencies'

type Web3LibraryProps = { web3Library: 'contract-kit' | 'viem' }

Expand Down Expand Up @@ -539,30 +538,21 @@ interface SendEventsProperties {
}
[SendEvents.send_cancel]: undefined
[SendEvents.send_amount_back]: undefined
[SendEvents.send_amount_continue]:
| {
origin: SendOrigin
isScan: boolean
localCurrencyExchangeRate?: string | null
localCurrency: LocalCurrencyCode
localCurrencyAmount: string | null
underlyingCurrency: Currency
underlyingAmount: string | null
}
| {
origin: SendOrigin
recipientType: RecipientType
isScan: boolean
localCurrencyExchangeRate?: string | null
localCurrency: LocalCurrencyCode
localCurrencyAmount: string | null
underlyingTokenAddress: string | null
underlyingTokenSymbol: string
underlyingAmount: string | null
amountInUsd: string | null
tokenId: string | null
networkId: string | null
}
[SendEvents.send_amount_continue]: {
origin: SendOrigin
recipientType: RecipientType
isScan: boolean
localCurrencyExchangeRate?: string | null
localCurrency: LocalCurrencyCode
localCurrencyAmount: string | null
underlyingTokenAddress: string | null
underlyingTokenSymbol: string
underlyingAmount: string | null
amountInUsd: string | null
amountEnteredIn: AmountEnteredIn
tokenId: string | null
networkId: string | null
}
[SendEvents.send_confirm_back]: undefined
[SendEvents.send_confirm_send]:
| {
Expand Down Expand Up @@ -1528,7 +1518,9 @@ interface JumpstartEventsProperties {
[JumpstartEvents.jumpstart_send_amount_exceeds_threshold]: JumpstartDepositProperties & {
thresholdUsd: number
}
[JumpstartEvents.jumpstart_send_amount_continue]: JumpstartSendProperties
[JumpstartEvents.jumpstart_send_amount_continue]: JumpstartSendProperties & {
amountEnteredIn: AmountEnteredIn
}
[JumpstartEvents.jumpstart_send_confirm]: JumpstartSendProperties
[JumpstartEvents.jumpstart_send_start]: JumpstartSendProperties
[JumpstartEvents.jumpstart_send_succeeded]: JumpstartSendProperties
Expand Down
13 changes: 7 additions & 6 deletions src/jumpstart/JumpstartEnterAmount.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe('JumpstartEnterAmount', () => {
</Provider>
)

fireEvent.changeText(getByTestId('SendEnterAmount/Input'), '.25')
fireEvent.changeText(getByTestId('SendEnterAmount/TokenAmountInput'), '.25')

await waitFor(() => expect(executeSpy).toHaveBeenCalledTimes(1))
expect(executeSpy).toHaveBeenCalledWith({
Expand All @@ -142,7 +142,7 @@ describe('JumpstartEnterAmount', () => {
)

// default selected token is cEUR, priceUsd: '1.16' so max send amount will be 50 / 1.16 = 43.10
fireEvent.changeText(getByTestId('SendEnterAmount/Input'), '43.5')
fireEvent.changeText(getByTestId('SendEnterAmount/TokenAmountInput'), '43.5')

await waitFor(() => expect(executeSpy).toHaveBeenCalledTimes(1))
expect(getByText('review')).toBeDisabled()
Expand All @@ -153,7 +153,7 @@ describe('JumpstartEnterAmount', () => {
).toBeTruthy()
expect(getByText('jumpstartEnterAmountScreen.maxAmountWarning.description')).toBeTruthy()

fireEvent.changeText(getByTestId('SendEnterAmount/Input'), '43')
fireEvent.changeText(getByTestId('SendEnterAmount/TokenAmountInput'), '43')

await waitFor(() => expect(getByText('review')).not.toBeDisabled())
expect(queryByText('jumpstartEnterAmountScreen.maxAmountWarning.title')).toBeFalsy()
Expand All @@ -169,7 +169,7 @@ describe('JumpstartEnterAmount', () => {
</Provider>
)

fireEvent.changeText(getByTestId('SendEnterAmount/Input'), '.25')
fireEvent.changeText(getByTestId('SendEnterAmount/TokenAmountInput'), '.25')

await waitFor(() => expect(executeSpy).toHaveBeenCalledTimes(1))
fireEvent.press(getByText('review'))
Expand All @@ -192,6 +192,7 @@ describe('JumpstartEnterAmount', () => {
tokenAmount: '0.25',
tokenId: mockCeurTokenId,
tokenSymbol: 'cEUR',
amountEnteredIn: 'token',
}
)
})
Expand All @@ -205,7 +206,7 @@ describe('JumpstartEnterAmount', () => {

expect(store.getActions()).toEqual([depositTransactionFlowStarted()])

fireEvent.changeText(getByTestId('SendEnterAmount/Input'), '.25')
fireEvent.changeText(getByTestId('SendEnterAmount/TokenAmountInput'), '.25')
await waitFor(() => expect(executeSpy).toHaveBeenCalledTimes(1))
expect(getByTestId('SendEnterAmount/ReviewButton')).toBeEnabled()

Expand All @@ -225,7 +226,7 @@ describe('JumpstartEnterAmount', () => {

// depositTransactionFlowStarted should not be dispatched
expect(updatedStore.getActions()).toEqual([])
fireEvent.changeText(getByTestId('SendEnterAmount/Input'), '.30')
fireEvent.changeText(getByTestId('SendEnterAmount/TokenAmountInput'), '.30')
// prepare transaction for a second time on this screen
await waitFor(() => expect(executeSpy).toHaveBeenCalledTimes(2))
// review button should remain disabled
Expand Down
11 changes: 8 additions & 3 deletions src/jumpstart/JumpstartEnterAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { getLocalCurrencyCode, usdToLocalCurrencyRateSelector } from 'src/localC
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { useSelector } from 'src/redux/hooks'
import EnterAmount from 'src/send/EnterAmount'
import EnterAmount, { ProceedArgs } from 'src/send/EnterAmount'
import { AmountEnteredIn } from 'src/send/types'
import { getDynamicConfigParams } from 'src/statsig'
import { DynamicConfigs } from 'src/statsig/constants'
import { StatsigDynamicConfigs } from 'src/statsig/types'
Expand Down Expand Up @@ -66,23 +67,26 @@ function JumpstartEnterAmount() {
}, [jumpstartLink.privateKey])

const handleProceed = useAsyncCallback(
async (parsedAmount: BigNumber, token: TokenBalance) => {
async ({ tokenAmount, token, amountEnteredIn }: ProceedArgs) => {
const link = await createJumpstartLink(jumpstartLink.privateKey, token.networkId)
return {
link,
parsedAmount,
parsedAmount: tokenAmount,
token,
amountEnteredIn,
}
},
{
onSuccess: ({
link,
parsedAmount,
token,
amountEnteredIn,
}: {
link: string
parsedAmount: BigNumber
token: TokenBalance
amountEnteredIn: AmountEnteredIn
}) => {
if (prepareJumpstartTransactions.result?.type !== 'possible') {
// should never happen
Expand Down Expand Up @@ -110,6 +114,7 @@ function JumpstartEnterAmount() {
amountInUsd: parsedAmount.multipliedBy(token.priceUsd ?? 0).toFixed(2),
tokenId: token.tokenId,
networkId: token.networkId,
amountEnteredIn,
})
},
onError: (error) => {
Expand Down
Loading

0 comments on commit 7e7c5f3

Please sign in to comment.