From 8843033b68375a9d087b211e16165cedf9305625 Mon Sep 17 00:00:00 2001 From: Albina Nikiforova Date: Wed, 5 Feb 2025 14:08:38 +0100 Subject: [PATCH 01/17] feat(suite): fees redesign --- .../src/components/wallet/Fees/CustomFee.tsx | 35 +- .../src/components/wallet/Fees/FeeDetails.tsx | 296 +++++++++++--- .../suite/src/components/wallet/Fees/Fees.tsx | 364 ++++++++++++------ .../hooks/wallet/__fixtures__/useSendForm.ts | 17 +- packages/suite/src/support/messages.ts | 18 + .../common/TradingForm/TradingFormInputs.tsx | 61 ++- 6 files changed, 565 insertions(+), 226 deletions(-) diff --git a/packages/suite/src/components/wallet/Fees/CustomFee.tsx b/packages/suite/src/components/wallet/Fees/CustomFee.tsx index 5a201429462..00113b7788e 100644 --- a/packages/suite/src/components/wallet/Fees/CustomFee.tsx +++ b/packages/suite/src/components/wallet/Fees/CustomFee.tsx @@ -11,7 +11,18 @@ import { import { NetworkType } from '@suite-common/wallet-config'; import { FeeInfo, FormState } from '@suite-common/wallet-types'; import { getFeeUnits, getInputState, isInteger } from '@suite-common/wallet-utils'; -import { Banner, Column, Grid, Note, Text, useMediaQuery, variables } from '@trezor/components'; +import { + Banner, + Column, + Grid, + Icon, + Note, + Row, + Text, + useMediaQuery, + variables, +} from '@trezor/components'; +import { FeeLevel } from '@trezor/connect'; import { NumberInput } from '@trezor/product-components'; import { spacings } from '@trezor/theme'; import { HELP_CENTER_TRANSACTION_FEES_URL } from '@trezor/urls'; @@ -36,16 +47,16 @@ interface CustomFeeProps { control: Control; setValue: UseFormSetValue; getValues: UseFormGetValues; - changeFeeLimit?: (value: string) => void; composedFeePerByte: string; } +const getCurrentFee = (levels: FeeLevel[]) => `${levels[levels.length > 2 ? 1 : 0].feePerUnit}`; + export const CustomFee = ({ networkType, feeInfo, register, control, - changeFeeLimit, composedFeePerByte, ...props }: CustomFeeProps) => { @@ -67,6 +78,8 @@ export const CustomFee = ({ const feePerUnitError = errors.feePerUnit; const feeLimitError = errors.feeLimit; + const feeIconName = networkType === 'ethereum' ? 'gasPump' : 'receipt'; + const useFeeLimit = networkType === 'ethereum'; const isComposedFeeRateDifferent = !feePerUnitError && composedFeePerByte && feePerUnitValue !== composedFeePerByte; @@ -146,7 +159,7 @@ export const CustomFee = ({ feeLimitError?.type === 'feeLimit' ? feeLimitValidationProps : undefined; return ( - + ({ > + + + + + + + + {getCurrentFee(feeInfo.levels)} {getFeeUnits(networkType)} + + + + + {useFeeLimit ? ( ({ inputState={getInputState(feeLimitError)} name={FEE_LIMIT} data-testid={FEE_LIMIT} - onChange={changeFeeLimit} bottomText={ feeLimitError?.message ? ( void; + changeFeeLevel: (level: FeeLevel['label']) => void; transactionInfo?: PrecomposedTransaction | PrecomposedTransactionCardano; showFee: boolean; + columns: number; + setColumns: (columns: number) => void; }; -type ItemProps = { - label: React.ReactNode; - children: React.ReactNode; +export const FeeCardsWrapper = styled.div<{ $columns: number }>` + width: 100%; + display: grid; + grid-template-columns: repeat(${({ $columns }) => $columns}, 1fr); + gap: ${spacings.xs}; + align-items: stretch; +`; + +type FeeCardProps = { + value: FeeLevel['label']; + setSelectedLevelOption: (option: string) => void; + isSelected: boolean; + changeFeeLevel: (level: FeeLevel['label']) => void; + topLeftChild: React.ReactNode; + topRightChild: React.ReactNode; + bottomLeftChild: React.ReactNode; + bottomRightChild: React.ReactNode; }; -const Item = ({ label, children }: ItemProps) => ( - - {label}: - {children} - +const FEE_CARD_MIN_WIDTH = 180; + +type ResizeObserverType = ( + feeOptions: FeeOption[], + setColumns: (columns: number) => void, +) => ResizeObserver; + +const resizeObserver: ResizeObserverType = (feeOptions, setColumns) => + new ResizeObserver(entries => { + const borderBoxSize = entries[0].borderBoxSize?.[0]; + if (!borderBoxSize) { + return; + } + + const { inlineSize: elementWidth } = borderBoxSize; + + const minWidth = (FEE_CARD_MIN_WIDTH + spacings.xs) * feeOptions.length; + + const columns = elementWidth > minWidth ? feeOptions.length : 1; + + setColumns(columns); + }); + +const FeeCard = ({ + value, + setSelectedLevelOption, + isSelected, + changeFeeLevel, + topLeftChild, + topRightChild, + bottomLeftChild, + bottomRightChild, +}: FeeCardProps) => ( + + { + setSelectedLevelOption(value); + changeFeeLevel(value); + }} + isActive={isSelected} + > + + + {topLeftChild} + + {topRightChild} + + + + {bottomLeftChild} + + {bottomRightChild} + + + + + ); const BitcoinDetails = ({ networkType, feeInfo, - selectedLevel, transactionInfo, + feeOptions, showFee, + setSelectedLevelOption, + selectedLevelOption, + changeFeeLevel, + symbol, + columns, + setColumns, }: DetailsProps) => { const hasInfo = transactionInfo && transactionInfo.type !== 'error'; + const ref = useRef(null); + + useEffect(() => { + if (!ref.current) return; + + resizeObserver(feeOptions, setColumns).observe(ref.current); + + return () => resizeObserver(feeOptions, setColumns).disconnect(); + }, [feeOptions.length, feeOptions, setColumns]); return ( showFee && ( - <> - }> - {formatDuration(feeInfo.blockTime * selectedLevel.blocks * 60)} - - - }> - - {hasInfo ? ` (${transactionInfo.bytes} B)` : ''} - - + + {feeOptions && + feeOptions.map((fee, index) => ( + {fee.label} + } + topRightChild={ + <>~{formatDuration(feeInfo.blockTime * (fee?.blocks || 0) * 60)} + } + bottomLeftChild={ + + } + bottomRightChild={ + <> + + {hasInfo ? ` (${transactionInfo.bytes} B)` : ''} + + } + /> + ))} + ) ); }; const EthereumDetails = ({ - networkType, - selectedLevel, - transactionInfo, showFee, + feeOptions, + setSelectedLevelOption, + selectedLevelOption, + changeFeeLevel, + symbol, + networkType, }: DetailsProps) => { - // States to remember the last known values of feeLimit and feePerByte when isComposedTx was true. - const [lastKnownFeeLimit, setLastKnownFeeLimit] = useState(''); - const [lastKnownFeePerByte, setLastKnownFeePerByte] = useState(''); + const isMainnet = symbol === 'eth'; - const isComposedTx = transactionInfo && transactionInfo.type !== 'error'; + const formatFeePerUnit = (feePerUnit?: string) => { + if (!feePerUnit) return '0.00'; + const num = Number(feePerUnit); - useEffect(() => { - if (isComposedTx && transactionInfo.feeLimit) { - setLastKnownFeeLimit(transactionInfo.feeLimit); - setLastKnownFeePerByte(transactionInfo.feePerByte); - } - }, [isComposedTx, transactionInfo]); - - const gasLimit = isComposedTx - ? transactionInfo.feeLimit - : lastKnownFeeLimit || selectedLevel.feeLimit; - const gasPrice = isComposedTx - ? transactionInfo.feePerByte - : lastKnownFeePerByte || selectedLevel.feePerUnit; + return (Math.ceil(num * 100) / 100).toFixed(isMainnet ? 2 : 4); + }; return ( showFee && ( - <> - }>{gasLimit} - - }> - - - + + + {feeOptions && + feeOptions.map((fee, index) => ( + {fee.label} + } + topRightChild="" + bottomLeftChild={ + + } + bottomRightChild={ + <> + {formatFeePerUnit(fee?.feePerUnit)}{' '} + {getFeeUnits(networkType)} + + } + /> + ))} + + ) ); }; -const RippleDetails = ({ networkType, selectedLevel, showFee }: DetailsProps) => - showFee && {selectedLevel.feePerUnit}; +// Solana, Ripple, Cardano and other networks with only one option +const MiscDetails = ({ networkType, showFee, feeOptions, symbol }: DetailsProps) => { + //In the future Solana should have it's own Details component + const feeOption = feeOptions[0]; + const feeAmount = networkType === 'solana' ? feeOption.feePerTx : feeOption.feePerUnit; + + return ( + showFee && ( + + {}} + isSelected={true} + changeFeeLevel={() => {}} + topLeftChild={ + {feeOption.label} + } + topRightChild="" + bottomLeftChild={ + + } + bottomRightChild={ + + {feeAmount} {getFeeUnits(networkType)} + + } + /> + + ) + ); +}; export const FeeDetails = (props: DetailsProps) => { const { networkType } = props; + const DetailsComponent = () => { + switch (networkType) { + case 'bitcoin': + return ; + case 'ethereum': + return ; + default: + return ; + } + }; + return ( - - - {networkType === 'bitcoin' && } - {networkType === 'ethereum' && } - {networkType === 'ripple' && } + + + ); diff --git a/packages/suite/src/components/wallet/Fees/Fees.tsx b/packages/suite/src/components/wallet/Fees/Fees.tsx index 86b69817217..46300e095d3 100644 --- a/packages/suite/src/components/wallet/Fees/Fees.tsx +++ b/packages/suite/src/components/wallet/Fees/Fees.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { Control, FieldErrors, @@ -8,8 +9,10 @@ import { } from 'react-hook-form'; import { AnimatePresence, motion } from 'framer-motion'; +import styled from 'styled-components'; import { TranslationKey } from '@suite-common/intl-types'; +import { NetworkSymbol, NetworkType } from '@suite-common/wallet-config'; import { FeeInfo, FormState, @@ -19,8 +22,10 @@ import { } from '@suite-common/wallet-types'; import { formatNetworkAmount } from '@suite-common/wallet-utils'; import { + Badge, Banner, Column, + Divider, InfoItem, Note, Row, @@ -30,9 +35,11 @@ import { motionEasing, } from '@trezor/components'; import { FeeLevel } from '@trezor/connect'; -import { spacings } from '@trezor/theme'; +import { spacings, spacingsPx } from '@trezor/theme'; import { FiatValue, FormattedCryptoAmount, Translation } from 'src/components/suite'; +import { useTranslation } from 'src/hooks/suite'; +import { TranslationFunction } from 'src/hooks/suite/useTranslation'; import { Account } from 'src/types/wallet'; import { CustomFee } from './CustomFee'; @@ -46,11 +53,20 @@ const FEE_LEVELS_TRANSLATIONS: Record = { low: 'FEE_LEVEL_LOW', } as const; -const buildFeeOptions = (levels: FeeLevel[]) => - levels.map(({ label }) => ({ - label: , - value: label, - })); +export type FeeOption = { + label: string; + value: FeeLevel['label']; + blocks?: number; + feePerUnit?: string; + networkAmount?: string | null; + feePerTx?: string; +}; + +const SelectBarWrapper = styled.div` + width: 239px; + justify-self: end; + margin-top: ${spacingsPx.xs}; +`; export interface FeesProps { account: Account; @@ -61,19 +77,78 @@ export interface FeesProps { getValues: UseFormGetValues; errors: FieldErrors; changeFeeLevel: (level: FeeLevel['label']) => void; - changeFeeLimit?: (value: string) => void; composedLevels?: PrecomposedLevels | PrecomposedLevelsCardano; label?: TranslationKey; rbfForm?: boolean; helperText?: React.ReactNode; } +const buildFeeOptions = ( + levels: FeeLevel[], + networkType: NetworkType, + symbol: NetworkSymbol, + translationString: TranslationFunction, + composedLevels?: PrecomposedLevels | PrecomposedLevelsCardano, +) => { + const filteredLevels = levels.filter(level => level.label !== 'custom'); + + const getNetworkAmount = (level: FeeLevel) => { + const transactionInfo = composedLevels?.[level.label]; + const hasTransactionInfo = + transactionInfo !== undefined && transactionInfo.type !== 'error'; + const networkAmount = hasTransactionInfo + ? formatNetworkAmount(transactionInfo.fee, symbol) + : null; + // Needed only for Solana because of fee estimation on compose Tx + const fee = hasTransactionInfo ? transactionInfo.fee : level.feePerTx; + + return { networkAmount, fee }; + }; + + const buildBasicFeeOptions = (level: FeeLevel) => { + const { networkAmount } = getNetworkAmount(level); + + return { + label: translationString(FEE_LEVELS_TRANSLATIONS[level.label]), + value: level.label, + feePerUnit: level.feePerUnit, + networkAmount, + }; + }; + + switch (networkType) { + case 'solana': + return filteredLevels.map(level => { + const { fee } = getNetworkAmount(level); + const basicFeeOption = buildBasicFeeOptions(level); + + return { + ...basicFeeOption, + feePerTx: fee, + }; + }); + case 'ethereum': + //legacy fee format + return filteredLevels.map(level => buildBasicFeeOptions(level)); + case 'bitcoin': + return filteredLevels.map(level => { + const basicFeeOption = buildBasicFeeOptions(level); + + return { + ...basicFeeOption, + blocks: level.blocks, + }; + }); + default: + return filteredLevels.map(level => buildBasicFeeOptions(level)); + } +}; + export const Fees = ({ account: { symbol, networkType }, feeInfo, control, changeFeeLevel, - changeFeeLimit, composedLevels, label, rbfForm, @@ -82,134 +157,185 @@ export const Fees = ({ }: FeesProps) => { // Type assertion allowing to make the component reusable, see https://stackoverflow.com/a/73624072. const { getValues, register, setValue } = props as unknown as UseFormReturn; - const errors = props.errors as unknown as FieldErrors; - const selectedOption = getValues('selectedFee') || 'normal'; - const isCustomLevel = selectedOption === 'custom'; + const { translationString } = useTranslation(); + + const [isCustomFee, setIsCustomFee] = useState(false); + const [selectedLevelOption, setSelectedLevelOption] = useState('normal'); + const [columns, setColumns] = useState(1); + const errors = props.errors as unknown as FieldErrors; + const selectedOption = isCustomFee ? 'custom' : selectedLevelOption; const error = errors.selectedFee; const selectedLevel = feeInfo.levels.find(level => level.label === selectedOption)!; const transactionInfo = composedLevels?.[selectedOption]; - const hasTransactionInfo = transactionInfo !== undefined && transactionInfo.type !== 'error'; - // Solana has only `normal` fee level, so we do not display any feeOptions since there is nothing to choose from - const feeOptions = networkType === 'solana' ? [] : buildFeeOptions(feeInfo.levels); - const shouldAnimateNormalFee = !isCustomLevel; + const feeOptions = buildFeeOptions( + feeInfo.levels, + networkType, + symbol, + translationString, + composedLevels, + ); + const hasTransactionInfo = transactionInfo !== undefined && transactionInfo.type !== 'error'; const networkAmount = hasTransactionInfo ? formatNetworkAmount(transactionInfo.fee, symbol) : null; + const supportsCustomFee = networkType !== 'solana'; + return ( - - } - > - - - ) : ( - - ) - } - > - {networkAmount && ( - - + + + + + + ) : ( + + ) + } + > + + + + + + } + /> + + {supportsCustomFee && ( + + { + changeFeeLevel(isCustomFee ? 'normal' : 'custom'); + setIsCustomFee(!isCustomFee); + }} + isFullWidth /> - - + )} + + + <> + + {!isCustomFee && ( + + - - - )} - - - {feeOptions.length > 0 && ( - <> - - - {shouldAnimateNormalFee && ( - - - - )} - - - - {isCustomLevel && ( - - - - )} - - - {error && ( - - {error.message} - + + )} + + + + {isCustomFee && ( + + + + + + + : + + {networkAmount && ( + + + + + + + + + )} + + + )} + + {error && ( + + {error.message} + + )} - {helperText && {helperText}} - - )} + {helperText && {helperText}} + ); }; diff --git a/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts b/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts index f160ed7d93a..281c5a3572d 100644 --- a/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts +++ b/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts @@ -1724,7 +1724,7 @@ export const feeChange = [ actionSequence: [ { type: 'click', - element: 'select-bar/high', + element: 'fee-card/high', result: { composeTransactionCalls: 1, formValues: { @@ -1757,7 +1757,19 @@ export const feeChange = [ }, { type: 'click', - element: 'select-bar/economy', + element: 'select-bar/normal', + result: { + composeTransactionCalls: 1, + formValues: { + selectedFee: 'normal' as const, + feePerUnit: '40', + }, + }, + }, + + { + type: 'click', + element: 'fee-card/economy', result: { composeTransactionCalls: 1, formValues: { @@ -2006,6 +2018,7 @@ export const feeChange = [ element: 'send/close-ethereum-data', }, // switch back to normal + { type: 'click', element: 'select-bar/normal', diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts index 9f51425451a..b9341312724 100644 --- a/packages/suite/src/support/messages.ts +++ b/packages/suite/src/support/messages.ts @@ -3437,6 +3437,10 @@ export default defineMessages({ id: 'TR_GAS_PRICE', defaultMessage: 'Gas price', }, + TR_CURRENT_FEE_CUSTOM_FEES: { + id: 'TR_CURRENT_FEE_CUSTOM_FEES', + defaultMessage: 'Current fee:', + }, TR_GAS_LIMIT: { id: 'TR_GAS_LIMIT', defaultMessage: 'Gas limit', @@ -5601,6 +5605,10 @@ export default defineMessages({ description: 'Label in Send form for Ethereum network type', id: 'MAX_FEE', }, + WHY_FEES: { + defaultMessage: 'Why fees?', + id: 'WHY_FEES', + }, EXPECTED_FEE: { defaultMessage: 'Expected fee', description: 'Label in Send form for Solana network type', @@ -8894,6 +8902,16 @@ export default defineMessages({ defaultMessage: 'Maximum fee is the network transaction fee that you’re willing to pay on the network to ensure your transaction gets processed.', }, + TR_EVM_MAX_FEE_DESC: { + id: 'TR_EVM_MAX_FEE_DESC', + defaultMessage: + 'Set how much you’re willing to pay to process your transaction on the blockchain. A higher maximum fee may speed up your transaction confirmation.', + }, + TR_TRANSACTION_FEE_DESC: { + id: 'TR_TRANSACTION_FEE_DESC', + defaultMessage: + 'The highest transaction fee you’re willing to pay to ensure your transaction gets processed on the blockchain. A higher fee can speed up confirmation times.', + }, TR_STAKE_MAX: { id: 'TR_STAKE_MAX', defaultMessage: 'Max', diff --git a/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormInputs.tsx b/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormInputs.tsx index 56877369cd3..b494f5e69bf 100644 --- a/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormInputs.tsx +++ b/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormInputs.tsx @@ -1,13 +1,6 @@ import { TokenAddress } from '@suite-common/wallet-types'; import { formatAmount } from '@suite-common/wallet-utils'; -import { - Card, - Column, - ElevationContext, - FractionButton, - FractionButtonProps, - Row, -} from '@trezor/components'; +import { Column, FractionButton, FractionButtonProps, Row } from '@trezor/components'; import { hasBitcoinOnlyFirmware } from '@trezor/device-utils/src/firmwareUtils'; import { spacings } from '@trezor/theme'; @@ -131,21 +124,17 @@ export const TradingFormInputs = () => { )} - - - - - + @@ -218,21 +207,17 @@ export const TradingFormInputs = () => { supportedCryptoCurrencies={supportedCryptoCurrencies} methods={{ ...context }} /> - - - - - + ); From 8da4bd7edc2eca6221ea54658d620fe824752891 Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 20:49:08 +0100 Subject: [PATCH 02/17] fixup! feat(suite): fees redesign --- .../common/TradingForm/TradingFormInputs.tsx | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormInputs.tsx b/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormInputs.tsx index b494f5e69bf..56877369cd3 100644 --- a/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormInputs.tsx +++ b/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormInputs.tsx @@ -1,6 +1,13 @@ import { TokenAddress } from '@suite-common/wallet-types'; import { formatAmount } from '@suite-common/wallet-utils'; -import { Column, FractionButton, FractionButtonProps, Row } from '@trezor/components'; +import { + Card, + Column, + ElevationContext, + FractionButton, + FractionButtonProps, + Row, +} from '@trezor/components'; import { hasBitcoinOnlyFirmware } from '@trezor/device-utils/src/firmwareUtils'; import { spacings } from '@trezor/theme'; @@ -124,17 +131,21 @@ export const TradingFormInputs = () => { )} - + + + + + @@ -207,17 +218,21 @@ export const TradingFormInputs = () => { supportedCryptoCurrencies={supportedCryptoCurrencies} methods={{ ...context }} /> - + + + + + ); From cf1442422b6f7b496fd5ef2a3dd693d82135912d Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 20:50:06 +0100 Subject: [PATCH 03/17] fixup! feat(suite): fees redesign --- packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts b/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts index 281c5a3572d..f125eda7274 100644 --- a/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts +++ b/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts @@ -2018,7 +2018,6 @@ export const feeChange = [ element: 'send/close-ethereum-data', }, // switch back to normal - { type: 'click', element: 'select-bar/normal', From 2d709c9b295c30265daa88742cac68ccc8a0d02d Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 20:55:56 +0100 Subject: [PATCH 04/17] fixup! feat(suite): fees redesign --- .../suite/src/components/wallet/Fees/FeeDetails.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx index cf2829ac2fb..f7d279c429b 100644 --- a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx +++ b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx @@ -1,4 +1,4 @@ - import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import styled from 'styled-components'; @@ -103,7 +103,7 @@ const FeeCard = ({ {topRightChild} - + {bottomLeftChild} {bottomRightChild} @@ -166,7 +166,11 @@ const BitcoinDetails = ({ bottomRightChild={ <> {hasInfo ? ` (${transactionInfo.bytes} B)` : ''} From 902539622d2d28a3ab78866996b9298c1a4d67d9 Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 21:05:44 +0100 Subject: [PATCH 05/17] fixup! feat(suite): fees redesign --- .../src/components/wallet/Fees/FeeDetails.tsx | 146 +++++++++--------- 1 file changed, 76 insertions(+), 70 deletions(-) diff --git a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx index f7d279c429b..515130d3cc1 100644 --- a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx +++ b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx @@ -122,6 +122,7 @@ const BitcoinDetails = ({ showFee, setSelectedLevelOption, selectedLevelOption, + selectedLevel, changeFeeLevel, symbol, columns, @@ -141,43 +142,42 @@ const BitcoinDetails = ({ return ( showFee && ( - {feeOptions && - feeOptions.map((fee, index) => ( - {fee.label} - } - topRightChild={ - <>~{formatDuration(feeInfo.blockTime * (fee?.blocks || 0) * 60)} - } - bottomLeftChild={ - ( + {fee.label} + } + topRightChild={ + <>~{formatDuration(feeInfo.blockTime * (fee?.blocks ?? 0) * 60)} + } + bottomLeftChild={ + + } + bottomRightChild={ + <> + - } - bottomRightChild={ - <> - - {hasInfo ? ` (${transactionInfo.bytes} B)` : ''} - - } - /> - ))} + {hasInfo ? ` (${transactionInfo.bytes} B)` : ''} + + } + /> + ))} ) ); @@ -205,34 +205,32 @@ const EthereumDetails = ({ showFee && ( - {feeOptions && - feeOptions.map((fee, index) => ( - {fee.label} - } - topRightChild="" - bottomLeftChild={ - - } - bottomRightChild={ - <> - {formatFeePerUnit(fee?.feePerUnit)}{' '} - {getFeeUnits(networkType)} - - } - /> - ))} + {feeOptions?.map(fee => ( + {fee.label} + } + topRightChild="" + bottomLeftChild={ + + } + bottomRightChild={ + <> + {formatFeePerUnit(fee?.feePerUnit)} {getFeeUnits(networkType)} + + } + /> + ))} ) @@ -240,9 +238,17 @@ const EthereumDetails = ({ }; // Solana, Ripple, Cardano and other networks with only one option -const MiscDetails = ({ networkType, showFee, feeOptions, symbol }: DetailsProps) => { - //In the future Solana should have it's own Details component - const feeOption = feeOptions[0]; +const MiscDetails = ({ + networkType, + showFee, + feeOptions, + symbol, + setSelectedLevelOption, + changeFeeLevel, +}: DetailsProps) => { + if (!feeOptions?.length) return null; + + const feeOption = feeOptions[0]; // in the future Solana should have it's own Details component const feeAmount = networkType === 'solana' ? feeOption.feePerTx : feeOption.feePerUnit; return ( @@ -250,11 +256,11 @@ const MiscDetails = ({ networkType, showFee, feeOptions, symbol }: DetailsProps) {}} + setSelectedLevelOption={setSelectedLevelOption} isSelected={true} - changeFeeLevel={() => {}} + changeFeeLevel={changeFeeLevel} topLeftChild={ - {feeOption.label} + {feeOption.label} } topRightChild="" bottomLeftChild={ From 19fc704f0b357f3907258ff70a39bd3bbdd1ca56 Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 21:14:59 +0100 Subject: [PATCH 06/17] feat(wallet-config): introduce settlement layer param --- .../src/components/CoinLogo/CoinLogo.tsx | 13 +++---------- suite-common/wallet-config/src/networksConfig.ts | 3 +++ suite-common/wallet-config/src/types.ts | 1 + 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/product-components/src/components/CoinLogo/CoinLogo.tsx b/packages/product-components/src/components/CoinLogo/CoinLogo.tsx index 4d5d9245f0e..5edeadb2be5 100644 --- a/packages/product-components/src/components/CoinLogo/CoinLogo.tsx +++ b/packages/product-components/src/components/CoinLogo/CoinLogo.tsx @@ -3,7 +3,7 @@ import { ReactSVG } from 'react-svg'; import styled from 'styled-components'; -import { NetworkSymbol, getNetworkDisplaySymbol } from '@suite-common/wallet-config'; +import { NetworkSymbol, getNetworkOptional } from '@suite-common/wallet-config'; import { borders } from '@trezor/theme'; import { COINS, LegacyNetworkSymbol } from './coins'; @@ -81,15 +81,8 @@ export const CoinLogo = ({ } else if (type === 'network') { symbolSrc = NETWORK_ICONS[symbol as NetworkSymbol]; } else { - // TODO: should be changed, this is hacky way - let networkSymbol; - try { - networkSymbol = getNetworkDisplaySymbol( - symbol as NetworkSymbol, - ).toLowerCase() as NetworkSymbol; - } catch { - networkSymbol = symbol as NetworkSymbol; - } + const network = getNetworkOptional(symbol); + const networkSymbol = network ? network.settlementLayer || symbol : symbol; badge = networkSymbol !== symbol ? NETWORK_ICONS[symbol as NetworkSymbol] : null; symbolSrc = COINS[networkSymbol !== symbol ? networkSymbol : symbol]; diff --git a/suite-common/wallet-config/src/networksConfig.ts b/suite-common/wallet-config/src/networksConfig.ts index 8a8e7513de5..a6aacf8b1c5 100644 --- a/suite-common/wallet-config/src/networksConfig.ts +++ b/suite-common/wallet-config/src/networksConfig.ts @@ -126,6 +126,7 @@ export const networks = { }, arb: { symbol: 'arb', + settlementLayer: 'eth', displaySymbol: 'ETH', displaySymbolName: 'Arbitrum One Ethereum', name: 'Arbitrum One', @@ -150,6 +151,7 @@ export const networks = { }, base: { symbol: 'base', + settlementLayer: 'eth', displaySymbol: 'ETH', displaySymbolName: 'Base Ethereum', name: 'Base', @@ -174,6 +176,7 @@ export const networks = { }, op: { symbol: 'op', + settlementLayer: 'eth', displaySymbol: 'ETH', displaySymbolName: 'Optimism Ethereum', name: 'Optimism', diff --git a/suite-common/wallet-config/src/types.ts b/suite-common/wallet-config/src/types.ts index 3038ce36355..e513149ecfd 100644 --- a/suite-common/wallet-config/src/types.ts +++ b/suite-common/wallet-config/src/types.ts @@ -96,6 +96,7 @@ export type NetworkDeviceSupport = Partial>; type NetworkWithSpecificKey = { symbol: TKey; + settlementLayer?: NetworkSymbol; displaySymbol: string; displaySymbolName?: string; name: string; From 7cdcb73abdf6363d0322b6f853ccec32f25b2f6b Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 21:20:53 +0100 Subject: [PATCH 07/17] fixup! feat(suite): fees redesign --- packages/suite/src/components/wallet/Fees/FeeDetails.tsx | 7 +++---- suite-common/wallet-utils/src/sendFormUtils.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx index 515130d3cc1..17dabf7f2c5 100644 --- a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx +++ b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useRef } from 'react'; import styled from 'styled-components'; import { formatDuration } from '@suite-common/suite-utils'; -import { NetworkSymbol, NetworkType } from '@suite-common/wallet-config'; +import { NetworkSymbol, NetworkType, getNetwork } from '@suite-common/wallet-config'; import { FeeInfo, PrecomposedTransaction, @@ -192,11 +192,10 @@ const EthereumDetails = ({ symbol, networkType, }: DetailsProps) => { - const isMainnet = symbol === 'eth'; + const isMainnet = !getNetwork(symbol).settlementLayer; const formatFeePerUnit = (feePerUnit?: string) => { - if (!feePerUnit) return '0.00'; - const num = Number(feePerUnit); + const num = Number(feePerUnit) || 0; return (Math.ceil(num * 100) / 100).toFixed(isMainnet ? 2 : 4); }; diff --git a/suite-common/wallet-utils/src/sendFormUtils.ts b/suite-common/wallet-utils/src/sendFormUtils.ts index 5bc6823befb..37f451bc95e 100644 --- a/suite-common/wallet-utils/src/sendFormUtils.ts +++ b/suite-common/wallet-utils/src/sendFormUtils.ts @@ -221,7 +221,7 @@ export const isLowAnonymityWarning = (error?: Merge = { bitcoin: 'sat/vB', cardano: 'Lovelaces/B', - ethereum: 'GWEI', + ethereum: 'Gwei', ripple: 'Drops', solana: 'Lamports', }; From f1f5068e70c96d503214febfaf6e2deaf7e61000 Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 21:41:58 +0100 Subject: [PATCH 08/17] fixup! feat(suite): fees redesign --- packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts b/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts index f125eda7274..74bb25c8f33 100644 --- a/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts +++ b/packages/suite/src/hooks/wallet/__fixtures__/useSendForm.ts @@ -1724,7 +1724,7 @@ export const feeChange = [ actionSequence: [ { type: 'click', - element: 'fee-card/high', + element: '@fee-card/high', result: { composeTransactionCalls: 1, formValues: { @@ -1769,7 +1769,7 @@ export const feeChange = [ { type: 'click', - element: 'fee-card/economy', + element: '@fee-card/economy', result: { composeTransactionCalls: 1, formValues: { From f7b9affb977dcd7c956bf94f43701b0c9ba9bc5d Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 21:42:34 +0100 Subject: [PATCH 09/17] fix(suite): fee rounding for layer twos --- packages/suite/src/components/wallet/Fees/FeeDetails.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx index 17dabf7f2c5..83cfeccf082 100644 --- a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx +++ b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx @@ -197,7 +197,11 @@ const EthereumDetails = ({ const formatFeePerUnit = (feePerUnit?: string) => { const num = Number(feePerUnit) || 0; - return (Math.ceil(num * 100) / 100).toFixed(isMainnet ? 2 : 4); + const numOfDecimalPlaces = isMainnet ? 2 : 4; + + const multiplier = Math.pow(10, numOfDecimalPlaces); + + return (Math.ceil(num * multiplier) / multiplier).toFixed(numOfDecimalPlaces); }; return ( From efa7229a3183a7b4f388ae0de576aa6710f6fe2d Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 21:50:04 +0100 Subject: [PATCH 10/17] fixup! feat(wallet-config): introduce settlement layer param --- .../product-components/src/components/CoinLogo/CoinLogo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/product-components/src/components/CoinLogo/CoinLogo.tsx b/packages/product-components/src/components/CoinLogo/CoinLogo.tsx index 5edeadb2be5..7a72c41f92b 100644 --- a/packages/product-components/src/components/CoinLogo/CoinLogo.tsx +++ b/packages/product-components/src/components/CoinLogo/CoinLogo.tsx @@ -82,7 +82,7 @@ export const CoinLogo = ({ symbolSrc = NETWORK_ICONS[symbol as NetworkSymbol]; } else { const network = getNetworkOptional(symbol); - const networkSymbol = network ? network.settlementLayer || symbol : symbol; + const networkSymbol = network?.settlementLayer ?? symbol; badge = networkSymbol !== symbol ? NETWORK_ICONS[symbol as NetworkSymbol] : null; symbolSrc = COINS[networkSymbol !== symbol ? networkSymbol : symbol]; From fd7c779050b49dab99ffb984cd30e0f7c5cc5107 Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 21:54:11 +0100 Subject: [PATCH 11/17] fixup! feat(suite): fees redesign --- packages/suite/src/components/wallet/Fees/CustomFee.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/suite/src/components/wallet/Fees/CustomFee.tsx b/packages/suite/src/components/wallet/Fees/CustomFee.tsx index 00113b7788e..4692e5e1821 100644 --- a/packages/suite/src/components/wallet/Fees/CustomFee.tsx +++ b/packages/suite/src/components/wallet/Fees/CustomFee.tsx @@ -50,7 +50,12 @@ interface CustomFeeProps { composedFeePerByte: string; } -const getCurrentFee = (levels: FeeLevel[]) => `${levels[levels.length > 2 ? 1 : 0].feePerUnit}`; +// TODO: revisit with priority fees +const getCurrentFee = (levels: FeeLevel[]) => { + const middleIndex = Math.floor((levels.length - 1) / 2); + + return levels[middleIndex].feePerUnit; +}; export const CustomFee = ({ networkType, From a8c76913366cb06c1a417ce0f2b867111620fa9f Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 22:09:58 +0100 Subject: [PATCH 12/17] fixup! feat(suite): fees redesign --- .../src/components/FeeRate/FeeRate.tsx | 3 ++- .../src/components/wallet/Fees/CustomFee.tsx | 7 ++++--- .../src/components/wallet/Fees/FeeDetails.tsx | 16 ++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/product-components/src/components/FeeRate/FeeRate.tsx b/packages/product-components/src/components/FeeRate/FeeRate.tsx index 472d155a5bb..6672e0d9dca 100644 --- a/packages/product-components/src/components/FeeRate/FeeRate.tsx +++ b/packages/product-components/src/components/FeeRate/FeeRate.tsx @@ -12,7 +12,8 @@ export const FeeRate = ({ feeRate, networkType }: FeeRateProps) => { return ( - {fee.toFixed(2)} {getFeeUnits(networkType)} + {networkType === 'bitcoin' ? fee.toFixed(2) : fee.toString()}  + {getFeeUnits(networkType)} ); }; diff --git a/packages/suite/src/components/wallet/Fees/CustomFee.tsx b/packages/suite/src/components/wallet/Fees/CustomFee.tsx index 4692e5e1821..660d3302215 100644 --- a/packages/suite/src/components/wallet/Fees/CustomFee.tsx +++ b/packages/suite/src/components/wallet/Fees/CustomFee.tsx @@ -83,8 +83,6 @@ export const CustomFee = ({ const feePerUnitError = errors.feePerUnit; const feeLimitError = errors.feeLimit; - const feeIconName = networkType === 'ethereum' ? 'gasPump' : 'receipt'; - const useFeeLimit = networkType === 'ethereum'; const isComposedFeeRateDifferent = !feePerUnitError && composedFeePerByte && feePerUnitValue !== composedFeePerByte; @@ -187,7 +185,10 @@ export const CustomFee = ({ {getCurrentFee(feeInfo.levels)} {getFeeUnits(networkType)} - + diff --git a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx index 83cfeccf082..c7a86d5adcb 100644 --- a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx +++ b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx @@ -288,21 +288,17 @@ const MiscDetails = ({ export const FeeDetails = (props: DetailsProps) => { const { networkType } = props; - const DetailsComponent = () => { - switch (networkType) { - case 'bitcoin': - return ; - case 'ethereum': - return ; - default: - return ; - } + const detailsComponentMap: Partial>> = { + bitcoin: BitcoinDetails, + ethereum: EthereumDetails, }; + const DetailsComponent = detailsComponentMap[networkType] ?? MiscDetails; + return ( - + ); From d49472faa64917d4009b5071157f37576c72cec6 Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 22:18:56 +0100 Subject: [PATCH 13/17] fixup! feat(suite): fees redesign --- .../suite/src/components/wallet/Fees/Fees.tsx | 21 +++++-------------- packages/suite/src/support/messages.ts | 8 +++++++ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/suite/src/components/wallet/Fees/Fees.tsx b/packages/suite/src/components/wallet/Fees/Fees.tsx index 46300e095d3..d472f248345 100644 --- a/packages/suite/src/components/wallet/Fees/Fees.tsx +++ b/packages/suite/src/components/wallet/Fees/Fees.tsx @@ -38,8 +38,6 @@ import { FeeLevel } from '@trezor/connect'; import { spacings, spacingsPx } from '@trezor/theme'; import { FiatValue, FormattedCryptoAmount, Translation } from 'src/components/suite'; -import { useTranslation } from 'src/hooks/suite'; -import { TranslationFunction } from 'src/hooks/suite/useTranslation'; import { Account } from 'src/types/wallet'; import { CustomFee } from './CustomFee'; @@ -54,7 +52,7 @@ const FEE_LEVELS_TRANSLATIONS: Record = { } as const; export type FeeOption = { - label: string; + label: React.ReactNode; value: FeeLevel['label']; blocks?: number; feePerUnit?: string; @@ -87,7 +85,6 @@ const buildFeeOptions = ( levels: FeeLevel[], networkType: NetworkType, symbol: NetworkSymbol, - translationString: TranslationFunction, composedLevels?: PrecomposedLevels | PrecomposedLevelsCardano, ) => { const filteredLevels = levels.filter(level => level.label !== 'custom'); @@ -109,7 +106,7 @@ const buildFeeOptions = ( const { networkAmount } = getNetworkAmount(level); return { - label: translationString(FEE_LEVELS_TRANSLATIONS[level.label]), + label: , value: level.label, feePerUnit: level.feePerUnit, networkAmount, @@ -158,8 +155,6 @@ export const Fees = ({ // Type assertion allowing to make the component reusable, see https://stackoverflow.com/a/73624072. const { getValues, register, setValue } = props as unknown as UseFormReturn; - const { translationString } = useTranslation(); - const [isCustomFee, setIsCustomFee] = useState(false); const [selectedLevelOption, setSelectedLevelOption] = useState('normal'); const [columns, setColumns] = useState(1); @@ -170,13 +165,7 @@ export const Fees = ({ const selectedLevel = feeInfo.levels.find(level => level.label === selectedOption)!; const transactionInfo = composedLevels?.[selectedOption]; - const feeOptions = buildFeeOptions( - feeInfo.levels, - networkType, - symbol, - translationString, - composedLevels, - ); + const feeOptions = buildFeeOptions(feeInfo.levels, networkType, symbol, composedLevels); const hasTransactionInfo = transactionInfo !== undefined && transactionInfo.type !== 'error'; const networkAmount = hasTransactionInfo @@ -222,8 +211,8 @@ export const Fees = ({ orientation="horizontal" selectedOption={isCustomFee ? 'custom' : 'normal'} options={[ - { label: 'Standard', value: 'normal' }, - { label: 'Advanced', value: 'custom' }, + { label: , value: 'normal' }, + { label: , value: 'custom' }, ]} onChange={() => { changeFeeLevel(isCustomFee ? 'normal' : 'custom'); diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts index b9341312724..dbaeedd4423 100644 --- a/packages/suite/src/support/messages.ts +++ b/packages/suite/src/support/messages.ts @@ -5618,6 +5618,14 @@ export default defineMessages({ defaultMessage: 'Custom', id: 'FEE_LEVEL_CUSTOM', }, + FEE_LEVEL_STANDARD: { + defaultMessage: 'Standard', + id: 'FEE_LEVEL_STANDARD', + }, + FEE_LEVEL_ADVANCED: { + defaultMessage: 'Advanced', + id: 'FEE_LEVEL_ADVANCED', + }, FEE_LEVEL_HIGH: { defaultMessage: 'High', id: 'FEE_LEVEL_HIGH', From b4572b0061ca10069c71358b07b0711da715b79c Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 22:24:36 +0100 Subject: [PATCH 14/17] fixup! feat(suite): fees redesign --- .../src/components/wallet/Fees/FeeDetails.tsx | 14 ++++++-------- packages/suite/src/components/wallet/Fees/Fees.tsx | 3 --- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx index c7a86d5adcb..2a9721b4514 100644 --- a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx +++ b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx @@ -32,8 +32,6 @@ type DetailsProps = { transactionInfo?: PrecomposedTransaction | PrecomposedTransactionCardano; showFee: boolean; - columns: number; - setColumns: (columns: number) => void; }; export const FeeCardsWrapper = styled.div<{ $columns: number }>` @@ -125,9 +123,9 @@ const BitcoinDetails = ({ selectedLevel, changeFeeLevel, symbol, - columns, - setColumns, }: DetailsProps) => { + const [columns, setColumns] = React.useState(1); + const hasInfo = transactionInfo && transactionInfo.type !== 'error'; const ref = useRef(null); @@ -142,9 +140,9 @@ const BitcoinDetails = ({ return ( showFee && ( - {feeOptions?.map(fee => ( + {feeOptions?.map((fee, index) => ( - {feeOptions?.map(fee => ( + {feeOptions?.map((fee, index) => ( ({ const [isCustomFee, setIsCustomFee] = useState(false); const [selectedLevelOption, setSelectedLevelOption] = useState('normal'); - const [columns, setColumns] = useState(1); const errors = props.errors as unknown as FieldErrors; const selectedOption = isCustomFee ? 'custom' : selectedLevelOption; @@ -250,8 +249,6 @@ export const Fees = ({ feeOptions={feeOptions} symbol={symbol} changeFeeLevel={changeFeeLevel} - columns={columns} - setColumns={setColumns} /> )} From 2cfc78b16bbda3b895545bfcfce0803436479a4f Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 22:34:14 +0100 Subject: [PATCH 15/17] feat(suite): translated estimated tx time based on fee --- .../TransactionReviewSummary.tsx | 6 ++++-- .../suite/src/components/wallet/Fees/FeeDetails.tsx | 12 ++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx index e7e3caf75b7..5eee69b5801 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx @@ -1,4 +1,4 @@ -import { formatDuration } from '@suite-common/suite-utils'; +import { formatDurationStrict } from '@suite-common/suite-utils'; import { NetworkType, networks } from '@suite-common/wallet-config'; import { FeeInfo, GeneralPrecomposedTransactionFinal, StakeType } from '@suite-common/wallet-types'; import { getFee } from '@suite-common/wallet-utils'; @@ -7,6 +7,7 @@ import { CoinLogo, FeeRate } from '@trezor/product-components'; import { spacings } from '@trezor/theme'; import { AccountLabel, Translation } from 'src/components/suite'; +import { useLocales } from 'src/hooks/suite'; import { useSelector } from 'src/hooks/suite/useSelector'; import { selectLabelingDataForSelectedAccount } from 'src/reducers/suite/metadataReducer'; import { Account } from 'src/types/wallet'; @@ -44,6 +45,7 @@ export const TransactionReviewSummary = ({ state => state.wallet.selectedAccount.account?.key, ) as string; const fees = useSelector(state => state.wallet.fees); + const locale = useLocales(); const network = networks[account.symbol]; const { symbol, accountType, index, networkType } = account; @@ -69,7 +71,7 @@ export const TransactionReviewSummary = ({ {estimateTime !== undefined && ( {'≈ '} - {formatDuration(estimateTime)} + {formatDurationStrict(estimateTime, locale)} )} diff --git a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx index 2a9721b4514..539e872817d 100644 --- a/packages/suite/src/components/wallet/Fees/FeeDetails.tsx +++ b/packages/suite/src/components/wallet/Fees/FeeDetails.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react'; import styled from 'styled-components'; -import { formatDuration } from '@suite-common/suite-utils'; +import { formatDurationStrict } from '@suite-common/suite-utils'; import { NetworkSymbol, NetworkType, getNetwork } from '@suite-common/wallet-config'; import { FeeInfo, @@ -16,6 +16,7 @@ import { FeeRate } from '@trezor/product-components'; import { spacings } from '@trezor/theme'; import { FiatValue } from 'src/components/suite/FiatValue'; +import { useLocales } from 'src/hooks/suite'; import { FeeOption } from './Fees'; @@ -125,6 +126,7 @@ const BitcoinDetails = ({ symbol, }: DetailsProps) => { const [columns, setColumns] = React.useState(1); + const locale = useLocales(); const hasInfo = transactionInfo && transactionInfo.type !== 'error'; const ref = useRef(null); @@ -151,7 +153,13 @@ const BitcoinDetails = ({ {fee.label} } topRightChild={ - <>~{formatDuration(feeInfo.blockTime * (fee?.blocks ?? 0) * 60)} + <> + ~ + {formatDurationStrict( + feeInfo.blockTime * (fee?.blocks ?? 0) * 60, + locale, + )} + } bottomLeftChild={ Date: Fri, 14 Feb 2025 22:58:51 +0100 Subject: [PATCH 16/17] fixup! feat(suite): fees redesign --- packages/components/src/components/InfoItem/InfoItem.tsx | 1 + packages/suite/src/components/wallet/Fees/FeeDetails.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/src/components/InfoItem/InfoItem.tsx b/packages/components/src/components/InfoItem/InfoItem.tsx index 0e865ebd8af..14f9466fbbc 100644 --- a/packages/components/src/components/InfoItem/InfoItem.tsx +++ b/packages/components/src/components/InfoItem/InfoItem.tsx @@ -86,6 +86,7 @@ export const InfoItem = ({ gap={mapTypographyStyleToIconGap(typographyStyle)} width={labelWidth} flex={labelWidth ? '0 0 auto' : '1 0 auto'} + height={24} > {iconName && ( resizeObserver(feeOptions, setColumns).disconnect(); - }, [feeOptions.length, feeOptions, setColumns]); + }, [feeOptions, setColumns]); return ( showFee && ( @@ -164,7 +164,7 @@ const BitcoinDetails = ({ bottomLeftChild={ From 06d0d4e27bd1d5a7847a3a54bc7a974184618be0 Mon Sep 17 00:00:00 2001 From: tomasklim Date: Fri, 14 Feb 2025 23:06:27 +0100 Subject: [PATCH 17/17] fixup! feat(suite): fees redesign --- .../suite/src/components/wallet/Fees/Fees.tsx | 194 +++++++++--------- packages/suite/src/support/messages.ts | 4 - 2 files changed, 96 insertions(+), 102 deletions(-) diff --git a/packages/suite/src/components/wallet/Fees/Fees.tsx b/packages/suite/src/components/wallet/Fees/Fees.tsx index a6f7de6c818..8404469c141 100644 --- a/packages/suite/src/components/wallet/Fees/Fees.tsx +++ b/packages/suite/src/components/wallet/Fees/Fees.tsx @@ -44,7 +44,7 @@ import { CustomFee } from './CustomFee'; import { FeeDetails } from './FeeDetails'; const FEE_LEVELS_TRANSLATIONS: Record = { - custom: 'FEE_LEVEL_CUSTOM', + custom: 'FEE_LEVEL_ADVANCED', high: 'FEE_LEVEL_HIGH', normal: 'FEE_LEVEL_NORMAL', economy: 'FEE_LEVEL_LOW', @@ -125,7 +125,7 @@ const buildFeeOptions = ( }; }); case 'ethereum': - //legacy fee format + // legacy fee format return filteredLevels.map(level => buildBasicFeeOptions(level)); case 'bitcoin': return filteredLevels.map(level => { @@ -223,105 +223,103 @@ export const Fees = ({ )} - <> - - {!isCustomFee && ( - - - - )} - + + {!isCustomFee && ( + + + + )} + - - {isCustomFee && ( - - - - - - - : - - {networkAmount && ( - - - - - - - - - )} - - - - )} - - {error && ( - - {error.message} - + + {isCustomFee && ( + + + + + + + : + + {networkAmount && ( + + + + + + + + + )} + + + )} + + {error && ( + + {error.message} + + )} - {helperText && {helperText}} - + {helperText && {helperText}} ); }; diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts index dbaeedd4423..063a4aa2653 100644 --- a/packages/suite/src/support/messages.ts +++ b/packages/suite/src/support/messages.ts @@ -5614,10 +5614,6 @@ export default defineMessages({ description: 'Label in Send form for Solana network type', id: 'EXPECTED_FEE', }, - FEE_LEVEL_CUSTOM: { - defaultMessage: 'Custom', - id: 'FEE_LEVEL_CUSTOM', - }, FEE_LEVEL_STANDARD: { defaultMessage: 'Standard', id: 'FEE_LEVEL_STANDARD',