Skip to content

Commit 7ec36f0

Browse files
authored
chore: explore v2 nit fixes (#29720)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** ### 🔗 Browser URL Display Improvements - **Enhanced URL display for user favorites and recents**: Updated browser favorites and recents to show more informative display URLs that include the full path and query parameters (e.g., `app.uniswap.org/swap?chain=1` instead of just `uniswap.org`) - **Maintained clean URLs for curated content**: Kept hostname-only display for curated site cards to preserve clean domain labels - **Added comprehensive test coverage**: Included new unit tests to verify the full path display functionality works correctly for both favorites and recents ### 🏈 Sports Section UI/UX Enhancements - **Renamed "Soccer" to "Football"**: Updated the label key from `trending.soccer` to `trending.football` for more universal terminology - **Improved Sports Tab list rendering**: Changed from `PredictionCarouselRowItem` to `PredictMarket` component for better visual consistency in the active sports markets list ### ⚡ Performance Optimizations - **Refactored Sports Tab architecture**: Converted from nested scroll structure to a single `FlashList` with header and footer components for better performance - **Added pull-to-refresh functionality**: Implemented `RefreshControl` for improved user interaction in the Sports Tab - **Streamlined layout structure**: Eliminated nested scrolling components that could cause performance issues ### 🎯 Filter Propagation Enhancement - **Enhanced perps navigation filtering**: Added ability to track selected pill state in `PillToggleCardList` component and propagate the active filter when navigating from Macro/RWA tabs to the perps market list - **Improved user experience**: Users now maintain their filter selection context when drilling down from trending sections to detailed market views - **Added callback support**: Extended `PillToggleCardList` with `onPillChange` callback to enable parent components to track active selections ### 🧪 Testing & Code Quality - **Added new utility functions**: Introduced `extractFullDisplayUrl` function with proper documentation and test coverage - **Enhanced test suites**: Updated and expanded test coverage for browser sites functionality - **Maintained backward compatibility**: All changes preserve existing functionality while adding new features <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: solved nit issues in explore v2 ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3145 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk due to a non-trivial refactor of the Sports tab to a single `FlashList` with new refresh/load-more wiring, plus changes that affect perps navigation filtering behavior. URL display changes are low risk but touch shared site/query matching utilities. > > **Overview** > Improves Explore URL labeling by introducing `extractFullDisplayUrl` and switching browser **favorites** and **recents** to display host+path+query (while keeping curated site cards on hostname-only `extractDisplayUrl`), with updated/added unit tests. > > Enhances Trending Explore UX by refactoring `SportsTab` into a single `FlashList` with header/footer, pull-to-refresh, and using `PredictMarket` rows for the “All Sports” list; also renames the soccer label key to `trending.football`. > > Adds an `onPillChange` callback to `PillToggleCardList` and updates `MacroTab`/`RwasTab` to pass the active pill as a filter when navigating to the perps market list. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 41f7071. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent dd0a6ab commit 7ec36f0

11 files changed

Lines changed: 256 additions & 132 deletions

File tree

app/components/UI/Sites/hooks/useBrowserFavoritesSites/useBrowserFavoritesSites.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const setBookmarks = (bookmarks: { url: string; name: string }[]) => {
1616
describe('useBrowserFavoritesSites', () => {
1717
beforeEach(() => mockUseSelector.mockReset());
1818

19-
it('maps bookmarks to SiteData with prefixed URL and extracted display URL', () => {
19+
it('maps bookmarks to SiteData with prefixed URL and full display URL', () => {
2020
setBookmarks([{ url: 'uniswap.org', name: 'Uniswap' }]);
2121
const { result } = renderHook(() => useBrowserFavoritesSites());
2222

@@ -31,6 +31,16 @@ describe('useBrowserFavoritesSites', () => {
3131
);
3232
});
3333

34+
it('shows full path in displayUrl for favorites', () => {
35+
setBookmarks([
36+
{ url: 'https://app.uniswap.org/swap?chain=1', name: 'Uniswap' },
37+
]);
38+
const { result } = renderHook(() => useBrowserFavoritesSites());
39+
expect(result.current.data[0].displayUrl).toBe(
40+
'app.uniswap.org/swap?chain=1',
41+
);
42+
});
43+
3444
it('falls back to the display URL when the bookmark has no name', () => {
3545
setBookmarks([{ url: 'https://example.com/path', name: '' }]);
3646
const { result } = renderHook(() => useBrowserFavoritesSites());

app/components/UI/Sites/hooks/useBrowserFavoritesSites/useBrowserFavoritesSites.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { selectBrowserBookmarksWithType } from '../../../../../selectors/browser
55
import type { SiteData } from '../../components/SiteRowItem/SiteRowItem';
66
import {
77
extractDisplayUrl,
8+
extractFullDisplayUrl,
89
matchesSiteQuery,
910
} from '../useSiteData/useSitesData';
1011

@@ -19,7 +20,7 @@ const toSiteData = (entry: BookmarkEntry, index: number): SiteData => {
1920
id: `browser-favorite-${url}-${index}`,
2021
name: entry.name?.trim() || extractDisplayUrl(url),
2122
url,
22-
displayUrl: extractDisplayUrl(url),
23+
displayUrl: extractFullDisplayUrl(url),
2324
storedBookmarkUrl: entry.url,
2425
};
2526
};

app/components/UI/Sites/hooks/useBrowserRecentsSites/useBrowserRecentsSites.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ describe('useBrowserRecentsSites', () => {
8585
setHistory([{ url: 'https://example.com/path', name: '' }]);
8686
const { result } = renderHook(() => useBrowserRecentsSites());
8787
expect(result.current.data[0].name).toBe('example.com');
88-
expect(result.current.data[0].displayUrl).toBe('example.com');
88+
expect(result.current.data[0].displayUrl).toBe('example.com/path');
89+
});
90+
91+
it('shows full path in displayUrl for recents', () => {
92+
setHistory([
93+
{ url: 'https://app.uniswap.org/swap?chain=1', name: 'Uniswap' },
94+
]);
95+
const { result } = renderHook(() => useBrowserRecentsSites());
96+
expect(result.current.data[0].displayUrl).toBe(
97+
'app.uniswap.org/swap?chain=1',
98+
);
8999
});
90100
});

app/components/UI/Sites/hooks/useBrowserRecentsSites/useBrowserRecentsSites.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { useSelector } from 'react-redux';
33
import { prefixUrlWithProtocol } from '../../../../../util/browser';
44
import { selectBrowserHistoryWithType } from '../../../../../selectors/browser';
55
import type { SiteData } from '../../components/SiteRowItem/SiteRowItem';
6-
import { extractDisplayUrl } from '../useSiteData/useSitesData';
6+
import {
7+
extractDisplayUrl,
8+
extractFullDisplayUrl,
9+
} from '../useSiteData/useSitesData';
710

811
const MAX_RECENT_SITES = 5;
912

@@ -29,7 +32,7 @@ const toSiteData = (entry: HistoryEntry, index: number): SiteData => {
2932
id: `browser-recent-${normalizeUrlKey(url)}-${index}`,
3033
name: entry.name?.trim() || extractDisplayUrl(url),
3134
url,
32-
displayUrl: extractDisplayUrl(url),
35+
displayUrl: extractFullDisplayUrl(url),
3336
};
3437
};
3538

app/components/UI/Sites/hooks/useSiteData/useSitesData.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
useSitesData,
44
clearSitesCache,
55
matchesSiteQuery,
6+
extractFullDisplayUrl,
67
} from './useSitesData';
78
import type { SiteData } from '../../components/SiteRowItem/SiteRowItem';
89
import Logger from '../../../../../util/Logger';
@@ -336,6 +337,28 @@ describe('useSitesData', () => {
336337
});
337338
});
338339

340+
describe('extractFullDisplayUrl', () => {
341+
it('strips the protocol and keeps path + query', () => {
342+
expect(extractFullDisplayUrl('https://app.uniswap.org/swap?chain=1')).toBe(
343+
'app.uniswap.org/swap?chain=1',
344+
);
345+
});
346+
347+
it('strips www prefix', () => {
348+
expect(extractFullDisplayUrl('https://www.example.com/path')).toBe(
349+
'example.com/path',
350+
);
351+
});
352+
353+
it('returns just the hostname when there is no path', () => {
354+
expect(extractFullDisplayUrl('https://uniswap.org')).toBe('uniswap.org');
355+
});
356+
357+
it('returns the raw string for invalid URLs', () => {
358+
expect(extractFullDisplayUrl('not a url')).toBe('not a url');
359+
});
360+
});
361+
339362
describe('matchesSiteQuery', () => {
340363
const site: SiteData = {
341364
id: '1',

app/components/UI/Sites/hooks/useSiteData/useSitesData.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ const PORTFOLIO_SITE: SiteData = {
6161
};
6262

6363
/**
64-
* Helper function to extract display URL from full URL
64+
* Returns just the hostname (no www., no path) — used for curated site cards
65+
* where a clean domain label is preferred.
6566
*/
6667
export const extractDisplayUrl = (url: string): string => {
6768
try {
@@ -72,6 +73,21 @@ export const extractDisplayUrl = (url: string): string => {
7273
}
7374
};
7475

76+
/**
77+
* Returns the full URL without the protocol (e.g. `uniswap.org/swap?chain=1`).
78+
* Used for user-generated entries (recents, favorites) where the full path
79+
* provides meaningful context.
80+
*/
81+
export const extractFullDisplayUrl = (url: string): string => {
82+
try {
83+
const urlObj = new URL(url);
84+
const withoutProtocol = url.slice(urlObj.protocol.length + 2); // strip "scheme://"
85+
return withoutProtocol.replace(/^www\./, '');
86+
} catch {
87+
return url;
88+
}
89+
};
90+
7591
export const matchesSiteQuery = (site: SiteData, query: string): boolean => {
7692
const q = query.toLowerCase();
7793
return (

app/components/Views/TrendingView/components/PillToggleCardList.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export interface PillToggleCardListProps<T> {
1818
idPrefix: string;
1919
/** Defaults to first tab. */
2020
defaultPillKey?: string;
21+
/** Called whenever the active pill changes. */
22+
onPillChange?: (key: string) => void;
2123
testIdPrefix?: string;
2224
listTestId?: string;
2325
}
@@ -35,6 +37,7 @@ function PillToggleCardList<T>({
3537
Skeleton,
3638
idPrefix,
3739
defaultPillKey,
40+
onPillChange,
3841
testIdPrefix = DEFAULT_TEST_ID_PREFIX,
3942
listTestId,
4043
}: PillToggleCardListProps<T>) {
@@ -43,12 +46,17 @@ function PillToggleCardList<T>({
4346
const active = tabs.find((p) => p.key === activeKey) ?? tabs[0];
4447
const pills: PillOption[] = tabs.map(({ key, name }) => ({ key, name }));
4548

49+
const handleSelect = (key: string) => {
50+
setActiveKey(key);
51+
onPillChange?.(key);
52+
};
53+
4654
return (
4755
<Box testID={testIdPrefix} twClassName="mt-2 mb-9">
4856
<PillRow
4957
pills={pills}
5058
activeKey={activeKey}
51-
onSelect={setActiveKey}
59+
onSelect={handleSelect}
5260
testIdPrefix={testIdPrefix}
5361
/>
5462
<CardList<T>

app/components/Views/TrendingView/feeds/predictions/useSportsMarketsFeed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const PAGE_SIZE = 20;
1414
// add a row here AND a usePredictMarketData call below (Rules of Hooks).
1515
const SOCCER = {
1616
key: 'soccer',
17-
labelKey: 'trending.soccer',
17+
labelKey: 'trending.football',
1818
customQueryParams: 'tag_id=100350',
1919
} as const;
2020
const BASKETBALL = {

app/components/Views/TrendingView/tabs/MacroTab.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useMemo } from 'react';
1+
import React, { useCallback, useMemo, useRef } from 'react';
22
import { useNavigation, NavigationProp } from '@react-navigation/native';
33
import { useSelector } from 'react-redux';
44
import { Box } from '@metamask/design-system-react-native';
@@ -31,14 +31,15 @@ const PerpsRowSingleSkeleton: React.FC = () => <PerpsRowSkeleton count={1} />;
3131

3232
interface MacroPerpsBlockProps {
3333
refresh: TabProps['refresh'];
34-
onViewAll: () => void;
34+
onViewAll: (filter: string) => void;
3535
}
3636

3737
const MacroPerpsBlock: React.FC<MacroPerpsBlockProps> = ({
3838
refresh,
3939
onViewAll,
4040
}) => {
4141
const perps = usePerpsFeed({ variant: 'macro', refresh });
42+
const activePillKey = useRef<string>('stocks');
4243

4344
const tabs = useMemo<PillToggleCardListTab<PerpsMarketData>[]>(() => {
4445
const stocks = perps.data
@@ -74,7 +75,7 @@ const MacroPerpsBlock: React.FC<MacroPerpsBlockProps> = ({
7475
<Box>
7576
<SectionHeader
7677
title={strings('trending.macro_stocks_commodity_perps')}
77-
onViewAll={onViewAll}
78+
onViewAll={() => onViewAll(activePillKey.current)}
7879
testID="section-header-view-all-macro_stocks_commodity_perps"
7980
/>
8081
<PillToggleCardList<PerpsMarketData>
@@ -83,6 +84,9 @@ const MacroPerpsBlock: React.FC<MacroPerpsBlockProps> = ({
8384
renderItem={renderItem}
8485
Skeleton={PerpsRowSingleSkeleton}
8586
idPrefix="macro_stocks_commodity_perps"
87+
onPillChange={(key) => {
88+
activePillKey.current = key;
89+
}}
8690
testIdPrefix="macro-stocks-commodity-pills"
8791
listTestId="macro-stocks-commodity-perps-list"
8892
/>
@@ -137,7 +141,9 @@ const MacroTab: React.FC<TabProps> = ({ refresh, refreshing, onRefresh }) => {
137141
<PerpsSectionProvider>
138142
<MacroPerpsBlock
139143
refresh={refresh}
140-
onViewAll={() => navigateToPerpsMarketList(perpsNavigation)}
144+
onViewAll={(filter) =>
145+
navigateToPerpsMarketList(perpsNavigation, filter)
146+
}
141147
/>
142148
</PerpsSectionProvider>
143149
)}

app/components/Views/TrendingView/tabs/RwasTab.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useMemo } from 'react';
1+
import React, { useCallback, useMemo, useRef } from 'react';
22
import { useNavigation, NavigationProp } from '@react-navigation/native';
33
import { useSelector } from 'react-redux';
44
import { Box } from '@metamask/design-system-react-native';
@@ -38,14 +38,15 @@ const PerpsRowSingleSkeleton: React.FC = () => <PerpsRowSkeleton count={1} />;
3838

3939
interface RwaPerpsBlockProps {
4040
refresh: TabProps['refresh'];
41-
onViewAll: () => void;
41+
onViewAll: (filter: string) => void;
4242
}
4343

4444
const RwaPerpsBlock: React.FC<RwaPerpsBlockProps> = ({
4545
refresh,
4646
onViewAll,
4747
}) => {
4848
const perps = usePerpsFeed({ variant: 'rwa', refresh });
49+
const activePillKey = useRef<string>('commodities');
4950

5051
const tabs = useMemo<PillToggleCardListTab<PerpsMarketData>[]>(() => {
5152
const byType = (type: PerpsMarketData['marketType']) =>
@@ -83,7 +84,7 @@ const RwaPerpsBlock: React.FC<RwaPerpsBlockProps> = ({
8384
<Box>
8485
<SectionHeader
8586
title={strings('trending.rwa_perps_section')}
86-
onViewAll={onViewAll}
87+
onViewAll={() => onViewAll(activePillKey.current)}
8788
testID="section-header-view-all-rwa_perps"
8889
/>
8990
<PillToggleCardList<PerpsMarketData>
@@ -92,6 +93,9 @@ const RwaPerpsBlock: React.FC<RwaPerpsBlockProps> = ({
9293
renderItem={renderItem}
9394
Skeleton={PerpsRowSingleSkeleton}
9495
idPrefix="rwa_perps"
96+
onPillChange={(key) => {
97+
activePillKey.current = key;
98+
}}
9599
testIdPrefix="rwa-perps-pills"
96100
listTestId="rwa-perps-pill-toggled-list"
97101
/>
@@ -178,7 +182,9 @@ const RwasTab: React.FC<TabProps> = ({ refresh, refreshing, onRefresh }) => {
178182
<PerpsSectionProvider>
179183
<RwaPerpsBlock
180184
refresh={refresh}
181-
onViewAll={() => navigateToPerpsMarketList(perpsNavigation)}
185+
onViewAll={(filter) =>
186+
navigateToPerpsMarketList(perpsNavigation, filter)
187+
}
182188
/>
183189
</PerpsSectionProvider>
184190
)}

0 commit comments

Comments
 (0)