Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor send modal #16465

Merged
merged 19 commits into from
Jan 30, 2025
Merged

Refactor send modal #16465

merged 19 commits into from
Jan 30, 2025

Conversation

adamhavel
Copy link
Contributor

@adamhavel adamhavel commented Jan 19, 2025

  • replaced Card fill type "none" with "flat"
  • added isTabular prop to FormattedCryptoAmount
  • refactored send and receive modals

Screenshots:

Basic send flow

Screenshot 2025-01-30 at 13 07 35

Ethereum

Screenshot 2025-01-30 at 13 08 26

Token

Screenshot 2025-01-30 at 13 10 01

Staking

Screenshot 2025-01-30 at 13 10 55

RBF

Screenshot 2025-01-30 at 13 12 28

Receive

Screenshot 2025-01-19 at 15 25 23

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new Address component for displaying addresses with optional truncation.
    • Introduced a new DotIndicator component for visual state representation.
    • Added support for row and column gaps in the Flex component.
    • Created a new TransactionReviewDetails component for displaying transaction details.
  • Improvements

    • Enhanced address formatting with utility functions.
    • Updated transaction review modal with improved layout and styling.
    • Refined transaction output handling and display.
    • Improved localization support with new translation keys.
    • Updated Card component styling with new fill types.
  • Bug Fixes

    • Updated Cardano transaction output type handling.
    • Fixed various styling and layout issues in modals and components.
  • Chores

    • Removed deprecated components and simplified code structure.
    • Updated component prop types and signatures.
    • Refactored transaction review and modal components.

@adamhavel adamhavel self-assigned this Jan 19, 2025
@adamhavel adamhavel linked an issue Jan 19, 2025 that may be closed by this pull request
@adamhavel adamhavel force-pushed the feat/refactor-send-modal branch from b897a28 to 3390737 Compare January 19, 2025 14:40
case 'opreturn':
return <Translation id="OP_RETURN" />;
default:
return label;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest doing an exhaustive switch instead of default fallback.

In case we add new type, the developer shall be notified by TS to handle the case here for the new type:

default: {
            const _unhandledCase: never = type;
            throw new Error(`Unhandled output type: ${_unhandledCase}`);
        }

image

},
];
} else {
outputLines = [
default:
Copy link
Contributor

@peter-sanderson peter-sanderson Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and similarly here

case 'destination-tag':
            return [
                {
                    id: 'default',
                    type: 'default',
                    value,
                },
            ];
        default: {
            const _unhandledCase: never = type;
            throw new Error(`Unhandled output type: ${_unhandledCase}`);
        }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this won't break something. What if there's something that relies on the default value as of now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the switch is truly exhaustive it shall be safe (unless typescript lies about type)

Copy link
Contributor

@peter-sanderson peter-sanderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a huge improvement 👏 . I haven't tested it tho.

Also I did only formal review of the code. I did not focused on the UI/UX stuff (components naming and organization, etc...).

Copy link
Member

@tomasklim tomasklim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First batch:

Black total box
Screenshot 2025-01-20 at 12 17 34

Confirm on Trezor before scanning can be outside of QR code
Screenshot 2025-01-20 at 12 14 56

Can this warning be just a note or delete it please cc @jirih-stsh
Screenshot 2025-01-20 at 12 40 01

  • I would not add network icon to header for now as we should resolve it for tokens somehow
  • token fiat is not available
    Screenshot 2025-01-20 at 12 19 21

Are we fiat first company?
Screenshot 2025-01-20 at 12 42 17

Copy link
Member

@tomasklim tomasklim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am missing this info
Screenshot 2025-01-20 at 12 45 13

@Hermez-cz
Copy link

I would not add network icon to header for now as we should resolve it for tokens somehow
@tomasklim , thanks, resolved in an updated design where the icon is tied to account (where there's always one)

image

@adamhavel
Copy link
Contributor Author

I am missing this info

Screenshot 2025-01-20 at 12 45 13

Some of this will be available in a final screen in future PR

@adamhavel
Copy link
Contributor Author

adamhavel commented Jan 20, 2025

First batch:

Black total box

Screenshot 2025-01-20 at 12 17 34

Confirm on Trezor before scanning can be outside of QR code

Screenshot 2025-01-20 at 12 14 56

Can this warning be just a note or delete it please cc @jirih-stsh

Screenshot 2025-01-20 at 12 40 01

  • I would not add network icon to header for now as we should resolve it for tokens somehow

  • token fiat is not available

Screenshot 2025-01-20 at 12 19 21

Are we fiat first company?

Screenshot 2025-01-20 at 12 42 17

Token fiat wasn't available before - there's comment that it's temporarily hidden before it's fixed. I don't know the context.

I'll fix the black box, thanks:)

@matejkriz matejkriz requested a review from PeKne January 21, 2025 10:11
@adamhavel
Copy link
Contributor Author

  • token fiat is not available

This is the reason:

{/* temporary solution until fiat value for ERC20 tokens will be fixed  */}
{symbol && fiatVisible && !(line.id !== 'fee' && token) && (

Do you know the reason?

@adamhavel adamhavel requested a review from vojtatranta January 23, 2025 20:13
@jvaclavik
Copy link
Contributor

Screen.Recording.2025-01-24.at.13.38.35.mp4

is this correct?

I go back on trezor but it goes next in suite

@jvaclavik
Copy link
Contributor

Screenshot 2025-01-24 at 13 32 30 copy

@adamhavel adamhavel force-pushed the feat/refactor-send-modal branch from d0ab2f3 to 03f54e5 Compare January 30, 2025 16:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/suite-web/e2e/tests/suite/passphrase.test.ts (1)

83-86: Consider extracting the formatting logic into a test helper function.

The address verification logic is correct, but since this pattern is used multiple times in the test, consider extracting it into a helper function to improve maintainability.

+const formatAddressForComparison = (address: string) => 
+  splitStringEveryNCharacters(address, 4).join(' ');

-cy.getTestElement('@modal/output-value').should(
-  'contain',
-  splitStringEveryNCharacters(abcAddr, 4).join(' '),
-);
+cy.getTestElement('@modal/output-value').should(
+  'contain',
+  formatAddressForComparison(abcAddr),
+);
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (3)

41-45: Consider enhancing type safety for the decision prop.

The decision prop's type could be more specific to better handle the resolution value.

 type TransactionReviewModalContentProps = {
-    decision: Deferred<boolean, string | number | undefined> | undefined;
+    decision: Deferred<boolean, string | number> | undefined;
     txInfoState: SendState | StakeState;
     cancelSignTx: () => void;
 };

115-135: Consider memoizing the analytics report function.

The reportTransactionCreatedEvent function is recreated on each render. Consider using useCallback to optimize performance.

-const reportTransactionCreatedEvent = (action: 'sent' | 'copied' | 'downloaded' | 'replaced') =>
+const reportTransactionCreatedEvent = useCallback((action: 'sent' | 'copied' | 'downloaded' | 'replaced') =>
     analytics.report({
         type: EventType.TransactionCreated,
         payload: {
             action,
             symbol,
             tokens: outputs
                 .filter(output => output.token?.symbol)
                 .map(output => output.token?.symbol)
                 .join(','),
             outputsCount: precomposedForm.outputs.length,
             broadcast: isBroadcastEnabled,
             bitcoinLockTime: !!options.includes('bitcoinLockTime'),
             ethereumData: !!options.includes('ethereumData'),
             rippleDestinationTag: !!options.includes('rippleDestinationTag'),
             ethereumNonce: !!options.includes('ethereumNonce'),
             selectedFee: selectedFee || 'normal',
             isCoinControlEnabled: precomposedForm.isCoinControlEnabled,
             hasCoinControlBeenOpened: precomposedForm.hasCoinControlBeenOpened,
         },
-    });
+    }), [outputs, precomposedForm, options, isBroadcastEnabled, selectedFee, symbol]);

193-222: Consider extracting bottom content into a separate component.

The bottom content logic is complex with nested conditions. Extracting it would improve readability and maintainability.

+const TransactionReviewButtons = ({
+    isBroadcastEnabled,
+    serializedTx,
+    isSending,
+    handleSend,
+    handleCopy,
+    handleDownload,
+    actionLabel,
+}: {
+    isBroadcastEnabled: boolean;
+    serializedTx: { tx: string } | undefined;
+    isSending: boolean;
+    handleSend: () => void;
+    handleCopy: () => void;
+    handleDownload: () => void;
+    actionLabel: string;
+}) => {
+    if (!isBroadcastEnabled) {
+        return (
+            <>
+                <NewModal.Button
+                    isDisabled={!serializedTx}
+                    onClick={handleCopy}
+                    data-testid="@send/copy-raw-transaction"
+                >
+                    <Translation id="COPY_TRANSACTION_TO_CLIPBOARD" />
+                </NewModal.Button>
+                <NewModal.Button
+                    variant="tertiary"
+                    isDisabled={!serializedTx}
+                    onClick={handleDownload}
+                >
+                    <Translation id="DOWNLOAD_TRANSACTION" />
+                </NewModal.Button>
+            </>
+        );
+    }
+
+    return (
+        <NewModal.Button
+            data-testid="@modal/send"
+            isDisabled={!serializedTx}
+            isLoading={isSending}
+            onClick={handleSend}
+        >
+            <Translation id={actionLabel} />
+        </NewModal.Button>
+    );
+};

Then use it in the modal:

 bottomContent={
-    !areDetailsVisible &&
-    (isBroadcastEnabled ? (
-        <NewModal.Button
-            data-testid="@modal/send"
-            isDisabled={!serializedTx}
-            isLoading={isSending}
-            onClick={handleSend}
-        >
-            <Translation id={actionLabel} />
-        </NewModal.Button>
-    ) : (
-        <>
-            <NewModal.Button
-                isDisabled={!serializedTx}
-                onClick={handleCopy}
-                data-testid="@send/copy-raw-transaction"
-            >
-                <Translation id="COPY_TRANSACTION_TO_CLIPBOARD" />
-            </NewModal.Button>
-            <NewModal.Button
-                variant="tertiary"
-                isDisabled={!serializedTx}
-                onClick={handleDownload}
-            >
-                <Translation id="DOWNLOAD_TRANSACTION" />
-            </NewModal.Button>
-        </>
-    ))
+    !areDetailsVisible && (
+        <TransactionReviewButtons
+            isBroadcastEnabled={isBroadcastEnabled}
+            serializedTx={serializedTx}
+            isSending={isSending}
+            handleSend={handleSend}
+            handleCopy={handleCopy}
+            handleDownload={handleDownload}
+            actionLabel={actionLabel}
+        />
+    )
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0ab2f3 and 03f54e5.

📒 Files selected for processing (29)
  • packages/components/src/components/Card/Card.tsx (6 hunks)
  • packages/components/src/components/Card/types.tsx (1 hunks)
  • packages/components/src/components/Card/utils.tsx (2 hunks)
  • packages/components/src/components/DotIndicator/DotIndicator.stories.tsx (1 hunks)
  • packages/components/src/components/DotIndicator/DotIndicator.tsx (1 hunks)
  • packages/components/src/components/Flex/Flex.stories.tsx (1 hunks)
  • packages/components/src/components/Flex/Flex.tsx (7 hunks)
  • packages/components/src/index.ts (1 hunks)
  • packages/suite-data/files/translations/en.json (1 hunks)
  • packages/suite-desktop-core/e2e/support/common.ts (2 hunks)
  • packages/suite-desktop-core/e2e/support/pageActions/devicePromptActions.ts (1 hunks)
  • packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts (1 hunks)
  • packages/suite-desktop-core/e2e/tests/coin-market/buy-coins.test.ts (2 hunks)
  • packages/suite-desktop-core/e2e/tests/wallet/cardano.test.ts (2 hunks)
  • packages/suite-desktop-core/e2e/tests/wallet/public-keys.test.ts (1 hunks)
  • packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts (2 hunks)
  • packages/suite-web/e2e/tests/suite/passphrase-cardano.test.ts (2 hunks)
  • packages/suite-web/e2e/tests/suite/passphrase-reconnection.test.ts (4 hunks)
  • packages/suite-web/e2e/tests/suite/passphrase.test.ts (4 hunks)
  • packages/suite/src/components/suite/Address.tsx (1 hunks)
  • packages/suite/src/components/suite/FormattedCryptoAmount.tsx (4 hunks)
  • packages/suite/src/components/suite/index.tsx (2 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmAddressModal.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx (4 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewDetails.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewEvmExplanation.tsx (0 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (3 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewDetails.tsx (0 hunks)
💤 Files with no reviewable changes (2)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewDetails.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewEvmExplanation.tsx
🚧 Files skipped from review as they are similar to previous changes (24)
  • packages/components/src/index.ts
  • packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts
  • packages/suite-desktop-core/e2e/support/common.ts
  • packages/components/src/components/Flex/Flex.stories.tsx
  • packages/suite-web/e2e/tests/suite/passphrase-cardano.test.ts
  • packages/suite-desktop-core/e2e/tests/coin-market/buy-coins.test.ts
  • packages/suite-desktop-core/e2e/tests/wallet/cardano.test.ts
  • packages/components/src/components/Card/types.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmAddressModal.tsx
  • packages/components/src/components/DotIndicator/DotIndicator.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx
  • packages/suite/src/components/suite/FormattedCryptoAmount.tsx
  • packages/suite/src/components/suite/index.tsx
  • packages/components/src/components/Card/Card.tsx
  • packages/suite-data/files/translations/en.json
  • packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts
  • packages/suite-desktop-core/e2e/support/pageActions/devicePromptActions.ts
  • packages/suite-web/e2e/tests/suite/passphrase-reconnection.test.ts
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewDetails.tsx
  • packages/components/src/components/Flex/Flex.tsx
  • packages/suite-desktop-core/e2e/tests/wallet/public-keys.test.ts
  • packages/components/src/components/DotIndicator/DotIndicator.stories.tsx
  • packages/suite/src/components/suite/Address.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx
⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: Type Checking
  • GitHub Check: Releases revision Checks
  • GitHub Check: Other Checks
  • GitHub Check: Build libs for publishing
  • GitHub Check: Linting and formatting
  • GitHub Check: Unit Tests
  • GitHub Check: run-desktop-tests (@group=wallet, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: run-desktop-tests (@group=other, trezor-user-env-unix)
  • GitHub Check: run-desktop-tests (@group=settings, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: EAS Update
  • GitHub Check: run-desktop-tests (@group=device-management, trezor-user-env-unix)
  • GitHub Check: run-desktop-tests (@group=suite, trezor-user-env-unix)
  • GitHub Check: build-web
  • GitHub Check: build-web
  • GitHub Check: Analyze with CodeQL (javascript)
  • GitHub Check: prepare_android_test_app
🔇 Additional comments (6)
packages/components/src/components/Card/utils.tsx (2)

63-63: LGTM! Good practice for consistent box sizing.

Adding a default transparent border prevents layout shifts when switching between fill types.


72-81: Improve code readability and maintainability.

While the implementation is functionally correct, there are a few improvements that could make it more maintainable:

  1. Extract theme-specific styles to improve readability
  2. Consider using elevation map for border colors to maintain consistency

Here's a suggested refactor:

        flat:
-            theme.variant === 'dark'
-                ? css`
-                      background: none;
-                      border: 1px solid ${theme.borderElevation3};
-                  `
-                : css`
-                      background: ${theme.backgroundSurfaceElevationNegative};
-                      border: 1px solid ${theme.borderElevation0};
-                  `,
+            css`
+                ${({ theme }) => {
+                    const styles = {
+                        dark: {
+                            background: 'none',
+                            border: theme.borderElevation3,
+                        },
+                        light: {
+                            background: theme.backgroundSurfaceElevationNegative,
+                            border: theme.borderElevation0,
+                        },
+                    };
+                    const { background, border } = styles[theme.variant === 'dark' ? 'dark' : 'light'];
+                    return css`
+                        background: ${background};
+                        border: 1px solid ${border};
+                    `;
+                }}
+            `,
packages/suite-web/e2e/tests/suite/passphrase.test.ts (3)

4-4: LGTM!

The import of the utility function is properly placed and aligns with the refactoring goals.


114-117: LGTM! Consistent with the first verification.

The address verification logic is consistently applied across all instances. This reinforces the suggestion to extract the formatting logic into a helper function.

Also applies to: 139-142


Line range hint 36-142: Verify test coverage for edge cases.

The test covers the main passphrase flows well. Consider adding test cases for:

  1. Very long addresses that might wrap in the UI
  2. Addresses with special characters
  3. Error cases when address formatting fails
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (1)

Line range hint 77-80: Address TODO comment for Connect Popup special case.

The TODO comment indicates missing implementation for the Connect Popup case. This should be addressed or tracked.

Would you like me to help create an issue to track this TODO or propose an implementation for the Connect Popup case?

Comment on lines +147 to +159
const handleCopy = () => {
const result = copyToClipboard(serializedTx!.tx);

if (typeof result !== 'string') {
dispatch(
notificationsActions.addToast({
type: 'copy-to-clipboard',
}),
);
}

reportTransactionCreatedEvent('copied');
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling in the copy handler.

The copy handler doesn't handle the case where copyToClipboard fails with an error. Consider adding error notification.

 const handleCopy = () => {
-    const result = copyToClipboard(serializedTx!.tx);
+    try {
+        const result = copyToClipboard(serializedTx!.tx);
+        if (typeof result !== 'string') {
+            dispatch(
+                notificationsActions.addToast({
+                    type: 'copy-to-clipboard',
+                }),
+            );
+        }
+        reportTransactionCreatedEvent('copied');
+    } catch (error) {
+        dispatch(
+            notificationsActions.addToast({
+                type: 'error',
+                error: 'Failed to copy transaction',
+            }),
+        );
+    }
-    if (typeof result !== 'string') {
-        dispatch(
-            notificationsActions.addToast({
-                type: 'copy-to-clipboard',
-            }),
-        );
-    }
-
-    reportTransactionCreatedEvent('copied');
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCopy = () => {
const result = copyToClipboard(serializedTx!.tx);
if (typeof result !== 'string') {
dispatch(
notificationsActions.addToast({
type: 'copy-to-clipboard',
}),
);
}
reportTransactionCreatedEvent('copied');
};
const handleCopy = () => {
try {
const result = copyToClipboard(serializedTx!.tx);
if (typeof result !== 'string') {
dispatch(
notificationsActions.addToast({
type: 'copy-to-clipboard',
}),
);
}
reportTransactionCreatedEvent('copied');
} catch (error) {
dispatch(
notificationsActions.addToast({
type: 'error',
error: 'Failed to copy transaction',
}),
);
}
};

@adamhavel adamhavel force-pushed the feat/refactor-send-modal branch from 03f54e5 to 4e96727 Compare January 30, 2025 16:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (4)

25-36: Enhance type safety in getCardanoFingerprint function.

The function could be improved to handle edge cases more explicitly and provide better type safety.

Consider this implementation:

 const getCardanoFingerprint = (
     tokens: Account['tokens'],
     symbol: string | undefined,
 ): string | undefined => {
-    if (!tokens) {
+    if (!tokens || !symbol) {
         return undefined;
     }
 
-    const token = tokens.find(token => token.symbol?.toLowerCase() === symbol?.toLowerCase());
+    const normalizedSymbol = symbol.toLowerCase();
+    const token = tokens.find(token => 
+        token.symbol?.toLowerCase() === normalizedSymbol
+    );
 
     return token?.fingerprint;
 };

88-88: Consider making isTabular prop configurable.

The isTabular prop is hardcoded to false. Consider making it configurable through component props if different formatting styles might be needed in other contexts.


95-97: Improve token address type casting.

The type casting of token contract to TokenAddress could be handled more elegantly.

-tokenAddress={
-    token && !isFee ? (token.contract as TokenAddress) : undefined
-}
+tokenAddress={
+    token && !isFee && token.contract ? (token.contract as TokenAddress) : undefined
+}

202-239: Extract Cardano-specific rendering logic.

The Cardano-specific rendering logic makes the component harder to maintain and understand. Consider extracting it into a separate component.

Example:

const CardanoTokenDetails = ({ 
    networkType,
    token,
    account,
    lineValue
}: {
    networkType: NetworkSymbol;
    token?: TokenInfo;
    account: Account;
    lineValue: string;
}) => {
    if (networkType !== 'cardano' || !token) return null;
    
    return (
        <>
            {token.symbol && (
                <Text typographyStyle="hint" as="div">
                    <InfoItem
                        label={<Text variant="default"><Translation id="TR_CARDANO_FINGERPRINT_HEADLINE" /></Text>}
                        direction="row"
                    >
                        <Column alignItems="flex-end" data-testid="@modal/cardano-fingerprint">
                            {getCardanoFingerprint(account?.tokens, token?.symbol)}
                        </Column>
                    </InfoItem>
                </Text>
            )}
            {token.decimals !== 0 && (
                <Text typographyStyle="hint" as="div">
                    <InfoItem
                        label={<Text variant="default"><Translation id="TR_CARDANO_TREZOR_AMOUNT_HEADLINE" /></Text>}
                        direction="row"
                    >
                        <Column alignItems="flex-end" data-testid="@modal/cardano-trezor-amount">
                            {lineValue}
                        </Column>
                    </InfoItem>
                </Text>
            )}
        </>
    );
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03f54e5 and 4e96727.

📒 Files selected for processing (3)
  • packages/suite-desktop-core/e2e/tests/coin-market/buy-coins.test.ts (2 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (1 hunks)
  • packages/suite/src/constants/suite/firmware.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/suite/src/constants/suite/firmware.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/suite-desktop-core/e2e/tests/coin-market/buy-coins.test.ts
⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: Releases revision Checks
  • GitHub Check: Other Checks
  • GitHub Check: Linting and formatting
  • GitHub Check: Build libs for publishing
  • GitHub Check: Unit Tests
  • GitHub Check: Type Checking
  • GitHub Check: run-desktop-tests (@group=wallet, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: run-desktop-tests (@group=other, trezor-user-env-unix)
  • GitHub Check: build-web
  • GitHub Check: run-desktop-tests (@group=settings, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: build-web
  • GitHub Check: run-desktop-tests (@group=device-management, trezor-user-env-unix)
  • GitHub Check: run-desktop-tests (@group=suite, trezor-user-env-unix)
  • GitHub Check: prepare_android_test_app
  • GitHub Check: EAS Update
  • GitHub Check: Analyze with CodeQL (javascript)
🔇 Additional comments (2)
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (2)

113-128: LGTM! Well-structured type definitions.

The type definitions are clear, well-organized, and properly typed with appropriate optional properties.


141-141: Verify the Card fillType change impact.

The PR objectives mention replacing Card fill type "none" with "flat". Ensure this change is consistent across all modals and doesn't affect the visual hierarchy.

✅ Verification successful

The Card fillType change is consistent with the codebase patterns

The conditional fillType change (state === 'done' ? 'flat' : 'default') aligns with the modal design patterns, where "flat" is consistently used for content sections while maintaining visual hierarchy through conditional states.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other Card components with fillType props
rg --type typescript --type typescriptreact 'fillType=("|'"'"'|{)' -B 2 -A 2

Length of output: 183


Script:

#!/bin/bash
# Search for Card components with fillType prop
rg -t typescript -t typescriptreact '<Card.*fillType' -B 1 -A 1

# Search specifically in modal-related files
rg -t typescript -t typescriptreact '<Card.*fillType' packages/suite/src/components/suite/modals -B 1 -A 1

Length of output: 244


Script:

#!/bin/bash
# Search for Card components with fillType in tsx files
rg '<Card.*fillType' --glob '*.tsx' -B 2 -A 2

# Search specifically in modal components
rg '<Card.*fillType' packages/suite/src/components/suite/modals --glob '*.tsx' -B 2 -A 2

# Search for all fillType occurrences
rg 'fillType=' --glob '*.tsx' -B 2 -A 2

Length of output: 16390

@adamhavel adamhavel force-pushed the feat/refactor-send-modal branch from 4e96727 to 50eb248 Compare January 30, 2025 18:22
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/suite/src/components/suite/Address.tsx (1)

25-27: ⚠️ Potential issue

Add error handling for invalid address formats.

The regex pattern assumes a minimum length of 16 characters (8 for beginning + 8 for end), which could fail with shorter addresses.

-    const [full, beginning, middle, end] = (value.match(/^(.{8})(.*)(.{8})$/) || []).map(part =>
+    const [full, beginning, middle, end] = (
+        (value.length >= 16 ? value.match(/^(.{8})(.*)(.{8})$/) : [value, value, '', '']) || []
+    ).map(part =>
         isChunked ? addSpacing(part) : part,
     );
🧹 Nitpick comments (27)
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx (4)

14-23: Improve clarity and maintainability of the getEstimatedTime function.

Consider the following improvements:

  1. Extract the magic number 60 into a named constant for clarity (seconds per minute).
  2. Make the early return condition more explicit by separating network type check and matchedFeeLevel check.
+const SECONDS_PER_MINUTE = 60;
+
 const getEstimatedTime = (
     networkType: NetworkType,
     symbolFees: FeeInfo,
     tx: GeneralPrecomposedTransactionFinal,
 ): number | undefined => {
     const matchedFeeLevel = symbolFees.levels.find(item => item.feePerUnit === tx.feePerByte);
 
-    if (networkType !== 'bitcoin' || !matchedFeeLevel) return;
+    if (networkType !== 'bitcoin') return undefined;
+    if (!matchedFeeLevel) return undefined;
 
-    return matchedFeeLevel.blocks * symbolFees.blockTime * 60;
+    return matchedFeeLevel.blocks * symbolFees.blockTime * SECONDS_PER_MINUTE;
 };

76-82: Consider using explicit network type check for better readability.

As discussed in the past review comments, consider replacing the negative Solana check with an explicit check for Ethereum networks, as the fee limit is only applicable to EVM chains.

-            {!!tx.feeLimit && network.networkType !== 'solana' && (
+            {!!tx.feeLimit && network.networkType === 'ethereum' && (

111-119: Track the IconButton margin issue.

The TODO comment indicates a known issue with IconButton margins. This should be tracked and addressed.

Would you like me to create an issue to track this UI component bug? The issue would cover:

  1. Current behavior: IconButton not respecting margin props
  2. Expected behavior: IconButton should handle margin props like other components
  3. Temporary workaround: Using Box wrapper

84-94: Consider simplifying the fee display logic.

The conditional rendering for fee display could be simplified by extracting the common parts and only conditionally rendering the different elements.

-            {networkType === 'ethereum' ? (
-                <Note iconName="gasPump">
-                    <Translation id="TR_GAS_PRICE" />
-                    {': '}
-                    {fee} {getFeeUnits(network.networkType)}
-                </Note>
-            ) : (
-                <Note iconName="receipt">
-                    {fee} {getFeeUnits(network.networkType)}
-                </Note>
-            )}
+            <Note iconName={networkType === 'ethereum' ? 'gasPump' : 'receipt'}>
+                {networkType === 'ethereum' && (
+                    <>
+                        <Translation id="TR_GAS_PRICE" />
+                        {': '}
+                    </>
+                )}
+                {fee} {getFeeUnits(network.networkType)}
+            </Note>
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (3)

44-53: Consider adding exhaustive type checking in the default case.

While the current implementation works, adding exhaustive type checking would make future maintenance safer by catching unhandled states at compile time.

-        default:
-            return <DotIndicator isActive={true} />;
+        case 'default':
+            return <DotIndicator isActive={true} />;
+        default: {
+            const _unhandledCase: never = state;
+            throw new Error(`Unhandled state: ${_unhandledCase}`);
+        }

202-239: Consider extracting Cardano-specific rendering logic into a separate component.

The current implementation mixes Cardano-specific token rendering with general output rendering. Extracting this into a separate component would:

  • Improve code organization
  • Make the main component more focused
  • Make it easier to maintain network-specific features
type CardanoTokenInfoProps = {
    token?: TokenInfo;
    account: Account;
    value: string;
};

const CardanoTokenInfo = ({ token, account, value }: CardanoTokenInfoProps) => {
    if (account.networkType !== 'cardano' || !token?.symbol) {
        return null;
    }

    return (
        <>
            <Text typographyStyle="hint" as="div">
                <InfoItem
                    label={
                        <Text variant="default">
                            <Translation id="TR_CARDANO_FINGERPRINT_HEADLINE" />
                        </Text>
                    }
                    direction="row"
                >
                    <Column alignItems="flex-end" data-testid="@modal/cardano-fingerprint">
                        {getCardanoFingerprint(account?.tokens, token?.symbol)}
                    </Column>
                </InfoItem>
            </Text>
            {token.decimals !== 0 && (
                <Text typographyStyle="hint" as="div">
                    <InfoItem
                        label={
                            <Text variant="default">
                                <Translation id="TR_CARDANO_TREZOR_AMOUNT_HEADLINE" />
                            </Text>
                        }
                        direction="row"
                    >
                        <Column alignItems="flex-end" data-testid="@modal/cardano-trezor-amount">
                            {value}
                        </Column>
                    </InfoItem>
                </Text>
            )}
        </>
    );
};

141-141: Consider simplifying the Card's fillType prop.

The ternary expression can be made more readable by extracting the logic.

-        <Card paddingType="none" fillType={state === 'done' ? 'flat' : 'default'}>
+        const cardFillType = state === 'done' ? 'flat' : 'default';
+        <Card paddingType="none" fillType={cardFillType}>
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx (2)

Line range hint 33-43: Consider extracting fee label mapping to a constant.

The network-to-fee-label mapping could be extracted into a constant object to improve maintainability and reduce the complexity of the function.

+const NETWORK_FEE_LABELS: Record<NetworkType, string> = {
+    ethereum: 'MAX_FEE',
+    solana: 'TR_TX_FEE',
+    bitcoin: 'TR_INCLUDING_FEE',
+    ripple: 'TR_INCLUDING_FEE',
+    cardano: 'TR_INCLUDING_FEE',
+};
+
 const feeLabel = ((network: NetworkType) => {
-    switch (network) {
-        case 'ethereum':
-            return 'MAX_FEE';
-        case 'solana':
-            return 'TR_TX_FEE';
-        default:
-            return 'TR_INCLUDING_FEE';
-    }
+    return NETWORK_FEE_LABELS[network];
 })(networkType);

104-130: Consider performance optimizations.

The component could benefit from memoization:

  1. Memoize the lines computation to prevent unnecessary recalculations
  2. Memoize the device selector if it's used in multiple components
+import { useMemo } from 'react';

 export const TransactionReviewTotalOutput = ({
     account,
     state,
     precomposedTx,
     stakeType,
     isRbf,
 }: TransactionReviewTotalOutputProps) => {
     const device = useSelector(selectSelectedDevice);
 
     if (!device) {
         return null;
     }
 
     const { networkType, symbol } = account;
-    const lines = getLines(device, networkType, precomposedTx, isRbf, stakeType);
+    const lines = useMemo(
+        () => getLines(device, networkType, precomposedTx, isRbf, stakeType),
+        [device, networkType, precomposedTx, isRbf, stakeType]
+    );
packages/suite/src/components/suite/Address.tsx (4)

14-14: Consider memoizing the helper function.

The addSpacing function could benefit from memoization if it's called frequently with the same inputs.

-const addSpacing = (value: string) => value.match(/.{1,4}/g)?.join(' ') || value;
+const addSpacing = React.useCallback(
+  (value: string) => value.match(/.{1,4}/g)?.join(' ') || value,
+  []
+);

16-19: Consider adding validation props.

The component could benefit from additional props for address validation and error handling.

 export type AddressProps = {
     value: string;
     isTruncated?: boolean;
+    isValid?: boolean;
+    onValidationError?: (error: string) => void;
 };

31-39: Optimize copy handler with useCallback.

The handleCopy function should be memoized to prevent unnecessary re-renders.

-    const handleCopy = (e: React.ClipboardEvent) => {
+    const handleCopy = React.useCallback((e: React.ClipboardEvent) => {
         const selection = window.getSelection()?.toString();
 
         e.preventDefault();
         e.clipboardData?.setData(
             'text/plain',
             selection?.replace(placeholder, middle).replace(/\s/g, '') ?? value,
         );
-    };
+    }, [middle, placeholder, value]);

41-42: Add error boundary or fallback UI.

Consider adding error handling for cases where the address formatting fails.

-    return <AddressWrapper onCopy={handleCopy}>{formattedValue ?? value}</AddressWrapper>;
+    return (
+        <AddressWrapper onCopy={handleCopy}>
+            {try {
+                formattedValue ?? value
+            } catch (error) {
+                console.error('Address formatting failed:', error);
+                return value;
+            }}
+        </AddressWrapper>
+    );
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx (3)

Line range hint 20-29: Consider using an exhaustive switch with a type assertion.

The switch statement could be improved by adding a type assertion to ensure all network types are handled.

 const getFeeLabel = (networkType: NetworkType) => {
     switch (networkType) {
         case 'ethereum':
             return 'MAX_FEE';
         case 'solana':
             return 'EXPECTED_FEE';
         default: {
-            return 'FEE';
+            const _exhaustiveCheck: never = networkType;
+            return 'FEE'; // This line is still reachable for known network types
         }
     }
 };

49-101: Consider extracting translation IDs to constants.

The function is well-implemented with exhaustive type checking, but the translation IDs could be extracted to named constants for better maintainability.

+const TRANSLATION_IDS = {
+    LOCKTIME_TIMESTAMP: 'LOCKTIME_TIMESTAMP',
+    LOCKTIME_BLOCKHEIGHT: 'LOCKTIME_BLOCKHEIGHT',
+    SUMMARY: 'TR_SUMMARY',
+    TOKEN: 'TR_TOKEN',
+    CONTRACT: 'TR_CONTRACT',
+    // ... other IDs
+} as const;

 const getOutputTitle = (
     type: ReviewOutput['type'],
     networkType: NetworkType,
     value: string,
     isRbf: boolean,
     stakeType: StakeType | undefined,
 ): ReactNode | undefined => {
     // ... rest of the implementation using TRANSLATION_IDS
 };

216-267: Consider performance optimizations and explicit handling.

A few suggestions to improve the component:

  1. Memoize complex calculations
  2. Make the early return condition more explicit
+import { useMemo } from 'react';

 export const TransactionReviewOutput = ({
     type,
     state,
     label,
     value,
     value2,
     token,
     account,
     stakeType,
     isRbf,
 }: TransactionReviewOutputProps) => {
     const { networkType, symbol } = account;
     const { translationString } = useTranslation();
-    const isFiatVisible =
+    const isFiatVisible = useMemo(() =>
         ['fee', 'amount', 'gas', 'fee-replace', 'reduce-output'].includes(type) &&
-        !isTestnet(symbol);
+        !isTestnet(symbol),
+        [type, symbol]
+    );

-    const outputTitle = getOutputTitle(type, networkType, value, isRbf, stakeType);
+    const outputTitle = useMemo(
+        () => getOutputTitle(type, networkType, value, isRbf, stakeType),
+        [type, networkType, value, isRbf, stakeType]
+    );

     // prevents double label when bumping stake type txs
     if (type === 'address' && isRbf && stakeType) {
-        return null;
+        return null; // Skipping render for stake type RBF transactions
     }
     // ... rest of the implementation
 };
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewDetails.tsx (3)

21-21: Consider using more specific types instead of any.

The prettify function uses the Record<any, any> type which is not type-safe. Consider using more specific types or Record<string, unknown> as a safer alternative.

-const prettify = (json: Record<any, any>) => JSON.stringify(json, null, 2);
+const prettify = (json: Record<string, unknown>) => JSON.stringify(json, null, 2);

24-24: Add a TODO comment with more context.

The comment "BTC-only, TODO: eth/ripple" could be more descriptive about what needs to be implemented for ETH and Ripple.

-    if (tx.inputs.length === 0) return null; // BTC-only, TODO: eth/ripple
+    if (tx.inputs.length === 0) return null; // BTC-only
+    // TODO: Implement transaction details view for ETH and Ripple transactions which don't have inputs

35-40: Consider extracting repeated Card pattern into a reusable component.

The same Card pattern with padding and Text styling is repeated three times. Consider extracting it into a reusable component to reduce code duplication.

const CodeCard = ({ children }: { children: React.ReactNode }) => (
    <Card paddingType="small">
        <Text typographyStyle="label" as="div">
            <Pre>{children}</Pre>
        </Text>
    </Card>
);

Then use it like:

-                    <Card paddingType="small">
-                        <Text typographyStyle="label" as="div">
-                            <Pre>{prettify(tx.inputs)}</Pre>
-                        </Text>
-                    </Card>
+                    <CodeCard>{prettify(tx.inputs)}</CodeCard>

Also applies to: 42-47, 50-55

packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputList.tsx (2)

101-101: Avoid ref assignment in expressions.

The ref assignment in the expression can be confusing and is flagged by static analysis. Consider using a separate function for ref handling.

-                    <Wrapper key={index} ref={ref => (outputRefs.current[index] = ref)}>
+                    <Wrapper
+                        key={index}
+                        ref={ref => {
+                            outputRefs.current[index] = ref;
+                        }}
+                    >
🧰 Tools
🪛 Biome (1.9.4)

[error] 101-101: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


83-89: Consider debouncing scroll behavior.

The scroll behavior in the useEffect hook might cause multiple unnecessary scrolls when buttonRequestsCount changes rapidly. Consider debouncing the scroll operation.

import { debounce } from 'lodash';

// Add this before the component:
const debouncedScroll = debounce((element: HTMLElement | null, behavior: ScrollBehavior) => {
    element?.scrollIntoView({ behavior });
}, 100);

// Then in useEffect:
useEffect(() => {
    if (buttonRequestsCount - 1 === outputs.length || signedTx) {
        debouncedScroll(totalOutputRef.current, signedTx ? 'instant' : 'smooth');
    } else if (buttonRequestsCount !== 0) {
        debouncedScroll(outputRefs.current[buttonRequestsCount - 1], 'smooth');
    }
    return () => debouncedScroll.cancel();
}, [buttonRequestsCount, outputs.length, signedTx]);
packages/components/src/components/Flex/Flex.tsx (2)

176-177: Consider using nullish coalescing for gap fallbacks.

Using the nullish coalescing operator would be more explicit about the fallback behavior for undefined values.

-    rowGap = gap,
-    columnGap = gap,
+    rowGap ?? = gap,
+    columnGap ?? = gap,

134-134: Consider handling zero gaps more efficiently.

When both gaps are zero, we could avoid setting the gap property altogether to reduce CSS output.

-    gap: ${({ $rowGap, $columnGap }) => `${$rowGap}px ${$columnGap}px`};
+    gap: ${({ $rowGap, $columnGap }) =>
+        $rowGap === 0 && $columnGap === 0 ? 'unset' : `${$rowGap}px ${$columnGap}px`};
packages/components/src/components/Card/Card.tsx (1)

69-72: Consider reducing transition duration.

A 0.5s duration for hover transitions might feel sluggish. Material Design guidelines suggest using shorter durations (0.2s-0.3s) for hover effects to maintain a snappy feel.

    transition:
-        background 0.5s,
-        border 0.5s,
-        box-shadow 0.5s;
+        background 0.2s,
+        border 0.2s,
+        box-shadow 0.2s;
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (3)

115-135: Consider extracting analytics reporting to a separate utility.

The analytics reporting logic could be moved to a separate utility function to improve maintainability and reusability.

+// transactionAnalytics.ts
+export const reportTransactionCreated = ({
+    action,
+    outputs,
+    precomposedForm,
+    symbol,
+    isBroadcastEnabled,
+    options,
+    selectedFee,
+}: TransactionAnalyticsParams) => {
+    analytics.report({
+        type: EventType.TransactionCreated,
+        payload: {
+            action,
+            symbol,
+            tokens: outputs
+                .filter(output => output.token?.symbol)
+                .map(output => output.token?.symbol)
+                .join(','),
+            outputsCount: precomposedForm.outputs.length,
+            broadcast: isBroadcastEnabled,
+            bitcoinLockTime: !!options.includes('bitcoinLockTime'),
+            ethereumData: !!options.includes('ethereumData'),
+            rippleDestinationTag: !!options.includes('rippleDestinationTag'),
+            ethereumNonce: !!options.includes('ethereumNonce'),
+            selectedFee: selectedFee || 'normal',
+            isCoinControlEnabled: precomposedForm.isCoinControlEnabled,
+            hasCoinControlBeenOpened: precomposedForm.hasCoinControlBeenOpened,
+        },
+    });
+};

180-192: Consider extracting description logic to a separate component.

The description prop contains complex conditional rendering logic that could be extracted to improve readability.

+const TransactionReviewModalDescription = ({
+    areDetailsVisible,
+    tx,
+    account,
+    broadcast,
+    onDetailsClick,
+    stakeType,
+}: DescriptionProps) =>
+    !areDetailsVisible && (
+        <TransactionReviewSummary
+            tx={tx}
+            account={account}
+            broadcast={broadcast}
+            onDetailsClick={onDetailsClick}
+            stakeType={stakeType}
+        />
+    );

193-221: Consider extracting button logic to a separate component.

The bottom content contains complex conditional rendering for buttons that could be extracted to improve maintainability.

+const TransactionReviewModalButtons = ({
+    isBroadcastEnabled,
+    serializedTx,
+    isSending,
+    onSend,
+    onCopy,
+    onDownload,
+    actionLabel,
+}: ButtonsProps) =>
+    !areDetailsVisible &&
+    (isBroadcastEnabled ? (
+        <NewModal.Button
+            data-testid="@modal/send"
+            isDisabled={!serializedTx}
+            isLoading={isSending}
+            onClick={onSend}
+        >
+            <Translation id={actionLabel} />
+        </NewModal.Button>
+    ) : (
+        <>
+            <NewModal.Button
+                isDisabled={!serializedTx}
+                onClick={onCopy}
+                data-testid="@send/copy-raw-transaction"
+            >
+                <Translation id="COPY_TRANSACTION_TO_CLIPBOARD" />
+            </NewModal.Button>
+            <NewModal.Button
+                variant="tertiary"
+                isDisabled={!serializedTx}
+                onClick={onDownload}
+            >
+                <Translation id="DOWNLOAD_TRANSACTION" />
+            </NewModal.Button>
+        </>
+    ));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e96727 and 50eb248.

📒 Files selected for processing (41)
  • packages/components/src/components/Card/Card.tsx (6 hunks)
  • packages/components/src/components/Card/types.tsx (1 hunks)
  • packages/components/src/components/Card/utils.tsx (2 hunks)
  • packages/components/src/components/DotIndicator/DotIndicator.stories.tsx (1 hunks)
  • packages/components/src/components/DotIndicator/DotIndicator.tsx (1 hunks)
  • packages/components/src/components/Flex/Flex.stories.tsx (1 hunks)
  • packages/components/src/components/Flex/Flex.tsx (7 hunks)
  • packages/components/src/index.ts (1 hunks)
  • packages/suite-data/files/translations/en.json (1 hunks)
  • packages/suite-desktop-core/e2e/support/common.ts (2 hunks)
  • packages/suite-desktop-core/e2e/support/pageActions/devicePromptActions.ts (1 hunks)
  • packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts (1 hunks)
  • packages/suite-desktop-core/e2e/tests/coin-market/buy-coins.test.ts (2 hunks)
  • packages/suite-desktop-core/e2e/tests/wallet/cardano.test.ts (2 hunks)
  • packages/suite-desktop-core/e2e/tests/wallet/public-keys.test.ts (1 hunks)
  • packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts (2 hunks)
  • packages/suite-web/e2e/tests/suite/passphrase-cardano.test.ts (2 hunks)
  • packages/suite-web/e2e/tests/suite/passphrase-reconnection.test.ts (4 hunks)
  • packages/suite-web/e2e/tests/suite/passphrase.test.ts (4 hunks)
  • packages/suite/src/components/suite/Address.tsx (1 hunks)
  • packages/suite/src/components/suite/FormattedCryptoAmount.tsx (4 hunks)
  • packages/suite/src/components/suite/index.tsx (2 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmAddressModal.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx (4 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewDetails.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewEvmExplanation.tsx (0 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (3 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewDetails.tsx (0 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx (2 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputList.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewStepIndicator.tsx (0 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx (3 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ChangeFee.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/AdvancedTxDetails.tsx (1 hunks)
  • packages/suite/src/support/messages.ts (1 hunks)
  • packages/suite/src/views/wallet/send/TotalSent/TotalSent.tsx (1 hunks)
  • packages/suite/src/views/wallet/trading/common/TradingTransactions/TradingTransaction/TradingTransactionContainer.tsx (1 hunks)
  • suite-common/wallet-utils/src/reviewTransactionUtils.ts (2 hunks)
💤 Files with no reviewable changes (3)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewStepIndicator.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewDetails.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewEvmExplanation.tsx
🚧 Files skipped from review as they are similar to previous changes (26)
  • packages/components/src/index.ts
  • packages/suite/src/views/wallet/trading/common/TradingTransactions/TradingTransaction/TradingTransactionContainer.tsx
  • packages/suite-web/e2e/tests/suite/passphrase-cardano.test.ts
  • packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/AdvancedTxDetails.tsx
  • packages/suite-desktop-core/e2e/support/common.ts
  • packages/suite/src/components/suite/index.tsx
  • packages/components/src/components/Flex/Flex.stories.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ChangeFee.tsx
  • packages/components/src/components/DotIndicator/DotIndicator.tsx
  • packages/suite-desktop-core/e2e/tests/coin-market/buy-coins.test.ts
  • packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts
  • packages/components/src/components/Card/types.tsx
  • packages/suite/src/views/wallet/send/TotalSent/TotalSent.tsx
  • packages/components/src/components/DotIndicator/DotIndicator.stories.tsx
  • packages/suite-desktop-core/e2e/tests/wallet/public-keys.test.ts
  • packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx
  • packages/suite-web/e2e/tests/suite/passphrase-reconnection.test.ts
  • packages/suite-desktop-core/e2e/tests/wallet/cardano.test.ts
  • packages/suite-desktop-core/e2e/support/pageActions/devicePromptActions.ts
  • suite-common/wallet-utils/src/reviewTransactionUtils.ts
  • packages/suite-data/files/translations/en.json
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmAddressModal.tsx
  • packages/suite/src/components/suite/FormattedCryptoAmount.tsx
  • packages/suite/src/support/messages.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputList.tsx

[error] 101-101: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: Unit Tests
  • GitHub Check: Type Checking
  • GitHub Check: Other Checks
  • GitHub Check: Linting and formatting
  • GitHub Check: Build libs for publishing
  • GitHub Check: Releases revision Checks
  • GitHub Check: run-desktop-tests (@group=wallet, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: run-desktop-tests (@group=other, trezor-user-env-unix)
  • GitHub Check: EAS Update
  • GitHub Check: run-desktop-tests (@group=settings, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: run-desktop-tests (@group=device-management, trezor-user-env-unix)
  • GitHub Check: run-desktop-tests (@group=suite, trezor-user-env-unix)
  • GitHub Check: Analyze with CodeQL (javascript)
  • GitHub Check: build-web
  • GitHub Check: build-web
  • GitHub Check: prepare_android_test_app
🔇 Additional comments (16)
packages/suite-web/e2e/tests/suite/passphrase.test.ts (2)

4-4: LGTM! Clean import of the utility function.

The addition of the splitStringEveryNCharacters utility function import helps centralize the address formatting logic.


83-86: LGTM! Consistent approach to address verification.

The changes improve the test by:

  • Using a consistent approach across all address verifications
  • Centralizing the address formatting logic in a utility function
  • Making the test more maintainable by removing duplicated formatting logic

The implementation correctly verifies the formatted addresses in the modal output.

Also applies to: 114-117, 139-142

packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (2)

25-36: LGTM! Well-implemented utility function.

The getCardanoFingerprint function correctly handles:

  • Case-insensitive token symbol comparison
  • Null safety checks
  • Clear return type

64-110: LGTM! Well-structured Value component with proper type safety.

The component:

  • Handles different value types appropriately
  • Includes proper exhaustive type checking
  • Correctly formats token and network amounts
  • Has clear separation of concerns
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx (3)

1-19: LGTM! Well-organized imports with clear separation of concerns.

The imports are logically grouped and properly sourced from common packages.


96-102: LGTM! Clear and well-defined props interface.

The props interface is concise and properly typed with all necessary properties.


48-65: Verify the handling of unknown staking claim value.

The special case for unknown staking claim value only returns the fee line. Please verify if this is the intended behavior for all staking claim scenarios.

✅ Verification successful

Special handling of claim transactions is verified and appears intentional

The codebase consistently shows special handling for claim operations, including fee-only displays in certain scenarios. This pattern is particularly evident in RBF (Replace-By-Fee) claim transactions where showing only the fee line aligns with the overall architecture.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other instances where staking claim values are handled
rg -A 5 "stakeType.*===.*'claim'" .

Length of output: 5010

packages/suite/src/components/suite/Address.tsx (1)

1-6: LGTM! Clean imports and well-defined constant.

The imports are appropriate for the component's needs, and the truncation placeholder is well-defined as a constant.

packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx (2)

31-47: LGTM! Clear and type-safe mapping implementation.

The stake type to translation key mapping is well-structured and type-safe.


102-214: Verify edge cases in amount handling.

The function handles various output types well, but there are some potential edge cases to consider:

  1. Empty strings for value and value2 in amount-related cases
  2. Negative values in fee calculations

Run this script to check for potential edge cases:

✅ Verification successful

Edge cases in amount handling are properly covered by tests

The codebase has comprehensive test coverage for amount-related edge cases:

  • Empty strings and invalid amounts are properly validated
  • Negative values are prevented through validation
  • Fee calculations handle edge cases like MAX_SAFE_INTEGER
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for amount/fee value handling in tests
rg -A 5 "amount|fee" --glob "**/*test*" --type ts

Length of output: 91594

packages/components/src/components/Card/utils.tsx (1)

72-81: Consider using elevation map for border colors.

The flat fill type implementation could benefit from using the elevation map for border colors to maintain consistency with the theme system.

packages/components/src/components/Card/Card.tsx (3)

3-3: LGTM! Clean import organization.

The imports are well-organized and the new imports (useTheme, ElevationContext) align with the component's enhanced functionality.

Also applies to: 22-22


41-41: LGTM! Consistent fill type handling.

The background condition now checks for 'flat' instead of 'none', aligning with the PR's objective to replace the Card fill type.


149-155: LGTM! Smart elevation handling based on theme.

The conditional rendering with ElevationContext correctly handles elevation differences between dark and light themes.

packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (2)

147-159: Improve error handling in the copy handler.

The copy handler should handle potential errors from copyToClipboard.


138-140: Document Solana-specific behavior.

The special case for Solana network type needs documentation explaining why it requires different loading state handling.

Comment on lines +8 to +12
const AddressWrapper = styled.p`
text-wrap: balance;
font-variant-numeric: tabular-nums;
letter-spacing: 0;
`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Add progressive enhancement for experimental CSS property

The text-wrap: balance property is experimental with limited browser support (Chrome 114+, Safari 16.5+). Consider using @supports for progressive enhancement:

const AddressWrapper = styled.p`
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    
    @supports (text-wrap: balance) {
        text-wrap: balance;
    }
`;
🔗 Analysis chain

Consider browser compatibility for CSS properties.

The text-wrap: balance property is experimental and has limited browser support. Consider adding a fallback or progressive enhancement strategy.

Run this script to check browser support:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check browser support for CSS properties
curl -s "https://raw.githubusercontent.com/mdn/browser-compat-data/main/css/properties/text-wrap.json" | jq '.css.properties["text-wrap"].support'

Length of output: 151


Script:

#!/bin/bash
# Check browser support for both CSS properties
echo "Checking text-wrap support:"
curl -s "https://raw.githubusercontent.com/mdn/browser-compat-data/main/css/properties/text-wrap/balance.json" | jq '.css.properties["text-wrap-balance"].support'

echo -e "\nChecking font-variant-numeric support:"
curl -s "https://raw.githubusercontent.com/mdn/browser-compat-data/main/css/properties/font-variant-numeric.json" | jq '.css.properties["font-variant-numeric"].support'

Length of output: 558

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (11)
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx (1)

102-214: Consider simplifying similar output line cases.

The getOutputLines function has several cases that follow similar patterns. Consider grouping similar cases to reduce code duplication.

For example, these cases could be grouped:

-        case 'txid':
-        case 'contract':
-        case 'opreturn':
-        case 'destination-tag':
-        case 'locktime':
-            return [
-                {
-                    id: type,
-                    type: 'data',
-                    value,
-                },
-            ];
+        case 'txid':
+        case 'contract':
+        case 'opreturn':
+        case 'destination-tag':
+        case 'locktime': {
+            const commonDataOutput = {
+                id: type,
+                type: 'data',
+                value,
+            };
+            return [commonDataOutput];
+        }
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx (3)

Line range hint 33-43: Consider extracting network-specific fee label logic.

The network-specific fee label logic could be moved to a separate function or constant mapping for better maintainability.

+const NETWORK_FEE_LABELS: Record<NetworkType, string> = {
+    ethereum: 'MAX_FEE',
+    solana: 'TR_TX_FEE',
+    bitcoin: 'TR_INCLUDING_FEE',
+    ripple: 'TR_INCLUDING_FEE',
+    cardano: 'TR_INCLUDING_FEE',
+};
+
 const feeLabel = ((network: NetworkType) => {
-    switch (network) {
-        case 'ethereum':
-            return 'MAX_FEE';
-        case 'solana':
-            return 'TR_TX_FEE';
-        default:
-            return 'TR_INCLUDING_FEE';
-    }
+    return NETWORK_FEE_LABELS[network];
 })(networkType);

51-83: Enhance type safety for amount and fee lines.

Consider creating a type union for line types and extracting common line properties to improve type safety and maintainability.

type LineType = 'amount' | 'fee' | 'total';

interface BaseOutputLine {
    id: string;
    label: React.ReactNode;
    value: string;
    type: LineType;
}

const createLine = (
    id: string,
    labelId: string,
    value: string,
    type: LineType
): BaseOutputLine => ({
    id,
    label: <Translation id={labelId} />,
    value,
    type,
});

111-115: Consider handling device check at a higher level.

The device null check could be moved up the component tree to avoid unnecessary component instantiation and improve reusability.

This component could be wrapped by a higher-order component or the check could be moved to the parent component:

// In parent component
if (!device) {
    return null;
}

return (
    <TransactionReviewTotalOutput
        account={account}
        state={state}
        precomposedTx={precomposedTx}
        stakeType={stakeType}
        isRbf={isRbf}
        device={device} // Pass device as prop instead of using selector
    />
);
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (2)

Line range hint 61-71: Consider memoizing the precomposedForm selector.

The selector combines multiple conditions and could benefit from memoization to prevent unnecessary recalculations.

-    const precomposedForm = useSelector(state =>
-        isStakeState(txInfoState)
-            ? selectStakePrecomposedForm(state)
-            : selectPrecomposedSendForm(state),
-    );
+    const precomposedForm = useSelector(
+        state =>
+            isStakeState(txInfoState)
+                ? selectStakePrecomposedForm(state)
+                : selectPrecomposedSendForm(state),
+        (prev, next) => prev?.selectedFee === next?.selectedFee
+    );

193-222: Improve readability of conditional rendering in bottomContent.

The nested conditional rendering using && and ternary operators could be simplified for better maintainability.

-                bottomContent={
-                    !areDetailsVisible &&
-                    (isBroadcastEnabled ? (
-                        <NewModal.Button
-                            data-testid="@modal/send"
-                            isDisabled={!serializedTx}
-                            isLoading={isSending}
-                            onClick={handleSend}
-                        >
-                            <Translation id={actionLabel} />
-                        </NewModal.Button>
-                    ) : (
-                        <>
-                            <NewModal.Button
-                                isDisabled={!serializedTx}
-                                onClick={handleCopy}
-                                data-testid="@send/copy-raw-transaction"
-                            >
-                                <Translation id="COPY_TRANSACTION_TO_CLIPBOARD" />
-                            </NewModal.Button>
-                            <NewModal.Button
-                                variant="tertiary"
-                                isDisabled={!serializedTx}
-                                onClick={handleDownload}
-                            >
-                                <Translation id="DOWNLOAD_TRANSACTION" />
-                            </NewModal.Button>
-                        </>
-                    ))
-                }
+                bottomContent={
+                    areDetailsVisible ? null : (
+                        isBroadcastEnabled ? (
+                            <NewModal.Button
+                                data-testid="@modal/send"
+                                isDisabled={!serializedTx}
+                                isLoading={isSending}
+                                onClick={handleSend}
+                            >
+                                <Translation id={actionLabel} />
+                            </NewModal.Button>
+                        ) : (
+                            <>
+                                <NewModal.Button
+                                    isDisabled={!serializedTx}
+                                    onClick={handleCopy}
+                                    data-testid="@send/copy-raw-transaction"
+                                >
+                                    <Translation id="COPY_TRANSACTION_TO_CLIPBOARD" />
+                                </NewModal.Button>
+                                <NewModal.Button
+                                    variant="tertiary"
+                                    isDisabled={!serializedTx}
+                                    onClick={handleDownload}
+                                >
+                                    <Translation id="DOWNLOAD_TRANSACTION" />
+                                </NewModal.Button>
+                            </>
+                        )
+                    )
+                }
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (3)

44-53: Add exhaustive type checking to the switch statement.

While the component logic is correct, adding exhaustive type checking would prevent future maintenance issues if new states are added.

 const Status = ({ state }: { state: TransactionReviewOutputElementProps['state'] }) => {
     switch (state) {
         case 'done':
             return <Icon size={spacings.md} variant="primary" name="check" />;
         case 'pending':
             return <DotIndicator />;
-        default:
+        case 'default':
             return <DotIndicator isActive={true} />;
+        default: {
+            const _unhandledCase: never = state;
+            throw new Error(`Unhandled state: ${_unhandledCase}`);
+        }
     }
 };

64-110: Consider extracting token amount formatting logic.

The token amount formatting logic in the Value component could be simplified by extracting it into a separate function for better maintainability.

+const formatTokenAmount = (
+    value: string,
+    token: TokenInfo | undefined,
+    symbol: NetworkSymbol,
+    isFee: boolean
+) => {
+    const isTokenAmount = !isFee && token;
+    return isTokenAmount
+        ? formatAmount(value, token.decimals)
+        : formatNetworkAmount(value, symbol);
+};

 const Value = ({ value, type, symbol, token, isFee, isFiatVisible }: ValueProps) => {
     switch (type) {
         case 'address':
             return <Address value={value} />;
         case 'data':
             return <DataWrapper>{value}</DataWrapper>;
         case 'total':
         case 'fee':
         case 'amount': {
-            const isTokenAmount = !isFee && token;
-            const formattedValue = isTokenAmount
-                ? formatAmount(value, token.decimals)
-                : formatNetworkAmount(value, symbol);
+            const formattedValue = formatTokenAmount(value, token, symbol, isFee);

202-239: Consider extracting Cardano-specific rendering logic.

The Cardano-specific rendering logic could be extracted into a separate component to improve maintainability and reduce the complexity of the main component.

+const CardanoTokenDetails = ({
+    networkType,
+    token,
+    account,
+    line,
+}: {
+    networkType: string;
+    token?: TokenInfo;
+    account: Account;
+    line: OutputElementLine;
+}) => {
+    if (networkType !== 'cardano') return null;
+
+    return (
+        <>
+            {token?.symbol && (
+                <Text typographyStyle="hint" as="div">
+                    <InfoItem
+                        label={
+                            <Text variant="default">
+                                <Translation id="TR_CARDANO_FINGERPRINT_HEADLINE" />
+                            </Text>
+                        }
+                        direction="row"
+                    >
+                        <Column alignItems="flex-end" data-testid="@modal/cardano-fingerprint">
+                            {getCardanoFingerprint(account?.tokens, token?.symbol)}
+                        </Column>
+                    </InfoItem>
+                </Text>
+            )}
+            {token && token.decimals !== 0 && (
+                <Text typographyStyle="hint" as="div">
+                    <InfoItem
+                        label={
+                            <Text variant="default">
+                                <Translation id="TR_CARDANO_TREZOR_AMOUNT_HEADLINE" />
+                            </Text>
+                        }
+                        direction="row"
+                    >
+                        <Column alignItems="flex-end" data-testid="@modal/cardano-trezor-amount">
+                            {line.value}
+                        </Column>
+                    </InfoItem>
+                </Text>
+            )}
+        </>
+    );
+};

// In the main component's render:
-            {networkType === 'cardano' && token?.symbol && (
-                // ... Cardano fingerprint rendering
-            )}
-            {networkType === 'cardano' && token && token.decimals !== 0 && (
-                // ... Cardano amount rendering
-            )}
+            <CardanoTokenDetails
+                networkType={networkType}
+                token={token}
+                account={account}
+                line={line}
+            />
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx (2)

14-23: Document the estimation calculation logic.

The getEstimatedTime function's calculation logic (blocks * blockTime * 60) would benefit from a comment explaining the formula and units.

Add JSDoc comment:

+/**
+ * Calculates estimated confirmation time in seconds for Bitcoin transactions.
+ * Formula: blocks * blockTime (minutes) * 60 (seconds)
+ * @returns undefined for non-Bitcoin networks, estimated time in seconds for Bitcoin
+ */
 const getEstimatedTime = (
     networkType: NetworkType,
     symbolFees: FeeInfo,
     tx: GeneralPrecomposedTransactionFinal,
 ): number | undefined => {

48-51: Consider memoizing expensive calculations.

The network, fee, and estimate time calculations could benefit from memoization to prevent unnecessary recalculations on re-renders.

Consider using useMemo:

-    const network = networks[account.symbol];
-    const { symbol, accountType, index, networkType } = account;
-    const fee = getFee(networkType, tx);
-    const estimateTime = getEstimatedTime(networkType, fees[account.symbol], tx);
+    const network = useMemo(() => networks[account.symbol], [account.symbol]);
+    const { symbol, accountType, index, networkType } = account;
+    const fee = useMemo(() => getFee(networkType, tx), [networkType, tx]);
+    const estimateTime = useMemo(
+        () => getEstimatedTime(networkType, fees[account.symbol], tx),
+        [networkType, fees, account.symbol, tx]
+    );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50eb248 and ebbdc86.

📒 Files selected for processing (12)
  • packages/components/src/components/DotIndicator/DotIndicator.tsx (1 hunks)
  • packages/suite-desktop-core/e2e/tests/coin-market/buy-coins.test.ts (2 hunks)
  • packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts (2 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx (4 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx (0 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewDetails.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (4 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx (2 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputList.tsx (1 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx (4 hunks)
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts
  • packages/suite-desktop-core/e2e/tests/coin-market/buy-coins.test.ts
  • packages/components/src/components/DotIndicator/DotIndicator.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewDetails.tsx
  • packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputList.tsx

[error] 101-101: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: Unit Tests
  • GitHub Check: Type Checking
  • GitHub Check: Build libs for publishing
  • GitHub Check: Linting and formatting
  • GitHub Check: Other Checks
  • GitHub Check: Releases revision Checks
  • GitHub Check: build-web
  • GitHub Check: run-desktop-tests (@group=wallet, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: run-desktop-tests (@group=other, trezor-user-env-unix)
  • GitHub Check: prepare_android_test_app
  • GitHub Check: run-desktop-tests (@group=settings, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: build-web
  • GitHub Check: run-desktop-tests (@group=device-management, trezor-user-env-unix)
  • GitHub Check: run-desktop-tests (@group=suite, trezor-user-env-unix)
  • GitHub Check: Analyze with CodeQL (javascript)
  • GitHub Check: EAS Update
🔇 Additional comments (20)
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx (4)

1-19: LGTM! Well-organized imports.

The imports are logically grouped and all dependencies appear to be necessary for the component's functionality.


Line range hint 20-48: LGTM! Type-safe utility functions.

Both utility functions are well-implemented with proper type safety:

  • getFeeLabel uses an exhaustive switch for network types
  • getDisplayModeStringsMap provides a clean mapping for stake operations

49-99: LGTM! Well-structured output title handling.

The getOutputTitle function effectively handles all output types with proper type safety using an exhaustive switch pattern.


216-267: LGTM! Clean component implementation.

The component is well-structured with:

  • Clear props interface
  • Proper edge case handling for stake transactions
  • Comprehensive visibility checks for fiat values
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx (2)

Line range hint 1-18: LGTM! Well-organized imports.

The imports are logically grouped and all dependencies are properly utilized in the code.


96-102: LGTM! Clear and well-typed props interface.

The props interface is well-defined with appropriate types for each property.

packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx (2)

Line range hint 3-45: LGTM! Clean imports and type definitions.

The imports are well-organized and the type definitions are concise and focused.


147-159: Improve error handling in the copy handler.

The copy handler should handle potential failures gracefully.

 const handleCopy = () => {
-    const result = copyToClipboard(serializedTx!.tx);
+    try {
+        const result = copyToClipboard(serializedTx!.tx);
+        if (typeof result !== 'string') {
+            dispatch(
+                notificationsActions.addToast({
+                    type: 'copy-to-clipboard',
+                }),
+            );
+        }
+        reportTransactionCreatedEvent('copied');
+    } catch (error) {
+        dispatch(
+            notificationsActions.addToast({
+                type: 'error',
+                error: 'Failed to copy transaction',
+            }),
+        );
+    }
-    if (typeof result !== 'string') {
-        dispatch(
-            notificationsActions.addToast({
-                type: 'copy-to-clipboard',
-            }),
-        );
-    }
-
-    reportTransactionCreatedEvent('copied');
 };
packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputList.tsx (5)

5-8: LGTM! Clean imports and well-structured props interface.

The imports are logically organized, and the props interface is well-typed with clear property definitions.

Also applies to: 17-26


28-42: LGTM! Clear and predictable state determination logic.

The getState function uses a straightforward if-else structure, making it easy to understand and maintain. The logic handles all possible states (done, default, pending) based on the index and button request count.


48-61: Consider extracting SectionHeading into a separate component.

The SectionHeading component has its own logic and rendering. Moving it to a separate file would improve modularity and make the code more maintainable.

Create a new file SectionHeading.tsx:

import { H4 } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { Translation } from 'src/components/suite';
import { ReviewOutput } from '@suite-common/wallet-types';

type SectionHeadingProps = {
    output: ReviewOutput;
    index: number;
};

export const SectionHeading = ({ output, index }: SectionHeadingProps) => (
    <H4 margin={{ top: index === 0 ? spacings.zero : spacings.xs }}>
        {output.type === 'address' ? (
            <Translation
                id="TR_SEND_RECIPIENT_ADDRESS"
                values={{
                    index: index + 1,
                }}
            />
        ) : (
            <Translation id="TR_SUMMARY" />
        )}
    </H4>
);

83-89: LGTM! Well-implemented scrolling behavior.

The useEffect hook handles scrolling behavior appropriately, considering both signed transactions (instant scroll) and in-progress transactions (smooth scroll).


92-143: Consider breaking down the component for better maintainability.

The component has grown quite large and handles multiple responsibilities:

  • Output list rendering
  • Scroll management
  • State management
  • Conditional rendering
  • Banner display

Consider splitting it into smaller, focused components:

  • OutputList (handling the map operation)
  • TotalOutputSection
  • SolanaWarningBanner

Would you like me to help generate the component structure for this refactoring?

🧰 Tools
🪛 Biome (1.9.4)

[error] 101-101: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx (4)

25-36: LGTM! Well-structured utility function.

The getCardanoFingerprint function is type-safe and handles the token lookup efficiently.


38-41: LGTM! Good typography choices.

The DataWrapper styled component uses appropriate CSS properties for numerical data display:

  • word-break: break-all prevents overflow
  • font-variant-numeric: tabular-nums ensures consistent number width
  • letter-spacing: 0 maintains readability

113-128: LGTM! Well-structured type definitions.

The types are well-defined with clear interfaces for both the output line elements and component props.


141-141: Good use of conditional styling.

The Card's fillType prop is appropriately set based on the state, providing visual feedback to users.

packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx (3)

26-32: LGTM! Clean props structure.

The props type definition is well-structured with clear types and necessary properties.


76-82: Improve network type check readability.

As discussed in previous reviews, the network type check could be more explicit.

Consider using a direct check for Ethereum-based networks:

-    {!!tx.feeLimit && network.networkType !== 'solana' && (
+    {!!tx.feeLimit && network.networkType === 'ethereum' && (

110-119: Address TODO comment about IconButton margin.

The TODO comment indicates an issue with IconButton margins that should be resolved.

Let's verify if this is a known issue:

.indexOf(output);

return (
<Wrapper key={index} ref={ref => (outputRefs.current[index] = ref)}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix ref assignment in expression.

The current ref assignment in an expression can be confusing and triggers a static analysis warning.

Refactor the ref assignment to be more explicit:

-<Wrapper key={index} ref={ref => (outputRefs.current[index] = ref)}>
+<Wrapper
+    key={index}
+    ref={ref => {
+        outputRefs.current[index] = ref;
+    }}
+>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Wrapper key={index} ref={ref => (outputRefs.current[index] = ref)}>
<Wrapper
key={index}
ref={ref => {
outputRefs.current[index] = ref;
}}
>
🧰 Tools
🪛 Biome (1.9.4)

[error] 101-101: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

@adamhavel adamhavel merged commit ee6bd69 into develop Jan 30, 2025
35 checks passed
@adamhavel adamhavel deleted the feat/refactor-send-modal branch January 30, 2025 18:46
@bosomt
Copy link
Contributor

bosomt commented Jan 31, 2025

QA NOK

i dont see amount in send modal 🤯
/im not able to reproduce it on T2T1/

image
Screenshot 2025-01-31 at 11 29 50

Info:

  • Suite version: web 25.2.0 (a56381e)
  • Browser: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
  • OS: MacIntel
  • Screen: 1470x956
  • Device: Trezor T1B1 1.13.0 regular (revision b8cf9daa2e6069185f042ea851ccd413c3b6f872)
  • Transport: WebUsbTransport

@bosomt bosomt added the QA NOK Issue doesn't pass the QA and some changes are needed. label Jan 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
QA NOK Issue doesn't pass the QA and some changes are needed.
Projects
Status: 🎯 To do
Development

Successfully merging this pull request may close these issues.

Send address comparison update
8 participants