Skip to content

Commit

Permalink
feat: UI facelift of the Affected transactions in the RBF flow
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-sanderson committed Jan 31, 2025
1 parent bd273e8 commit 49fd2cb
Show file tree
Hide file tree
Showing 17 changed files with 349 additions and 414 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { WalletAccountTransaction } from '@suite-common/wallet-types';
import { Transaction } from '@trezor/blockchain-link-types';
import { Icon, InfoSegments, Row, Text } from '@trezor/components';
import { spacings } from '@trezor/theme';

import { Address, FormattedDate, HiddenPlaceholder } from 'src/components/suite';

type RowIcon = {
txType: Transaction['type'];
isAccountOwned: boolean | undefined;
};

const RowIcon = ({ txType, isAccountOwned }: RowIcon) => {
const iconType = txType === 'recv' ? 'receive' : 'send';

return <Icon size={16} variant="disabled" name={isAccountOwned ? iconType : 'clock'} />;
};

type AffectedTransactionItemProps = {
tx: WalletAccountTransaction;
isAccountOwned?: boolean;
};

export const AffectedTransactionItem = ({ tx, isAccountOwned }: AffectedTransactionItemProps) => (
<Row gap={spacings.sm}>
<RowIcon isAccountOwned={isAccountOwned} txType={tx.type} />

<InfoSegments>
{tx.blockTime && <FormattedDate value={new Date(tx.blockTime * 1000)} date time />}

<Text typographyStyle="hint" variant="tertiary">
<HiddenPlaceholder>
<Address value={tx.txid} isTruncated />
</HiddenPlaceholder>
</Text>
</InfoSegments>
</Row>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ChainedTransactions } from '@suite-common/wallet-types';
import { Banner, Card, Column, Divider, Link, Row, Table, Text } from '@trezor/components';
import { spacings } from '@trezor/theme';

import { Translation } from 'src/components/suite';

import { AffectedTransactionItem } from './AffectedTransactionItem';

type AffectedTransactionsProps = {
chainedTxs?: ChainedTransactions;
showChained: () => void;
};

export const AffectedTransactions = ({ chainedTxs, showChained }: AffectedTransactionsProps) => {
if (chainedTxs === undefined) {
return null;
}

return (
<Card fillType="flat" paddingType="none">
<Row justifyContent="space-between" alignItems="center" padding={spacings.md}>
<Text typographyStyle="body">
<Translation id="TR_CHAINED_TXS" />
</Text>
<Text variant="primary" typographyStyle="hint">
<Link onClick={showChained} icon="arrowUpRight" variant="nostyle">
<Translation id="TR_SEE_DETAILS" />
</Link>
</Text>
</Row>
<Divider margin={spacings.zero} />
<Column padding={spacings.md} gap={spacings.md}>
<Banner variant="warning">
<Translation id="TR_AFFECTED_TXS" />
</Banner>
<Table>
<Table.Body>
{chainedTxs.own.map(tx => (
<Table.Row key={tx.txid}>
<Table.Cell>
<AffectedTransactionItem tx={tx} isAccountOwned />
</Table.Cell>
</Table.Row>
))}
{chainedTxs.others.map(tx => (
<Table.Row key={tx.txid}>
<Table.Cell>
<AffectedTransactionItem tx={tx} />
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</Column>
</Card>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { NetworkSymbol } from '@suite-common/wallet-config';
import { Account, FormState } from '@suite-common/wallet-types';
import { formatNetworkAmount } from '@suite-common/wallet-utils';
import {
Banner,
Card,
Column,
Divider,
Icon,
Link,
RadioCard,
Row,
Text,
} from '@trezor/components';
import { spacings } from '@trezor/theme';
import { HELP_CENTER_REPLACE_BY_FEE_BITCOIN } from '@trezor/urls';

import { Address, FormattedCryptoAmount, HiddenPlaceholder } from 'src/components/suite';
import { Translation, TranslationKey } from 'src/components/suite/Translation';
import { RbfContextValues, useRbfContext } from 'src/hooks/wallet/useRbfForm';

type AmountRowProps = {
labelTranslationKey: TranslationKey;
shouldSendInSats: boolean | undefined;
amount: string;
symbol: NetworkSymbol;
};

const AmountItem = ({ labelTranslationKey, shouldSendInSats, amount, symbol }: AmountRowProps) => {
const value = shouldSendInSats ? formatNetworkAmount(amount, symbol) : amount;

return (
<Column>
<Text variant="tertiary" typographyStyle="label">
<Translation id={labelTranslationKey} />
</Text>
<FormattedCryptoAmount value={value} symbol={symbol} />
</Column>
);
};

type ReducedAmount = {
composedLevels: RbfContextValues['composedLevels'];
setMaxOutputId: number;
account: Account;
selectedFee: FormState['selectedFee'];
};

const ReducedAmount = ({ composedLevels, setMaxOutputId, account, selectedFee }: ReducedAmount) => {
if (!composedLevels) {
return null;
}

const precomposedTx = composedLevels[selectedFee || 'normal'];

if (precomposedTx.type !== 'final') {
return null;
}

return (
<>
<Icon name="arrowRightLong" />
<AmountItem
labelTranslationKey="TR_RBF_NEW_AMOUNT"
amount={precomposedTx.outputs[setMaxOutputId].amount.toString()}
symbol={account.symbol}
shouldSendInSats={true} // precomposedTx.outputs is always in Sats
/>
</>
);
};

export const DecreasedOutputs = () => {
const {
showDecreasedOutputs,
formValues,
account,
coinjoinRegisteredUtxos,
getValues,
setValue,
composedLevels,
composeRequest,
shouldSendInSats,
} = useRbfContext();
const { selectedFee, setMaxOutputId } = getValues();

// no set-max means that no output was decreased
if (!showDecreasedOutputs || typeof setMaxOutputId !== 'number') return null;

// find all outputs possible to reduce
const useRadio = formValues.outputs.filter(o => typeof o.address === 'string').length > 1;

const getDecreaseWarring = (): TranslationKey => {
if (account.accountType === 'coinjoin') {
if (coinjoinRegisteredUtxos.length > 0) {
return 'TR_UTXO_REGISTERED_IN_COINJOIN_RBF_WARNING';
} else {
return 'TR_NOT_ENOUGH_ANONYMIZED_FUNDS_RBF_WARNING';
}
}

return 'TR_DECREASE_TX';
};

return (
<Card fillType="flat" paddingType="none">
<Row justifyContent="space-between" alignItems="center" padding={spacings.md}>
<Text typographyStyle="body">
<Translation id="TR_AMOUNT_REDUCED_TXS" />
</Text>
<Text variant="primary" typographyStyle="hint">
<Link
icon="arrowUpRight"
variant="nostyle"
href={HELP_CENTER_REPLACE_BY_FEE_BITCOIN}
>
<Translation id="TR_LEARN_MORE" />
</Link>
</Text>
</Row>

<Divider margin={spacings.zero} />
<Column margin={spacings.md} gap={spacings.md}>
<Banner variant="warning" data-testid="@send/decreased-outputs" icon="warning">
<Translation id={getDecreaseWarring()} />
</Banner>
{useRadio && (
<Text>
<Translation id="TR_DECREASED_AMOUNT_SELECTION_EXPLANATION" />
</Text>
)}
<Column gap={spacings.md} alignItems="center">
{formValues.outputs.flatMap((output, i) => {
if (typeof output.address !== 'string') return null;
const isChecked = setMaxOutputId === i;

return (
// it's safe to use array index as key since outputs do not change
<RadioCard
key={i}
onClick={() => {
if (useRadio) {
setValue('setMaxOutputId', i);
composeRequest();
}
}}
isActive={useRadio && isChecked}
>
<Row gap={spacings.sm}>
<AmountItem
labelTranslationKey="TR_RBF_ORIGINAL_AMOUNT"
amount={output.amount}
symbol={account.symbol}
shouldSendInSats={shouldSendInSats}
/>
{isChecked && (
<ReducedAmount
account={account}
selectedFee={selectedFee}
composedLevels={composedLevels}
setMaxOutputId={setMaxOutputId}
/>
)}
<Column margin={{ left: 'auto' }}>
<Text variant="tertiary" typographyStyle="label">
<Translation id="TR_RECIPIENT_ADDRESS" />
</Text>
<HiddenPlaceholder>
<Address value={output.address} isTruncated />
</HiddenPlaceholder>
</Column>
</Row>
</RadioCard>
);
})}
</Column>
</Column>
</Card>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { variables } from '@trezor/components';
import { Translation, TrezorLink } from 'src/components/suite';
import { TransactionItem } from 'src/components/wallet/TransactionItem/TransactionItem';

import { AffectedTransactionItem } from './ChangeFee/AffectedTransactionItem';
import { AffectedTransactionItem } from './AffectedTransactions/AffectedTransactionItem';

const Wrapper = styled.div`
text-align: left;
Expand Down

This file was deleted.

Loading

0 comments on commit 49fd2cb

Please sign in to comment.