Skip to content

Commit

Permalink
feat(suite): add address confirm to send modal
Browse files Browse the repository at this point in the history
  • Loading branch information
adamhavel committed Feb 3, 2025
1 parent 5e7ed63 commit 97621a3
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ test.describe('Doge Send', { tag: ['@group=wallet', '@snapshot'] }, () => {
});

await test.step('Verify info on modals and confirm', async () => {
await trezorUserEnvLink.pressYes();
await expect(devicePrompt.outputValueOf('amount')).toContainText(
`${localizeNumber(sendAmount)} DOGE`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ export const ConfirmValueModal = ({
const canConfirmOnDevice = !!(device?.connected && device?.available);
const addressConfirmed = isConfirmed || !canConfirmOnDevice;
const isCancelable = isActionAbortable || addressConfirmed;
const state = addressConfirmed ? 'done' : 'default';
const state = addressConfirmed ? 'confirmed' : 'active';
const outputLines: OutputElementLine[] = [
{
id: 'address',
value,
type: 'address',
type: 'safe-address',
},
];

Expand Down Expand Up @@ -109,7 +109,7 @@ export const ConfirmValueModal = ({
heading={heading}
description={description}
onCancel={isCancelable ? onCancel : undefined}
size="large"
size="huge"
>
<Column gap={spacings.xl}>
{!device?.connected && (
Expand Down Expand Up @@ -151,7 +151,6 @@ export const ConfirmValueModal = ({
isDisabled={!addressConfirmed}
onClick={copy}
data-testid={copyButtonDataTest}
isFullWidth
>
{copyButtonText}
</NewModal.Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { TranslationKey } from '@suite-common/intl-types';
import { NetworkSymbol, NetworkType, getNetworkDisplaySymbol } from '@suite-common/wallet-config';
import { BTC_LOCKTIME_VALUE } from '@suite-common/wallet-constants';
import { ReviewOutput, StakeType } from '@suite-common/wallet-types';
import { isTestnet } from '@suite-common/wallet-utils';
import { findAccountsByAddress, isTestnet } from '@suite-common/wallet-utils';
import { BigNumber } from '@trezor/utils/src/bigNumber';

import { Translation } from 'src/components/suite';
import { useTranslation } from 'src/hooks/suite';
import { useSelector, useTranslation } from 'src/hooks/suite';
import type { Account } from 'src/types/wallet';

import {
Expand Down Expand Up @@ -116,21 +116,21 @@ const getOutputLines = (
return [
{
id: type,
type: 'fee',
type: 'amount',
value,
},
];
case 'fee-replace':
return [
{
id: 'increase-fee-by',
type: 'fee',
type: 'amount',
label: <Translation id="TR_INCREASE_FEE_BY" />,
value,
},
{
id: 'increased-fee',
type: 'fee',
type: 'amount',
label: <Translation id="TR_INCREASED_FEE" />,
value: value2,
},
Expand Down Expand Up @@ -232,6 +232,7 @@ export const TransactionReviewOutput = ({
isRbf,
}: TransactionReviewOutputProps) => {
const { networkType, symbol } = account;
const accounts = useSelector(state => state.wallet.accounts);
const { translationString } = useTranslation();
const isFiatVisible =
['fee', 'amount', 'gas', 'fee-replace', 'reduce-output'].includes(type) &&
Expand All @@ -247,7 +248,21 @@ export const TransactionReviewOutput = ({
symbol,
stakeType,
translationString,
);
).map(line => {
if (line.type === 'address') {
const relevantAccounts = findAccountsByAddress(symbol, line.value, accounts);

return {
...line,
type:
relevantAccounts.length > 0
? ('safe-address' as OutputElementLine['type'])
: line.type,
};
}

return line;
});

// prevents double label when bumping stake type txs
if (type === 'address' && isRbf && stakeType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
H4,
Icon,
InfoItem,
Note,
Row,
Text,
} from '@trezor/components';
Expand Down Expand Up @@ -43,9 +44,9 @@ const DataWrapper = styled.p`

const Status = ({ state }: { state: TransactionReviewOutputElementProps['state'] }) => {
switch (state) {
case 'done':
case 'confirmed':
return <Icon size={spacings.md} variant="primary" name="check" />;
case 'pending':
case 'unconfirmed':
return <DotIndicator />;
default:
return <DotIndicator isActive={true} />;
Expand All @@ -58,17 +59,24 @@ type ValueProps = {
symbol: NetworkSymbol;
isFiatVisible: boolean;
isFee: boolean;
state: TransactionReviewOutputElementProps['state'];
token?: TokenInfo;
};

const Value = ({ value, type, symbol, token, isFee, isFiatVisible }: ValueProps) => {
const Value = ({ value, type, symbol, token, isFee, isFiatVisible, state }: ValueProps) => {
switch (type) {
case 'address':
return state !== 'confirmed' ? (
<Note>
<Translation id="TR_SEND_ADDRESS_CONFIRMATION_NOTE" />
</Note>
) : (
<Address value={value} />
);
case 'safe-address':
return <Address value={value} />;
case 'data':
return <DataWrapper>{value}</DataWrapper>;
case 'total':
case 'fee':
case 'amount': {
const isTokenAmount = !isFee && token;
const formattedValue = isTokenAmount
Expand Down Expand Up @@ -114,15 +122,15 @@ const Value = ({ value, type, symbol, token, isFee, isFiatVisible }: ValueProps)
export type OutputElementLine = {
id: string;
value: string;
type: 'default' | 'address' | 'data' | 'amount' | 'fee' | 'total';
type: 'default' | 'address' | 'safe-address' | 'data' | 'amount';
label?: ReactNode;
};

export type TransactionReviewOutputElementProps = {
title: ReactNode;
lines: OutputElementLine[];
account: Account;
state: 'default' | 'done' | 'pending';
state: 'active' | 'confirmed' | 'unconfirmed';
fiatVisible?: boolean;
token?: TokenInfo;
};
Expand All @@ -138,12 +146,12 @@ export const TransactionReviewOutputElement = ({
const { networkType, symbol } = account;

return (
<Card paddingType="none" fillType={state === 'done' ? 'flat' : 'default'}>
<Card paddingType="none" fillType={state === 'confirmed' ? 'flat' : 'default'}>
<Row padding={{ vertical: spacings.sm, horizontal: spacings.md }} gap={spacings.sm}>
<Status state={state} />
<H4
margin={{ left: spacings.xxxs }}
typographyStyle={state !== 'pending' ? 'callout' : 'hint'}
margin={{ left: spacings.xxs }}
typographyStyle={state !== 'unconfirmed' ? 'callout' : 'hint'}
>
{title}
</H4>
Expand All @@ -166,15 +174,12 @@ export const TransactionReviewOutputElement = ({
token={token}
isFiatVisible={fiatVisible}
isFee={line.id === 'fee'}
state={state}
/>
);

return (
<Column
data-testid={`@modal/output-${line.id}`}
key={line.id}
gap={spacings.md}
>
<Column data-testid={`@modal/output-${line.id}`} key={line.id}>
<Text typographyStyle="hint" as="div">
{line.label ? (
<InfoItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import styled from 'styled-components';

import type { GeneralPrecomposedTransactionFinal } from '@suite-common/wallet-types';
import { ReviewOutput, StakeType } from '@suite-common/wallet-types';
import { Banner, Column, H4, Text } from '@trezor/components';
import { findAccountsByAddress } from '@suite-common/wallet-utils';
import { Banner, BulletList, Card, Column, H3, H4, Text } from '@trezor/components';
import { spacings, spacingsPx } from '@trezor/theme';

import { Translation } from 'src/components/suite';
import { useSelector } from 'src/hooks/suite';
import type { Account } from 'src/types/wallet';

import { TransactionReviewOutput } from './TransactionReviewOutput';
Expand All @@ -31,14 +33,14 @@ const getState = (
hasSignedTx: boolean,
): TransactionReviewOutputElementProps['state'] => {
if (hasSignedTx || index < buttonRequestsCount - 1) {
return 'done';
return 'confirmed';
}

if (index === buttonRequestsCount - 1) {
return 'default';
return 'active';
}

return 'pending';
return 'unconfirmed';
};

const Wrapper = styled.div`
Expand Down Expand Up @@ -72,8 +74,15 @@ export const TransactionReviewOutputList = ({
}: TransactionReviewOutputListProps) => {
const outputRefs = useRef<(HTMLDivElement | null)[]>([]);
const totalOutputRef = useRef<HTMLDivElement | null>(null);
const { networkType } = account;
const accounts = useSelector(state => state.wallet.accounts);
const { networkType, symbol } = account;
const isMultirecipient = outputs.filter(({ type }) => type === 'address').length > 1;
const isFirstOutputAddress = outputs[0].type === 'address';
const isFirstStep = buttonRequestsCount === 1;
const isNotStaking = !stakeType;
const isInternalTransfer =
isFirstOutputAddress &&
findAccountsByAddress(symbol, outputs[0].value, accounts).length > 0;

const summaryIndex = outputs.findIndex(
({ type }) => !['address', 'amount', 'opreturn'].includes(type),
Expand All @@ -88,6 +97,47 @@ export const TransactionReviewOutputList = ({
}
}, [buttonRequestsCount, outputs.length, signedTx]);

if (isFirstOutputAddress && isFirstStep && isNotStaking && !isInternalTransfer) {
return (
<Card>
<Column gap={spacings.xxxl}>
<H3>
<Translation id="TR_SEND_ADDRESS_CONFIRMATION_HEADING" />
</H3>
<BulletList
isOrdered
bulletGap={spacings.md}
titleGap={spacings.zero}
gap={spacings.xxl}
>
<BulletList.Item
title={
<H4 typographyStyle="hint">
<Translation id="TR_SEND_ADDRESS_CONFIRMATION_ITEM_1_HEADING" />
</H4>
}
/>
<BulletList.Item
title={
<H4 typographyStyle="hint">
<Translation id="TR_SEND_ADDRESS_CONFIRMATION_ITEM_2_HEADING" />
</H4>
}
/>
<BulletList.Item
state="done"
title={
<H4 typographyStyle="hint">
<Translation id="TR_SEND_ADDRESS_CONFIRMATION_ITEM_3_HEADING" />
</H4>
}
/>
</BulletList>
</Column>
</Card>
);
}

return (
<Column gap={spacings.md}>
{outputs.map((output, index) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
getIsUpdatedSendFlow,
isTestnet,
} from '@suite-common/wallet-utils';
import { BulletListItemState } from '@trezor/components';
import { BigNumber } from '@trezor/utils/src/bigNumber';

import { Translation } from 'src/components/suite/Translation';
Expand All @@ -16,6 +15,7 @@ import { TrezorDevice } from 'src/types/suite';
import {
OutputElementLine,
TransactionReviewOutputElement,
TransactionReviewOutputElementProps,
} from './TransactionReviewOutputElement';

const getLines = (
Expand Down Expand Up @@ -59,7 +59,7 @@ const getLines = (
id: 'fee',
label: <Translation id="MAX_FEE" />,
value: precomposedTx.fee,
type: 'fee',
type: 'amount',
};

return isUnknownStakingClaimValue ? [feeLine] : [amountLine, feeLine];
Expand All @@ -72,13 +72,13 @@ const getLines = (
id: 'total',
label: <Translation id={showAmountWithoutFee ? 'AMOUNT' : 'TR_TOTAL_AMOUNT'} />,
value: tokenInfo ? precomposedTx.totalSpent : amount,
type: 'total',
type: 'amount',
},
{
id: 'fee',
label: <Translation id={feeLabel} />,
value: precomposedTx.fee,
type: 'fee',
type: 'amount',
},
];
}
Expand All @@ -88,13 +88,13 @@ const getLines = (
id: 'total',
label: <Translation id="TR_TOTAL" />,
value: precomposedTx.totalSpent,
type: 'total',
type: 'amount',
},
];
};

export type TransactionReviewTotalOutputProps = {
state: BulletListItemState;
state: TransactionReviewOutputElementProps['state'];
precomposedTx: GeneralPrecomposedTransactionFinal;
account: Account;
isRbf: boolean;
Expand Down
21 changes: 21 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8535,6 +8535,27 @@ export default defineMessages({
defaultMessage:
'{index, selectordinal, one {#st} two {#nd} few {#rd} other {#th} } Recipient',
},
TR_SEND_ADDRESS_CONFIRMATION_HEADING: {
id: 'TR_SEND_ADDRESS_CONFIRMATION_HEADING',
defaultMessage: 'Verify the address to avoid risking your funds',
},
TR_SEND_ADDRESS_CONFIRMATION_ITEM_1_HEADING: {
id: 'TR_SEND_ADDRESS_CONFIRMATION_ITEM_1_HEADING',
defaultMessage: 'Go to the app or place where you originally got the address.',
},
TR_SEND_ADDRESS_CONFIRMATION_ITEM_2_HEADING: {
id: 'TR_SEND_ADDRESS_CONFIRMATION_ITEM_2_HEADING',
defaultMessage: 'Compare that address with what is on your Trezor.',
},
TR_SEND_ADDRESS_CONFIRMATION_ITEM_3_HEADING: {
id: 'TR_SEND_ADDRESS_CONFIRMATION_ITEM_3_HEADING',
defaultMessage: 'Confirm on Trezor if they match exactly.',
},
TR_SEND_ADDRESS_CONFIRMATION_NOTE: {
id: 'TR_SEND_ADDRESS_CONFIRMATION_NOTE',
defaultMessage:
'Verify that the original address matches the address on your Trezor exactly.',
},
TR_DISCOVERY_NEW_COINS: {
id: 'TR_DISCOVERY_NEW_COINS',
defaultMessage: 'Activate coins',
Expand Down

0 comments on commit 97621a3

Please sign in to comment.