Skip to content

Commit 1de00bb

Browse files
authored
feat(money): Money Hub polish bundle (MUSD-728/729/730/731/732/733) (#29548)
## **Description** Bundles six small Money Hub UX changes: - **MUSD-728** — Drop the inline **Learn more** CTA from `MoneyConvertStablecoins`. The Convert section ends after the feature tags / per-token rows; only the primary action remains. - **MUSD-729** — Money Hub balance heading reads **Your balance** (replacing the duplicate "Money" inline title; the page header still shows "Money"). Standalone "3% bonus" label removed from mUSD rows in the homepage token list — the row falls back to the standard percentage rail when there's no Convert CTA. - **MUSD-730** — `AssetOverviewClaimBonus` claim button switches from primary to **secondary** styling (no logic change). - **MUSD-731** — No code change required: the existing `MUSD_CONVERSION_APY` constant already drives every "3%" surface consistently. No outdated/conflicting variants remain. - **MUSD-732** — Five-item checklist (`Dollar-backed`, `No lockups`, `Daily bonus`, `MetaMask stablecoins`, `No MetaMask fee`) added to the **Get 3% on stablecoins** education splash with green checkmarks. - **MUSD-733** — Mounts the Convert section on the mUSD asset detail page via a new `AssetOverviewConvertSection` wrapper that reuses `MoneyConvertStablecoins`. The component already supports the three target states (no-mUSD informational, has-mUSD with stablecoins per-token rows, has-mUSD without stablecoins informational). The secondary claim CTA from MUSD-730 covers the claim-styling requirement. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: [MUSD-728](https://consensyssoftware.atlassian.net/browse/MUSD-728), [MUSD-729](https://consensyssoftware.atlassian.net/browse/MUSD-729), [MUSD-730](https://consensyssoftware.atlassian.net/browse/MUSD-730), [MUSD-731](https://consensyssoftware.atlassian.net/browse/MUSD-731), [MUSD-732](https://consensyssoftware.atlassian.net/browse/MUSD-732), [MUSD-733](https://consensyssoftware.atlassian.net/browse/MUSD-733) ## **Manual testing steps** ```gherkin Feature: Money Hub polish Scenario: Money Hub Convert section (MUSD-728) Given I open the Money Hub Then the Convert to mUSD section ends after the feature tags or per-token rows And no "Learn more" button is rendered Scenario: Money Hub balance heading (MUSD-729) Given I open the Money Hub Then the balance section heading reads "Your balance" Scenario: mUSD token row label (MUSD-729) Given I view the wallet token list with mUSD on Mainnet/Linea Then the mUSD row no longer renders the green "3% bonus" label And the row falls back to the standard percentage rail when no Convert CTA applies Scenario: Asset detail claim button (MUSD-730) Given I open the mUSD asset detail page with a claimable bonus Then the "Claim" button uses secondary button styling Scenario: Education splash checklist (MUSD-732) Given I view the "Get 3% on stablecoins" education splash Then I see a checklist with: Dollar-backed, No lockups, Daily bonus, MetaMask stablecoins, No MetaMask fee And each item shows a green check icon Scenario: Asset detail Convert module (MUSD-733) Given I open the mUSD asset detail page And mUSD conversion is enabled and I am geo-eligible Then a Convert section renders below the claim card And it shows per-token rows for any eligible stablecoins, or the empty/info layout otherwise ``` ## **Screenshots/Recordings** <img width="1206" height="2622" alt="image" src="https://github.com/user-attachments/assets/474cfcf1-d538-43c7-b798-4ca060fa1a88" /> <img width="1206" height="2622" alt="image" src="https://github.com/user-attachments/assets/82ee004e-40c1-4972-bf93-f6ca44822150" /> <img width="1206" height="2622" alt="image" src="https://github.com/user-attachments/assets/c7ae2bd0-cf04-4475-826a-806498a33a92" /> ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] 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 - [ ] I've tested with a power user scenario - [ ] I've instrumented key operations with Sentry traces for production performance metrics ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. [MUSD-728]: https://consensyssoftware.atlassian.net/browse/MUSD-728?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate risk: refactors conversion UI wiring and token list rendering paths (including a new compact mUSD layout and new analytics events/locations), which could affect navigation and tracking if misconfigured. > > **Overview** > **Money Hub UX polish for mUSD conversion and balances.** The Convert section (`MoneyConvertStablecoins`) is simplified by removing the inline *Learn more* CTA and now self-manages token fetching plus conversion initiation, emitting `MONEY_HUB_TOKEN_ROW_CONVERT_CLICKED` analytics with a passed-in `location`. > > **mUSD balance presentation is adjusted across Money surfaces.** `CashTokensFullView` adds a **"Your balance"** heading when Money Hub is enabled, introduces a new `MoneyMusdEmptyBalanceRow` for the zero-balance state, and passes a new `hideSecondaryPriceRow` flag through `Tokens`/`TokenList` to render a compact mUSD row without the price/percentage rail. Separately, mUSD token list items no longer show the standalone green `"3% bonus"` secondary label. > > **Other small UI/text tweaks.** `AssetOverviewClaimBonus` switches its CTA button to `Secondary` styling and removes the leading `+` from the estimated annual bonus display; the mUSD education screen is redesigned to use design-system components, adds a 5-item checklist, and removes the external “Terms apply” link while updating the education copy and i18n keys (cash → money, new checklist strings, new `money.your_balance`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 6696fdc. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0b6a6a0 commit 1de00bb

30 files changed

Lines changed: 860 additions & 490 deletions

app/components/UI/Earn/Views/EarnMusdConversionEducationView/EarnMusdConversionEducationView.styles.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

app/components/UI/Earn/Views/EarnMusdConversionEducationView/EarnMusdConversionEducationView.view.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describeForPlatforms('EarnMusdConversionEducationView', () => {
5858
).toBeOnTheScreen();
5959
expect(
6060
getByText(
61-
/Convert your stablecoins to mUSD.*earn up to a \d+% annualized bonus/,
61+
/Convert your stablecoins to mUSD.*earn a \d+%.+annualized bonus/,
6262
),
6363
).toBeOnTheScreen();
6464
expect(
@@ -391,10 +391,10 @@ describeForPlatforms('EarnMusdConversionEducationView', () => {
391391

392392
// Assert
393393
const description = getByText(
394-
/Convert your stablecoins to mUSD.*earn up to a \d+% annualized bonus/,
394+
/Convert your stablecoins to mUSD.*earn a \d+%.+annualized bonus/,
395395
);
396396
expect(description).toBeOnTheScreen();
397-
expect(description.props.children[0]).toContain(`${MUSD_CONVERSION_APY}%`);
397+
expect(description.props.children).toContain(`${MUSD_CONVERSION_APY}%`);
398398
});
399399

400400
it('renders education screen when education has been seen', () => {

app/components/UI/Earn/Views/EarnMusdConversionEducationView/index.test.tsx

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { fireEvent, waitFor, act } from '@testing-library/react-native';
33
import { useNavigation, useFocusEffect } from '@react-navigation/native';
44
import { useDispatch } from 'react-redux';
55
import { Hex } from '@metamask/utils';
6-
import { Linking } from 'react-native';
76
import EarnMusdConversionEducationView from './index';
87
import {
98
setMusdConversionEducationSeen,
@@ -20,7 +19,6 @@ import { EARN_TEST_IDS } from '../../constants/testIds';
2019
import { useMusdConversionFlowData } from '../../hooks/useMusdConversionFlowData';
2120
import { useRampNavigation } from '../../../Ramp/hooks/useRampNavigation';
2221
import Routes from '../../../../../constants/navigation/Routes';
23-
import AppConstants from '../../../../../core/AppConstants';
2422
import { selectMoneyHubEnabledFlag } from '../../../Money/selectors/featureFlags';
2523
import { MUSD_EVENTS_CONSTANTS } from '../../constants/events';
2624
import { MONEY_EVENTS_CONSTANTS } from '../../../Money/constants/moneyEvents';
@@ -262,9 +260,6 @@ describe('EarnMusdConversionEducationView', () => {
262260
),
263261
).toBeOnTheScreen();
264262
expect(getByText(descriptionText, { exact: false })).toBeOnTheScreen();
265-
expect(
266-
getByText(strings('earn.musd_conversion.education.terms_apply')),
267-
).toBeOnTheScreen();
268263
expect(
269264
getByText(strings('earn.musd_conversion.education.primary_button')),
270265
).toBeOnTheScreen();
@@ -934,43 +929,6 @@ describe('EarnMusdConversionEducationView', () => {
934929
});
935930
});
936931

937-
describe('external links', () => {
938-
it('opens bonus terms of use when "Terms apply" is pressed', () => {
939-
const openUrlSpy = jest
940-
.spyOn(Linking, 'openURL')
941-
.mockResolvedValueOnce(undefined);
942-
943-
const { getByText } = renderWithProvider(
944-
<EarnMusdConversionEducationView />,
945-
{ state: {} },
946-
);
947-
948-
mockTrackEvent.mockClear();
949-
mockCreateEventBuilder.mockClear();
950-
mockAddProperties.mockClear();
951-
mockBuild.mockClear();
952-
953-
fireEvent.press(
954-
getByText(strings('earn.musd_conversion.education.terms_apply')),
955-
);
956-
957-
expect(mockCreateEventBuilder).toHaveBeenCalledWith(
958-
MetaMetricsEvents.MUSD_BONUS_TERMS_OF_USE_PRESSED,
959-
);
960-
expect(mockAddProperties).toHaveBeenCalledWith({
961-
location:
962-
MUSD_EVENTS_CONSTANTS.EVENT_LOCATIONS.CONVERSION_EDUCATION_SCREEN,
963-
url: AppConstants.URLS.MUSD_CONVERSION_BONUS_TERMS_OF_USE,
964-
});
965-
expect(mockTrackEvent).toHaveBeenCalledWith({ name: 'mock-built-event' });
966-
967-
expect(openUrlSpy).toHaveBeenCalledTimes(1);
968-
expect(openUrlSpy).toHaveBeenCalledWith(
969-
AppConstants.URLS.MUSD_CONVERSION_BONUS_TERMS_OF_USE,
970-
);
971-
});
972-
});
973-
974932
describe('redux actions', () => {
975933
it('dispatches setMusdConversionEducationSeen when continue button pressed', async () => {
976934
const { getByTestId } = renderWithProvider(

app/components/UI/Earn/Views/EarnMusdConversionEducationView/index.tsx

Lines changed: 101 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
11
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
22
import { Hex } from '@metamask/utils';
33
import { useDispatch, useSelector } from 'react-redux';
4-
import { View, Image, useColorScheme, Linking } from 'react-native';
4+
import { Image, StyleSheet, useColorScheme } from 'react-native';
55
import { setMusdConversionEducationSeen } from '../../../../../actions/user';
66
import Logger from '../../../../../util/Logger';
7-
import Text, {
8-
TextVariant,
9-
} from '../../../../../component-library/components/Texts/Text';
10-
import Button, {
11-
ButtonSize,
12-
ButtonVariants,
13-
ButtonWidthTypes,
14-
} from '../../../../../component-library/components/Buttons/Button';
15-
import { useStyles } from '../../../../../component-library/hooks';
16-
import { styleSheet } from './EarnMusdConversionEducationView.styles';
177
import musdEducationBackgroundV2Dark from '../../../../../images/musd-conversion-education-screen-v2-dark-3x.png';
188
import musdEducationBackgroundV2Light from '../../../../../images/musd-conversion-education-screen-v2-light-3x.png';
199
import { SafeAreaView } from 'react-native-safe-area-context';
@@ -22,9 +12,28 @@ import { useParams } from '../../../../../util/navigation/navUtils';
2212
import { useNavigation } from '@react-navigation/native';
2313
import { StackNavigationProp } from '@react-navigation/stack';
2414
import {
25-
Button as DesignSystemButton,
26-
ButtonVariant as DesignSystemButtonVariant,
15+
Box,
16+
BoxFlexDirection,
17+
BoxJustifyContent,
18+
Button,
19+
ButtonSize,
20+
ButtonVariant,
21+
FontWeight,
22+
Icon,
23+
IconColor,
24+
IconName,
25+
IconSize,
26+
Text,
27+
TextColor,
28+
TextVariant,
2729
} from '@metamask/design-system-react-native';
30+
// component-library TagBase: DSRN Tag has no startAccessory + Rectangle shape support
31+
import TagBase from '../../../../../component-library/base-components/TagBase';
32+
import {
33+
TagShape,
34+
TagSeverity,
35+
} from '../../../../../component-library/base-components/TagBase/TagBase.types';
36+
import { TextVariant as ComponentTextVariant } from '../../../../../component-library/components/Texts/Text/Text.types';
2837
import { strings } from '../../../../../../locales/i18n';
2938
import { useAnalytics } from '../../../../hooks/useAnalytics/useAnalytics';
3039
import { MetaMetricsEvents } from '../../../../../core/Analytics';
@@ -39,12 +48,31 @@ import Routes from '../../../../../constants/navigation/Routes';
3948
import { useRampNavigation } from '../../../Ramp/hooks/useRampNavigation';
4049
import { RampIntent } from '../../../Ramp/types';
4150
import { EARN_TEST_IDS } from '../../constants/testIds';
42-
import AppConstants from '../../../../../core/AppConstants';
4351
import { MusdNavigationTarget } from '../../types/musd.types';
4452
import { toChecksumAddress } from '../../../../../util/address';
4553
import { safeFormatChainIdToHex } from '../../../Card/util/safeFormatChainIdToHex';
4654
import { MONEY_EVENTS_CONSTANTS } from '../../../Money/constants/moneyEvents';
4755
import { selectMoneyHubEnabledFlag } from '../../../Money/selectors/featureFlags';
56+
57+
const styles = StyleSheet.create({
58+
container: {
59+
flex: 1,
60+
paddingTop: 40,
61+
},
62+
heading: {
63+
fontFamily: 'MMPoly-Regular',
64+
fontSize: 40,
65+
lineHeight: 40,
66+
paddingVertical: 16,
67+
textAlign: 'center',
68+
},
69+
backgroundImage: {
70+
width: '100%',
71+
height: '100%',
72+
resizeMode: 'contain',
73+
},
74+
});
75+
4876
interface EarnMusdConversionEducationViewRouteParams {
4977
/**
5078
* Indicates if this navigation originated from a deeplink
@@ -94,8 +122,6 @@ const EarnMusdConversionEducationView = () => {
94122
conversionTokens,
95123
} = useMusdConversionFlowData();
96124

97-
const { styles } = useStyles(styleSheet, {});
98-
99125
const navigation =
100126
useNavigation<StackNavigationProp<Record<string, object | undefined>>>();
101127

@@ -363,73 +389,93 @@ const EarnMusdConversionEducationView = () => {
363389
}
364390
};
365391

366-
const handleTermsOfUsePressed = () => {
367-
trackEvent(
368-
createEventBuilder(MetaMetricsEvents.MUSD_BONUS_TERMS_OF_USE_PRESSED)
369-
.addProperties({
370-
location: MUSD_EVENT_LOCATIONS.CONVERSION_EDUCATION_SCREEN,
371-
url: AppConstants.URLS.MUSD_CONVERSION_BONUS_TERMS_OF_USE,
372-
})
373-
.build(),
374-
);
375-
376-
Linking.openURL(AppConstants.URLS.MUSD_CONVERSION_BONUS_TERMS_OF_USE);
377-
};
378-
379392
return (
380393
// Do not remove the top edge as this screen does not have a navbar set in the route options.
381394
<SafeAreaView
382395
style={styles.container}
383396
edges={['top', 'bottom']}
384397
testID={EARN_TEST_IDS.MUSD.CONVERSION_EDUCATION_VIEW.CONTAINER}
385398
>
386-
<View style={styles.content}>
399+
<Box twClassName="px-4 items-center">
387400
<Text style={styles.heading} numberOfLines={2} adjustsFontSizeToFit>
388401
{strings('earn.musd_conversion.education.heading', {
389402
percentage: MUSD_CONVERSION_APY,
390403
})}
391404
</Text>
392-
<Text variant={TextVariant.BodyMD} style={styles.bodyText}>
393-
{strings('earn.musd_conversion.education.description', {
394-
percentage: MUSD_CONVERSION_APY,
395-
})}{' '}
396-
<Text
397-
variant={TextVariant.BodyMD}
398-
style={styles.termsText}
399-
onPress={handleTermsOfUsePressed}
400-
>
401-
{strings('earn.musd_conversion.education.terms_apply')}
402-
</Text>
403-
</Text>
404-
</View>
405-
<View style={styles.imageContainer}>
405+
<Box
406+
flexDirection={BoxFlexDirection.Row}
407+
justifyContent={BoxJustifyContent.Center}
408+
twClassName="flex-wrap gap-1 mt-4"
409+
>
410+
{[
411+
'earn.musd_conversion.education.checklist.dollar_backed',
412+
'earn.musd_conversion.education.checklist.no_lockups',
413+
'earn.musd_conversion.education.checklist.daily_bonus',
414+
'earn.musd_conversion.education.checklist.metamask_stablecoins',
415+
'earn.musd_conversion.education.checklist.no_metamask_fee',
416+
].map((key) => (
417+
<TagBase
418+
key={key}
419+
shape={TagShape.Rectangle}
420+
severity={TagSeverity.Neutral}
421+
gap={4}
422+
startAccessory={
423+
<Icon
424+
name={IconName.CheckBold}
425+
size={IconSize.Sm}
426+
color={IconColor.SuccessDefault}
427+
/>
428+
}
429+
textProps={{
430+
variant: ComponentTextVariant.BodySMMedium,
431+
numberOfLines: 1,
432+
}}
433+
>
434+
{strings(key)}
435+
</TagBase>
436+
))}
437+
</Box>
438+
</Box>
439+
<Box twClassName="flex-1 min-h-[100px]">
406440
<Image
407441
source={backgroundImage}
408442
style={styles.backgroundImage}
409443
testID={EARN_TEST_IDS.MUSD.CONVERSION_EDUCATION_VIEW.BACKGROUND_IMAGE}
410444
/>
411-
</View>
445+
</Box>
446+
<Box twClassName="px-4 items-center">
447+
<Text
448+
variant={TextVariant.BodyMd}
449+
color={TextColor.TextAlternative}
450+
twClassName="text-center mt-4 mb-6"
451+
>
452+
{strings('earn.musd_conversion.education.description', {
453+
percentage: MUSD_CONVERSION_APY,
454+
})}
455+
</Text>
456+
</Box>
412457

413-
<View style={styles.buttonsContainer}>
458+
<Box twClassName="mx-4 mb-4 gap-2">
414459
<Button
415-
variant={ButtonVariants.Primary}
416-
label={primaryButtonText}
460+
variant={ButtonVariant.Primary}
417461
onPress={handleContinue}
418462
size={ButtonSize.Lg}
419-
width={ButtonWidthTypes.Full}
463+
isFullWidth
420464
testID={EARN_TEST_IDS.MUSD.CONVERSION_EDUCATION_VIEW.PRIMARY_BUTTON}
421-
/>
422-
<DesignSystemButton
423-
variant={DesignSystemButtonVariant.Tertiary}
465+
>
466+
{primaryButtonText}
467+
</Button>
468+
<Button
469+
variant={ButtonVariant.Tertiary}
424470
isFullWidth
425471
onPress={handleNotNow}
426472
testID={EARN_TEST_IDS.MUSD.CONVERSION_EDUCATION_VIEW.SECONDARY_BUTTON}
427473
>
428-
<Text variant={TextVariant.BodyMDMedium}>
474+
<Text variant={TextVariant.BodyMd} fontWeight={FontWeight.Medium}>
429475
{strings('earn.musd_conversion.education.secondary_button')}
430476
</Text>
431-
</DesignSystemButton>
432-
</View>
477+
</Button>
478+
</Box>
433479
</SafeAreaView>
434480
);
435481
};

0 commit comments

Comments
 (0)