diff --git a/src/app/actions.ts b/src/app/actions.ts index 0edef57820d..b84e78250b0 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -38,7 +38,7 @@ export enum Actions { PUSH_NOTIFICATIONS_PERMISSION_CHANGED = 'APP/PUSH_NOTIFICATIONS_PERMISSION_CHANGED', IN_APP_REVIEW_REQUESTED = 'APP/IN_APP_REVIEW_REQUESTED', NOTIFICATION_SPOTLIGHT_SEEN = 'APP/NOTIFICATION_SPOTLIGHT_SEEN', - TOGGLE_HIDE_BALANCES = 'APP/TOGGLE_HIDE_BALANCES', + TOGGLE_HIDE_HOME_BALANCES = 'APP/TOGGLE_HIDE_HOME_BALANCES', } export interface SetAppState { @@ -170,8 +170,8 @@ export interface NotificationSpotlightSeen { type: Actions.NOTIFICATION_SPOTLIGHT_SEEN } -interface ToggleHideBalances { - type: Actions.TOGGLE_HIDE_BALANCES +interface ToggleHideHomeBalances { + type: Actions.TOGGLE_HIDE_HOME_BALANCES } export type ActionTypes = @@ -201,7 +201,7 @@ export type ActionTypes = | PushNotificationsPermissionChanged | inAppReviewRequested | NotificationSpotlightSeen - | ToggleHideBalances + | ToggleHideHomeBalances export const setAppState = (state: string): SetAppState => ({ type: Actions.SET_APP_STATE, @@ -369,8 +369,8 @@ export const notificationSpotlightSeen = (): NotificationSpotlightSeen => { } } -export const toggleHideBalances = (): ToggleHideBalances => { +export const toggleHideHomeBalances = (): ToggleHideHomeBalances => { return { - type: Actions.TOGGLE_HIDE_BALANCES, + type: Actions.TOGGLE_HIDE_HOME_BALANCES, } } diff --git a/src/app/reducers.ts b/src/app/reducers.ts index 47843d3ee9f..d994b4672eb 100644 --- a/src/app/reducers.ts +++ b/src/app/reducers.ts @@ -268,7 +268,7 @@ export const appReducer = ( ...state, inAppReviewLastInteractionTimestamp: action.inAppReviewLastInteractionTimestamp, } - case Actions.TOGGLE_HIDE_BALANCES: + case Actions.TOGGLE_HIDE_HOME_BALANCES: return { ...state, hideHomeBalances: !state.hideHomeBalances, diff --git a/src/components/TokenBalance.test.tsx b/src/components/TokenBalance.test.tsx index 7baded406a6..409009461ab 100644 --- a/src/components/TokenBalance.test.tsx +++ b/src/components/TokenBalance.test.tsx @@ -681,6 +681,48 @@ describe('FiatExchangeTokenBalance and HomeTokenBalance', () => { expect(tree.getByTestId('EyeIcon')).toBeTruthy() }) + it('renders correctly when feature flag is off, hideBalance is false', async () => { + jest + .mocked(getFeatureGate) + .mockImplementation( + (featureGate) => + featureGate !== StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN && + featureGate !== StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE + ) + const store = createMockStore(defaultStore) + + const tree = render( + + + + ) + + expect(getElementText(tree.getByTestId('TotalTokenBalance'))).toEqual('$8.41') + expect(tree.queryByTestId('EyeIcon')).toBeFalsy() + expect(tree.queryByTestId('HiddenEyeIcon')).toBeFalsy() + }) + + it('renders correctly when feature flag is off, hideBalance is true', async () => { + jest + .mocked(getFeatureGate) + .mockImplementation( + (featureGate) => + featureGate !== StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN && + featureGate !== StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE + ) + const store = createMockStore({ ...defaultStore, app: { hideHomeBalances: true } }) + + const tree = render( + + + + ) + + expect(getElementText(tree.getByTestId('TotalTokenBalance'))).toEqual('$8.41') + expect(tree.queryByTestId('EyeIcon')).toBeFalsy() + expect(tree.queryByTestId('HiddenEyeIcon')).toBeFalsy() + }) + it('tracks analytics event when eye icon is pressed', async () => { const store = createMockStore(defaultStore) diff --git a/src/components/TokenBalance.tsx b/src/components/TokenBalance.tsx index 71975fe81af..db97e24ec4c 100644 --- a/src/components/TokenBalance.tsx +++ b/src/components/TokenBalance.tsx @@ -17,7 +17,7 @@ import { useDispatch, useSelector } from 'react-redux' import { hideAlert, showToast } from 'src/alert/actions' import { AssetsEvents, FiatExchangeEvents, HomeEvents } from 'src/analytics/Events' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' -import { toggleHideBalances } from 'src/app/actions' +import { toggleHideHomeBalances } from 'src/app/actions' import { hideHomeBalancesSelector } from 'src/app/selectors' import Dialog from 'src/components/Dialog' import { formatValueToDisplay } from 'src/components/TokenDisplay' @@ -55,11 +55,11 @@ import { getSupportedNetworkIdsForTokenBalances } from 'src/tokens/utils' function TokenBalance({ style = styles.balance, singleTokenViewEnabled = true, - hideBalance = false, + showHideHomeBalancesToggle = false, }: { style?: StyleProp singleTokenViewEnabled?: boolean - hideBalance?: boolean + showHideHomeBalancesToggle?: boolean }) { const supportedNetworkIds = getSupportedNetworkIdsForTokenBalances() const tokensWithUsdValue = useTokensWithUsdValue(supportedNetworkIds) @@ -77,16 +77,25 @@ function TokenBalance({ : undefined const { decimalSeparator } = getNumberFormatSettings() + const hideHomeBalanceState = useSelector(hideHomeBalancesSelector) + const hideBalance = showHideHomeBalancesToggle && hideHomeBalanceState const balanceDisplay = hideBalance ? `XX${decimalSeparator}XX` : totalBalanceLocal?.toFormat(2) - if (tokenFetchError || tokenFetchLoading || tokensAreStale) { - // Show '-' if we haven't fetched the tokens yet or prices are stale + const TotalTokenBalance = ({ balanceDisplay }: { balanceDisplay: string }) => { return ( - - {localCurrencySymbol} - {'-'} - + + + {!hideBalance && localCurrencySymbol} + {balanceDisplay} + + {showHideHomeBalancesToggle && } + ) + } + + if (tokenFetchError || tokenFetchLoading || tokensAreStale) { + // Show '-' if we haven't fetched the tokens yet or prices are stale + return } else if ( singleTokenViewEnabled && tokensWithUsdValue.length === 1 && @@ -97,10 +106,7 @@ function TokenBalance({ - - {!hideBalance && localCurrencySymbol} - {balanceDisplay ?? '-'} - + {!hideBalance && ( {formatValueToDisplay(tokenBalance)} {tokensWithUsdValue[0].symbol} @@ -110,13 +116,21 @@ function TokenBalance({ ) } else { - return ( - - {!hideBalance && localCurrencySymbol} - {balanceDisplay ?? new BigNumber(0).toFormat(2)} - - ) + return + } +} + +function HideBalanceButton({ hideBalance }: { hideBalance: boolean }) { + const dispatch = useDispatch() + const eyeIconOnPress = () => { + ValoraAnalytics.track(hideBalance ? HomeEvents.show_balances : HomeEvents.hide_balances) + dispatch(toggleHideHomeBalances()) } + return ( + + {hideBalance ? : } + + ) } function useErrorMessageWithRefresh() { @@ -218,7 +232,6 @@ export function AssetsTokenBalance({ showInfo }: { showInfo: boolean }) { export function HomeTokenBalance() { const { t } = useTranslation() - const dispatch = useDispatch() const totalBalance = useTotalTokenBalance() const tokenBalances = useTokensWithTokenBalance() @@ -242,13 +255,6 @@ export function HomeTokenBalance() { const [infoVisible, setInfoVisible] = useState(false) - const hideBalance = useSelector(hideHomeBalancesSelector) - - const eyeIconOnPress = () => { - ValoraAnalytics.track(hideBalance ? HomeEvents.show_balances : HomeEvents.hide_balances) - dispatch(toggleHideBalances()) - } - return ( @@ -278,19 +284,16 @@ export function HomeTokenBalance() { )} - - - - {hideBalance ? : } - - + ) } diff --git a/src/statsig/constants.ts b/src/statsig/constants.ts index c398b99bd2a..3cd5bb2fc98 100644 --- a/src/statsig/constants.ts +++ b/src/statsig/constants.ts @@ -42,6 +42,7 @@ export const FeatureGates = { [StatsigFeatureGates.USE_VIEM_FOR_WALLETCONNECT_TRANSACTIONS]: false, [StatsigFeatureGates.USE_NEW_RECIPIENT_SCREEN]: false, [StatsigFeatureGates.USE_NEW_SEND_FLOW]: false, + [StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE]: false, } export const ExperimentConfigs = { diff --git a/src/statsig/types.ts b/src/statsig/types.ts index faf35002111..3f64bb6b55c 100644 --- a/src/statsig/types.ts +++ b/src/statsig/types.ts @@ -40,6 +40,7 @@ export enum StatsigFeatureGates { USE_VIEM_FOR_WALLETCONNECT_TRANSACTIONS = 'use_viem_for_walletconnect_transactions', USE_NEW_RECIPIENT_SCREEN = 'use_new_recipient_screen', USE_NEW_SEND_FLOW = 'use_new_send_flow', + SHOW_HIDE_HOME_BALANCES_TOGGLE = 'show_hide_home_balances_toggle', } export enum StatsigExperiments { diff --git a/src/transactions/feed/SwapFeedItem.test.tsx b/src/transactions/feed/SwapFeedItem.test.tsx index c0fd0ad62dd..9468d1c30fe 100644 --- a/src/transactions/feed/SwapFeedItem.test.tsx +++ b/src/transactions/feed/SwapFeedItem.test.tsx @@ -2,6 +2,7 @@ import { render } from '@testing-library/react-native' import React from 'react' import { Provider } from 'react-redux' import { RootState } from 'src/redux/reducers' +import { getFeatureGate } from 'src/statsig' import SwapFeedItem from 'src/transactions/feed/SwapFeedItem' import { Fee, @@ -16,6 +17,8 @@ import { mockCeurTokenId, mockCusdTokenId } from 'test/values' const MOCK_TX_HASH = '0x006b866d20452a24d1d90c7514422188cc7c5d873e2f1ed661ec3f810ad5331c' +jest.mock('src/statsig') + jest.mock('src/web3/networkConfig', () => { const originalModule = jest.requireActual('src/web3/networkConfig') return { @@ -33,6 +36,10 @@ jest.mock('src/web3/networkConfig', () => { }) describe('SwapFeedItem', () => { + beforeEach(() => { + jest.clearAllMocks() + jest.mocked(getFeatureGate).mockReturnValue(true) + }) function renderScreen({ storeOverrides = {}, inAmount, @@ -117,7 +124,28 @@ describe('SwapFeedItem', () => { expect(getElementText(getByTestId('SwapFeedItem/outgoingAmount'))).toEqual('-17.54 cEUR') }) - it('hides balance when flag is set', async () => { + it('still shows balances when feature gate false, hide balances root state true', async () => { + jest.mocked(getFeatureGate).mockReturnValue(false) + const { getByTestId } = renderScreen({ + inAmount: { + tokenId: mockCeurTokenId, + value: 2.93, + }, + outAmount: { + tokenId: mockCusdTokenId, + value: 2.87, + }, + }) + + expect(getElementText(getByTestId('SwapFeedItem/title'))).toEqual('swapScreen.title') + expect(getElementText(getByTestId('SwapFeedItem/subtitle'))).toEqual( + 'feedItemSwapPath, {"token1":"cUSD","token2":"cEUR"}' + ) + expect(getElementText(getByTestId('SwapFeedItem/incomingAmount'))).toEqual('+2.93 cEUR') + expect(getElementText(getByTestId('SwapFeedItem/outgoingAmount'))).toEqual('-2.87 cUSD') + }) + + it('hides balance when feature gate true, root state hide home balances flag is set', async () => { const { queryByTestId } = renderScreen({ inAmount: { tokenId: mockCeurTokenId, diff --git a/src/transactions/feed/SwapFeedItem.tsx b/src/transactions/feed/SwapFeedItem.tsx index eed17c6a2aa..9a5a1bba36c 100644 --- a/src/transactions/feed/SwapFeedItem.tsx +++ b/src/transactions/feed/SwapFeedItem.tsx @@ -9,6 +9,8 @@ import TokenDisplay from 'src/components/TokenDisplay' import Touchable from 'src/components/Touchable' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' +import { getFeatureGate } from 'src/statsig' +import { StatsigFeatureGates } from 'src/statsig/types' import colors from 'src/styles/colors' import fontStyles from 'src/styles/fonts' import variables from 'src/styles/variables' @@ -29,7 +31,10 @@ function SwapFeedItem({ exchange }: Props) { navigate(Screens.TransactionDetailsScreen, { transaction: exchange }) ValoraAnalytics.track(HomeEvents.transaction_feed_item_select) } - const hideBalance = useSelector(hideHomeBalancesSelector) + + const hideHomeBalanceState = useSelector(hideHomeBalancesSelector) + const hideBalance = + getFeatureGate(StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE) && hideHomeBalanceState return ( diff --git a/src/transactions/feed/TransferFeedItem.test.tsx b/src/transactions/feed/TransferFeedItem.test.tsx index 919798878c1..5db8d548223 100644 --- a/src/transactions/feed/TransferFeedItem.test.tsx +++ b/src/transactions/feed/TransferFeedItem.test.tsx @@ -9,6 +9,7 @@ import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote' import { CICOFlow } from 'src/fiatExchanges/utils' import { LocalCurrencyCode } from 'src/localCurrency/consts' import { RootState } from 'src/redux/reducers' +import { getFeatureGate } from 'src/statsig' import TransferFeedItem from 'src/transactions/feed/TransferFeedItem' import { Fee, @@ -41,6 +42,8 @@ const MOCK_CONTACT = { address: MOCK_ADDRESS, } +jest.mock('src/statsig') + jest.mock('src/web3/networkConfig', () => { const originalModule = jest.requireActual('src/web3/networkConfig') return { @@ -58,6 +61,10 @@ jest.mock('src/web3/networkConfig', () => { }) describe('TransferFeedItem', () => { + beforeEach(() => { + jest.clearAllMocks() + jest.mocked(getFeatureGate).mockReturnValue(true) + }) function renderScreen({ storeOverrides = {}, type = TokenTransactionTypeV2.Sent, @@ -618,7 +625,14 @@ describe('TransferFeedItem', () => { }) }) - it('hides balance when flag is set', async () => { + it('shows balance when feature gate false, root state hide home balances flag is set', async () => { + jest.mocked(getFeatureGate).mockReturnValue(false) + const { getByTestId } = renderScreen({ storeOverrides: { app: { hideHomeBalances: true } } }) + expect(getByTestId('TransferFeedItem/amount')).toBeTruthy() + expect(getByTestId('TransferFeedItem/tokenAmount')).toBeTruthy() + }) + + it('hides balance when feature gate true, root state hide home balances flag is set', async () => { const { queryByTestId } = renderScreen({ storeOverrides: { app: { hideHomeBalances: true } } }) expect(queryByTestId('TransferFeedItem/amount')).toBeNull() expect(queryByTestId('TransferFeedItem/tokenAmount')).toBeNull() diff --git a/src/transactions/feed/TransferFeedItem.tsx b/src/transactions/feed/TransferFeedItem.tsx index a33854fda75..83a51dda1e9 100644 --- a/src/transactions/feed/TransferFeedItem.tsx +++ b/src/transactions/feed/TransferFeedItem.tsx @@ -9,6 +9,8 @@ import TokenDisplay from 'src/components/TokenDisplay' import Touchable from 'src/components/Touchable' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' +import { getFeatureGate } from 'src/statsig' +import { StatsigFeatureGates } from 'src/statsig/types' import colors from 'src/styles/colors' import fontStyles from 'src/styles/fonts' import variables from 'src/styles/variables' @@ -38,7 +40,9 @@ function TransferFeedItem({ transfer }: Props) { const colorStyle = new BigNumber(amount.value).isPositive() ? { color: colors.greenUI } : {} - const hideBalance = useSelector(hideHomeBalancesSelector) + const hideHomeBalanceState = useSelector(hideHomeBalancesSelector) + const hideBalance = + getFeatureGate(StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE) && hideHomeBalanceState return (