Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fc4cbd4
feat: implement wallet home onboarding steps functionality
PatrykLucka Apr 15, 2026
6632528
refactor: update WalletHomeOnboardingSteps tests to use toBeOnTheScre…
PatrykLucka Apr 15, 2026
c335185
feat: add onboarding checklist animation and update WalletHomeOnboard…
PatrykLucka Apr 20, 2026
abfc11d
feat: enhance AccountGroupBalance and WalletHomeOnboardingSteps with …
PatrykLucka Apr 27, 2026
b86fe96
feat: enhance onboarding actions and components with skipInitialBalan…
PatrykLucka Apr 30, 2026
77ec4e7
refactor: update AccountGroupBalance and WalletHomeOnboarding tests t…
PatrykLucka Apr 30, 2026
943b267
refactor: remove unused props and layout effects from AccountGroupBal…
PatrykLucka Apr 30, 2026
a60d9fa
refactor: simplify mock implementation for wallet post-onboarding che…
PatrykLucka Apr 30, 2026
ac79179
feat: add onTradePrimaryPress and onNotificationsPrimaryPress handler…
PatrykLucka Apr 30, 2026
9f4a8a1
feat: enhance onboarding success flow with error logging for account …
PatrykLucka Apr 30, 2026
4e21447
refactor: improve logic for static step body in WalletHomeOnboardingS…
PatrykLucka Apr 30, 2026
308b9fd
feat: enhance WalletHomeOnboardingSteps with deferred navigation hand…
PatrykLucka May 4, 2026
c711907
refactor: streamline Wallet post-onboarding checklist tests by replac…
PatrykLucka May 4, 2026
8b96395
refactor: simplify state management in AccountGroupBalance and enhanc…
PatrykLucka May 5, 2026
44bcf82
feat: integrate useRampNavigation into AccountGroupBalance and Wallet…
PatrykLucka May 5, 2026
cef8fdb
chore: merge origin/main into TMCU-610-shadowbox-checklist-logic and …
Copilot May 6, 2026
d64c94f
feat(analytics): onboarding checklist Segment events (TMCU-680)
wachunei May 6, 2026
475e44e
fix(wallet): wire post-onboarding checklist props on homepage v1 header
wachunei May 6, 2026
3379393
fix: ensure buttons are enabled during deferred navigation by resetti…
PatrykLucka May 7, 2026
e97cc13
Merge branch 'TMCU-610-shadowbox-checklist-logic' of github.com:MetaM…
PatrykLucka May 7, 2026
09ad2c7
chore: format fix
PatrykLucka May 7, 2026
0a5591f
refactor: remove unused onboarding selectors and dispatch calls in Ac…
PatrykLucka May 7, 2026
b186d93
refactor: update AccountGroupBalance and AssetDetailsActions componen…
PatrykLucka May 7, 2026
bc4fe2c
merge: resolve conflicts with TMCU-610 base branch
Copilot May 7, 2026
b8a5fb5
Merge branch 'main' into TMCU-610-shadowbox-checklist-logic
PatrykLucka May 8, 2026
ee0f7bc
Merge branch 'main' into TMCU-610-shadowbox-checklist-logic
PatrykLucka May 8, 2026
db8bfd6
Merge branch 'TMCU-610-shadowbox-checklist-logic' into feat/TMCU-680-…
wachunei May 8, 2026
0fd1a8f
merge: resolve conflicts with main
Copilot May 8, 2026
1875af5
Merge branch 'main' into feat/TMCU-680-onboarding-checklist-segment-e…
wachunei May 12, 2026
b691028
Merge branch 'main' into feat/TMCU-680-onboarding-checklist-segment-e…
PatrykLucka May 13, 2026
d520637
Merge branch 'main' into feat/TMCU-680-onboarding-checklist-segment-e…
wachunei May 13, 2026
5ab8549
Merge branch 'main' into feat/TMCU-680-onboarding-checklist-segment-e…
wachunei May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -266,7 +269,7 @@ const AccountGroupBalance = ({
isAwaitingBalance={awaitBalanceForPostOnboardingSteps}
onCoordinatedFlowExit={onCoordinatedFlowExit}
suspendRiveForCurtain={suspendRiveForCurtain}
onFundPrimaryPress={goToBuy}
onFundPrimaryPress={onFundPrimaryPressWithChecklistAnalytics}
canAdvanceFundStepAfterBalance={canAdvanceFundStepAfterBalance}
onTradePrimaryPress={onTradePrimaryPress}
onNotificationsPrimaryPress={onNotificationsPrimaryPress}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -381,13 +388,17 @@ export const useSwapBridgeNavigation = ({
destTokenOverride?: BridgeToken,
buttonLabel?: string,
scrollToTopOnNav?: boolean,
swapButtonClickLocationOverride?:
| ActionLocation
| SwapBridgeNavigationLocation,
) => {
goToNativeBridge(
BridgeViewMode.Unified,
tokenOverride,
destTokenOverride,
buttonLabel,
scrollToTopOnNav,
swapButtonClickLocationOverride,
);
},
[goToNativeBridge],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
walletHomeOnboardingSubtitleForStep,
walletHomeOnboardingTitleForStep,
} from './walletHomeOnboardingStepsStrings';
import { useWalletHomeOnboardingChecklistHomeViewed } from './useWalletHomeOnboardingChecklistHomeViewed';

const SLIDE_DISTANCE = Dimensions.get('window').width;

Expand Down Expand Up @@ -133,6 +134,12 @@ const WalletHomeOnboardingSteps: React.FC<WalletHomeOnboardingStepsProps> = ({
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 =
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
}),
);
});
});
Original file line number Diff line number Diff line change
@@ -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';

Check warning on line 34 in app/components/UI/WalletHomeOnboardingSteps/useWalletHomeOnboardingChecklistFundPress.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ4cizVXPpyc3SpV7UuJ&open=AZ4cizVXPpyc3SpV7UuJ&pullRequest=29824

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,
]);
}
Loading
Loading