Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ describe('MoneyActivityView', () => {
transfers: MOCK_TRANSFERS,
submittedTransactions: [],
moneyAddress: '0x0000000000000000000000000000000000000001',
mockDataEnabled: false,
});
});

Expand Down Expand Up @@ -144,6 +145,7 @@ describe('MoneyActivityView', () => {
transfers: [],
submittedTransactions: [],
moneyAddress: '0x0000000000000000000000000000000000000001',
mockDataEnabled: false,
});

const { getByTestId } = renderWithProvider(<MoneyActivityView />);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import MoneyActivityItem from '../../components/MoneyActivityItem';
import { useMoneyAccountTransactions } from '../../hooks/useMoneyAccountTransactions';
import { getMoneyActivityDateKeyUtc } from '../../constants/moneyActivityFilters';
import { MoneyActivityFilter } from '../../constants/mockActivityData';
import { showMoneyActivityUnderConstructionAlert } from '../../constants/showMoneyActivityUnderConstructionAlert';
import Routes from '../../../../../constants/navigation/Routes';
import { MoneyActivityViewTestIds } from './MoneyActivityView.testIds';

const styles = StyleSheet.create({
Expand Down Expand Up @@ -67,16 +67,22 @@ const MoneyActivityView = () => {
const { colors } = useTheme();
const [filter, setFilter] = useState(MoneyActivityFilter.All);

const { allTransactions, deposits, transfers, moneyAddress } =
const { allTransactions, deposits, transfers, moneyAddress, mockDataEnabled } =
useMoneyAccountTransactions();

const handleBackPress = useCallback(() => {
navigation.goBack();
}, [navigation]);

const handleItemPress = useCallback(() => {
showMoneyActivityUnderConstructionAlert();
}, []);
const handleItemPress = useCallback(
(transactionId: string) => {
navigation.navigate(Routes.MONEY.MODALS.ROOT, {
screen: Routes.MONEY.MODALS.TRANSACTION_DETAILS_SHEET,
params: { transactionId },
});
},
[navigation],
);

const filtered = useMemo(() => {
if (filter === MoneyActivityFilter.All) {
Expand Down Expand Up @@ -110,7 +116,7 @@ const MoneyActivityView = () => {
<MoneyActivityItem
tx={item}
moneyAddress={moneyAddress}
onPress={handleItemPress}
onPress={mockDataEnabled ? undefined : handleItemPress}
/>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ describe('MoneyHomeView', () => {
transfers: [],
submittedTransactions: [],
moneyAddress: '0x0000000000000000000000000000000000000001',
mockDataEnabled: false,
});
});

Expand Down Expand Up @@ -432,6 +433,7 @@ describe('MoneyHomeView', () => {
transfers: [],
submittedTransactions: [],
moneyAddress: '0x0000000000000000000000000000000000000001',
mockDataEnabled: false,
});

const { getByTestId } = renderWithProvider(<MoneyHomeView />);
Expand Down Expand Up @@ -530,6 +532,7 @@ describe('MoneyHomeView', () => {
transfers: [],
submittedTransactions: [],
moneyAddress: '0x0000000000000000000000000000000000000001',
mockDataEnabled: false,
});
});

Expand Down Expand Up @@ -591,6 +594,7 @@ describe('MoneyHomeView', () => {
transfers: [],
submittedTransactions: [],
moneyAddress: '0x0000000000000000000000000000000000000001',
mockDataEnabled: false,
});
mockSelectIsCardholder.mockReturnValue(true);
// Non-US so MetaMask card renders in link mode (manage mode is US-only).
Expand Down Expand Up @@ -774,6 +778,7 @@ describe('MoneyHomeView', () => {
transfers: [],
submittedTransactions: [],
moneyAddress: '0x0000000000000000000000000000000000000001',
mockDataEnabled: false,
});
});

Expand Down Expand Up @@ -996,6 +1001,7 @@ describe('MoneyHomeView', () => {
transfers: [],
submittedTransactions: [],
moneyAddress: '0x0000000000000000000000000000000000000001',
mockDataEnabled: false,
});
});

Expand Down
17 changes: 11 additions & 6 deletions app/components/UI/Money/Views/MoneyHomeView/MoneyHomeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import styleSheet from './MoneyHomeView.styles';
import { useMusdConversionTokens } from '../../../Earn/hooks/useMusdConversionTokens';
import { useMusdConversion } from '../../../Earn/hooks/useMusdConversion';
import { useMoneyAccountTransactions } from '../../hooks/useMoneyAccountTransactions';
import { showMoneyActivityUnderConstructionAlert } from '../../constants/showMoneyActivityUnderConstructionAlert';
import useMoneyAccountBalance from '../../hooks/useMoneyAccountBalance';
import { selectCurrentCurrency } from '../../../../../selectors/currencyRateController';
import { moneyFormatFiat } from '../../utils/moneyFormatFiat';
Expand Down Expand Up @@ -68,7 +67,7 @@ const MoneyHomeView = () => {

const { tokens: conversionTokens } = useMusdConversionTokens();
const { initiateCustomConversion } = useMusdConversion();
const { allTransactions, moneyAddress } = useMoneyAccountTransactions();
const { allTransactions, moneyAddress, mockDataEnabled } = useMoneyAccountTransactions();

const isCardholder = useSelector(selectIsCardholder);
const { canLink, openLinkCardSheet } = useMoneyAccountCardLinkage();
Expand Down Expand Up @@ -206,9 +205,15 @@ const MoneyHomeView = () => {
}, [navigation]);
const handleActivityHeaderPress = handleViewAllActivityPress;

const handleActivityItemPress = useCallback(() => {
showMoneyActivityUnderConstructionAlert();
Comment thread
shane-t marked this conversation as resolved.
}, []);
const handleActivityItemPress = useCallback(
(transactionId: string) => {
navigation.navigate(Routes.MONEY.MODALS.ROOT, {
screen: Routes.MONEY.MODALS.TRANSACTION_DETAILS_SHEET,
params: { transactionId },
});
},
[navigation],
);

const handleOnboardingCtaPress = useCallback(() => {
if (isCardholderWithMilestone) {
Expand Down Expand Up @@ -305,7 +310,7 @@ const MoneyHomeView = () => {
: handleViewAllActivityPress
}
onHeaderPress={handleActivityHeaderPress}
onItemPress={handleActivityItemPress}
onItemPress={mockDataEnabled ? undefined : handleActivityItemPress}
/>
<Divider />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import {
type TransactionMeta,
TransactionStatus,
TransactionType,
} from '@metamask/transaction-controller';
import type { Hex } from '@metamask/utils';
Expand Down Expand Up @@ -83,6 +84,9 @@ describe('MoneyActivityItem', () => {
primaryAmount: '+$0.00',
fiatAmount: '$0.00',
isIncoming: true,
sourceTokenSymbol: undefined,
sourceTokenImage: undefined,
sourceTokenChainId: undefined,
});
});

Expand All @@ -107,6 +111,9 @@ describe('MoneyActivityItem', () => {
primaryAmount: '+$0.00',
fiatAmount: '$0.00',
isIncoming: false,
sourceTokenSymbol: undefined,
sourceTokenImage: undefined,
sourceTokenChainId: undefined,
});

const { queryByText } = renderWithProvider(
Expand All @@ -127,6 +134,21 @@ describe('MoneyActivityItem', () => {
expect(onPress).toHaveBeenCalledTimes(1);
});

it('shows "Failed" in the description slot for a failed transaction', () => {
const failedTx = {
...baseTx,
status: TransactionStatus.failed,
} as unknown as TransactionMeta;

const { getByText, queryByText } = renderWithProvider(
<MoneyActivityItem tx={failedTx} moneyAddress="0x1" />,
);

expect(getByText('Failed')).toBeOnTheScreen();
// Normal description should not appear for failed rows
expect(queryByText('Description')).toBeNull();
});

it('renders network badge subtree when showNetworkBadge is true', () => {
const { getByTestId } = renderWithProvider(
<MoneyActivityItem tx={baseTx} moneyAddress="0x1" showNetworkBadge />,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React from 'react';
import { Pressable } from 'react-native';
import {
Box,
Expand All @@ -9,7 +9,11 @@
TextVariant,
} from '@metamask/design-system-react-native';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import type { TransactionMeta } from '@metamask/transaction-controller';
import {
type TransactionMeta,
TransactionStatus,
} from '@metamask/transaction-controller';
import { strings } from '../../../../../../locales/i18n';
import { getNetworkImageSource } from '../../../../../util/networks';
import AvatarToken from '../../../../../component-library/components/Avatars/Avatar/variants/AvatarToken';
import { AvatarSize } from '../../../../../component-library/components/Avatars/Avatar';
Expand All @@ -28,7 +32,7 @@
export interface MoneyActivityItemProps {
tx: TransactionMeta;
moneyAddress: string | undefined;
onPress?: () => void;
onPress?: (transactionId: string) => void;
/** When true, shows the chain network badge on the token avatar. Defaults to false. */
showNetworkBadge?: boolean;
}
Expand All @@ -43,22 +47,29 @@

const display = useMoneyTransactionDisplayInfo(tx, moneyAddress);

const networkImageSource = useMemo(
() =>
showNetworkBadge
? getNetworkImageSource({ chainId: tx.chainId })
: undefined,
[tx.chainId, showNetworkBadge],
);
const networkImageSource = showNetworkBadge
? getNetworkImageSource({ chainId: tx.chainId })
: undefined;

// use the token's own image URI, or the source chain's network icon, or the mUSD icon
const tokenAvatarImageSource = display.sourceTokenImage
? { uri: display.sourceTokenImage }
: display.sourceTokenChainId
? getNetworkImageSource({ chainId: display.sourceTokenChainId })
: MUSD_TOKEN.imageSource;

Check warning on line 59 in app/components/UI/Money/components/MoneyActivityItem/MoneyActivityItem.tsx

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=AZ4rBg1uJOticlVzmq7u&open=AZ4rBg1uJOticlVzmq7u&pullRequest=30175

const amountColor = display.isIncoming
const isFailed = tx.status === TransactionStatus.failed;

const amountColor = isFailed
? TextColor.TextAlternative
: display.isIncoming
? TextColor.SuccessDefault
: TextColor.TextDefault;

Check warning on line 67 in app/components/UI/Money/components/MoneyActivityItem/MoneyActivityItem.tsx

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=AZ4rBg1uJOticlVzmq7v&open=AZ4rBg1uJOticlVzmq7v&pullRequest=30175

return (
<Pressable
accessibilityRole="button"
onPress={onPress}
onPress={onPress ? () => onPress(tx.id) : undefined}
testID={`${MoneyActivityItemTestIds.ROW}-${tx.id}`}
style={({ pressed }) =>
tw.style(
Expand All @@ -81,16 +92,16 @@
}
>
<AvatarToken
name={MUSD_TOKEN.name}
imageSource={MUSD_TOKEN.imageSource}
name={display.sourceTokenSymbol ?? MUSD_TOKEN.name}
imageSource={tokenAvatarImageSource}
size={AvatarSize.Lg}
/>
</BadgeWrapper>
) : (
<Box twClassName="self-center">
<AvatarToken
name={MUSD_TOKEN.name}
imageSource={MUSD_TOKEN.imageSource}
name={display.sourceTokenSymbol ?? MUSD_TOKEN.name}
imageSource={tokenAvatarImageSource}
size={AvatarSize.Lg}
/>
</Box>
Expand All @@ -104,16 +115,25 @@
>
{display.label}
</Text>
{display.description ? (
{isFailed ? (
<Text
variant={TextVariant.BodySm}
fontWeight={FontWeight.Medium}
color={TextColor.ErrorDefault}
numberOfLines={1}
>
{strings('money.transaction.failed')}
</Text>
) : display.description ? (
<Text
variant={TextVariant.BodySm}
fontWeight={FontWeight.Medium}
color={TextColor.TextAlternative}
numberOfLines={1}
>
{display.description}
</Text>
) : null}

Check warning on line 136 in app/components/UI/Money/components/MoneyActivityItem/MoneyActivityItem.tsx

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=AZ4rBg1uJOticlVzmq7w&open=AZ4rBg1uJOticlVzmq7w&pullRequest=30175
</Box>
<Box alignItems={BoxAlignItems.End} twClassName="shrink-0 gap-0.5">
<Text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface MoneyActivityListProps {
moneyAddress?: string;
onViewAllPress?: () => void;
onHeaderPress?: () => void;
onItemPress?: () => void;
onItemPress?: (transactionId: string) => void;
}

const MoneyActivityList = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useCallback, useRef } from 'react';
import { useNavigation } from '@react-navigation/native';
import {
BottomSheet,
type BottomSheetRef,
BottomSheetHeader,
Text,
TextVariant,
} from '@metamask/design-system-react-native';
import {
type TransactionMeta,
TransactionType,
} from '@metamask/transaction-controller';
import { strings } from '../../../../../../locales/i18n';
import { TransactionDetails } from '../../../../Views/confirmations/components/activity/transaction-details/transaction-details';
import { useTransactionDetails } from '../../../../Views/confirmations/hooks/activity/useTransactionDetails';

const TITLE_KEYS: Partial<Record<TransactionType, string>> = {
[TransactionType.moneyAccountDeposit]:
'transaction_details.title.money_account_deposit',
[TransactionType.moneyAccountWithdraw]:
'transaction_details.title.money_account_withdraw',
[TransactionType.musdConversion]: 'transaction_details.title.musd_conversion',
[TransactionType.musdClaim]: 'transaction_details.title.musd_claim',
[TransactionType.perpsDeposit]: 'transaction_details.title.perps_deposit',
[TransactionType.perpsWithdraw]: 'transaction_details.title.perps_withdraw',
[TransactionType.predictClaim]: 'transaction_details.title.predict_claim',
[TransactionType.predictDeposit]: 'transaction_details.title.predict_deposit',
[TransactionType.predictWithdraw]:
'transaction_details.title.predict_withdraw',
};

function getTitle(tx: TransactionMeta | undefined): string {
const type =
tx?.type === TransactionType.batch
? ((tx.nestedTransactions?.find((n) => n.type && n.type in TITLE_KEYS)
?.type as TransactionType | undefined) ?? tx.type)

Check warning on line 37 in app/components/UI/Money/components/MoneyTransactionDetailsSheet/MoneyTransactionDetailsSheet.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ4mlVXh_9oVCvgyEILv&open=AZ4mlVXh_9oVCvgyEILv&pullRequest=30175
: tx?.type;
return strings(
(type && TITLE_KEYS[type]) ?? 'transaction_details.title.default',
);
}

const MoneyTransactionDetailsSheet = () => {
const sheetRef = useRef<BottomSheetRef>(null);
const navigation = useNavigation();
const { transactionMeta } = useTransactionDetails();
const title = getTitle(transactionMeta);

const handleClose = useCallback(() => {
sheetRef.current?.onCloseBottomSheet();
}, []);

return (
<BottomSheet
ref={sheetRef}
isFullscreen
goBack={navigation.goBack}
keyboardAvoidingViewEnabled={false}
>
<BottomSheetHeader onClose={handleClose}>
<Text variant={TextVariant.HeadingMd}>{title}</Text>
</BottomSheetHeader>
<TransactionDetails />
</BottomSheet>
);
};

export default MoneyTransactionDetailsSheet;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './MoneyTransactionDetailsSheet';
Loading
Loading