Skip to content

Commit 6abfeee

Browse files
fix(social-ai): make back from notification-driven trader position behave naturally
When a TraderPositionView was opened from a notification, pressing back used to slide TraderProfileView in from the right (a forward animation), because handleBack fell back to navigation.replace. Replace that fallback with a plain navigation.goBack() and stop trying to synthesize a Profile underneath in the deeplink handler. Back now returns to wherever the user was before opening the position: - cold-start push: Wallet Home - in-app notification panel tap: Notifications panel - in-app row tap from Profile: Profile (unchanged) The trader's name in the position header remains the affordance for navigating onward to the trader's Profile. Also: assign the two social deeplink handlers to @MetaMask/social-ai in CODEOWNERS. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent c1071c4 commit 6abfeee

6 files changed

Lines changed: 58 additions & 94 deletions

File tree

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ app/core/Engine/messengers/social-controller-messenger* @MetaMask/s
217217
app/core/Engine/messengers/social-service-messenger* @MetaMask/social-ai
218218
app/core/Engine/controllers/ai-digest-controller-init* @MetaMask/social-ai
219219
app/core/Engine/messengers/ai-digest-controller-messenger* @MetaMask/social-ai
220+
app/core/DeeplinkManager/handlers/legacy/handleSocialLeaderboardUrl.ts @MetaMask/social-ai
221+
app/core/DeeplinkManager/handlers/legacy/handleSocialTraderPositionUrl.ts @MetaMask/social-ai
220222
app/selectors/featureFlagController/socialLeaderboard/ @MetaMask/social-ai
221223
app/selectors/featureFlagController/marketInsights/ @MetaMask/social-ai
222224
app/selectors/featureFlagController/whatsHappening/ @MetaMask/social-ai

app/components/Views/SocialLeaderboard/TraderPositionView/TraderPositionView.test.tsx

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@ import TraderPositionView from './TraderPositionView';
66
import { TraderPositionViewSelectorsIDs } from './TraderPositionView.testIds';
77
import type { Position, Trade } from '@metamask/social-controllers';
88
import { handleFetch } from '@metamask/controller-utils';
9-
import Routes from '../../../../constants/navigation/Routes';
109
import ClipboardManager from '../../../../core/ClipboardManager';
1110

1211
const mockGoBack = jest.fn();
1312
const mockNavigate = jest.fn();
14-
const mockReplace = jest.fn();
15-
const mockGetState = jest.fn();
1613
const mockGetAssetImageUrl = jest.fn();
1714
const mockHandleFetch = handleFetch as jest.MockedFunction<typeof handleFetch>;
1815
const mockPriceChart = jest.fn();
@@ -193,8 +190,6 @@ jest.mock('@react-navigation/native', () => {
193190
useNavigation: () => ({
194191
goBack: mockGoBack,
195192
navigate: mockNavigate,
196-
replace: mockReplace,
197-
getState: mockGetState,
198193
}),
199194
useRoute: () => ({
200195
params: mockRouteParams,
@@ -217,7 +212,6 @@ describe('TraderPositionView', () => {
217212
jest.clearAllMocks();
218213
mockRefetchPosition.mockResolvedValue(undefined);
219214
mockRefreshProfile.mockResolvedValue(undefined);
220-
mockGetState.mockReturnValue({ routes: [{ name: 'Home' }], index: 0 });
221215
mockHandleFetch.mockResolvedValue({});
222216
global.fetch = jest.fn().mockResolvedValue({
223217
ok: true,
@@ -251,36 +245,7 @@ describe('TraderPositionView', () => {
251245
expect(screen.getByText('No trades for this interval')).toBeOnTheScreen();
252246
});
253247

254-
it('replaces the current screen with the trader profile when the back button is pressed and no profile is in the back stack (deeplink)', () => {
255-
// default mockGetState has no profile route behind the position screen
256-
renderWithProvider(<TraderPositionView />, { state: mockState });
257-
258-
fireEvent.press(
259-
screen.getByTestId(TraderPositionViewSelectorsIDs.BACK_BUTTON),
260-
);
261-
262-
expect(mockReplace).toHaveBeenCalledWith(
263-
Routes.SOCIAL_LEADERBOARD.PROFILE,
264-
{
265-
traderId: 'trader-1',
266-
traderName: 'dutchiono',
267-
},
268-
);
269-
expect(mockNavigate).not.toHaveBeenCalled();
270-
expect(mockGoBack).not.toHaveBeenCalled();
271-
});
272-
273-
it('calls goBack when the back button is pressed and the trader profile is already in the stack', () => {
274-
mockGetState.mockReturnValue({
275-
routes: [
276-
{ name: 'Home' },
277-
{ name: Routes.SOCIAL_LEADERBOARD.VIEW },
278-
{ name: Routes.SOCIAL_LEADERBOARD.PROFILE },
279-
{ name: Routes.SOCIAL_LEADERBOARD.POSITION },
280-
],
281-
index: 3,
282-
});
283-
248+
it('calls goBack when the back button is pressed', () => {
284249
renderWithProvider(<TraderPositionView />, { state: mockState });
285250

286251
fireEvent.press(
@@ -289,7 +254,6 @@ describe('TraderPositionView', () => {
289254

290255
expect(mockGoBack).toHaveBeenCalledTimes(1);
291256
expect(mockNavigate).not.toHaveBeenCalled();
292-
expect(mockReplace).not.toHaveBeenCalled();
293257
});
294258

295259
it('renders the fallback when position is undefined and no positionId is provided', () => {

app/components/Views/SocialLeaderboard/TraderPositionView/TraderPositionView.tsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
} from '../../../../component-library/components/Toast';
2424
import { IconName as ComponentLibraryIconName } from '../../../../component-library/components/Icons/Icon';
2525
import ClipboardManager from '../../../../core/ClipboardManager';
26-
import Routes from '../../../../constants/navigation/Routes';
2726
import { TraderPositionViewSelectorsIDs } from './TraderPositionView.testIds';
2827
import { useTheme } from '../../../../util/theme';
2928
import QuickBuyBottomSheet from './components/QuickBuyBottomSheet';
@@ -114,22 +113,13 @@ const TraderPositionView = () => {
114113
}
115114
}, [refetchPosition, refreshProfile]);
116115

116+
// Plain goBack: returns to whatever the user was on before opening this
117+
// screen — Profile (in-app row tap), Wallet Home (cold-start push), or the
118+
// Notifications panel (in-app notification tap). The trader's name in the
119+
// header is the affordance for navigating onward to Profile.
117120
const handleBack = useCallback(() => {
118-
const state = navigation.getState();
119-
const previousRoute = state?.routes[state.index - 1];
120-
121-
if (previousRoute?.name === Routes.SOCIAL_LEADERBOARD.PROFILE) {
122-
// Normal flow: profile is already in the stack, goes back to it
123-
navigation.goBack();
124-
} else {
125-
// Deeplink flow: position was opened directly. Replace position with
126-
// profile so pressing back from profile doesn't return to position.
127-
navigation.replace(Routes.SOCIAL_LEADERBOARD.PROFILE, {
128-
traderId,
129-
traderName,
130-
});
131-
}
132-
}, [navigation, traderId, traderName]);
121+
navigation.goBack();
122+
}, [navigation]);
133123

134124
const handleCopyTokenAddress = useCallback(async () => {
135125
if (!resolvedPosition?.tokenAddress) {

app/components/Views/SocialLeaderboard/TraderProfileView/TraderProfileView.tsx

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,57 @@
1-
import React, { useCallback, useMemo, useRef, useState } from 'react';
2-
import { RefreshControl, TouchableOpacity } from 'react-native';
3-
import { ScrollView } from 'react-native-gesture-handler';
41
import {
5-
ImpactMoment,
6-
playImpact,
7-
playSelection,
8-
} from '../../../../util/haptics';
2+
Box,
3+
BoxAlignItems,
4+
BoxFlexDirection,
5+
BoxJustifyContent,
6+
Button,
7+
ButtonIcon,
8+
ButtonIconSize,
9+
ButtonVariant,
10+
FontWeight,
11+
IconName,
12+
Text,
13+
TextColor,
14+
TextVariant,
15+
} from '@metamask/design-system-react-native';
16+
import { useTailwind } from '@metamask/design-system-twrnc-preset';
17+
import type { Position } from '@metamask/social-controllers';
918
import {
1019
useNavigation,
1120
useRoute,
1221
type NavigationProp,
1322
type RouteProp,
1423
} from '@react-navigation/native';
15-
import type { RootStackParamList } from '../../../../core/NavigationService/types';
24+
import React, { useCallback, useMemo, useRef, useState } from 'react';
25+
import { RefreshControl, TouchableOpacity } from 'react-native';
26+
import { ScrollView } from 'react-native-gesture-handler';
1627
import { SafeAreaView } from 'react-native-safe-area-context';
17-
import { useTailwind } from '@metamask/design-system-twrnc-preset';
18-
import {
19-
Box,
20-
Text,
21-
TextVariant,
22-
TextColor,
23-
FontWeight,
24-
ButtonIcon,
25-
ButtonIconSize,
26-
IconName,
27-
BoxFlexDirection,
28-
BoxAlignItems,
29-
BoxJustifyContent,
30-
Button,
31-
ButtonVariant,
32-
} from '@metamask/design-system-react-native';
3328
import { strings } from '../../../../../locales/i18n';
3429
import Routes from '../../../../constants/navigation/Routes';
30+
import type { RootStackParamList } from '../../../../core/NavigationService/types';
31+
import {
32+
ImpactMoment,
33+
playImpact,
34+
playSelection,
35+
} from '../../../../util/haptics';
36+
import ErrorState from '../../Homepage/components/ErrorState/ErrorState';
37+
import { useNotificationPreferences } from '../NotificationPreferencesView/hooks';
3538
import { TraderProfileViewSelectorsIDs } from './TraderProfileView.testIds';
36-
import { useTraderProfile, useTraderPositions } from './hooks';
37-
import type { Position } from '@metamask/social-controllers';
38-
import ProfileHeader from './components/ProfileHeader';
39-
import StatsRow from './components/StatsRow';
4039
import PositionRow from './components/PositionRow';
40+
import ProfileHeader from './components/ProfileHeader';
41+
import {
42+
PositionRowSkeleton,
43+
ProfileHeaderSkeleton,
44+
StatsRowSkeleton,
45+
} from './components/Skeletons';
4146
import SortButton from './components/SortButton';
47+
import StatsRow from './components/StatsRow';
48+
import TopTradersNotificationsSetupBottomSheet, {
49+
type TopTradersNotificationsSetupBottomSheetRef,
50+
} from './components/TopTradersNotificationsSetupBottomSheet';
51+
import TraderNotificationsBottomSheet, {
52+
type TraderNotificationsBottomSheetRef,
53+
} from './components/TraderNotificationsBottomSheet';
54+
import { useTraderPositions, useTraderProfile } from './hooks';
4255
import {
4356
CLOSED_SORT_CYCLE,
4457
OPEN_SORT_CYCLE,
@@ -47,19 +60,6 @@ import {
4760
type OpenSortKey,
4861
type SortKey,
4962
} from './utils/sortPositions';
50-
import {
51-
ProfileHeaderSkeleton,
52-
StatsRowSkeleton,
53-
PositionRowSkeleton,
54-
} from './components/Skeletons';
55-
import ErrorState from '../../Homepage/components/ErrorState/ErrorState';
56-
import { useNotificationPreferences } from '../NotificationPreferencesView/hooks';
57-
import TraderNotificationsBottomSheet, {
58-
type TraderNotificationsBottomSheetRef,
59-
} from './components/TraderNotificationsBottomSheet';
60-
import TopTradersNotificationsSetupBottomSheet, {
61-
type TopTradersNotificationsSetupBottomSheetRef,
62-
} from './components/TopTradersNotificationsSetupBottomSheet';
6363

6464
const POSITION_SKELETON_COUNT = 4;
6565
const POSITION_SKELETON_KEYS = Array.from(

app/core/DeeplinkManager/handlers/legacy/__tests__/handleSocialTraderPositionUrl.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe('handleSocialTraderPositionUrl', () => {
3939
'?positionId=92d9001b-8b64-4b13-9c1b-ba9292a6099a&traderId=trader-1&deduplication_id=dedup-1&notification_event=follow_newtrade_buy',
4040
});
4141

42+
expect(mockNavigate).toHaveBeenCalledTimes(1);
4243
expect(mockNavigate).toHaveBeenCalledWith(
4344
Routes.SOCIAL_LEADERBOARD.POSITION,
4445
{
@@ -53,6 +54,7 @@ describe('handleSocialTraderPositionUrl', () => {
5354
actionPath: '?positionId=position-1&traderId=trader-1',
5455
});
5556

57+
expect(mockNavigate).toHaveBeenCalledTimes(1);
5658
expect(mockNavigate).toHaveBeenCalledWith(
5759
Routes.SOCIAL_LEADERBOARD.POSITION,
5860
{

app/core/DeeplinkManager/handlers/legacy/handleSocialTraderPositionUrl.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ export const handleSocialTraderPositionUrl = ({
8181
);
8282
}
8383

84+
// Push Position on top of whatever the user was on. Back returns there:
85+
// - cold-start push: returns to Wallet Home (the app's initial route).
86+
// - in-app notification tap: returns to the Notifications panel.
87+
// - row tap from Profile (in-app): returns to Profile.
88+
// The trader's name in the position header is the affordance for
89+
// navigating onward to the trader's Profile.
8490
NavigationService.navigation.navigate(Routes.SOCIAL_LEADERBOARD.POSITION, {
8591
positionId,
8692
traderId,

0 commit comments

Comments
 (0)