diff --git a/app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx b/app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx index 37c37397597..10f3ec7362b 100644 --- a/app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx +++ b/app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx @@ -37,6 +37,7 @@ import AccountGroupBalanceChange from '../../components/BalanceChange/AccountGro import BalanceEmptyState from '../../../BalanceEmptyState'; import WalletHomeOnboardingSteps from '../../../WalletHomeOnboardingSteps'; import { useRampNavigation } from '../../../Ramp/hooks/useRampNavigation'; +import { useWalletHomeOnboardingChecklistFundPress } from '../../../WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistFundPress'; /** * Timeout for account group balance fetch @@ -82,6 +83,8 @@ const AccountGroupBalance = ({ selectWalletHomeOnboardingSkipInitialBalanceWait, ); const { goToBuy } = useRampNavigation(); + const onFundPrimaryPressWithChecklistAnalytics = + useWalletHomeOnboardingChecklistFundPress(goToBuy); const { popularNetworks } = useNetworkEnablement(); // Stabilize chain IDs by content so selector identity doesn't change every render (avoids max depth / infinite loop). @@ -266,7 +269,7 @@ const AccountGroupBalance = ({ isAwaitingBalance={awaitBalanceForPostOnboardingSteps} onCoordinatedFlowExit={onCoordinatedFlowExit} suspendRiveForCurtain={suspendRiveForCurtain} - onFundPrimaryPress={goToBuy} + onFundPrimaryPress={onFundPrimaryPressWithChecklistAnalytics} canAdvanceFundStepAfterBalance={canAdvanceFundStepAfterBalance} onTradePrimaryPress={onTradePrimaryPress} onNotificationsPrimaryPress={onNotificationsPrimaryPress} diff --git a/app/components/UI/Bridge/hooks/useSwapBridgeNavigation/index.ts b/app/components/UI/Bridge/hooks/useSwapBridgeNavigation/index.ts index e2636c5f59f..8bded865c15 100644 --- a/app/components/UI/Bridge/hooks/useSwapBridgeNavigation/index.ts +++ b/app/components/UI/Bridge/hooks/useSwapBridgeNavigation/index.ts @@ -168,6 +168,10 @@ export const useSwapBridgeNavigation = ({ destTokenOverride?: BridgeToken, buttonLabel?: string, scrollToTopOnNav?: boolean, + /** Per-call override for {@link MetaMetricsEvents.SWAP_BUTTON_CLICKED} `location`. */ + swapButtonClickLocationOverride?: + | ActionLocation + | SwapBridgeNavigationLocation, ) => { // Use tokenOverride if provided, otherwise fall back to tokenBase const effectiveSourceTokenBase = sourceTokenOverride ?? sourceTokenBase; @@ -336,7 +340,10 @@ export const useSwapBridgeNavigation = ({ trackActionButtonClick(trackEvent, createEventBuilder, actionButtonProps); const swapEventProperties = { - location: swapButtonEventLocationOverride ?? location, + location: + swapButtonClickLocationOverride ?? + swapButtonEventLocationOverride ?? + location, chain_id_source: getDecimalChainId(sourceToken.chainId), token_symbol_source: sourceToken?.symbol, token_address_source: sourceToken?.address, @@ -381,6 +388,9 @@ export const useSwapBridgeNavigation = ({ destTokenOverride?: BridgeToken, buttonLabel?: string, scrollToTopOnNav?: boolean, + swapButtonClickLocationOverride?: + | ActionLocation + | SwapBridgeNavigationLocation, ) => { goToNativeBridge( BridgeViewMode.Unified, @@ -388,6 +398,7 @@ export const useSwapBridgeNavigation = ({ destTokenOverride, buttonLabel, scrollToTopOnNav, + swapButtonClickLocationOverride, ); }, [goToNativeBridge], diff --git a/app/components/UI/Bridge/hooks/useSwapBridgeNavigation/useSwapBridgeNavigation.test.ts b/app/components/UI/Bridge/hooks/useSwapBridgeNavigation/useSwapBridgeNavigation.test.ts index 289ab26b528..03c2346ae81 100644 --- a/app/components/UI/Bridge/hooks/useSwapBridgeNavigation/useSwapBridgeNavigation.test.ts +++ b/app/components/UI/Bridge/hooks/useSwapBridgeNavigation/useSwapBridgeNavigation.test.ts @@ -1166,6 +1166,35 @@ describe('useSwapBridgeNavigation', () => { expect(mockTrackEvent).toHaveBeenCalledWith({ category: 'test' }); }); + it('tracks swap button click with per-call onboarding_checklist location override', () => { + const { result } = renderHookWithProvider( + () => + useSwapBridgeNavigation({ + location: SwapBridgeNavigationLocation.MainView, + sourcePage: mockSourcePage, + }), + { state: initialState }, + ); + + result.current.goToSwaps( + undefined, + undefined, + undefined, + undefined, + ActionLocation.ONBOARDING_CHECKLIST, + ); + + expect(mockAddProperties).toHaveBeenCalledWith( + expect.objectContaining({ + location: ActionLocation.ONBOARDING_CHECKLIST, + chain_id_source: expect.any(String), + token_symbol_source: expect.anything(), + token_address_source: expect.anything(), + from_trending: expect.any(Boolean), + }), + ); + }); + it('tracks action button click with correct properties when location is TokenView', () => { const { result } = renderHookWithProvider( () => diff --git a/app/components/UI/WalletHomeOnboardingSteps/WalletHomeOnboardingSteps.tsx b/app/components/UI/WalletHomeOnboardingSteps/WalletHomeOnboardingSteps.tsx index eab67740724..996e91753c6 100644 --- a/app/components/UI/WalletHomeOnboardingSteps/WalletHomeOnboardingSteps.tsx +++ b/app/components/UI/WalletHomeOnboardingSteps/WalletHomeOnboardingSteps.tsx @@ -74,6 +74,7 @@ import { walletHomeOnboardingSubtitleForStep, walletHomeOnboardingTitleForStep, } from './walletHomeOnboardingStepsStrings'; +import { useWalletHomeOnboardingChecklistHomeViewed } from './useWalletHomeOnboardingChecklistHomeViewed'; const SLIDE_DISTANCE = Dimensions.get('window').width; @@ -133,6 +134,12 @@ const WalletHomeOnboardingSteps: React.FC = ({ selectWalletHomeOnboardingSteps, ); const stepIndex = walletHomeOnboardingStepsState.stepIndex ?? 0; + + useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance, + stepIndex, + isFocused, + }); const displayStepIndex = isAwaitingBalance ? 0 : stepIndex; /** Capped index for progress + hero; matches visible steps bounds before Redux clamps persisted step. */ const visualStepIndexForProgress = diff --git a/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistFundPress.test.ts b/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistFundPress.test.ts new file mode 100644 index 00000000000..fb9695dce56 --- /dev/null +++ b/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistFundPress.test.ts @@ -0,0 +1,112 @@ +import { renderHook, act } from '@testing-library/react-native'; +import { MetaMetricsEvents } from '../../../core/Analytics'; +import { ActionLocation } from '../../../util/analytics/actionButtonTracking'; +import { useWalletHomeOnboardingChecklistFundPress } from './useWalletHomeOnboardingChecklistFundPress'; + +const mockTrackEvent = jest.fn(); +const mockAddProperties = jest.fn().mockReturnThis(); +const mockBuild = jest.fn().mockReturnValue({ event: 'built' }); +const mockCreateEventBuilder = jest.fn(() => ({ + addProperties: mockAddProperties, + build: mockBuild, +})); + +jest.mock('../../hooks/useAnalytics/useAnalytics', () => ({ + useAnalytics: () => ({ + trackEvent: mockTrackEvent, + createEventBuilder: mockCreateEventBuilder, + }), +})); + +const mockUseRampsButtonClickData = jest.fn(); +jest.mock('../Ramp/hooks/useRampsButtonClickData', () => ({ + useRampsButtonClickData: () => mockUseRampsButtonClickData(), +})); + +const mockUseRampsUnifiedV1Enabled = jest.fn(); +jest.mock('../Ramp/hooks/useRampsUnifiedV1Enabled', () => ({ + __esModule: true, + default: () => mockUseRampsUnifiedV1Enabled(), +})); + +const mockUseRampsUnifiedV2Enabled = jest.fn(); +jest.mock('../Ramp/hooks/useRampsUnifiedV2Enabled', () => ({ + __esModule: true, + default: () => mockUseRampsUnifiedV2Enabled(), +})); + +jest.mock('react-redux', () => ({ + useSelector: jest.fn((selector: (...args: unknown[]) => unknown) => + selector({} as never), + ), +})); + +jest.mock('../../../reducers/fiatOrders', () => ({ + getDetectedGeolocation: () => 'US', +})); + +jest.mock('./walletHomeOnboardingStepsStrings', () => ({ + walletHomeOnboardingPrimaryLabelForStep: jest.fn(() => 'Add funds'), +})); + +const defaultButtonClickData = { + ramp_routing: 'SMART_ROUTING' as const, + is_authenticated: true, + preferred_provider: 'test-provider', + order_count: 2, +}; + +describe('useWalletHomeOnboardingChecklistFundPress', () => { + const goToBuy = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + mockUseRampsButtonClickData.mockReturnValue(defaultButtonClickData); + mockUseRampsUnifiedV1Enabled.mockReturnValue(false); + mockUseRampsUnifiedV2Enabled.mockReturnValue(false); + }); + + it('fires RAMPS_BUTTON_CLICKED with location onboarding_checklist then calls goToBuy', () => { + const { result } = renderHook(() => + useWalletHomeOnboardingChecklistFundPress(goToBuy), + ); + + act(() => { + result.current(); + }); + + expect(mockCreateEventBuilder).toHaveBeenCalledWith( + MetaMetricsEvents.RAMPS_BUTTON_CLICKED, + ); + expect(mockAddProperties).toHaveBeenCalledWith({ + button_text: 'Add funds', + location: ActionLocation.ONBOARDING_CHECKLIST, + ramp_type: 'BUY', + region: 'US', + ramp_routing: 'SMART_ROUTING', + is_authenticated: true, + preferred_provider: 'test-provider', + order_count: 2, + }); + expect(mockTrackEvent).toHaveBeenCalledWith({ event: 'built' }); + expect(goToBuy).toHaveBeenCalledTimes(1); + }); + + it('uses UNIFIED_BUY_2 ramp_type when V2 unified is enabled', () => { + mockUseRampsUnifiedV2Enabled.mockReturnValue(true); + + const { result } = renderHook(() => + useWalletHomeOnboardingChecklistFundPress(goToBuy), + ); + + act(() => { + result.current(); + }); + + expect(mockAddProperties).toHaveBeenCalledWith( + expect.objectContaining({ + ramp_type: 'UNIFIED_BUY_2', + }), + ); + }); +}); diff --git a/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistFundPress.ts b/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistFundPress.ts new file mode 100644 index 00000000000..648a51ecdec --- /dev/null +++ b/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistFundPress.ts @@ -0,0 +1,64 @@ +import { useCallback } from 'react'; +import { useSelector } from 'react-redux'; +import { MetaMetricsEvents } from '../../../core/Analytics'; +import { useAnalytics } from '../../hooks/useAnalytics/useAnalytics'; +import { ActionLocation } from '../../../util/analytics/actionButtonTracking'; +import { getDetectedGeolocation } from '../../../reducers/fiatOrders'; +import useRampsUnifiedV1Enabled from '../Ramp/hooks/useRampsUnifiedV1Enabled'; +import useRampsUnifiedV2Enabled from '../Ramp/hooks/useRampsUnifiedV2Enabled'; +import { useRampsButtonClickData } from '../Ramp/hooks/useRampsButtonClickData'; +import { walletHomeOnboardingPrimaryLabelForStep } from './walletHomeOnboardingStepsStrings'; + +type GoToBuyFromRampNavigation = ReturnType< + typeof import('../Ramp/hooks/useRampNavigation').useRampNavigation +>['goToBuy']; + +/** + * Wraps `goToBuy` with {@link MetaMetricsEvents.RAMPS_BUTTON_CLICKED} for the wallet + * home onboarding fund step (TMCU-680: `location` = onboarding_checklist). + */ +export function useWalletHomeOnboardingChecklistFundPress( + goToBuy: GoToBuyFromRampNavigation, +): () => void { + const { trackEvent, createEventBuilder } = useAnalytics(); + const buttonClickData = useRampsButtonClickData(); + const rampUnifiedV1Enabled = useRampsUnifiedV1Enabled(); + const isV2UnifiedEnabled = useRampsUnifiedV2Enabled(); + const region = useSelector(getDetectedGeolocation); + + return useCallback(() => { + const rampType = isV2UnifiedEnabled + ? 'UNIFIED_BUY_2' + : rampUnifiedV1Enabled + ? 'UNIFIED_BUY' + : 'BUY'; + + trackEvent( + createEventBuilder(MetaMetricsEvents.RAMPS_BUTTON_CLICKED) + .addProperties({ + button_text: walletHomeOnboardingPrimaryLabelForStep('fund'), + location: ActionLocation.ONBOARDING_CHECKLIST, + ramp_type: rampType, + region, + ramp_routing: buttonClickData.ramp_routing, + is_authenticated: buttonClickData.is_authenticated, + preferred_provider: buttonClickData.preferred_provider, + order_count: buttonClickData.order_count, + }) + .build(), + ); + + goToBuy(); + }, [ + buttonClickData.is_authenticated, + buttonClickData.order_count, + buttonClickData.preferred_provider, + buttonClickData.ramp_routing, + createEventBuilder, + goToBuy, + isV2UnifiedEnabled, + rampUnifiedV1Enabled, + region, + trackEvent, + ]); +} diff --git a/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistHomeViewed.test.ts b/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistHomeViewed.test.ts new file mode 100644 index 00000000000..5e925f04195 --- /dev/null +++ b/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistHomeViewed.test.ts @@ -0,0 +1,172 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { MetaMetricsEvents } from '../../../core/Analytics'; +import { WALLET_HOME_ONBOARDING_CHECKLIST_INTERACTION_TYPE } from './walletHomeOnboardingChecklistAnalytics'; +import { useWalletHomeOnboardingChecklistHomeViewed } from './useWalletHomeOnboardingChecklistHomeViewed'; + +const mockTrackEvent = jest.fn(); +const mockBuild = jest.fn(() => ({ builtEvent: true })); +const mockAddProperties = jest.fn(() => ({ build: mockBuild })); +const mockCreateEventBuilder = jest.fn(() => ({ + addProperties: mockAddProperties, +})); + +jest.mock('../../hooks/useAnalytics/useAnalytics', () => ({ + useAnalytics: () => ({ + trackEvent: mockTrackEvent, + createEventBuilder: mockCreateEventBuilder, + }), +})); + +const mockUseHomepageScrollContext = jest.fn(); + +jest.mock('../../Views/Homepage/context/HomepageScrollContext', () => ({ + useHomepageScrollContext: () => mockUseHomepageScrollContext(), +})); + +describe('useWalletHomeOnboardingChecklistHomeViewed', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockUseHomepageScrollContext.mockReturnValue({ + entryPoint: 'app_opened', + visitId: 1, + appSessionId: 'session-abc', + }); + }); + + it('does not fire when visitId is 0', () => { + mockUseHomepageScrollContext.mockReturnValue({ + entryPoint: 'app_opened', + visitId: 0, + appSessionId: 'session-abc', + }); + + renderHook(() => + useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance: false, + stepIndex: 0, + isFocused: true, + }), + ); + + expect(mockTrackEvent).not.toHaveBeenCalled(); + }); + + it('does not fire when screen is not focused', () => { + renderHook(() => + useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance: false, + stepIndex: 0, + isFocused: false, + }), + ); + + expect(mockTrackEvent).not.toHaveBeenCalled(); + }); + + it('fires HOME_VIEWED with onboarding_checklist and on_ramp for fund step', () => { + renderHook(() => + useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance: false, + stepIndex: 0, + isFocused: true, + }), + ); + + expect(mockCreateEventBuilder).toHaveBeenCalledWith( + MetaMetricsEvents.HOME_VIEWED, + ); + expect(mockAddProperties).toHaveBeenCalledWith({ + interaction_type: WALLET_HOME_ONBOARDING_CHECKLIST_INTERACTION_TYPE, + location: 'home', + section_name: 'on_ramp', + section_index: 0, + total_sections_loaded: 3, + is_empty: false, + item_count: 1, + entry_point: 'app_opened', + app_session_id: 'session-abc', + visit_number: 1, + }); + expect(mockTrackEvent).toHaveBeenCalled(); + }); + + it('fires section_name first_trade when stepIndex is trade', () => { + renderHook(() => + useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance: false, + stepIndex: 1, + isFocused: true, + }), + ); + + expect(mockAddProperties).toHaveBeenCalledWith( + expect.objectContaining({ + section_name: 'first_trade', + section_index: 1, + }), + ); + }); + + it('fires section_name notifications on last step', () => { + renderHook(() => + useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance: false, + stepIndex: 2, + isFocused: true, + }), + ); + + expect(mockAddProperties).toHaveBeenCalledWith( + expect.objectContaining({ + section_name: 'notifications', + section_index: 2, + }), + ); + }); + + it('refires when visitId increments', () => { + const { rerender } = renderHook(() => + useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance: false, + stepIndex: 0, + isFocused: true, + }), + ); + + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + + mockUseHomepageScrollContext.mockReturnValue({ + entryPoint: 'home_tab', + visitId: 2, + appSessionId: 'session-abc', + }); + + rerender(); + + expect(mockTrackEvent).toHaveBeenCalledTimes(2); + expect(mockAddProperties).toHaveBeenLastCalledWith( + expect.objectContaining({ + visit_number: 2, + entry_point: 'home_tab', + }), + ); + }); + + it('does not duplicate fire for the same visit and step', () => { + const { rerender } = renderHook( + ({ stepIndex }) => + useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance: false, + stepIndex, + isFocused: true, + }), + { initialProps: { stepIndex: 0 } }, + ); + + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + + rerender({ stepIndex: 0 }); + + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + }); +}); diff --git a/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistHomeViewed.ts b/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistHomeViewed.ts new file mode 100644 index 00000000000..82e8c6d4ef7 --- /dev/null +++ b/app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistHomeViewed.ts @@ -0,0 +1,82 @@ +import { useEffect, useRef } from 'react'; +import { MetaMetricsEvents } from '../../../core/Analytics'; +import { useAnalytics } from '../../hooks/useAnalytics/useAnalytics'; +import { useHomepageScrollContext } from '../../Views/Homepage/context/HomepageScrollContext'; +import { + WALLET_HOME_ONBOARDING_VISIBLE_STEPS, + walletHomeOnboardingCappedVisualStepIndex, +} from './walletHomeOnboardingStepsModel'; +import { + WALLET_HOME_ONBOARDING_CHECKLIST_INTERACTION_TYPE, + walletHomeOnboardingStepKindToHomeViewedSectionName, +} from './walletHomeOnboardingChecklistAnalytics'; + +/** + * Fires {@link MetaMetricsEvents.HOME_VIEWED} when the user views a checklist step + * (`interaction_type`: onboarding_checklist, `section_name` per step). + * + * Aligns core props with {@link useHomeViewedEvent} / homepage scroll context. + * + * @see https://consensyssoftware.atlassian.net/browse/TMCU-680 + */ +export function useWalletHomeOnboardingChecklistHomeViewed({ + isAwaitingBalance, + stepIndex, + isFocused, +}: { + isAwaitingBalance: boolean; + stepIndex: number; + isFocused: boolean; +}): void { + const { entryPoint, visitId, appSessionId } = useHomepageScrollContext(); + const { trackEvent, createEventBuilder } = useAnalytics(); + const lastFiredVisitStepKeyRef = useRef(null); + + useEffect(() => { + lastFiredVisitStepKeyRef.current = null; + }, [visitId]); + + useEffect(() => { + if (!isFocused || visitId === 0) { + return; + } + + const displayIdx = isAwaitingBalance ? 0 : stepIndex; + const cappedVisual = walletHomeOnboardingCappedVisualStepIndex(displayIdx); + const stepKind = WALLET_HOME_ONBOARDING_VISIBLE_STEPS[cappedVisual].kind; + const sectionName = + walletHomeOnboardingStepKindToHomeViewedSectionName(stepKind); + + const dedupeKey = `${visitId}:${cappedVisual}`; + if (lastFiredVisitStepKeyRef.current === dedupeKey) { + return; + } + lastFiredVisitStepKeyRef.current = dedupeKey; + + trackEvent( + createEventBuilder(MetaMetricsEvents.HOME_VIEWED) + .addProperties({ + interaction_type: WALLET_HOME_ONBOARDING_CHECKLIST_INTERACTION_TYPE, + location: 'home', + section_name: sectionName, + section_index: cappedVisual, + total_sections_loaded: WALLET_HOME_ONBOARDING_VISIBLE_STEPS.length, + is_empty: isAwaitingBalance && cappedVisual === 0, + item_count: 1, + entry_point: entryPoint, + app_session_id: appSessionId, + visit_number: visitId, + }) + .build(), + ); + }, [ + appSessionId, + createEventBuilder, + entryPoint, + isAwaitingBalance, + isFocused, + stepIndex, + trackEvent, + visitId, + ]); +} diff --git a/app/components/UI/WalletHomeOnboardingSteps/walletHomeOnboardingChecklistAnalytics.ts b/app/components/UI/WalletHomeOnboardingSteps/walletHomeOnboardingChecklistAnalytics.ts new file mode 100644 index 00000000000..e4c54efa2ec --- /dev/null +++ b/app/components/UI/WalletHomeOnboardingSteps/walletHomeOnboardingChecklistAnalytics.ts @@ -0,0 +1,34 @@ +import type { WalletHomeOnboardingStepKind } from './walletHomeOnboardingStepsModel'; + +/** `HOME_VIEWED.interaction_type` when the wallet home onboarding checklist is shown. */ +export const WALLET_HOME_ONBOARDING_CHECKLIST_INTERACTION_TYPE = + 'onboarding_checklist' as const; + +/** + * `HOME_VIEWED.section_name` per checklist step (Segment / analytics). + * @see https://consensyssoftware.atlassian.net/browse/TMCU-680 + */ +export const WalletHomeOnboardingChecklistSectionName = { + ON_RAMP: 'on_ramp', + FIRST_TRADE: 'first_trade', + NOTIFICATIONS: 'notifications', +} as const; + +export type WalletHomeOnboardingChecklistSectionNameKey = + (typeof WalletHomeOnboardingChecklistSectionName)[keyof typeof WalletHomeOnboardingChecklistSectionName]; + +/** + * Maps checklist step kind to `section_name` on {@link MetaMetricsEvents.HOME_VIEWED}. + */ +export function walletHomeOnboardingStepKindToHomeViewedSectionName( + kind: WalletHomeOnboardingStepKind, +): WalletHomeOnboardingChecklistSectionNameKey { + switch (kind) { + case 'fund': + return WalletHomeOnboardingChecklistSectionName.ON_RAMP; + case 'trade': + return WalletHomeOnboardingChecklistSectionName.FIRST_TRADE; + case 'notifications': + return WalletHomeOnboardingChecklistSectionName.NOTIFICATIONS; + } +} diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index 6b50cb16dbb..8a3937513e7 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -749,6 +749,16 @@ const Wallet = ({ sourcePage: 'MainView', }); + /** Trade checklist primary — Segment location per TMCU-680. */ + const goToSwapsFromOnboardingChecklist = useCallback(() => { + goToSwaps( + undefined, + undefined, + undefined, + undefined, + ActionLocation.ONBOARDING_CHECKLIST, + ); + }, [goToSwaps]); const handleWalletHomeOnboardingNotificationsPrimary = useCallback(() => { navigation.navigate(Routes.SETTINGS_VIEW, { screen: Routes.SETTINGS.NOTIFICATIONS, @@ -1445,30 +1455,33 @@ const Wallet = ({ ); + /** Same wiring as legacy `content` cluster — homepage v1 header paths must hide main actions and pass checklist callbacks. */ + const walletHomeAccountGroupBalanceProps = { + onCoordinatedFlowExit: runWalletHomePostOnboardingComplete, + suspendRiveForCurtain: postOnboardingExitAnimating, + onTradePrimaryPress: goToSwapsFromOnboardingChecklist, + onNotificationsPrimaryPress: handleWalletHomeOnboardingNotificationsPrimary, + }; + + const walletHomeMainAssetDetailsActions = showWalletHomeMainActions ? ( + + ) : null; + const portfolioHeaderBase = ( <> {bannerContent} - - {showWalletHomeMainActions ? ( - - ) : null} + + {walletHomeMainAssetDetailsActions} {isCarouselBannersEnabled && } {isMoneyHomeScreenEnabled && } @@ -1478,28 +1491,9 @@ const Wallet = ({ <> {bannerContent} - + - {showWalletHomeMainActions ? ( - - ) : null} + {walletHomeMainAssetDetailsActions} {isCarouselBannersEnabled && } {isMoneyHomeScreenEnabled && } @@ -1530,30 +1524,9 @@ const Wallet = ({ testID={WalletViewSelectorsIDs.WALLET_TOP_CLUSTER_INNER} style={styles.walletTopCluster} > - + - {showWalletHomeMainActions ? ( - - ) : null} + {walletHomeMainAssetDetailsActions} { expect(ActionLocation.HOME).toBe('home'); expect(ActionLocation.ASSET_DETAILS).toBe('asset details'); expect(ActionLocation.NAVBAR).toBe('navbar'); + expect(ActionLocation.ONBOARDING_CHECKLIST).toBe('onboarding_checklist'); }); it('covers all location types', () => { // Given: all expected location types - const expectedLocations = ['home', 'asset details', 'navbar']; + const expectedLocations = [ + 'home', + 'asset details', + 'navbar', + 'onboarding_checklist', + ]; // When: checking enum values const actualLocations = Object.values(ActionLocation); @@ -94,7 +100,7 @@ describe('actionButtonTracking', () => { expect(actualLocations).toEqual( expect.arrayContaining(expectedLocations), ); - expect(actualLocations).toHaveLength(3); + expect(actualLocations).toHaveLength(4); }); }); diff --git a/app/util/analytics/actionButtonTracking.ts b/app/util/analytics/actionButtonTracking.ts index a2be268bcde..b745090c595 100644 --- a/app/util/analytics/actionButtonTracking.ts +++ b/app/util/analytics/actionButtonTracking.ts @@ -5,6 +5,8 @@ export enum ActionLocation { HOME = 'home', ASSET_DETAILS = 'asset details', NAVBAR = 'navbar', + /** Wallet home post-onboarding checklist (TMCU-680). */ + ONBOARDING_CHECKLIST = 'onboarding_checklist', } export enum ActionButtonType {