Skip to content

Commit

Permalink
feat(suite-native): Mobile Trade: improve Buy section stub coin inter…
Browse files Browse the repository at this point in the history
…actions
  • Loading branch information
jbazant committed Feb 13, 2025
1 parent 9940bb4 commit f2bcb53
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 25 deletions.
1 change: 1 addition & 0 deletions suite-native/intl/src/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,7 @@ export const en = {
},
defaultSearchLabel: 'Search',
notSelected: 'Not selected',
selectCoinFirst: 'Select coin first',
},
};

Expand Down
42 changes: 30 additions & 12 deletions suite-native/module-trading/src/components/buy/BuyCard.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
import { useEffect } from 'react';

import { useFormatters } from '@suite-common/formatters';
import { Card, HStack, Text, VStack } from '@suite-native/atoms';
import { Icon } from '@suite-native/icons';
import { Translation } from '@suite-native/intl';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';

import { ReceiveAccountCryptoBalance } from './ReceiveAccountCryptoBalance';
import { ReceiveAccountPicker } from './ReceiveAccountPicker';
import { TradeableAssetPicker } from './TradeableAssetPicker';
import { useTradeSheetControls } from '../../hooks/useTradeSheetControls';
import { TradeableAsset } from '../../types';
import { ReceiveAccount, TradeableAsset } from '../../types';

const buySectionStyle = prepareNativeStyle(({ borders, colors, spacings }) => ({
borderBottomWidth: borders.widths.small,
borderBottomColor: colors.backgroundSurfaceElevation0,
padding: spacings.sp20,
gap: spacings.sp20,
}));

export const BuyCard = () => {
const { FiatAmountFormatter, CryptoAmountFormatter } = useFormatters();
const { FiatAmountFormatter } = useFormatters();
const { applyStyle } = useNativeStyles();

const { selectedValue, ...restControls } = useTradeSheetControls<TradeableAsset>();
const { selectedValue: selectedAsset, ...restAssetControls } =
useTradeSheetControls<TradeableAsset>();
const {
selectedValue: selectedReceiveAccount,
setSelectedValue: setReceiveAccount,
...restReceiveAccountControls
} = useTradeSheetControls<ReceiveAccount>();

const selectedSymbol = selectedAsset?.symbol;

useEffect(() => {
setReceiveAccount(undefined);
}, [selectedSymbol, setReceiveAccount]);

return (
<Card noPadding>
Expand All @@ -28,19 +44,16 @@ export const BuyCard = () => {
<Translation id="moduleTrading.tradingScreen.buyTitle" />
</Text>
<HStack justifyContent="space-between" alignItems="center">
<TradeableAssetPicker selectedValue={selectedValue} {...restControls} />
<TradeableAssetPicker selectedValue={selectedAsset} {...restAssetControls} />
<Text variant="titleMedium" color="textDisabled">
0.0
</Text>
</HStack>
<HStack justifyContent="space-between" alignItems="center">
<Text variant="body" color="textSubdued">
{selectedValue?.symbol ? (
<CryptoAmountFormatter value="0" symbol={selectedValue.symbol} />
) : (
'-'
)}
</Text>
<ReceiveAccountCryptoBalance
symbol={selectedReceiveAccount?.account?.symbol}
balance={selectedReceiveAccount?.account?.balance}
/>
<HStack>
<Text variant="body" color="textSubdued">
<FiatAmountFormatter value={0} />
Expand All @@ -49,7 +62,12 @@ export const BuyCard = () => {
</HStack>
</HStack>
</VStack>
<ReceiveAccountPicker selectedSymbol={selectedValue?.symbol} />
<ReceiveAccountPicker
selectedSymbol={selectedSymbol}
selectedValue={selectedReceiveAccount}
setSelectedValue={setReceiveAccount}
{...restReceiveAccountControls}
/>
</Card>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useFormatters } from '@suite-common/formatters';
import { NetworkSymbol } from '@suite-common/wallet-config';
import { Box, Text } from '@suite-native/atoms';

export type ReceiveAccountBalanceProps = {
symbol: NetworkSymbol | undefined;
balance: string | undefined;
};

export const RECEIVE_ACCOUNT_BALANCE_TEST_ID = '@module-trading/receive-account-balance';

export const ReceiveAccountCryptoBalance = ({ symbol, balance }: ReceiveAccountBalanceProps) => {
const { CryptoAmountFormatter } = useFormatters();

const shouldDisplayBalance = symbol && balance;

return (
<Box testID={RECEIVE_ACCOUNT_BALANCE_TEST_ID}>
{shouldDisplayBalance && (
<Text variant="body" color="textSubdued">
<CryptoAmountFormatter value={balance} symbol={symbol} />
</Text>
)}
</Box>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ type RightTextProps = {
type ReceiveAccountPickerRightProps = {
selectedAccountLabel: string | undefined;
selectedAddress: string | undefined;
selectedSymbol: NetworkSymbol | undefined;
};

type ReceiveAccountPickerProps = {
export type ReceiveAccountPickerProps = {
selectedSymbol?: NetworkSymbol;
};
} & ReturnType<typeof useTradeSheetControls<ReceiveAccount>>;

const RightText = ({ color, variant = 'body', children }: RightTextProps) => (
<Text color={color} variant={variant} textAlign="right" ellipsizeMode="tail" numberOfLines={1}>
Expand All @@ -34,7 +35,16 @@ const RightText = ({ color, variant = 'body', children }: RightTextProps) => (
const ReceiveAccountPickerRight = ({
selectedAccountLabel,
selectedAddress,
selectedSymbol,
}: ReceiveAccountPickerRightProps) => {
if (!selectedSymbol) {
return (
<RightText color="textDisabled">
<Translation id="moduleTrading.selectCoinFirst" />
</RightText>
);
}

if (!selectedAccountLabel) {
return (
<RightText color="textDisabled">
Expand All @@ -57,32 +67,41 @@ const ReceiveAccountPickerRight = ({
);
};

export const ReceiveAccountPicker = ({ selectedSymbol }: ReceiveAccountPickerProps) => {
export const ReceiveAccountPicker = ({
selectedSymbol,
isSheetVisible,
hideSheet,
showSheet,
setSelectedValue,
selectedValue,
}: ReceiveAccountPickerProps) => {
const { translate } = useTranslate();

const { isSheetVisible, hideSheet, showSheet, setSelectedValue, selectedValue } =
useTradeSheetControls<ReceiveAccount>();
const onPress = selectedSymbol ? showSheet : undefined;

return (
<>
<TradingOverviewRow
title={translate('moduleTrading.tradingScreen.receiveAccount')}
onPress={showSheet}
onPress={onPress}
noBottomBorder
>
<VStack spacing={0} paddingLeft="sp20">
<ReceiveAccountPickerRight
selectedAccountLabel={selectedValue?.account.accountLabel}
selectedAddress={selectedValue?.address?.address}
selectedSymbol={selectedSymbol}
/>
</VStack>
</TradingOverviewRow>
<AccountSheet
symbol={selectedSymbol ?? 'btc'}
onAccountSelect={setSelectedValue}
isVisible={isSheetVisible}
onClose={hideSheet}
/>
{selectedSymbol && (
<AccountSheet
symbol={selectedSymbol}
onAccountSelect={setSelectedValue}
isVisible={isSheetVisible}
onClose={hideSheet}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { render } from '@suite-native/test-utils';

import {
RECEIVE_ACCOUNT_BALANCE_TEST_ID,
ReceiveAccountCryptoBalance,
} from '../ReceiveAccountCryptoBalance';

describe('ReceiveAccountBalance', () => {
it('should display empty box when symbol is not specified', () => {
const { getByTestId } = render(
<ReceiveAccountCryptoBalance symbol={undefined} balance="10000" />,
);

expect(getByTestId(RECEIVE_ACCOUNT_BALANCE_TEST_ID)).toHaveTextContent('');
});

it('should display empty box when balance is not specified', () => {
const { getByTestId } = render(
<ReceiveAccountCryptoBalance symbol="btc" balance={undefined} />,
);

expect(getByTestId(RECEIVE_ACCOUNT_BALANCE_TEST_ID)).toHaveTextContent('');
});

it('should display balance when symbol and balance is specified', () => {
const { getByTestId } = render(
<ReceiveAccountCryptoBalance symbol="btc" balance="1000000" />,
);

expect(getByTestId(RECEIVE_ACCOUNT_BALANCE_TEST_ID)).toHaveTextContent('0.01 BTC');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Account } from '@suite-common/wallet-types';
import { fireEvent, render } from '@suite-native/test-utils';
import { Address } from '@trezor/blockchain-link-types';

import { ReceiveAccountPicker, ReceiveAccountPickerProps } from '../ReceiveAccountPicker';

jest.mock('../../general/AccountSheet/AccountSheet');

describe('ReceiveAccountPicker', () => {
const renderPicker = ({
selectedSymbol,
selectedValue,
setSelectedValue = jest.fn(),
isSheetVisible = false,
hideSheet = jest.fn(),
showSheet = jest.fn(),
}: Partial<ReceiveAccountPickerProps>) =>
render(
<ReceiveAccountPicker
selectedSymbol={selectedSymbol}
isSheetVisible={isSheetVisible}
hideSheet={hideSheet}
showSheet={showSheet}
setSelectedValue={setSelectedValue}
selectedValue={selectedValue}
/>,
);

it('should display "Select coin first" when selectedSymbol is not specified', () => {
const { getByText } = renderPicker({ selectedSymbol: undefined });

expect(getByText('Select coin first')).toBeDefined();
});

it('should not call showSheet when selectedSymbol is not specified', () => {
const showSheet = jest.fn();
const { getByText } = renderPicker({ selectedSymbol: undefined, showSheet });

fireEvent.press(getByText('Receive account'));

expect(showSheet).not.toHaveBeenCalled();
});

it('should display "Not selected" when selectedValue is not specified', () => {
const { getByText } = renderPicker({ selectedSymbol: 'btc', selectedValue: undefined });

expect(getByText('Not selected')).toBeDefined();
});

it('should call showSheet when selectedSymbol is specified and picker pressed', () => {
const showSheet = jest.fn();
const { getByText } = renderPicker({ selectedSymbol: 'btc', showSheet });

fireEvent.press(getByText('Receive account'));

expect(showSheet).toHaveBeenCalledTimes(1);
});

it('should display selected account name', () => {
const { getByText } = renderPicker({
selectedSymbol: 'eth',
selectedValue: {
account: {
accountLabel: 'Account label',
} as unknown as Account,
},
});

expect(getByText('Account label')).toBeDefined();
});

it('should display selected account name and address', () => {
const { getByText } = renderPicker({
selectedSymbol: 'btc',
selectedValue: {
account: {
accountLabel: 'Account label',
} as unknown as Account,
address: {
address: 'Address',
} as unknown as Address,
},
});

expect(getByText('Account label')).toBeDefined();
expect(getByText('Address')).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
export type TradeOverviewOptionProps = {
title: string;
children: ReactNode;
onPress: () => void;
onPress?: () => void;
noBottomBorder?: boolean;
};

Expand Down

0 comments on commit f2bcb53

Please sign in to comment.