diff --git a/locales/base/translation.json b/locales/base/translation.json index d489b8bbf93..27982d5f4ff 100644 --- a/locales/base/translation.json +++ b/locales/base/translation.json @@ -2785,12 +2785,14 @@ "youNeedTitle": "You Need {{tokenSymbol}} on {{tokenNetwork}} to Deposit", "depositTitle": "Deposit to pool", "crossChainAlternativeDescription": "If you don’t want to use your tokens on {{tokenNetwork}}, choose an option below. You’ll need to return to complete your pool deposit later.", + "alternativeDescription": "If you don’t want to use your tokens, choose an option below. You’ll need to return to complete your pool deposit later.", "beforeYouCanDepositTitle": "Before you can deposit...", "beforeYouCanDepositDescription": "You’ll need to add one of the pool tokens. Once added, you’ll need to return to complete your pool deposit.", "beforeYouCanDepositDescriptionV1_101": "You’ll need to add {{tokenSymbol}} on {{tokenNetwork}}. Once added, you’ll need to return to complete your pool deposit.", "action": { "swapAndDeposit": "Swap & Deposit", "swapAndDepositDescription": "Choose any token on {{tokenNetwork}}. We’ll swap and deposit it simultaneously for you.", + "swapAndDepositAllTokensDescription": "Choose any token—we’ll swap and deposit it simultaneously for you.", "crossChainSwap": "Cross-chain Swap", "crossChainSwapDescription": "Swap a token on another network for {{tokenSymbol}}", "swap": "Swap", diff --git a/src/earn/poolInfoScreen/BeforeDepositBottomSheet.test.tsx b/src/earn/poolInfoScreen/BeforeDepositBottomSheet.test.tsx index d13a8e567f8..15ea905245a 100644 --- a/src/earn/poolInfoScreen/BeforeDepositBottomSheet.test.tsx +++ b/src/earn/poolInfoScreen/BeforeDepositBottomSheet.test.tsx @@ -57,7 +57,7 @@ describe('BeforeDepositBottomSheet', () => { ${'has all types of tokens, cross chain swap disabled, cannot buy'} | ${['Deposit', 'SwapAndDeposit', 'Transfer']} | ${true} | ${true} | ${true} | ${false} | ${false} | ${false} ${'has all types of tokens, cannot buy'} | ${['Deposit', 'SwapAndDeposit', 'CrossChainSwap']} | ${true} | ${true} | ${true} | ${false} | ${false} | ${true} `( - 'shows correct title and actions when user $scenario', + 'shows correct title and actions when cross chain swap and deposit is disabled and user $scenario', ({ hasDepositToken, hasTokensOnSameNetwork, @@ -80,7 +80,7 @@ describe('BeforeDepositBottomSheet', () => { .mockImplementation( (gate) => gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS && allowCrossChainSwaps ) - const { getAllByTestId, getByText, queryByTestId } = render( + const { getAllByTestId, getByText, queryByTestId, queryByText } = render( { expect(getAllByTestId(/^Earn\/ActionCard/).map((element) => element.props.testID)).toEqual( expectedActions.map((action) => `Earn/ActionCard/${action}`) ) + const canDeposit = + expectedActions.includes('Deposit') || expectedActions.includes('SwapAndDeposit') expect( getByText( - `earnFlow.beforeDepositBottomSheet.${expectedActions.includes('Deposit') || expectedActions.includes('SwapAndDeposit') ? 'depositTitle' : 'beforeYouCanDepositTitle'}` + `earnFlow.beforeDepositBottomSheet.${canDeposit ? 'depositTitle' : 'beforeYouCanDepositTitle'}` ) ).toBeTruthy() expect(!!queryByTestId('Earn/BeforeDepositBottomSheet/AlternativeDescription')).toBe( + canDeposit + ) + expect( + !!queryByText( + 'earnFlow.beforeDepositBottomSheet.crossChainAlternativeDescription, {"tokenNetwork":"Arbitrum Sepolia"}' + ) + ).toBe(canDeposit) + } + ) + + // The has other tokens case either sets hasTokensOnSameNetwork or + // hasTokensOnOtherNetworks to true, we don't need to test every combination individually + it.each` + scenario | expectedActions | hasDepositToken | hasTokensOnSameNetwork | hasTokensOnOtherNetworks | canAdd | poolCannotSwapAndDeposit + ${'does not have any tokens'} | ${['Add', 'Transfer']} | ${false} | ${false} | ${false} | ${true} | ${false} + ${'does not have any tokens, cannot buy'} | ${['Transfer']} | ${false} | ${false} | ${false} | ${false} | ${false} + ${'only has deposit token'} | ${['Deposit', 'AddMore']} | ${true} | ${false} | ${false} | ${true} | ${false} + ${'only has deposit token, cannot buy'} | ${['Deposit', 'Transfer']} | ${true} | ${false} | ${false} | ${false} | ${false} + ${'only has other tokens'} | ${['SwapAndDeposit', 'Add']} | ${false} | ${true} | ${false} | ${true} | ${false} + ${'only has other tokens, cannot buy'} | ${['SwapAndDeposit', 'Transfer']} | ${false} | ${false} | ${true} | ${false} | ${false} + ${'only has other tokens, pool cannot swap and deposit'} | ${['Swap', 'Add']} | ${false} | ${true} | ${false} | ${true} | ${true} + ${'only has other tokens, pool cannot swap and deposit, cannot buy'} | ${['Swap', 'Transfer']} | ${false} | ${true} | ${true} | ${false} | ${true} + ${'has deposit token and other tokens'} | ${['Deposit', 'SwapAndDeposit', 'AddMore']} | ${true} | ${false} | ${true} | ${true} | ${false} + ${'has deposit token and other tokens, cannot buy'} | ${['Deposit', 'SwapAndDeposit', 'Transfer']} | ${true} | ${true} | ${false} | ${false} | ${false} + ${'has deposit token and other tokens, pool cannot swap and deposit'} | ${['Deposit', 'Swap', 'AddMore']} | ${true} | ${true} | ${true} | ${true} | ${true} + ${'has deposit token and other tokens, pool cannot swap and deposit, cannot buy'} | ${['Deposit', 'Swap', 'Transfer']} | ${true} | ${true} | ${true} | ${false} | ${true} + `( + 'shows correct title and actions when cross chain swap and deposit is enabled and user $scenario', + ({ + hasDepositToken, + hasTokensOnSameNetwork, + hasTokensOnOtherNetworks, + canAdd, + expectedActions, + poolCannotSwapAndDeposit, + }: { + hasDepositToken: boolean + hasTokensOnSameNetwork: boolean + hasTokensOnOtherNetworks: boolean + canAdd: boolean + expectedActions: string[] + poolCannotSwapAndDeposit: boolean + }) => { + jest + .mocked(getFeatureGate) + .mockImplementation( + (gate) => + gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAP_AND_DEPOSIT || + gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS + ) + const { getAllByTestId, getByText, queryByTestId, queryByText } = render( + + + + ) + expect(getAllByTestId(/^Earn\/ActionCard/).map((element) => element.props.testID)).toEqual( + expectedActions.map((action) => `Earn/ActionCard/${action}`) + ) + const canDeposit = expectedActions.includes('Deposit') || expectedActions.includes('SwapAndDeposit') + expect( + getByText( + `earnFlow.beforeDepositBottomSheet.${canDeposit ? 'depositTitle' : 'beforeYouCanDepositTitle'}` + ) + ).toBeTruthy() + expect(!!queryByTestId('Earn/BeforeDepositBottomSheet/AlternativeDescription')).toBe( + canDeposit + ) + expect(!!queryByText('earnFlow.beforeDepositBottomSheet.alternativeDescription')).toBe( + canDeposit ) } ) + it('shows correct swap and deposit action description when cross chain swap and deposit is disabled', () => { + const { getByTestId, getByText } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toContainElement( + getByText( + 'earnFlow.beforeDepositBottomSheet.action.swapAndDepositDescription, {"tokenNetwork":"Arbitrum Sepolia"}' + ) + ) + }) + + it('shows correct swap and deposit action description when cross chain swap and deposit is enabled', () => { + jest + .mocked(getFeatureGate) + .mockImplementation( + (gate) => + gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAP_AND_DEPOSIT || + gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS + ) + const { getByTestId, getByText } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toContainElement( + getByText('earnFlow.beforeDepositBottomSheet.action.swapAndDepositAllTokensDescription') + ) + }) + it('navigates correctly when deposit action item is tapped', () => { const { getByTestId } = render( diff --git a/src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx b/src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx index 67765cc25e5..eea00498c2b 100644 --- a/src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx +++ b/src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx @@ -1,4 +1,4 @@ -import React, { RefObject } from 'react' +import React, { RefObject, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { StyleSheet, Text, View } from 'react-native' import AppAnalytics from 'src/analytics/AppAnalytics' @@ -200,21 +200,24 @@ function SwapAndDepositAction({ pool, forwardedRef, analyticsProps, + allowCrossChainSwapAndDeposit, }: { token: TokenBalance pool: EarnPosition forwardedRef: React.RefObject analyticsProps: EarnCommonProperties & TokenProperties + allowCrossChainSwapAndDeposit: boolean }) { const { t } = useTranslation() const action: BeforeDepositAction = { name: 'SwapAndDeposit', title: t('earnFlow.beforeDepositBottomSheet.action.swapAndDeposit'), - details: t('earnFlow.beforeDepositBottomSheet.action.swapAndDepositDescription', { - tokenSymbol: token.symbol, - tokenNetwork: NETWORK_NAMES[token.networkId], - }), + details: allowCrossChainSwapAndDeposit + ? t('earnFlow.beforeDepositBottomSheet.action.swapAndDepositAllTokensDescription') + : t('earnFlow.beforeDepositBottomSheet.action.swapAndDepositDescription', { + tokenNetwork: NETWORK_NAMES[token.networkId], + }), iconComponent: SwapAndDeposit, onPress: () => { AppAnalytics.track(EarnEvents.earn_before_deposit_action_press, { @@ -284,17 +287,6 @@ export default function BeforeDepositBottomSheet({ }) { const { t } = useTranslation() - const { availableShortcutIds } = pool - const allowCrossChainSwaps = getFeatureGate(StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS) - const canCrossChainSwap = allowCrossChainSwaps && hasTokensOnOtherNetworks - - const canSwapDeposit = availableShortcutIds.includes('swap-deposit') && hasTokensOnSameNetwork - - const title = - canSwapDeposit || hasDepositToken - ? t('earnFlow.beforeDepositBottomSheet.depositTitle') - : t('earnFlow.beforeDepositBottomSheet.beforeYouCanDepositTitle') - const analyticsProps = { ...getTokenAnalyticsProps(token), poolId: pool.positionId, @@ -302,20 +294,66 @@ export default function BeforeDepositBottomSheet({ depositTokenId: pool.dataProps.depositTokenId, } - const showCrossChainSwap = canSwapDeposit && canCrossChainSwap - // Show a generic swap option if the pool doesn't support swap and deposit. - const showSwap = !canSwapDeposit && (hasTokensOnSameNetwork || canCrossChainSwap) + const { availableShortcutIds } = pool + const allowCrossChainSwaps = getFeatureGate(StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS) + const allowCrossChainSwapAndDeposit = getFeatureGate( + StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAP_AND_DEPOSIT + ) + + const { canSwapDeposit, showCrossChainSwap, showSwap, showAdd, showAddMore, showTransfer } = + useMemo(() => { + if (allowCrossChainSwapAndDeposit) { + const hasOtherTokens = hasTokensOnOtherNetworks || hasTokensOnSameNetwork + // should never have a case where allowCrossChainSwapAndDeposit is true + // and allowCrossChainSwaps is false, so just presence of any token + // should enable swap / swap and deposit + const canSwapDeposit = availableShortcutIds.includes('swap-deposit') && hasOtherTokens + return { + canSwapDeposit, + // no longer need to show separate cross chain swap action + showCrossChainSwap: false, + showSwap: !canSwapDeposit && hasOtherTokens, + showAdd: canAdd && !hasDepositToken, + showAddMore: canAdd && hasDepositToken, + // Show Transfer if add is not an option or if the user has no tokens + showTransfer: !canAdd || (!hasDepositToken && !hasOtherTokens), + } + } else { + const canCrossChainSwap = allowCrossChainSwaps && hasTokensOnOtherNetworks + const hasAllDepositAndSwapOptions = + hasDepositToken && hasTokensOnSameNetwork && canCrossChainSwap + const hasNoDepositOrSwapOptions = + !hasDepositToken && !hasTokensOnSameNetwork && !canCrossChainSwap - const hasAllDepositAndSwapOptions = hasDepositToken && hasTokensOnSameNetwork && canCrossChainSwap - const hasNoDepositOrSwapOptions = - !hasDepositToken && !hasTokensOnSameNetwork && !canCrossChainSwap + const canSwapDeposit = + availableShortcutIds.includes('swap-deposit') && hasTokensOnSameNetwork - const showAdd = canAdd && !hasDepositToken - // Don't show add more if the user has all deposit and swap options are available - const showAddMore = canAdd && hasDepositToken && !hasAllDepositAndSwapOptions - // Show Transfer if the user cannot deposit or swap options or if the token - // does not support buy and if not all deposit and swap options are available - const showTransfer = hasNoDepositOrSwapOptions || (!canAdd && !hasAllDepositAndSwapOptions) + return { + canSwapDeposit, + showCrossChainSwap: canSwapDeposit && canCrossChainSwap, + showSwap: !canSwapDeposit && (hasTokensOnSameNetwork || canCrossChainSwap), + showAdd: canAdd && !hasDepositToken, + // Don't show add more if the user has all deposit and swap options are available + showAddMore: canAdd && hasDepositToken && !hasAllDepositAndSwapOptions, + // Show Transfer if the user has no deposit or swap options or if the token + // does not support buy and if not all deposit and swap options are available + showTransfer: hasNoDepositOrSwapOptions || (!canAdd && !hasAllDepositAndSwapOptions), + } + } + }, [ + hasDepositToken, + hasTokensOnSameNetwork, + hasTokensOnOtherNetworks, + canAdd, + availableShortcutIds, + allowCrossChainSwapAndDeposit, + allowCrossChainSwaps, + ]) + + const title = + canSwapDeposit || hasDepositToken + ? t('earnFlow.beforeDepositBottomSheet.depositTitle') + : t('earnFlow.beforeDepositBottomSheet.beforeYouCanDepositTitle') return ( )} {(canSwapDeposit || hasDepositToken) && @@ -355,9 +394,11 @@ export default function BeforeDepositBottomSheet({ testID={'Earn/BeforeDepositBottomSheet/AlternativeDescription'} style={styles.actionDetails} > - {t('earnFlow.beforeDepositBottomSheet.crossChainAlternativeDescription', { - tokenNetwork: NETWORK_NAMES[token.networkId], - })} + {allowCrossChainSwapAndDeposit + ? t('earnFlow.beforeDepositBottomSheet.alternativeDescription') + : t('earnFlow.beforeDepositBottomSheet.crossChainAlternativeDescription', { + tokenNetwork: NETWORK_NAMES[token.networkId], + })} )} {showCrossChainSwap && (