Skip to content

Conversation

@romanstasianok
Copy link

@romanstasianok romanstasianok commented May 5, 2025

Description

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Summary by CodeRabbit

  • New Features

    • Introduced a cryptocurrency swap application with a full user interface for exchanging digital assets.
    • Added multi-step swap flow: exchange selection, recipient input, confirmation, success, cancellation, and error views.
    • Integrated support for real-time swap status updates, toast notifications, and FAQ/help sections.
    • Provided support for copying addresses, memos/tags, and transaction IDs with user feedback.
    • Included customizable buttons, cards, loaders, input fields, select dropdowns, and modal dialogs.
    • Implemented clipboard and input validation for addresses, amounts, and emails.
    • Added support for Russian localization and accessible, responsive design.
  • Bug Fixes

    • Not applicable (initial implementation).
  • Documentation

    • Added Russian translations for app title and description.
    • Included terms and conditions download link.
  • Style

    • Applied custom Tailwind CSS styling for consistent appearance and animations.
  • Tests

    • Added unit tests for email validation logic.
  • Chores

    • Registered new Redux slices and API integrations for state and data management.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 5, 2025

Walkthrough

A new "emcd-swap" cryptocurrency swap application is introduced, featuring a full Redux state management setup, RTK Query API integration, reusable UI components, custom hooks for business logic, helper utilities, and a complete set of views for the swap flow. The implementation includes styles, configuration, and manifest files.

Changes

File(s) / Area Change Summary
src/apps/emcd-swap/api/coinsApi.tsx Introduced RTK Query API slice (emcdSwapApi) for swap operations with endpoints for coins, estimates, swaps, users, tickets, and status, with error handling and React hooks exports.
src/apps/emcd-swap/components/Button/Button.tsx
.../Card/Card.tsx
.../DefaultInput.tsx
.../FAQ/FAQ.tsx
.../FAQ/components/FAQItem.tsx
.../FormInput/FormInput.tsx
.../Header/Header.tsx
.../Loader/Loader.tsx
.../Modals/SupportModal.tsx
.../Select/Select.tsx
.../Select/components/SelectActivator.tsx
.../Select/components/SelectDropdown.tsx
.../Select/components/SelectOption.tsx
.../Step/Step.tsx
.../SwapTimer/SwapTimer.tsx
.../ToastNotification/ToastNotification.tsx
Added a suite of reusable, typed React UI components for buttons, cards, inputs, FAQ lists, loaders, modals, select dropdowns, step indicators, timers, and toast notifications, each with their own props and styling logic.
src/apps/emcd-swap/components/icons/ArrowRightIcon.tsx
.../CloseIcon.tsx
.../ConfirmIcon.tsx
.../CopyIcon.tsx
.../InfoIcon.tsx
.../LogoIcon.tsx
.../SupportIcon.tsx
.../SwapIcon.tsx
Added SVG-based icon React components for various UI elements (arrows, close, confirm, copy, info, logo, support, swap), each exported as default.
src/apps/emcd-swap/constants/views.ts Introduced view type enum, view settings interface, and a mapping object for managing swap app UI navigation and view relationships.
src/apps/emcd-swap/enums/status.enum.ts Added status enumeration for swap/transaction workflow states.
src/apps/emcd-swap/helpers/copy.helper.ts
.../crypto-address-validator.ts
.../email-validator.helper.ts
.../input-validator.helper.ts
Added helper functions for copying to clipboard with toast, validating crypto addresses, validating emails, and validating input amounts (with error messages and min/max checks).
src/apps/emcd-swap/hooks/useConfirmLogic.ts
.../useExchangeLogic.ts
.../useRecipientLogic.ts
.../useValidatedInput.ts
Added custom React hooks for encapsulating logic related to confirmation, exchange, recipient input, and validated input state management, integrating Redux and API calls.
src/apps/emcd-swap/index.tsx Introduced the main app entry component (App), mapping Redux view state to view components and rendering the main layout and notifications.
src/apps/emcd-swap/layouts/MainLayout.tsx Added a main layout component with responsive padding and centered content.
src/apps/emcd-swap/manifest.json Added application manifest with metadata, version, title, description, category, and Russian translations.
src/apps/emcd-swap/reducer/emcdSwapSlice.ts Implemented Redux slice for swap state, including form data, coins, view navigation, and selectors.
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts Implemented Redux slice for toast notifications, with actions and selectors for message and type.
src/apps/emcd-swap/styles/tailwindDeposit.css Added custom Tailwind CSS stylesheet for deposit-related UI, with resets and custom focus/animation styles.
src/apps/emcd-swap/tailwind.deposit.config.js Added custom Tailwind CSS configuration with extended colors, radius, font sizes, dimensions, and breakpoints.
src/apps/emcd-swap/views/CancelledView.tsx
.../Confirm/ConfirmView.tsx
.../Confirm/components/forms/ConfirmDepositForm.tsx
.../Confirm/components/forms/ExchangeDepositForm.tsx
.../Confirm/components/forms/SendToWalletForm.tsx
.../Confirm/components/forms/WaitingDepositForm.tsx
.../ErrorView.tsx
.../ExchangeView.tsx
.../FaqView.tsx
.../RecipientView.tsx
.../SuccessView.tsx
Added all main view components for the swap flow: exchange, recipient, confirmation (with sub-forms), FAQ, success, cancelled, and error screens, each with their own logic and UI.
src/store.ts Registered the new emcdSwapSlice and emcdSwapToastSlice reducers in the Redux store.
src/apps/emcd-swap/helpers/email-validator.helper.spec.ts Added unit tests for the email validation helper, covering valid and invalid cases.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ExchangeView
    participant ReduxStore
    participant emcdSwapApi
    participant RecipientView
    participant ConfirmView
    participant Backend

    User->>ExchangeView: Select coins, enter amounts
    ExchangeView->>emcdSwapApi: getEstimate
    emcdSwapApi-->>ExchangeView: Estimated rate, min/max

    User->>ExchangeView: Submit form
    ExchangeView->>ReduxStore: setFormData
    ExchangeView->>ReduxStore: setCurrentView(RECIPIENT)

    User->>RecipientView: Enter address, email, tag
    User->>RecipientView: Submit form
    RecipientView->>emcdSwapApi: createSwap, createUser
    emcdSwapApi-->>RecipientView: Swap ID, deposit address
    RecipientView->>ReduxStore: setSwapID, setDepositAddress
    RecipientView->>ReduxStore: setDetailSwapFormData
    RecipientView->>ReduxStore: setCurrentView(CONFIRM)

    ConfirmView->>Backend: SSE subscribe to swap status
    Backend-->>ConfirmView: Status updates
    ConfirmView->>ReduxStore: setDetailSwapStatus
    alt Status = DONE
        ConfirmView->>ReduxStore: setCurrentView(SUCCESS)
    else Status = CANCELED
        ConfirmView->>ReduxStore: setCurrentView(CANCELLED)
    else Status = ERROR
        ConfirmView->>ReduxStore: setCurrentView(ERROR)
    end
Loading

Poem

🐇
In a meadow of code where the swap bunnies dwell,
New forms and new hooks now ring the bell.
Coins are exchanged, addresses checked twice,
With toasts and timers, the UX feels nice!
From FAQ to Success, each view hops along—
This rabbit’s delighted: the swap app is strong!
🥕✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm warn config production Use --omit=dev instead.
npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-05-22T14_33_31_152Z-debug-0.log

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@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: 66

🔭 Outside diff range comments (2)
src/apps/the-exchange/components/CardsSwap/test/CardSwap.test.tsx (2)

187-205: 🛠️ Refactor suggestion

Global store is reused across tests – consider isolating state per test

beforeEach mutates the singleton store that is imported from the actual app.
If another test somewhere else in the suite also imports the same store, state leakage becomes very likely, leading to order-dependent tests.

A cheap fix is to create a fresh store instance for this test file:

-import { store } from '../../../../../store';
+import { configureAppStore } from '../../../../../store'; // expose a factory
+
+let store: ReturnType<typeof configureAppStore>;

and inside beforeEach:

-beforeEach(() => {
+beforeEach(() => {
+  store = configureAppStore();

This keeps each test hermetic.


291-293: ⚠️ Potential issue

Possible typo in test-ID breaks the assertion

queryByTestId('select-token-car') is missing the trailing “d” that is present everywhere else (select-token-card).
Because queryByTestId returns null on a typo, this test will “pass” even if the element is unexpectedly present.

-    expect(screen.queryByTestId('select-token-car')).not.toBeInTheDocument();
+    expect(screen.queryByTestId('select-token-card')).not.toBeInTheDocument();

Please adjust or, if the ID really changed, rename consistently in the component.

🧹 Nitpick comments (88)
src/services/pillarXApiSearchTokens.ts (1)

22-22: Increased API retry attempts from 5 to 10

Increasing the maximum retry attempts improves resilience against transient API failures, but may also delay error responses.

Consider implementing exponential backoff for a more sophisticated retry strategy. This would provide better user experience by giving up faster when the service is completely down, while still being resilient to transient failures:

-  { maxRetries: 10 }
+  { 
+    maxRetries: 10, 
+    backoff: (attempt, error) => {
+      const delay = Math.min(1000 * (2 ** attempt), 10000);
+      return delay;
+    }
+  }
src/apps/the-exchange/components/DropdownTokensList/TokenRow.tsx (1)

26-29: Improved prop passing with explicit token properties

Instead of passing the entire token object, the component now passes individual token properties (tokenName, tokenSymbol, chainName, tokenLogo) to the TokenListItem component. This makes the interface between components more explicit and easier to maintain.

Consider adding TypeScript prop validation for these new individual props in the TokenListItem component interface to ensure type safety.

src/apps/the-exchange/components/DropdownTokensList/test/DropdownTokensList.test.tsx (1)

9-23: Consider removing unused imports.

The actions setSwapChain and setReceiveChain are imported and used in the test setup, but the tests that specifically tested chain reset behavior have been removed. Consider cleaning up these imports if they're no longer needed in the remaining test cases.

src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (1)

96-120: The map operation is unnecessary in the filtering logic

The token filtering logic has been properly simplified to rely solely on API results. However, the .map((tokens) => tokens) operation on line 118 is redundant as it simply returns the same objects without transformation.

-              .map((tokens) => tokens)

This unnecessary mapping operation can be safely removed without affecting functionality.

src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx (1)

220-233: Filtering list on every render – memoise to avoid unnecessary work

itemData.tokenList is created with an inline filter each render.
With large search results (hundreds of tokens) this becomes expensive and will re-render the virtual list unnecessarily.

- itemData={{
-   tokenList: isSwapOpen
-     ? searchTokenResult.filter( … )
-     : searchTokenResult.filter( … ),
-   handleClick,
- }}
+ const filteredTokenList = useMemo(
+   () =>
+     (isSwapOpen
+       ? searchTokenResult.filter(…)
+       : searchTokenResult.filter(…)),
+   [searchTokenResult, isSwapOpen, swapToken, receiveToken],
+ );
+
+ itemData={{ tokenList: filteredTokenList, handleClick }}

This keeps the same behaviour while reducing render cost.

src/apps/token-atlas/index.tsx (1)

192-199: Graph loading flag never resets on request failure

setIsGraphLoading(false) is dispatched only when neither isLoading nor isFetching are true.
If the query errors out early (isError === true, isLoading === false, isFetching === false), the flag resets correctly, but we lose the opportunity to surface the error.

Consider extending the slice with an isGraphErroring flag (removed earlier) or handling isError/error from the hook so the UI can react.

src/apps/token-atlas/api/token.ts (3)

76-76: Improve URL parameter construction for consistency and maintainability.

For consistency with the other API endpoints and to avoid potential URL formatting issues, use URLSearchParams.

-      query: () => `?${chainIdsQuery}&testnets=${String(isTestnet)}`,
+      query: () => {
+        const params = new URLSearchParams();
+        chainIds.forEach(id => params.append('chainIds', String(id)));
+        params.append('testnets', String(isTestnet));
+        return `?${params.toString()}`;
+      },

90-90: Improve URL parameter construction for consistency and maintainability.

For consistency with the other API endpoints and to avoid potential URL formatting issues, use URLSearchParams.

-      query: () => `?${chainIdsQuery}&testnets=${String(isTestnet)}`,
+      query: () => {
+        const params = new URLSearchParams();
+        chainIds.forEach(id => params.append('chainIds', String(id)));
+        params.append('testnets', String(isTestnet));
+        return `?${params.toString()}`;
+      },

22-39: Consider creating a helper function to reduce code duplication.

Each API slice follows a similar pattern for creating the query parameters. You could reduce duplication by creating a helper function.

// Helper function to create common query params
const createCommonQueryParams = () => {
  const params = new URLSearchParams();
  chainIds.forEach(id => params.append('chainIds', String(id)));
  params.append('testnets', String(isTestnet));
  return params;
};

// Example usage in one of the endpoints
query: ({ id, asset, symbol }) => {
  const params = createCommonQueryParams();
  if (id !== undefined) params.append('id', String(id));
  if (asset) params.append('asset', asset);
  params.append('symbol', symbol);
  return `?${params.toString()}`;
},

Also applies to: 41-65, 67-79, 81-93

src/apps/emcd-swap/components/icons/LogoIcon.tsx (1)

1-12: Approved with suggestions for accessibility and reusability

The component looks good overall, but I have two suggestions:

  1. Add accessibility attributes to the SVG for better screen reader support:
- <svg xmlns="http://www.w3.org/2000/svg" width="36" height="32" viewBox="0 0 36 32" fill="none">
+ <svg xmlns="http://www.w3.org/2000/svg" width="36" height="32" viewBox="0 0 36 32" fill="none" aria-hidden="true" role="img" aria-label="EMCD Logo">
  1. Consider making the colors configurable through props to support different themes, similar to the approach used in other icon components like the one in simpleswap.
src/apps/emcd-swap/components/icons/InfoIcon.tsx (1)

3-11: Consider adding TypeScript typing for the component

While this is a simple icon component without props, it's a good practice to explicitly define its return type for consistency with TypeScript standards.

-const InfoIcon = () => {
+const InfoIcon: React.FC = () => {
  return (
    <svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path fillRule="evenodd" clipRule="evenodd"
            d="M7.99992 3.08344C5.0088 3.08344 2.58325 5.50898 2.58325 8.5001C2.58325 11.4912 5.0088 13.9168 7.99992 13.9168C10.9917 13.9168 13.4166 11.4913 13.4166 8.5001C13.4166 5.50893 10.9917 3.08344 7.99992 3.08344ZM1.08325 8.5001C1.08325 4.68055 4.18037 1.58344 7.99992 1.58344C11.8202 1.58344 14.9166 4.68061 14.9166 8.5001C14.9166 12.3196 11.8202 15.4168 7.99992 15.4168C4.18037 15.4168 1.08325 12.3196 1.08325 8.5001ZM7.99658 5.21951C8.4108 5.21951 8.74658 5.55529 8.74658 5.96951V8.91551C8.74658 9.32972 8.4108 9.66551 7.99658 9.66551C7.58237 9.66551 7.24658 9.32972 7.24658 8.91551V5.96951C7.24658 5.55529 7.58237 5.21951 7.99658 5.21951ZM7.99658 10.2807C7.58237 10.2807 7.24658 10.6165 7.24658 11.0307C7.24658 11.4449 7.58237 11.7807 7.99658 11.7807H8.00325C8.41746 11.7807 8.75325 11.4449 8.75325 11.0307C8.75325 10.6165 8.41746 10.2807 8.00325 10.2807H7.99658Z"
            fill="#8F42FF" />
    </svg>
  );
};
src/apps/emcd-swap/hooks/useConfirmLogic.ts (1)

4-14: LGTM, but consider adding types for improved maintainability

The hook correctly extracts data from Redux store using selectors. For better maintainability, consider adding explicit return type and TypeScript annotations.

import { useSelector } from 'react-redux';
import { selectDetailFormDataSwap, selectDetailSwapStatus, selectSwapID } from '../reducer/emcdSwapSlice';

+interface ConfirmLogicResult {
+  detailFormDataSwap: ReturnType<typeof selectDetailFormDataSwap>;
+  swapID: ReturnType<typeof selectSwapID>;
+  status: ReturnType<typeof selectDetailSwapStatus>;
+}

-export const useConfirmLogic = () => {
+export const useConfirmLogic = (): ConfirmLogicResult => {
  const detailFormDataSwap = useSelector(selectDetailFormDataSwap)
  const status = useSelector(selectDetailSwapStatus)
  const swapID = useSelector(selectSwapID)

  return {
    detailFormDataSwap,
    swapID,
    status,
  }
}
src/apps/emcd-swap/components/icons/ConfirmIcon.tsx (2)

3-21: LGTM, but consider adding TypeScript type annotations

The icon component is well-implemented with proper camelCase SVG attributes. For consistency with TypeScript standards, consider adding an explicit return type.

-const ConfirmIcon = () => {
+const ConfirmIcon: React.FC = () => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
    >
      <path
        d="M6 12.75L10.5 17.25L18 7.5"
        stroke="#4DB051"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
};

1-23: Consider adding an aria-label for accessibility

For better accessibility, SVG icons should include appropriate ARIA attributes to provide context for screen readers.

const ConfirmIcon: React.FC = () => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
+     aria-hidden="true"
+     role="img"
+     aria-label="Confirmation check mark"
    >
      <path
        d="M6 12.75L10.5 17.25L18 7.5"
        stroke="#4DB051"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
};
src/apps/emcd-swap/components/icons/CopyIcon.tsx (1)

1-16: Enhance icon component for better reusability

The CopyIcon component is correctly implemented but lacks customization options that would make it more reusable.

Consider enhancing the component to accept common props like className for styling overrides and onClick for handling click events:

-import React from 'react';
+import React, { SVGProps } from 'react';

-const CopyIcon = () => {
+interface CopyIconProps extends SVGProps<SVGSVGElement> {
+  // You can add additional custom props here
+}
+
+const CopyIcon: React.FC<CopyIconProps> = ({ className = '', ...props }) => {
  return (
    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"
+        className={className}
+        {...props}
    >
      <path
        d="M12.7378 4.79002C12.5258 3.42297 11.5414 2.5 10.0579 2.5H5.24571C3.57046 2.5 2.51807 3.6899 2.51807 5.37081V9.90308C2.51807 11.4344 3.39331 12.5628 4.82831 12.7408"
        stroke="#8F42FF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
      <path fill-rule="evenodd" clip-rule="evenodd"
            d="M14.755 7.23306H9.94441C8.26837 7.23306 7.2168 8.41974 7.2168 10.0998V14.6322C7.2168 16.3122 8.26271 17.4989 9.94441 17.4989H14.7542C16.4367 17.4989 17.4827 16.3122 17.4827 14.6322V10.0998C17.4827 8.41974 16.4367 7.23306 14.755 7.23306Z"
            stroke="#8F42FF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
    </svg>
  );
};

This allows for better component reuse and styling flexibility across your application.

src/apps/emcd-swap/layouts/MainLayout.tsx (1)

1-17: Enhance layout component for better flexibility

The MainLayout component is correctly implemented, but it could benefit from additional customization options.

Consider adding className prop to allow for custom styling overrides:

-import React, { ReactNode } from 'react';
+import React, { ReactNode } from 'react';

interface MainLayoutProps {
  children: ReactNode;
+  className?: string;
}

-const MainLayout = ({ children }: MainLayoutProps) => {
+const MainLayout = ({ children, className = '' }: MainLayoutProps) => {
  return (
-    <div className="flex flex-col items-center py-12 h-full">
+    <div className={`flex flex-col items-center py-12 h-full ${className}`}>
      <div className='w-full px-6 md:w-137.5'>
        {children}
      </div>
    </div>
  );
};

export default MainLayout;

This allows parent components to further customize the layout when needed without modifying the core component.

src/apps/emcd-swap/helpers/copy.helper.ts (1)

12-15: Apply consistent export style

The export style could be simplified.

Consider using a simpler export syntax:

-
-
-export {
-  copyToClipboard
-}
+export { copyToClipboard };

Or even better, use a named export directly:

-const copyToClipboard = async (text: any, setToast: any) => {
+export const copyToClipboard = async (text: any, setToast: any) => {
  // Function implementation...
}
-
-
-export {
-  copyToClipboard
-}
src/apps/emcd-swap/views/FaqView.tsx (1)

1-25: Consider adding component type annotation and localization

The component structure is clean, but a few improvements could be made:

  1. Add TypeScript type annotation for the component (React.FC)
  2. The title "Как работает обмен" is hardcoded in Russian, which might be problematic for internationalization
-const FaqView = () => {
+const FaqView: React.FC = () => {

Consider using a localization system instead of hardcoded text:

-      <Header title={'Как работает обмен'} close onClose={handleButtonClick} />
+      <Header title={t('faq.howExchangeWorks')} close onClose={handleButtonClick} />
src/apps/emcd-swap/components/Step/Step.tsx (1)

10-10: Fix extra whitespace in className and consider extracting styles

There are multiple consecutive spaces in the className string which should be cleaned up. Consider extracting the Tailwind classes for better readability.

-    <div className={`flex justify-center font-medium text-2.5 w-6 h-6 items-center text-center  rounded-full ${!success ? 'bg-bg-35 text-color-1' : 'bg-success-bg text-success'}`}>
+    <div className={`flex justify-center font-medium text-2.5 w-6 h-6 items-center text-center rounded-full ${!success ? 'bg-bg-35 text-color-1' : 'bg-success-bg text-success'}`}>

For improved readability, you could also extract these styles:

const baseStyles = "flex justify-center font-medium text-2.5 w-6 h-6 items-center text-center rounded-full";
const defaultStyles = "bg-bg-35 text-color-1";
const successStyles = "bg-success-bg text-success";

// Then use:
<div className={`${baseStyles} ${!success ? defaultStyles : successStyles}`}>
src/apps/emcd-swap/components/icons/CloseIcon.tsx (1)

1-1: Remove unused ReactNode import

The ReactNode type is imported but not used anywhere in the component.

-import React, { ReactNode } from 'react';
+import React from 'react';
src/apps/emcd-swap/hooks/useValidatedInput.ts (1)

3-22: Add documentation and consider enhancing the hook's API

The hook lacks documentation and could benefit from additional functionality.

  1. Add JSDoc comments to explain usage:
/**
 * A hook for managing validated input state.
 * @param initialValue - The initial value of the input (string or null)
 * @returns An object containing the input value, setter, validation state, error message, and utility functions
 */
export function useValidatedInput(
  initialValue: string | null,
) {
  1. Consider adding a reset function to restore initial state:
  const [value, setValue] = useState<string | null>(initialValue)
  const [valid, setValid] = useState<boolean>(true)
  const [error, setError] = useState<string | undefined>(undefined)

+  const reset = () => {
+    setValue(initialValue)
+    setValid(true)
+    setError(undefined)
+  }

  // In the return statement:
  return {
    value,
    setValue,
    changeValid,
    valid,
    error,
+   reset,
  }
  1. Consider accepting an initial validation state parameter:
export function useValidatedInput(
  initialValue: string | null,
+ initialValid: boolean = true,
) {
  const [value, setValue] = useState<string | null>(initialValue)
- const [valid, setValid] = useState<boolean>(true)
+ const [valid, setValid] = useState<boolean>(initialValid)
src/apps/emcd-swap/components/FAQ/components/FAQItem.tsx (1)

27-29: Improve transition smoothness for FAQ expansion

The current transition only has height and opacity properties, which might not create the smoothest expansion effect. Consider adding padding and transition duration for a better user experience.

-<div className={ `text-xs text-color-3 transition-all overflow-hidden ${isOpen ? 'h-auto' : 'h-0 opacity-0'}` }>
+<div className={ `text-xs text-color-3 transition-all duration-300 overflow-hidden ${isOpen ? 'h-auto opacity-100 pt-2' : 'h-0 opacity-0 pt-0'}` }>
  { answer }
</div>
src/apps/emcd-swap/components/Select/components/SelectOption.tsx (3)

1-2: Translate or remove non-English comments

There are comments in Russian in the code. For better maintainability and collaboration, all comments should be in English.

-// Интерфейс пропсов
+// Props interface
 import { Option } from '../Select';

...

-// Компонент
+// Component
 const SelectOption = <T extends Option>({

Also applies to: 12-13


24-28: Improve image handling with fallbacks

The image element doesn't have proper error handling if the image fails to load. Additionally, the conditional check for itemIcon seems redundant since withIcon is already being checked.

 {withIcon && (
   <div className='w-4 h-4'>
-    <img src={itemIcon && option[itemIcon]} alt={option[itemText]} />
+    <img 
+      src={option[itemIcon as keyof T]} 
+      alt={`${option[itemText]} icon`} 
+      onError={(e) => {
+        e.currentTarget.style.display = 'none';
+      }}
+    />
   </div>
 )}

4-10: Enhance the TypeScript interface design

The current interface could be improved for better type safety and clarity. Consider making withIcon and itemIcon properties have a clearer relationship.

 interface SelectOptionProps<T extends Option> {
   option: T;
   onClick: () => void;
   itemText: keyof T;
-  withIcon?: boolean;
-  itemIcon?: keyof T;
+  withIcon?: boolean;
+  itemIcon?: withIcon extends true ? keyof T : never;
 }

Alternatively, you could consider creating a more explicit relationship:

interface SelectOptionProps<T extends Option> {
  option: T;
  onClick: () => void;
  itemText: keyof T;
  withIcon?: boolean;
  itemIcon?: withIcon extends true ? keyof T : undefined;
}
src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (4)

1-1: Update or remove incorrect file path comment

The comment indicates this file is at src/components/ToastNotification.tsx, but the actual path is src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx.

-// src/components/ToastNotification.tsx
+// ToastNotification component for emcd-swap app

7-7: Consider using absolute imports instead of relative paths

Using relative imports with multiple levels (../../reducer/emcdSwapToastSlice) can make code harder to maintain, especially when files are moved. Consider using absolute imports configured in your tsconfig.

-import { clearToast, selectToastMessage, selectToastType } from '../../reducer/emcdSwapToastSlice';
+import { clearToast, selectToastMessage, selectToastType } from 'src/apps/emcd-swap/reducer/emcdSwapToastSlice';

28-28: Enhance ToastContainer configuration

The current ToastContainer has limited options. Consider adding more configuration to improve the user experience, like positioning and transition effects.

-  return <ToastContainer autoClose={1000} stacked limit={3} />
+  return (
+    <ToastContainer 
+      position="top-right"
+      autoClose={1000} 
+      stacked 
+      limit={3}
+      hideProgressBar={false}
+      newestOnTop
+      closeOnClick
+      rtl={false}
+      pauseOnFocusLoss
+      draggable
+      pauseOnHover
+      theme="light"
+    />
+  )

12-13: Add type safety for the toast type

The current implementation doesn't ensure type safety for the toast type. Consider using a TypeScript union type to ensure only valid toast types are used.

+  type ToastType = 'success' | 'error' | 'info' | 'warning';
+
   const message = useSelector(selectToastMessage)
-  const type = useSelector(selectToastType)
+  const type = useSelector(selectToastType) as ToastType | undefined
src/apps/emcd-swap/components/SwapTimer/SwapTimer.tsx (3)

20-25: Improve time formatting for better user experience

The current formatTime function always includes hours, which may not be necessary for short durations. Consider a more adaptive formatting approach.

 const formatTime = (seconds: number) => {
   const hrs = String(Math.floor(seconds / 3600)).padStart(2, '0');
   const mins = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
   const secs = String(seconds % 60).padStart(2, '0');
-  return `${hrs}:${mins}:${secs}`;
+  // Only include hours if they exist
+  return seconds >= 3600 
+    ? `${hrs}:${mins}:${secs}` 
+    : `${mins}:${secs}`;
 };

27-31: Enhance timer display with visual feedback

The timer display is static regardless of the remaining time. Consider adding visual feedback when time is running low to improve user awareness.

 return (
-  <div className="text-color-1 font-medium">
+  <div className={`font-medium ${
+    remaining < 60 ? 'text-red-500 animate-pulse' : 
+    remaining < 300 ? 'text-amber-500' : 
+    'text-color-1'
+  }`}>
     {formatTime(remaining)}
   </div>
 );

34-35: Remove consecutive blank lines

There are two consecutive blank lines before the export statement, which should be reduced to one for consistent formatting.

 };

-
 export default SwapTimer;
src/apps/emcd-swap/helpers/email-validator.helper.ts (2)

1-6: Add comprehensive documentation for email validation rules

The function lacks documentation to explain its behavior and validation rules. Add JSDoc comments to make the function's purpose and limitations clear.

+/**
+ * Validates if the provided string is a valid email address.
+ * 
+ * Validation rules:
+ * - Must have a non-empty string before the @ symbol
+ * - Must have a non-empty string after the @ symbol
+ * - Must have a domain with at least one dot
+ * - Cannot contain whitespace
+ * 
+ * @param email - The email string to validate
+ * @returns true if the email is valid, false otherwise
+ */
 const isValidEmail = (email: string | null | undefined): boolean => {
   if (!email) return false;

   const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
   return emailRegex.test(email);
 }

4-5: Consider validating additional email requirements

The current regex doesn't enforce maximum length limits or other requirements that might be important for your application.

 const isValidEmail = (email: string | null | undefined): boolean => {
   if (!email) return false;
+  
+  // Check if email exceeds maximum length (RFC suggests 254 characters)
+  if (email.length > 254) return false;

   const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
   return emailRegex.test(email);
src/apps/emcd-swap/views/ErrorView.tsx (2)

59-63: Consider implementing internationalization for text content.

The error messages are hardcoded in Russian ("Что-то пошло не так" and "Попробуйте еще раз или напишите в поддержку"). To support multiple languages and improve maintainability, consider using an internationalization library or at least extracting these strings to a constants file.


24-26: Simplify toast dispatch with a more concise pattern.

The current implementation creates a separate function that then dispatches an action. This could be simplified.

-const setToast = ({ message, type, }: { message: string; type: ToastType; }) => {
-  dispatch(showToast({ message, type }));
-};

+// Use this directly where needed:
+// dispatch(showToast({ message, type }));
src/apps/emcd-swap/components/Card/Card.tsx (3)

3-7: Add JSDoc comments to describe prop usage.

The interface definition is clear, but it would be helpful to add JSDoc comments explaining how each prop affects the component, especially the behavior of valid when it's null.

interface CardProps {
+  /** Content to be rendered inside the card */
  children: ReactNode;
+  /** Additional CSS classes to apply to the card */
  className?: string;
+  /** Controls the border color. When true: normal border, when false: error border, when null: [explain behavior] */
  valid?: boolean | null;
}

11-11: Fix extra space in className string.

There's an extra space at the end of the className string interpolation which could lead to unnecessary whitespace in the rendered HTML.

-<div className={`p-5 border w-full bg-bg-7/70 rounded-5 ${valid ? 'border-bg-5' : 'border-error'} ${className} `}>
+<div className={`p-5 border w-full bg-bg-7/70 rounded-5 ${valid ? 'border-bg-5' : 'border-error'} ${className || ''}`}>

9-15: Consider enhancing the Card component with default styling when valid is null.

The current implementation handles two states (valid true/false), but doesn't clearly define behavior when valid is null. Consider adding a distinct styling for the null state.

-<div className={`p-5 border w-full bg-bg-7/70 rounded-5 ${valid ? 'border-bg-5' : 'border-error'} ${className} `}>
+<div className={`p-5 border w-full bg-bg-7/70 rounded-5 ${
+  valid === true 
+    ? 'border-bg-5' 
+    : valid === false 
+      ? 'border-error' 
+      : 'border-bg-7'
+} ${className || ''}`}>
src/apps/emcd-swap/components/Header/Header.tsx (3)

36-38: Remove unnecessary whitespace in JSX.

The extra whitespace ({' '}) around the LogoIcon component is unnecessary and might lead to unexpected spacing in the rendered output.

-        <div onClick={handleLogoClick} className="cursor-pointer">
-          {' '}
-          <LogoIcon />{' '}
-        </div>
+        <div onClick={handleLogoClick} className="cursor-pointer">
+          <LogoIcon />
+        </div>

23-26: Improve URL handling for opening in new tab.

The current implementation directly manipulates the URL, which might not handle all URL scenarios correctly. Consider using a more robust approach.

const handleLogoClick = () => {
-  const url = window.location.origin + location.pathname;
+  // This preserves the query parameters and hash if present
+  const url = new URL(location.pathname, window.location.origin).toString();
  window.open(url, '_blank');
};

56-56: Consider extracting the hardcoded language indicator.

The "RU" text appears to be a language indicator but is hardcoded. Consider making this configurable or based on the application's current language setting.

-        <div className="bg-[#232323] py-2 px-3 rounded-sm text-color-1">RU</div>
+        <div className="bg-[#232323] py-2 px-3 rounded-sm text-color-1">{currentLanguage}</div>
src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx (1)

16-26: Add handling for empty options array.

The component doesn't handle the case when the options array is empty. Consider adding a message or placeholder when there are no options.

    <ul className="absolute left-0 right-0 mt-1 rounded-sm bg-bg-2 border border-border shadow-md z-10 max-h-61.25 overflow-y-auto">
-      {options.map((option) => (
+      {options.length === 0 ? (
+        <li className="py-2 px-3 text-color-5">No options available</li>
+      ) : options.map((option) => (
        <SelectOption
          key={option[itemValue] ?? option["id"]}
          option={option}
          withIcon={withIcon}
          itemIcon={itemIcon}
          onClick={() => handleOptionClick(option)}
          itemText={itemText}
        />
-      ))}
+      ))}
    </ul>
src/apps/emcd-swap/views/SuccessView.tsx (3)

35-44: Consider more robust UUID validation and formatting

The UUID formatting function assumes a specific format and length of the swap ID. Consider adding more validation to handle edge cases.

  const formatUUID = () => {
    if (!swapID) {
      return '';
    }

+   // Ensure we're working with a valid swap ID format
+   if (typeof swapID !== 'string') {
+     return 'Invalid ID';
+   }
+
    const clean = swapID.replace(/-/g, '');
+   // Handle case where ID might be too short
+   if (clean.length < 18) {
+     return clean;
+   }
+
    const firstPart = clean.slice(0, 8);
    const lastPart = clean.slice(-10);
    return `${firstPart} •••• ${lastPart}`;
  };

60-62: Consider adding internationalization support

The UI text is in Russian, which suggests the app might be region-specific. Consider implementing i18n if the application needs to support multiple languages in the future.


69-74: Add accessibility attributes to clickable div

The div with the copy icon is clickable but doesn't have appropriate accessibility attributes like role="button" and keyboard support.

  <div
    onClick={() => copyToClipboard(swapID, setToast)}
-   className="w-4 h-4 cursor-pointer"
+   className="w-4 h-4 cursor-pointer"
+   role="button"
+   tabIndex={0}
+   onKeyDown={(e) => {
+     if (e.key === 'Enter' || e.key === ' ') {
+       copyToClipboard(swapID, setToast);
+     }
+   }}
+   aria-label="Copy transaction ID"
  >
    <CopyIcon />
  </div>
src/apps/emcd-swap/views/CancelledView.tsx (2)

24-30: Add missing semicolons for consistency

Add semicolons after function closures to maintain consistency with the rest of the codebase.

  const handleOpenSupportModal = () => {
    setSupportModal(true);
- }
+ };

  const handleCloseSupportModal = () => {
    setSupportModal(false);
- }
+ };

66-66: Improve conditional rendering with explicit null

For clarity and maintainability, consider using the ternary operator with an explicit null fallback.

- { supportModal && <SupportModal onClose={handleCloseSupportModal} /> }
+ { supportModal ? <SupportModal onClose={handleCloseSupportModal} /> : null }
src/apps/emcd-swap/components/FAQ/FAQ.tsx (3)

3-3: Fix import path with extra slash

There's an extra slash in the import path.

- import FAQItem from './components//FAQItem'
+ import FAQItem from './components/FAQItem'

10-39: Consider extracting FAQ content to a separate file

The FAQ list is quite large. Consider moving it to a separate file to improve maintainability, especially if content may change frequently.

// Create a new file: src/apps/emcd-swap/constants/faq.ts
+ import { FAQItemData } from '../components/FAQ/FAQ';
+ 
+ export const faqList: FAQItemData[] = [
+   {
+     question: 'Что такое ESWAP?',
+     answer: 'ESWAP — это сервис для мгновенного обмена криптовалют...',
+   },
+   // ... other items
+ ];

// Then in FAQ.tsx:
+ import { faqList } from '../constants/faq';
- const faqList: FAQItemData[] = [ ... ]

41-49: Consider adding search/filter capability for FAQ

For a better user experience with larger FAQ lists, consider adding search or filtering capabilities.

This would involve adding a search input above the list and filtering the FAQs based on the search term:

const FAQList: React.FC = () => {
  const [searchTerm, setSearchTerm] = useState('');
  
  const filteredFaqList = faqList.filter(item => 
    item.question.toLowerCase().includes(searchTerm.toLowerCase()) || 
    item.answer.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div className="faq-container max-w-4xl mx-auto p-4 mt-5">
      <input
        type="text"
        placeholder="Поиск..."
        className="w-full p-2 mb-4 border rounded"
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      {filteredFaqList.map((item, index) => (
        <FAQItem key={index} question={item.question} answer={item.answer} />
      ))}
    </div>
  );
};
src/apps/emcd-swap/components/Modals/SupportModal.tsx (4)

43-43: Avoid using any type in TypeScript

Replace the any type with a more specific type for better type safety.

- setFormData((prev: any) => ({
+ setFormData((prev: {name: string; email: string; text: string}) => ({

50-54: Enhance form validation

The current validation only checks if fields are empty. Consider adding more robust validation, especially for the email field.

  const handleSubmit = () => {
-   if (!formData.name || !formData.email || !formData.text || isLoading) {
+   const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email);
+   if (!formData.name || !formData.email || !formData.text || !isValidEmail || isLoading) {
+     // Optionally show validation errors
+     if (!isValidEmail && formData.email) {
+       dispatch(showToast({ message: 'Пожалуйста, введите корректный email', type: 'error' }));
+     }
      return
    }

    createTicket(formData)
  };

75-75: Avoid inline !important styling

The inputs use many utility classes with !important flags (!border, !p-4, etc.) which suggests styling conflicts. Consider refactoring to avoid these overrides.

Create a consistent input style using a reusable component or custom CSS class instead of multiple override classes:

// Create a common input component or class
const FormInput = ({ type = 'text', name, placeholder, onChange, ...props }) => (
  <input
    type={type}
    name={name}
    placeholder={placeholder}
    onChange={onChange}
    className="w-full outline-none border p-4 text-color-1 rounded-sm bg-bg-1 transition-all border-input hover:border-brand-hover focus:border-brand-active"
    {...props}
  />
);

// Then use it in your form
<FormInput 
  name="name" 
  placeholder="Как вас зовут?" 
  onChange={handleChange} 
/>

Also applies to: 83-83, 91-91


97-98: Use a utility like classnames for conditional classes

The conditional class application using a template literal is harder to read and maintain than using a utility like classnames.

+ import classNames from 'classnames';

// then in the button
- className={`w-full min-h-7 text-color-1 text-sm border p-4 rounded-sm text-center border-bg-35 mt-10 transition-all ${!formData.name || !formData.email || !formData.text || isLoading ? 'bg-transparent' : 'bg-brand'}`}
+ className={classNames(
+   'w-full min-h-7 text-color-1 text-sm border p-4 rounded-sm text-center border-bg-35 mt-10 transition-all',
+   {
+     'bg-transparent': !formData.name || !formData.email || !formData.text || isLoading,
+     'bg-brand': formData.name && formData.email && formData.text && !isLoading
+   }
+ )}
src/apps/emcd-swap/views/Confirm/components/forms/ExchangeDepositForm.tsx (1)

51-58: Consider using i18n for text localization

The component contains hardcoded Russian text. For better internationalization support, consider using a localization library or solution to make the application accessible to users of different languages.

src/apps/emcd-swap/helpers/input-validator.helper.ts (3)

10-13: Improve number validation

The current number validation using parseFloat is not strict enough, as it would accept strings like "123abc" as valid numbers (returning 123). Consider adding a more stringent validation to ensure the entire string is a valid number.

const num = parseFloat(value)
-if (isNaN(num)) {
+if (isNaN(num) || !/^-?\d*\.?\d+$/.test(value.trim())) {
  return { valid: false, error: 'Введите корректное число' }
}

1-24: Add type definition for return value

Adding explicit TypeScript return type annotations would improve type safety and code readability.

+type ValidationResult = {
+  valid: boolean;
+  error?: string;
+};
+
const amountFromValidator = (
  value: string | null,
  max: number | null,
  min: number | null
-) => {
+): ValidationResult => {
  // function body...
}

7-7: Consider using i18n for error messages

The error messages are hardcoded in Russian. For better internationalization support, consider using a localization library or solution to make the application accessible to users of different languages.

src/apps/emcd-swap/components/FormInput/FormInput.tsx (2)

5-12: Add disabled state support

The component doesn't handle a disabled state. Consider adding a disabled prop to enhance the component's versatility.

interface FormInputProps {
  value: string | null;
  onChange: (value: string | null) => void;
  loading?: boolean;
+ disabled?: boolean;
  
  valid?: boolean | null;
  error?: string | null;
}

Then add the disabled attribute to the input element:

<input
  value={stateValue}
  onChange={handleChange}
  className="w-full text-xl text-medium py-1 outline-none focus:outline-none"
+ disabled={disabled}
/>

29-32: Consistency in handling null vs empty string

The component accepts null values but always passes string values in the onChange callback. This inconsistency could cause issues in the parent components. Consider normalizing the behavior.

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setStateValue(e.target.value);
-  onChange(e.target.value);
+  // Normalize empty strings to null if that's how the parent component expects it
+  onChange(e.target.value || null);
};
src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (2)

27-27: Improve readability of Tailwind class string

The long string of Tailwind classes is difficult to read and maintain. Consider organizing it for better readability.

-<input placeholder={placeholder} value={stateValue} onChange={handleChange} className='w-full bg-bg-6 p-4 text-color-1 border border-bg-2 hover:border-brand-hover focus:border-brand-active text-sm outline-none rounded-sm transition-all' />
+<input
+  placeholder={placeholder}
+  value={stateValue}
+  onChange={handleChange}
+  className='w-full bg-bg-6 p-4 text-color-1 text-sm rounded-sm
+             border border-bg-2 
+             hover:border-brand-hover focus:border-brand-active
+             outline-none transition-all'
+/>

19-23: Consistency in handling null vs empty string

Similar to FormInput, this component accepts null values but always passes string values in the onChange callback. Consider normalizing this behavior.

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setStateValue(e.target.value);
-  onChange(e.target.value);
+  // Normalize empty strings to null if that's how the parent component expects it
+  onChange(e.target.value || null);
}
src/apps/emcd-swap/views/ExchangeView.tsx (2)

167-172: Extract button conditional styling to a separate variable

The inline conditional styling for the button is complex and makes the JSX harder to read. Extract this logic to a variable before the return statement.

+  const buttonClassName = `w-full min-h-7 text-color-1 text-sm border py-2 rounded-sm text-center border-bg-35 mt-10 transition-all ${
+    !amountTo.value || !amountFrom.value || amountTo.error || amountFrom.error || isFetchingEstimate 
+      ? 'bg-transparent' 
+      : 'bg-brand'
+  }`

  return (
    {/* ... */}
        <button
-          className={`w-full min-h-7 text-color-1 text-sm border py-2 rounded-sm text-center border-bg-35 mt-10 transition-all ${!amountTo.value || !amountFrom.value || amountTo.error || amountFrom.error || isFetchingEstimate ? 'bg-transparent' : 'bg-brand'}`}
+          className={buttonClassName}
          onClick={submitForm}
        >
          Начать
        </button>

1-176: Consider implementing internationalization

The component has hardcoded Russian text throughout. Consider implementing a proper internationalization solution like i18next to support multiple languages and make the application more accessible.

🧰 Tools
🪛 Biome (1.9.4)

[error] 103-104: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 110-110: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/apps/emcd-swap/components/Select/components/SelectActivator.tsx (1)

13-13: Fix spacing in React.FC type definition

There should be no space between React.FC and the generic type.

- const SelectActivator:React.FC<SelectActivatorProps> = ({ isOpen, toggleDropdown, withIcon, option, itemIcon, itemText, placeholder }) => {
+ const SelectActivator: React.FC<SelectActivatorProps> = ({ isOpen, toggleDropdown, withIcon, option, itemIcon, itemText, placeholder }) => {
src/apps/emcd-swap/tailwind.deposit.config.js (3)

4-7: Simplify content paths to avoid duplicate scanning

The current configuration has potentially overlapping glob patterns that might cause files to be scanned multiple times. Consider consolidating them.

  content: [
-    './src/apps/emcd-swap/**/**/*.{js,ts,jsx,tsx,html,mdx}',
-    './src/apps/emcd-swap/**/*.{js,ts,jsx,tsx,html,mdx}',
+    './src/apps/emcd-swap/**/*.{js,ts,jsx,tsx,html,mdx}',
  ],

32-46: Add comments to explain color naming scheme

The color naming convention mixes semantic names and numeric identifiers without clear explanation. Consider adding comments to explain the purpose of each color group.

+        // Primary text colors - from lightest to darkest
        'color-1': '#FAFAFA',
        'color-2': '#D0D0D0',
        'color-3': '#A3A3A3',
        'color-6': '#7A7A7A',

+        // Border and separator colors
        'color-7': '#363439',
        'color-8': '#111111',

+        // Background colors - various dark shades for different UI layers
        'bg-1': '#0A0A0A',
        'bg-2': '#1A1A1A',
        'bg-35': '#494646',
        'bg-5': '#2d2a2a',
        'bg-6': '#0A0A0A',
        'bg-7': '#120F17',
        'bg-8': '#211C29',

11-48: Consider using CSS variables for brand colors

For better maintainability, consider defining brand colors as CSS variables in a separate file and referencing them in Tailwind. This makes it easier to update the color scheme across the application.

This approach would allow you to:

  1. Maintain a single source of truth for your brand colors
  2. Enable easier theming and dark mode switching
  3. Share colors between Tailwind and non-Tailwind CSS if needed

Example implementation:

  1. Create a colors.css file with variables
  2. Import it in your main CSS file
  3. Reference those variables in your Tailwind config
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (3)

1-1: Update the file path comment.

The comment references an incorrect file path (src/slices/toastSlice.ts) that doesn't match the actual file location. Update it to reflect the current path.

-// src/slices/toastSlice.ts
+// src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts

38-42: Improve type safety for selectors.

The selectors use a simplified state type that assumes the reducer will be mounted directly at state.emcdSwapToast. This can cause type errors if the store structure changes.

Consider using a more flexible approach with a RootState type:

-export const selectToastMessage = (state: { emcdSwapToast: ToastState }) =>
+export const selectToastMessage = (state: RootState) =>
  state.emcdSwapToast.message

-export const selectToastType = (state: { emcdSwapToast: ToastState }) =>
+export const selectToastType = (state: RootState) =>
  state.emcdSwapToast.type

Then define a RootState type in a separate types file:

// src/apps/emcd-swap/types/store.ts
export interface RootState {
  emcdSwapToast: ToastState;
  // other slices...
}

44-44: Export the reducer instead of the slice object.

It's a standard Redux practice to export the reducer as the default export and action creators separately.

-export default emcdSwapToast
+export default emcdSwapToast.reducer
src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx (2)

9-14: Consider simplifying props types.

The props are defined with nullable boolean values, which is redundant when using optional props.

 interface SendToWalletFormProps {
-  confirm?: boolean | null;
-  active?: boolean | null;
-  needLine?: boolean | null;
+  confirm?: boolean;
+  active?: boolean;
+  needLine?: boolean;
   formData: DetailSwapFormData;
 }

49-57: Extract hardcoded text for internationalization.

The component contains hardcoded Russian text, which limits localization capabilities.

Consider using an internationalization solution:

+import { useTranslation } from 'react-i18next';

 const SendToWalletForm: React.FC<SendToWalletFormProps> = ({ active, confirm, needLine, formData }) => {
+  const { t } = useTranslation();
   // ...

   <div className={`transition-all font-semibold ${getStyleTitle()}`}>
-     Мы отправляем на ваш кошелек
+     {t('swap.sending_to_wallet')}
   </div>

   {active && (
     <div className="text-sm leading-[1.3] text-color-3">
-       Обмен успешно завершен. Сейчас мы отправим криптовалюту на ваш
-       адрес
+       {t('swap.exchange_completed')}
     </div>
   )}
src/apps/emcd-swap/hooks/useExchangeLogic.ts (1)

48-49: Translate or remove non-English comments.

The codebase contains non-English comments, which may be difficult for some developers to understand.

Consider translating comments to English for consistency and better collaboration:

- // Инициализация монет и начальных значений
+ // Initialize coins and initial values

This applies to all non-English comments in the file.

src/apps/emcd-swap/views/Confirm/ConfirmView.tsx (1)

73-73: Fix typo in text.

There's a typo in the Russian text for deposit time.

-<div className="text-color-2 text-sm">Время на внесение депозиита</div>
+<div className="text-color-2 text-sm">Время на внесение депозита</div>
src/apps/emcd-swap/views/Confirm/components/forms/WaitingDepositForm.tsx (1)

97-99: Use a common pattern for copy buttons

There are multiple copy buttons with repeated logic and styling. Consider extracting this to a reusable component.

Create a reusable CopyButton component:

interface CopyButtonProps {
  textToCopy: string | null;
  setToast: ({ message, type }: { message: string; type: ToastType }) => void;
}

const CopyButton: React.FC<CopyButtonProps> = ({ textToCopy, setToast }) => {
  return (
    <div className='cursor-pointer' onClick={() => copyToClipboard(textToCopy, setToast)}>
      <CopyIcon />
    </div>
  );
};

Then use it throughout the component:

- <div className='cursor-pointer' onClick={() => copyToClipboard(formData.amount_from, setToast)}>
-   <CopyIcon />
- </div>
+ <CopyButton textToCopy={formData.amount_from} setToast={setToast} />

Also applies to: 111-113

src/apps/emcd-swap/components/Select/Select.tsx (3)

13-14: Remove non-English comment

There's a Russian comment in the code. Ensure all comments are in English for consistency and maintainability.

  itemValue?: string;
- itemText?: string; // Поле для отображения текста (по умолчанию "name")
+ itemText?: string; // Field for displaying text (default is "name")
  withIcon?: boolean;

33-36: Use optional chaining for callback

The current implementation manually checks for the existence of the onChange callback before calling it. Using optional chaining would make this more concise.

  const handleOptionClick = (option: Option) => {
    setSelected(option);
-   onChange && onChange(option);
+   onChange?.(option);
    setIsOpen(false);
  };

52-52: Hard-coded width constraint

The select component has a hard-coded width of w-36 (Tailwind's 9rem/144px), which may not be flexible enough for different use cases.

Consider making the width configurable through props:

interface CustomSelectProps {
  options: Option[];
  currentValue: Record<string, any> | null;
  placeholder: string;
  itemValue?: string;
  itemText?: string;
  withIcon?: boolean;
  itemIcon?: string;
  onChange?: (value: any) => void;
+ width?: string; // Accept custom width class
}

const CustomSelect: React.FC<CustomSelectProps> = ({ 
  options, 
  placeholder, 
  currentValue, 
  itemValue = "id", 
  itemText = "name", 
  onChange, 
  withIcon, 
  itemIcon,
+ width = "w-36" 
}) => {
  // ...
  
  return (
-   <div ref={dropdownRef} className="relative w-36">
+   <div ref={dropdownRef} className={`relative ${width}`}>
src/apps/emcd-swap/views/Confirm/components/forms/ConfirmDepositForm.tsx (2)

28-28: Remove console.log statement.

Console.log statements should be removed before production code is deployed as they can expose sensitive information and affect performance.

-  console.log('formData', formData);

50-97: Consider implementing internationalization.

The UI text is hardcoded in Russian. If your application needs to support multiple languages, consider implementing an internationalization solution.

Consider using a library like i18next or react-intl to make text translatable. For example:

import { useTranslation } from 'react-i18next';

const ConfirmDepositForm: React.FC<ConfirmDepositFormProps> = ({ ... }) => {
  const { t } = useTranslation();
  
  return (
    // ...
    <div className={`transition-all font-semibold ${getStyleTitle()}`}>
      {t('confirm.deposit.title')}
    </div>
    // ...
  );
};
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (4)

113-114: Use consistent language for comments.

The comment "Геттер" is in Russian while the codebase is primarily in English. Keep comments consistent with the codebase language.

-// Геттер
+// Selectors

114-133: Define a root state type and use it for all selectors.

The selectors have a repetitive state type annotation. Define a RootState type and use it consistently.

+// Define a type for the root state
+export type RootState = {
+  emcdSwap: EmcdSwapState;
+  // other slices...
+};

-export const selectCurrentView = (state: { emcdSwap: EmcdSwapState }) =>
+export const selectCurrentView = (state: RootState) =>
  state.emcdSwap.currentView;

-export const selectCoins = (state: { emcdSwap: EmcdSwapState }) =>
+export const selectCoins = (state: RootState) =>
  state.emcdSwap.coins;

// Apply the same change to all other selectors

44-67: Consider using non-null assertion for setDetailSwapStatus reducer.

The status field in detailSwapFormData is defined as potentially null, but the setDetailSwapStatus reducer assigns a number to it. Consider using proper type handling or non-null assertion.

  setDetailSwapStatus(state, action: PayloadAction<number>) {
-    state.detailSwapFormData.status = action.payload
+    state.detailSwapFormData.status = action.payload;
  }

Also applies to: 105-107


135-135: Export reducer directly instead of slice object.

The default export is the slice object, not the reducer. Most Redux patterns recommend exporting the reducer as the default.

-export default emcdSwapSlice
+export default emcdSwapSlice.reducer;
src/apps/emcd-swap/constants/views.ts (2)

1-9: Consider using string literals for VIEW_TYPE enum.

Using numeric values for enums can make debugging more difficult as the values aren't self-descriptive in logs or state. String literals would make the code more maintainable.

-enum VIEW_TYPE {
-  EXCHANGE = 1,
-  FAQ = 2,
-  RECIPIENT = 3,
-  CONFIRM = 4,
-  SUCCESS = 5,
-  CANCELLED = 6,
-  ERROR= 7,
-}
+export enum VIEW_TYPE {
+  EXCHANGE = 'exchange',
+  FAQ = 'faq',
+  RECIPIENT = 'recipient',
+  CONFIRM = 'confirm',
+  SUCCESS = 'success',
+  CANCELLED = 'cancelled',
+  ERROR = 'error',
+}

63-66: Use export declarations directly with enum/const.

The export style is inconsistent with typical TypeScript patterns. Consider exporting the enum and const directly rather than at the end of the file.

-enum VIEW_TYPE {
+export enum VIEW_TYPE {
  ...
}

-const VIEWS_SETTINGS: Record<keyof typeof VIEW_TYPE, IViewSettings> = {
+export const VIEWS_SETTINGS: Record<keyof typeof VIEW_TYPE, IViewSettings> = {
  ...
}

-export {
-  VIEWS_SETTINGS,
-  VIEW_TYPE,
-}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a7fa2fe and 7b6757c.

⛔ Files ignored due to path filters (22)
  • package-lock.json is excluded by !**/package-lock.json
  • src/apps/emcd-swap/assets/cancelled.png is excluded by !**/*.png
  • src/apps/emcd-swap/assets/error.png is excluded by !**/*.png
  • src/apps/emcd-swap/assets/success.png is excluded by !**/*.png
  • src/apps/emcd-swap/documents/CryptoTerms.pdf is excluded by !**/*.pdf
  • src/apps/emcd-swap/icon.png is excluded by !**/*.png
  • src/apps/pillarx-app/components/MediaGridCollection/tests/__snapshots__/DisplayCollectionImage.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pillarx-app/components/PointsTile/test/__snapshots__/PointsTile.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pillarx-app/components/PortfolioOverview/test/__snapshots__/PortfolioOverview.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pillarx-app/components/TokenMarketDataRow/tests/__snapshots__/LeftColumnTokenMarketDataRow.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pillarx-app/components/TokenMarketDataRow/tests/__snapshots__/RightColumnTokenMarketDataRow.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pillarx-app/components/TokensWithMarketDataTile/test/__snapshots__/TokensWithMarketDataTile.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pillarx-app/images/token-market-data-copy.png is excluded by !**/*.png
  • src/apps/the-exchange/components/CardsSwap/test/__snapshots__/CardSwap.test.tsx.snap is excluded by !**/*.snap
  • src/apps/the-exchange/components/DropdownTokensList/test/__snapshots__/DropdownTokensList.test.tsx.snap is excluded by !**/*.snap
  • src/apps/the-exchange/components/EnterAmount/test/__snapshots__/EnterAmount.test.tsx.snap is excluded by !**/*.snap
  • src/apps/the-exchange/components/SelectToken/test/__snapshots__/SelectToken.test.tsx.snap is excluded by !**/*.snap
  • src/apps/the-exchange/components/TokenListItem/test/__snapshots__/TokenListItem.test.tsx.snap is excluded by !**/*.snap
  • src/apps/token-atlas/components/TokenGraphColumn/test/__snapshots__/TokenGraphColumn.test.tsx.snap is excluded by !**/*.snap
  • src/apps/token-atlas/components/TokensSlider/test/__snapshots__/TokenSlider.test.tsx.snap is excluded by !**/*.snap
  • src/components/BottomMenu/__snapshots__/BottomMenu.test.tsx.snap is excluded by !**/*.snap
  • src/components/Form/Select/__snapshots__/Select.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (107)
  • package.json (1 hunks)
  • src/apps/emcd-swap/api/coinsApi.tsx (1 hunks)
  • src/apps/emcd-swap/components/Button/Button.tsx (1 hunks)
  • src/apps/emcd-swap/components/Card/Card.tsx (1 hunks)
  • src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (1 hunks)
  • src/apps/emcd-swap/components/FAQ/FAQ.tsx (1 hunks)
  • src/apps/emcd-swap/components/FAQ/components/FAQItem.tsx (1 hunks)
  • src/apps/emcd-swap/components/FormInput/FormInput.tsx (1 hunks)
  • src/apps/emcd-swap/components/Header/Header.tsx (1 hunks)
  • src/apps/emcd-swap/components/Loader/Loader.tsx (1 hunks)
  • src/apps/emcd-swap/components/Modals/SupportModal.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/Select.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectActivator.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectOption.tsx (1 hunks)
  • src/apps/emcd-swap/components/Step/Step.tsx (1 hunks)
  • src/apps/emcd-swap/components/SwapTimer/SwapTimer.tsx (1 hunks)
  • src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/ArrowRightIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/CloseIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/ConfirmIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/CopyIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/InfoIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/LogoIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/SupportIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/SwapIcon.tsx (1 hunks)
  • src/apps/emcd-swap/constants/views.ts (1 hunks)
  • src/apps/emcd-swap/enums/status.enum.ts (1 hunks)
  • src/apps/emcd-swap/helpers/copy.helper.ts (1 hunks)
  • src/apps/emcd-swap/helpers/email-validator.helper.ts (1 hunks)
  • src/apps/emcd-swap/helpers/input-validator.helper.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useConfirmLogic.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useExchangeLogic.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useRecipientLogic.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useValidatedInput.ts (1 hunks)
  • src/apps/emcd-swap/index.tsx (1 hunks)
  • src/apps/emcd-swap/layouts/MainLayout.tsx (1 hunks)
  • src/apps/emcd-swap/manifest.json (1 hunks)
  • src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1 hunks)
  • src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (1 hunks)
  • src/apps/emcd-swap/styles/tailwindDeposit.css (1 hunks)
  • src/apps/emcd-swap/tailwind.deposit.config.js (1 hunks)
  • src/apps/emcd-swap/views/CancelledView.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/ConfirmView.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/ConfirmDepositForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/ExchangeDepositForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/WaitingDepositForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/ErrorView.tsx (1 hunks)
  • src/apps/emcd-swap/views/ExchangeView.tsx (1 hunks)
  • src/apps/emcd-swap/views/FaqView.tsx (1 hunks)
  • src/apps/emcd-swap/views/RecipientView.tsx (1 hunks)
  • src/apps/emcd-swap/views/SuccessView.tsx (1 hunks)
  • src/apps/pillarx-app/components/HorizontalToken/HorizontalToken.tsx (1 hunks)
  • src/apps/pillarx-app/components/PointsTile/PointsTile.tsx (1 hunks)
  • src/apps/pillarx-app/components/PortfolioOverview/PortfolioOverview.tsx (1 hunks)
  • src/apps/pillarx-app/components/TileTitle/TileTitle.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokenInfoHorizontal/TokenInfoHorizontal.tsx (1 hunks)
  • src/apps/pillarx-app/components/TokenMarketDataRow/LeftColumnTokenMarketDataRow.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokenMarketDataRow/RightColumnTokenMarketDataRow.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokenMarketDataRow/TokenLogoMarketDataRow.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokenMarketDataRow/TokenMarketDataRow.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokenMarketDataRow/tests/LeftColumnTokenMarketDataRow.test.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokenMarketDataRow/tests/RightColumnTokenMarketDataRow.test.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokensHorizontalTile/TokensHorizontalTile.tsx (1 hunks)
  • src/apps/pillarx-app/components/TokensHorizontalTile/test/TokensHorizontalTile.test.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokensVerticalList/TokensVerticalList.tsx (1 hunks)
  • src/apps/pillarx-app/components/TokensVerticalList/test/TokensVerticalList.test.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokensVerticalTile/test/TokensVerticalTile.test.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokensWithMarketDataTile/TokensWithMarketDataTile.tsx (0 hunks)
  • src/apps/pillarx-app/components/TokensWithMarketDataTile/test/TokensWithMarketDataTile.test.tsx (0 hunks)
  • src/apps/pillarx-app/tailwind.pillarx.config.js (0 hunks)
  • src/apps/pillarx-app/utils/configComponent.ts (0 hunks)
  • src/apps/the-exchange/components/CardsSwap/CardsSwap.tsx (1 hunks)
  • src/apps/the-exchange/components/CardsSwap/test/CardSwap.test.tsx (5 hunks)
  • src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx (2 hunks)
  • src/apps/the-exchange/components/DropdownTokensList/TokenRow.tsx (1 hunks)
  • src/apps/the-exchange/components/DropdownTokensList/test/DropdownTokensList.test.tsx (1 hunks)
  • src/apps/the-exchange/components/EnterAmount/EnterAmount.tsx (8 hunks)
  • src/apps/the-exchange/components/EnterAmount/test/EnterAmount.test.tsx (4 hunks)
  • src/apps/the-exchange/components/SelectToken/SelectToken.tsx (1 hunks)
  • src/apps/the-exchange/components/SwapReceiveCard/SwapReceiveCard.tsx (2 hunks)
  • src/apps/the-exchange/components/TokenListItem/TokenListItem.tsx (2 hunks)
  • src/apps/the-exchange/components/TokenListItem/test/TokenListItem.test.tsx (2 hunks)
  • src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (3 hunks)
  • src/apps/the-exchange/reducer/theExchangeSlice.ts (3 hunks)
  • src/apps/token-atlas/api/token.ts (2 hunks)
  • src/apps/token-atlas/components/ChainCard/ChainCard.tsx (1 hunks)
  • src/apps/token-atlas/components/TokenGraph/TokenGraph.tsx (5 hunks)
  • src/apps/token-atlas/components/TokenGraph/test/TokenGraph.test.tsx (2 hunks)
  • src/apps/token-atlas/components/TokenGraphColumn/TokenGraphColumn.tsx (4 hunks)
  • src/apps/token-atlas/components/TokenGraphColumn/test/TokenGraphColumn.test.tsx (3 hunks)
  • src/apps/token-atlas/components/TokensSlider/TokensSlider.tsx (2 hunks)
  • src/apps/token-atlas/components/TokensSlider/test/TokenSlider.test.tsx (2 hunks)
  • src/apps/token-atlas/index.tsx (5 hunks)
  • src/apps/token-atlas/reducer/tokenAtlasSlice.ts (3 hunks)
  • src/apps/token-atlas/utils/converters.ts (0 hunks)
  • src/components/AppsList.tsx (0 hunks)
  • src/components/BottomMenu/index.tsx (0 hunks)
  • src/components/BottomMenuModal/AccountModal.tsx (1 hunks)
  • src/components/Form/FormTabSelect/index.tsx (0 hunks)
  • src/components/Form/Select/index.tsx (1 hunks)
  • src/components/SkeletonLoader.tsx (0 hunks)
  • src/pages/App/index.tsx (3 hunks)
  • src/providers/AllowedAppsProvider.tsx (1 hunks)
  • src/services/pillarXApiSearchTokens.ts (1 hunks)
  • src/services/pillarXApiWalletPortfolio.ts (0 hunks)
⛔ Files not processed due to max files limit (4)
  • src/services/tokensData.ts
  • src/store.ts
  • src/types/api.ts
  • src/utils/common.ts
💤 Files with no reviewable changes (20)
  • src/apps/pillarx-app/components/TokensVerticalList/test/TokensVerticalList.test.tsx
  • src/apps/pillarx-app/tailwind.pillarx.config.js
  • src/components/BottomMenu/index.tsx
  • src/components/Form/FormTabSelect/index.tsx
  • src/components/AppsList.tsx
  • src/apps/pillarx-app/components/TokensVerticalTile/test/TokensVerticalTile.test.tsx
  • src/apps/pillarx-app/utils/configComponent.ts
  • src/apps/pillarx-app/components/TokensHorizontalTile/test/TokensHorizontalTile.test.tsx
  • src/components/SkeletonLoader.tsx
  • src/apps/pillarx-app/components/TokenMarketDataRow/TokenMarketDataRow.tsx
  • src/apps/pillarx-app/components/TokenMarketDataRow/tests/LeftColumnTokenMarketDataRow.test.tsx
  • src/apps/pillarx-app/components/TokenMarketDataRow/TokenLogoMarketDataRow.tsx
  • src/apps/token-atlas/utils/converters.ts
  • src/apps/pillarx-app/components/TileTitle/TileTitle.tsx
  • src/apps/pillarx-app/components/TokensWithMarketDataTile/TokensWithMarketDataTile.tsx
  • src/apps/pillarx-app/components/TokenMarketDataRow/RightColumnTokenMarketDataRow.tsx
  • src/services/pillarXApiWalletPortfolio.ts
  • src/apps/pillarx-app/components/TokenMarketDataRow/tests/RightColumnTokenMarketDataRow.test.tsx
  • src/apps/pillarx-app/components/TokenMarketDataRow/LeftColumnTokenMarketDataRow.tsx
  • src/apps/pillarx-app/components/TokensWithMarketDataTile/test/TokensWithMarketDataTile.test.tsx
🧰 Additional context used
🧠 Learnings (3)
src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx (1)
Learnt from: RanaBug
PR: pillarwallet/x#275
File: src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx:180-195
Timestamp: 2025-03-28T09:22:22.712Z
Learning: In the Exchange app, `swapTokenList` and `receiveTokenList` are derived from `searchTokenResult` when search is active, so including `searchToken` in the useEffect dependency array that uses these lists would be redundant as the lists will update when search results change.
src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (1)
Learnt from: RanaBug
PR: pillarwallet/x#275
File: src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx:180-195
Timestamp: 2025-03-28T09:22:22.712Z
Learning: In the Exchange app, `swapTokenList` and `receiveTokenList` are derived from `searchTokenResult` when search is active, so including `searchToken` in the useEffect dependency array that uses these lists would be redundant as the lists will update when search results change.
src/apps/the-exchange/components/SwapReceiveCard/SwapReceiveCard.tsx (1)
Learnt from: RanaBug
PR: pillarwallet/x#275
File: src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx:180-195
Timestamp: 2025-03-28T09:22:22.712Z
Learning: In the Exchange app, `swapTokenList` and `receiveTokenList` are derived from `searchTokenResult` when search is active, so including `searchToken` in the useEffect dependency array that uses these lists would be redundant as the lists will update when search results change.
🧬 Code Graph Analysis (27)
src/apps/emcd-swap/components/Header/Header.tsx (3)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1)
  • setCurrentView (73-76)
src/apps/emcd-swap/constants/views.ts (1)
  • VIEW_TYPE (65-65)
src/apps/simpleswap/components/icons/LogoIcon.jsx (1)
  • LogoIcon (4-191)
src/apps/emcd-swap/components/icons/LogoIcon.tsx (1)
src/apps/simpleswap/components/icons/LogoIcon.jsx (1)
  • LogoIcon (4-191)
src/apps/emcd-swap/views/CancelledView.tsx (3)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1)
  • setCurrentView (73-76)
src/apps/emcd-swap/constants/views.ts (1)
  • VIEW_TYPE (65-65)
src/components/LandingPage/Header/index.jsx (1)
  • Header (7-102)
src/apps/token-atlas/components/TokenGraph/TokenGraph.tsx (1)
src/types/api.ts (1)
  • TokenMarketHistory (222-229)
src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (1)
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (2)
  • selectToastMessage (38-39)
  • selectToastType (41-42)
src/apps/token-atlas/components/ChainCard/ChainCard.tsx (1)
src/apps/token-atlas/reducer/tokenAtlasSlice.ts (1)
  • setBlockchainList (103-108)
src/apps/emcd-swap/hooks/useConfirmLogic.ts (1)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (3)
  • selectDetailFormDataSwap (123-124)
  • selectDetailSwapStatus (132-133)
  • selectSwapID (126-127)
src/apps/emcd-swap/views/ErrorView.tsx (4)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (2)
  • selectSwapID (126-127)
  • setCurrentView (73-76)
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (1)
  • ToastType (5-5)
src/apps/emcd-swap/constants/views.ts (1)
  • VIEW_TYPE (65-65)
src/components/LandingPage/Header/index.jsx (1)
  • Header (7-102)
src/apps/token-atlas/components/TokenGraph/test/TokenGraph.test.tsx (2)
src/types/api.ts (1)
  • TokenMarketHistory (222-229)
src/apps/token-atlas/reducer/tokenAtlasSlice.ts (1)
  • setTokenDataGraph (85-90)
src/apps/token-atlas/components/TokenGraphColumn/test/TokenGraphColumn.test.tsx (1)
src/types/api.ts (1)
  • TokenMarketHistory (222-229)
src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx (1)
src/apps/emcd-swap/components/Select/Select.tsx (1)
  • Option (6-6)
src/apps/emcd-swap/components/Modals/SupportModal.tsx (3)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1)
  • setFormData (94-99)
src/apps/simpleswap/hooks/useComponentVisible.js (1)
  • handleClickOutside (7-14)
src/components/LandingPage/Input/index.jsx (1)
  • handleChange (58-60)
src/apps/the-exchange/components/TokenListItem/TokenListItem.tsx (1)
src/services/tokensData.ts (1)
  • chainNameDataCompatibility (44-64)
src/apps/emcd-swap/index.tsx (2)
src/apps/emcd-swap/constants/views.ts (1)
  • VIEW_TYPE (65-65)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1)
  • selectCurrentView (114-115)
src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (1)
src/components/LandingPage/Input/index.jsx (1)
  • handleChange (58-60)
src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx (1)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1)
  • DetailSwapFormData (17-32)
src/apps/emcd-swap/hooks/useRecipientLogic.ts (4)
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (1)
  • ToastType (5-5)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (7)
  • selectFormData (120-121)
  • selectCoins (117-118)
  • setDetailSwapFormData (101-103)
  • setCurrentView (73-76)
  • setFormData (94-99)
  • setSwapID (86-88)
  • setDepositAddress (90-92)
src/apps/emcd-swap/constants/views.ts (1)
  • VIEW_TYPE (65-65)
src/apps/emcd-swap/helpers/email-validator.helper.ts (1)
  • isValidEmail (9-9)
src/apps/emcd-swap/views/Confirm/components/forms/ExchangeDepositForm.tsx (1)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (2)
  • DetailSwapFormData (17-32)
  • selectDepositAddress (129-130)
src/apps/the-exchange/components/EnterAmount/test/EnterAmount.test.tsx (2)
src/providers/AccountBalancesProvider.tsx (1)
  • AccountBalancesListenerRef (42-45)
src/apps/the-exchange/reducer/theExchangeSlice.ts (2)
  • setUsdPriceSwapToken (86-88)
  • setUsdPriceReceiveToken (89-91)
src/apps/emcd-swap/views/Confirm/components/forms/WaitingDepositForm.tsx (2)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (3)
  • DetailSwapFormData (17-32)
  • selectCoins (117-118)
  • selectDepositAddress (129-130)
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (1)
  • ToastType (5-5)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1)
src/apps/emcd-swap/constants/views.ts (1)
  • VIEW_TYPE (65-65)
src/apps/token-atlas/reducer/tokenAtlasSlice.ts (1)
src/types/api.ts (1)
  • TokenMarketHistory (222-229)
src/apps/emcd-swap/hooks/useExchangeLogic.ts (4)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (4)
  • selectCoins (117-118)
  • setCoins (82-84)
  • setCurrentView (73-76)
  • setFormData (94-99)
src/apps/emcd-swap/hooks/useValidatedInput.ts (1)
  • useValidatedInput (3-22)
src/apps/emcd-swap/helpers/input-validator.helper.ts (2)
  • amountFromValidator (53-53)
  • amountToValidator (52-52)
src/apps/emcd-swap/constants/views.ts (1)
  • VIEW_TYPE (65-65)
src/apps/the-exchange/components/CardsSwap/test/CardSwap.test.tsx (2)
src/providers/AccountBalancesProvider.tsx (1)
  • AccountBalancesListenerRef (42-45)
src/apps/the-exchange/reducer/theExchangeSlice.ts (2)
  • setUsdPriceSwapToken (86-88)
  • setUsdPriceReceiveToken (89-91)
src/apps/emcd-swap/views/Confirm/components/forms/ConfirmDepositForm.tsx (1)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1)
  • DetailSwapFormData (17-32)
src/apps/emcd-swap/components/Select/Select.tsx (2)
src/apps/simpleswap/hooks/useComponentVisible.js (1)
  • handleClickOutside (7-14)
src/apps/pillar-swap/AlphaRouterService.js (1)
  • options (43-48)
src/apps/token-atlas/api/token.ts (3)
src/utils/blockchain.ts (1)
  • isTestnet (29-35)
src/types/api.ts (4)
  • TokenAtlasInfoApiResponse (213-215)
  • TokenAtlasGraphApiResponse (231-233)
  • TrendingTokens (240-242)
  • BlockchainList (302-304)
src/store.ts (1)
  • addMiddleware (50-60)
🪛 Biome (1.9.4)
src/apps/emcd-swap/views/ExchangeView.tsx

[error] 103-104: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 110-110: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/apps/emcd-swap/components/Select/Select.tsx

[error] 35-38: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (86)
src/providers/AllowedAppsProvider.tsx (1)

70-70: Dependencies array correctly updated after state removal.

The useMemo dependencies array has been properly updated to only include the remaining states (isLoading and allowed) after the removal of the isAnimated state. This change is consistent with the larger refactoring effort to remove animation-related state management across the application.

src/pages/App/index.tsx (3)

76-77: Animation delay now uses fixed value instead of conditional logic.

The spring animation delay has been updated to use a fixed 1500ms delay instead of conditionally using different values based on an animation flag. This simplifies the code while maintaining the animation effect.


85-86: Component loading delay now uses fixed timing.

The artificial delay for lazy loading has been updated to use a consistent 1500ms delay instead of conditionally determining the delay based on animation state. This simplifies the code while ensuring a smooth transition experience.


96-97: Simplified Suspense fallback rendering.

The Suspense fallback now unconditionally renders the AnimatedAppTitle component, removing conditional logic that previously depended on animation state. This is consistent with the broader refactoring to simplify animation handling throughout the application.

src/apps/pillarx-app/components/PortfolioOverview/PortfolioOverview.tsx (1)

86-86:

Details

✅ Verification successful

ID attribute removed from wallet balance container

The removal of the id="wallet-portfolio-balance" attribute aligns with the PR's broader goal of removing wallet portfolio integration.

Make sure this ID wasn't used in any tests or CSS selectors that might now break. Run the following to check for potential references:


🏁 Script executed:

#!/bin/bash
# Search for wallet-portfolio-balance in test files and CSS
echo "Checking for references to wallet-portfolio-balance ID in tests..."
rg "wallet-portfolio-balance" --type=test

echo "Checking for CSS selectors targeting this ID..."
rg "#wallet-portfolio-balance" --type=css

echo "Checking for querySelector/getElementById references..."
rg "getElementById\(['\"]wallet-portfolio-balance" --type=js --type=tsx
rg "querySelector\(['\"]#wallet-portfolio-balance" --type=js --type=tsx

Length of output: 691


🏁 Script executed:

#!/bin/bash
echo "Searching entire repo for references to wallet-portfolio-balance..."
rg "wallet-portfolio-balance" -n

echo "Searching for CSS ID selectors targeting wallet-portfolio-balance..."
rg "#wallet-portfolio-balance" -n

echo "Searching for getElementById references..."
rg "getElementById\(['\"]wallet-portfolio-balance" -n

echo "Searching for querySelector references..."
rg "querySelector\(['\"]#wallet-portfolio-balance" -n

Length of output: 664


No references to wallet-portfolio-balance ID – removal is safe

I’ve searched the entire repository (tests, CSS, JS/TSX code, querySelector/getElementById calls) and found zero occurrences of wallet-portfolio-balance. Removing this ID will not break any existing tests or styles.

src/apps/pillarx-app/components/TokenInfoHorizontal/TokenInfoHorizontal.tsx (1)

41-41:

Details

✅ Verification successful

Removed optional chaining when accessing blockchain data

The code now directly accesses blockchainListData.data instead of using optional chaining with blockchainListData?.result?.data. While the surrounding conditional check helps prevent errors, this change assumes a specific API response structure.

Run the following to verify this change is consistent across other components in the codebase:


🏁 Script executed:

#!/bin/bash
# Search for other instances of accessing blockchain data
echo "Looking for other components accessing blockchain list data..."
rg "blockchainListData\??\.result\??\.data" --type=tsx

echo "Checking the API response structure in the token API..."
ast-grep --pattern 'useGetBlockchainsListQuery' -A 10

Length of output: 7092


Direct data access is consistent and safe

Verified that ChainCard, TokenInfoHorizontal, and HorizontalToken all guard on isSuccess before accessing blockchainListData.data. RTK Query guarantees data is populated when isSuccess is true, so optional chaining here is redundant.

src/apps/pillarx-app/components/HorizontalToken/HorizontalToken.tsx (1)

47-47: Removed optional chaining when accessing blockchain data

Consistent with the changes in TokenInfoHorizontal, this file also directly accesses blockchainListData.data instead of using optional chaining. This indicates a structural change in how API responses are handled throughout the application.

This change is valid as long as the API response structure has been updated consistently across the codebase, which appears to be the case based on the surrounding context and similar changes in other components.

src/apps/token-atlas/components/TokensSlider/test/TokenSlider.test.tsx (3)

27-27: Test data structure updated to match API response format

The top-level key in the mock trending tokens data has been changed from result to data, aligning with the updated API response format.


33-33: Simplified blockchain naming in test data

Specific blockchain names have been replaced with generic placeholders (blockchain1 and blockchain2) to match the simplified token selection logic in the component.

Also applies to: 40-40


134-134: Updated expected chainId value for token selection

The expected chainId in the token selection test has been changed from a specific value to undefined to align with the updated token selection logic in the TokensSlider component, which no longer extracts chain IDs from token contracts.

src/apps/token-atlas/components/TokensSlider/TokensSlider.tsx (2)

51-52: Simplified token filtering logic

The filtering logic has been simplified to directly slice the first 20 trending tokens without applying any blockchain compatibility checks. This aligns with the broader simplification of token handling in the application.


97-97:

Details

❓ Verification inconclusive

Simplified token selection with default values

The token selection handler now uses default values (address: '' and chainId: undefined) instead of extracting these values from token contracts. This simplification removes the dependency on chain compatibility checks.

Please verify that other components consuming this selected token data can handle these default values properly. Run the following script to find components that might expect specific chainId values:

Also applies to: 99-99


🏁 Script executed:

#!/bin/bash
# Find components that might rely on selectedToken.chainId having a specific value
rg -A 2 -B 2 "selectedToken.chainId" --glob "*.{ts,tsx}" src/

Length of output: 1104


Ensure safe consumption of default token values

The token selection handler now emits default values (address: '', chainId: undefined). Please verify that every consumer of selectedToken can handle these fallbacks without errors or unintended behavior:

• In src/apps/token-atlas/index.tsx, we do

const nativeToken = getNativeAssetForChainId(selectedToken.chainId || 0);

– Confirm that passing 0 (when chainId is undefined) yields a sensible default/native token or is explicitly guarded.
• Also in index.tsx, we include

chainId: selectedToken.chainId

in analytics and payloads. Ensure downstream systems tolerate undefined (or adjust to a valid default).

If any consumer expects a non-nullable chainId or address, add appropriate guards or defaults to prevent runtime issues.

src/apps/token-atlas/components/ChainCard/ChainCard.tsx (1)

26-26:

Details

✅ Verification successful

Updated data structure access pattern

The blockchain list data is now accessed using .data instead of .result, aligning with the updated API response format. This change ensures consistency in how blockchain data is accessed throughout the application.

Check if there are any other components that might still be using the old .result structure:

Also applies to: 34-34


🏁 Script executed:

#!/bin/bash
# Find any remaining uses of the old .result structure
rg "\.result\." --glob "*.{ts,tsx}" src/

Length of output: 42


Consistent .data Access Verified

The code now uniformly uses .data instead of .result to access blockchain list data, matching the updated API response format. A project-wide search of all .ts/.tsx files found no remaining .result references, confirming this change is applied consistently.

src/apps/the-exchange/components/DropdownTokensList/test/DropdownTokensList.test.tsx (1)

1-1: LGTM: Import cleanup.

The removal of the waitFor import from @testing-library/react aligns with the removal of the asynchronous chain reset tests.

src/apps/the-exchange/components/CardsSwap/CardsSwap.tsx (3)

5-9: LGTM: Import cleanup.

The removal of setReceiveChain and setSwapChain imports aligns with the simplified token selection flow where chain resets during token list opening have been removed.


55-63: LGTM: Function simplification.

The handleOpenTokenList function has been appropriately simplified to remove chain reset actions, focusing only on resetting the search results and opening the correct dropdown.


113-115: LGTM: Improved conditional rendering.

The conditional rendering of the SwitchCardsButton has been simplified by removing the environment variable check, making the code more straightforward while maintaining the same functional behavior.

src/apps/token-atlas/components/TokenGraph/TokenGraph.tsx (5)

27-27: LGTM: Updated type imports.

The import now correctly includes TokenMarketHistory which reflects the new data structure being used in the component.


50-52: LGTM: Updated state selector type.

The type for tokenDataGraph has been appropriately updated to TokenMarketHistory | undefined to match the new data structure.


95-125: LGTM: Simplified data access pattern.

The code now directly accesses the price_history property from tokenDataGraph rather than a nested data structure, which simplifies the code and dependency tracking.


131-133: LGTM: Improved data validation.

The check for price history data has been simplified to directly verify the existence and length of the price_history array, making the code more readable.


147-155: LGTM: Direct data mapping.

The chart data preparation now directly maps from the flattened data structure, using timestamp and priceUSD properties from each data point in the price_history array.

src/apps/token-atlas/components/TokenGraphColumn/test/TokenGraphColumn.test.tsx (4)

21-22: LGTM: Updated imports.

The type import has been updated to include TokenMarketHistory instead of the previous type, aligning with the component changes.


25-30: LGTM: Simplified mock data structure.

The mock data has been updated to use the new TokenMarketHistory type with a streamlined price_history array, reflecting the actual data structure used by the component.


70-71: LGTM: Simplified test component rendering.

The test setup has been streamlined by removing unnecessary wrapper components, making the tests more focused on the component under test.

Also applies to: 81-82


87-87: LGTM: Updated assertion.

The assertion now correctly checks for the token price from mockTokenDataInfo.price instead of a previously hardcoded value, improving test accuracy.

src/apps/pillarx-app/components/TokensVerticalList/TokensVerticalList.tsx (1)

22-37: Code simplification looks good

The token mapping has been simplified to directly use the token's name and symbol for navigation, removing previous complexity around contract validation and blockchain compatibility checks.

src/apps/the-exchange/components/SelectToken/SelectToken.tsx (1)

32-32: UI margin adjustment matches functional changes

The removal of the mb-4 class is consistent with the removal of token balance and price display functionality mentioned in the summary.

src/apps/pillarx-app/components/TokensHorizontalTile/TokensHorizontalTile.tsx (1)

71-85: Navigation simplification is properly implemented

The simplified token mapping and navigation logic now uses a consistent URL pattern with just token name and symbol parameters, removing previous complexity around contract filtering and blockchain compatibility checks.

src/apps/the-exchange/components/TokenListItem/test/TokenListItem.test.tsx (4)

8-11: Test data structure updated correctly

The test now properly uses individual token properties instead of a single token object, consistent with component API changes.


14-27: Test fixture properly updated

The renderer test has been correctly updated to use the new prop structure.


29-44: Test assertions properly updated

Test assertions have been updated to focus on token name, symbol, chain name, and logo, removing previous assertions related to balance and price that are no longer relevant.


46-102: Remaining tests correctly updated

All remaining tests have been properly updated to use the new component prop structure while maintaining test coverage.

src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (2)

85-87: LGTM: Simplified chain ID filtering logic

The chain filtering logic has been simplified to only use the active chain ID based on whether the swap UI is open, which aligns with removing wallet portfolio integration.


103-104: Improved sequence for setting search token

Moving the setSearchToken dispatch after the null check is a good improvement, preventing unnecessary state updates when there's no search data.

src/apps/token-atlas/components/TokenGraph/test/TokenGraph.test.tsx (3)

19-20: LGTM: Updated type import to match new data model

The import change aligns with the broader refactoring of token market data handling in the app.


26-31: LGTM: Updated mock data structure

The mock data structure has been properly updated to use the new TokenMarketHistory type with a price_history array of timestamped price points, which matches the type definition in the relevant code snippets.


81-81: LGTM: Updated test case for no price history

The test case for handling empty price history has been correctly updated to use the new data structure.

src/apps/the-exchange/components/SwapReceiveCard/SwapReceiveCard.tsx (3)

8-8: LGTM: Removed unnecessary dispatch import

Removing useAppDispatch is consistent with the refactor to remove side effects and API-driven token selection logic from this component.


41-41: LGTM: Simplified container CSS class

Replacing min-h-[200px] and h-full with a fixed h-[200px] creates more predictable layout behavior.


59-66: LGTM: Simplified EnterAmount component usage

Removing the tokenBalance prop from EnterAmount aligns with the broader refactoring to fetch token balances asynchronously via hooks from @etherspot/transaction-kit instead of using static token properties.

src/apps/the-exchange/components/TokenListItem/TokenListItem.tsx (3)

9-16: LGTM: Refactored props structure

Breaking down the single token object into individual props (tokenName, tokenSymbol, chainName, tokenLogo) is a cleaner approach that makes the component more reusable and explicitly defines what properties it requires.


34-39: LGTM: Updated TokenLogo component props

The TokenLogo component props have been correctly updated to match the new property structure.


49-51: LGTM: Improved chain name display

Using chainNameDataCompatibility ensures consistent and user-friendly chain name displays across the application, handling special cases like "xdai" → "Gnosis".

src/apps/token-atlas/reducer/tokenAtlasSlice.ts (1)

10-12: Type alignment looks good

Switching to TokenMarketHistory cleans up the slice and matches the new API model. No further issues spotted in the touched lines.

Also applies to: 25-27, 85-90

src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx (1)

194-209: Good UX tweak – clear “Search tokens” placeholder & empty-state copy

The new placeholder and the “Start searching for tokens.” message make the UI clearer.

src/apps/the-exchange/reducer/theExchangeSlice.ts (3)

22-22: Type change: searchToken field now requires a string value

The type has been changed from optional to required, removing the possibility of undefined values. This change simplifies the code by eliminating null checks and aligns with the removal of wallet portfolio integration mentioned in the PR summary.


47-47: Initial state updated to match type requirements

Setting the initial state to an empty string instead of undefined ensures type consistency and provides a reasonable default value that can be safely used in string operations without null checks.


95-95: Action payload type narrowed from string | undefined to string

This change to the setSearchToken reducer aligns with the updated state type and ensures proper type safety throughout the Redux flow.

src/apps/the-exchange/components/EnterAmount/test/EnterAmount.test.tsx (8)

21-22: Added import for setUsdPriceReceiveToken reducer

The addition of this import is needed to properly set up the test state with both swap and receive token prices, which is consistent with how the component now handles price data.


25-26: Added AccountBalancesListenerRef import

This import supports the new account balances hook mock, which is necessary for testing the component after removing the tokenBalance prop in favor of dynamic balance retrieval.


33-52: Simplified mock token assets

The test tokens now omit the balance and price properties, which aligns with the component changes that now obtain this data dynamically from hooks rather than through props.


62-65: Added useEtherspotPrices mock

This mock properly simulates the price fetching functionality by providing a getPrice method that resolves with a consistent USD value of 1200, enabling predictable test outcomes.


73-76: Added useEtherspotBalances mock

The mock implementation provides the necessary methods for balance retrieval that the component will use, ensuring the tests can run without actual blockchain connections.


78-92: Added useAccountBalances hook mock

This comprehensive mock returns structured balance data with sample values that match the expected format in the component, including token addresses that correspond to the mock tokens used in tests.


108-109: Added dispatch calls to set USD prices in test setup

These dispatches initialize the Redux store with necessary price data that the component expects, ensuring the test environment properly reflects the component's dependencies.


149-149: Updated price expectation to match new mock value

The test now expects a USD price of 1200 instead of 0.1, correctly aligning with the mock implementation's return value. This ensures the test verifies that the component is properly using the price data from the hooks.

src/apps/token-atlas/components/TokenGraphColumn/TokenGraphColumn.tsx (4)

18-19: Updated API type imports

The imports now use TokenMarketHistory instead of MarketHistoryPairData, reflecting the API refactoring mentioned in the PR summary. This change ensures type consistency with the updated API responses.


47-47: Updated tokenDataGraph type

The state selector now correctly casts to the new TokenMarketHistory type, ensuring proper type checking when accessing data properties.


176-182: Simplified price display logic

The price display has been refactored to directly access tokenDataInfo?.price, removing unnecessary state management and conditional rendering. This simplification makes the code more maintainable.


219-222: Updated data path check for time filter styling

The conditional now checks for the existence of tokenDataGraph?.price_history instead of the previous data structure, correctly adapting to the new API response format.

src/apps/the-exchange/components/EnterAmount/EnterAmount.tsx (9)

1-5: Added imports for Etherspot hooks

The component now uses hooks from the @etherspot/transaction-kit library to dynamically retrieve wallet address, balance, and price data, supporting the removal of static props.


26-26: Added useAccountBalances hook import

This hook provides access to wallet balances across different chains, replacing the removed tokenBalance prop with dynamic balance retrieval.


34-34: Added processEth utility import

This utility function handles formatting of ETH values with proper decimal precision, which is essential for accurate balance display.


72-76: Added hooks for wallet data access

The component now obtains wallet address, price data, balances, and utility functions through hooks, enabling dynamic data fetching and eliminating the need for props passed from parent components.


84-98: Updated swap token price fetching logic

The component now asynchronously fetches the USD price for the swap token using the Etherspot API, with proper error handling that logs failures and defaults to zero for the price.


104-118: Updated receive token price fetching logic

Similar to the swap token price fetching, this code now uses the Etherspot API to dynamically retrieve the receive token's USD price when it changes.


162-192: Rewrote tokenBalanceLimit function

This function has been completely refactored to dynamically find the correct token balance from the wallet data, supporting both native tokens (like ETH) and contract tokens. The error message precision was also reduced from 6 to 4 decimal places for better readability.


204-204: Simplified the condition to trigger offer fetching

The condition for fetching the best swap offer now only depends on whether amountSwap has a value, which streamlines the logic and potentially improves user experience.


243-249: Updated balance error display

The error message display now directly uses the new tokenBalanceLimit function, maintaining the same user experience with dynamically fetched balance data.

src/apps/pillarx-app/components/PointsTile/PointsTile.tsx (1)

107-107: ID attribute removed from points display element.

The removal of the id="points-tile-points-number" attribute seems to be part of a broader pattern in this PR where several UI components have had id attributes removed or simplified. This change won't affect functionality but does reduce potentially unnecessary DOM identifiers.

src/components/BottomMenuModal/AccountModal.tsx (1)

326-326: Simplified ID attribute for token items.

The ID attribute for TokenItem has been changed from a dynamic value based on the token symbol to a static value "token-item-account-modal". This standardization aligns with other ID simplifications in this PR and makes DOM element identification more consistent.

package.json (1)

92-92:

Details

✅ Verification successful

Added react-toastify dependency for notification support.

The addition of the react-toastify package supports the new toast notification component in the EMCD swap application. This library is commonly used for displaying success/error messages and aligns with the new functionality being introduced.


🏁 Script executed:

#!/bin/bash
# Verify how the react-toastify package is being used in the codebase

# Look for imports of react-toastify
echo "Searching for react-toastify imports..."
rg -A 3 "from ['|\"]react-toastify" --type typescript --type tsx

# Look for ToastContainer components
echo "Searching for ToastContainer usage..."
rg "ToastContainer" --type typescript --type tsx

Length of output: 361


🏁 Script executed:

#!/bin/bash
# Refined search for react-toastify usage in TS/TSX files

echo "🔍 Searching for react-toastify imports in .ts and .tsx files..."
rg -n "from ['\"]react-toastify" -g "*.ts" -g "*.tsx"

echo "🔍 Searching for ToastContainer usage..."
rg -n "ToastContainer" -g "*.ts" -g "*.tsx"

echo "🔍 Searching for toast(...) calls..."
rg -n "toast\(" -g "*.ts" -g "*.tsx"

Length of output: 843


Approve react-toastify integration

Verified that the react-toastify dependency is correctly added to package.json and is in use by the new ToastNotification component:

  • Import and usage found in src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (ToastContainer, toast)

No further changes required.

src/components/Form/Select/index.tsx (1)

35-35: Good improvement in ID generation using unique identifiers

Changing from using title to ID for element identification is a good practice since IDs are designed to be unique, while titles might contain spaces, special characters, or be duplicated.

src/apps/emcd-swap/components/Loader/Loader.tsx (1)

1-21:

Details

✅ Verification successful

Fix missing CSS animation definition

The Loader component uses a CSS animation named 'fade', but it's not defined in this file. This could lead to the animation not working properly.

Add the missing CSS animation definition:

import React from 'react';

+// Define the fade animation
+const fadeAnimation = `
+  @keyframes fade {
+    0%, 100% { opacity: 1; }
+    50% { opacity: 0.3; }
+  }
+`;

const Loader: React.FC = () => {
  const dotStyle = (delay: string) => ({
    animation: 'fade 1.5s infinite',
    animationDelay: delay,
  });

  return (
    <div className="flex items-center justify-center">
+      <style>{fadeAnimation}</style>
      <div className="flex gap-2">
        <div className='w-2 h-2 rounded-full bg-brand' style={dotStyle('0s')}></div>
        <div className='w-2 h-2 rounded-full bg-brand' style={dotStyle('0.3s')}></div>
        <div className='w-2 h-2 rounded-full bg-brand' style={dotStyle('0.6s')}></div>
      </div>
    </div>
  );
};

export default Loader;

Alternatively, if your project has a global CSS file where animations are defined, ensure that the 'fade' animation is defined there.


🏁 Script executed:

#!/bin/bash
# Check if the fade animation is defined elsewhere in the codebase
grep -r "@keyframes fade" --include="*.css" --include="*.scss" --include="*.tsx" --include="*.ts" .

Length of output: 167


The ‘fade’ animation is already defined globally
The @keyframes fade rule lives in src/apps/emcd-swap/styles/tailwindDeposit.css, so you don’t need to add an inline <style> block in Loader.tsx. Ensure that your global styles (including tailwindDeposit.css) are imported at your app entry point so the animation is applied.

src/apps/emcd-swap/enums/status.enum.ts (1)

1-9: LGTM: Well-structured status enum

The enum is properly defined with explicit numeric values, which is good practice for values that may be used in API communication or persistence.

src/apps/emcd-swap/index.tsx (2)

23-31: LGTM: Clean view component mapping approach

The use of a Record type with VIEW_TYPE enum for mapping views to components is a clean and type-safe approach. This pattern makes it easy to add or modify views in the future.


33-45: LGTM: Well-structured main App component

The App component is well implemented with proper Redux integration for view selection. The conditional rendering ensures that only valid components are displayed, and the ToastNotification is always available regardless of the current view.

src/apps/emcd-swap/views/SuccessView.tsx (2)

1-19: Clear organization of imports

The imports are well-structured, grouped logically by functionality: React and Redux dependencies, slice imports, components/assets, helpers, and constants.


50-84: Overall component structure is well-designed

The component is well-structured with clear separation of concerns. It properly retrieves data from Redux state, formats it for display, and provides clear interaction paths for the user.

src/apps/emcd-swap/views/CancelledView.tsx (2)

1-13: Clean and organized imports

Imports are well-organized and grouped logically.


32-67: Well-structured component with good user flow

The component layout is clean and provides clear paths for users after a cancelled transaction: contacting support or starting a new exchange.

src/apps/emcd-swap/components/FAQ/FAQ.tsx (1)

5-8: Well-defined TypeScript interface

The FAQItemData interface is appropriately typed with clear property names.

src/apps/emcd-swap/views/ExchangeView.tsx (1)

109-113: 🛠️ Refactor suggestion

Use optional chaining for the swap button

Similar to the previous comment, use optional chaining to safely access object properties.

          <div>
-            <Button onClick={handleSwap} type={'main'} size={'xs'}>
+            <Button onClick={handleSwap} type={'main'} size={'xs'}>
              <SwapIcon />
            </Button>
          </div>

Likely an incorrect or invalid review comment.

🧰 Tools
🪛 Biome (1.9.4)

[error] 110-110: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/apps/emcd-swap/components/Select/Select.tsx (1)

44-49: 🛠️ Refactor suggestion

Potential event listener memory leak

Event listeners are attached directly to the document. If this component is mounted and unmounted frequently, there could be performance implications.

Add a dependency array to the useEffect to ensure it only runs once on mount:

  useEffect(() => {
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
-  }, []);
+  }, []); // Empty dependency array is correct here since handleClickOutside doesn't change

Additionally, consider using a memoized callback with useCallback to prevent unnecessary re-renders:

+ const handleClickOutside = useCallback((event: MouseEvent) => {
+   if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
+     setIsOpen(false);
+   }
+ }, []);

- const handleClickOutside = (event: MouseEvent) => {
-   if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
-     setIsOpen(false);
-   }
- };

Likely an incorrect or invalid review comment.

src/apps/emcd-swap/views/Confirm/components/forms/ConfirmDepositForm.tsx (1)

86-96:

Details

❌ Incorrect review comment

Verify network property usage.

Line 87 references formData.address_to while line 96 references formData.network_from. This seems inconsistent - should it be network_to if we're showing details about the destination?


🏁 Script executed:

#!/bin/bash
# Let's verify how these fields are typically used together in the codebase
rg -A 1 "address_to.*network_" --type=typescript

Length of output: 85


🏁 Script executed:

#!/bin/bash
# Search for usage of address_to, network_from, and network_to in TS/TSX files
rg -n "address_to" -t ts,tsx -A1 -B1
rg -n "network_from" -t ts,tsx -A1 -B1
rg -n "network_to" -t ts,tsx -A1 -B1

Length of output: 205


🏁 Script executed:

#!/bin/bash
# Search across the entire repository for relevant formData fields without file-type filters
rg -n "address_to" -A1 -B1
rg -n "network_from" -A1 -B1
rg -n "network_to" -A1 -B1

Length of output: 7978


No change needed: network_from is correct here.

In ConfirmDepositForm, formData.address_to is the deposit address on the source network, so showing formData.network_from is intentional. The network_to field is only used in ExchangeDepositForm for cross‐chain swaps.

Likely an incorrect or invalid review comment.

Comment on lines 146 to 166
const id = query.get('id');
const asset = query.get('asset');
const chain = query.get('blockchain');

// API call to search tokens and assets
const { data: searchData } = useGetSearchTokensQuery(
{
searchInput: asset || '',
filterBlockchains: chain || undefined,
},
{ skip: !asset && !chain }
);
const symbol = query.get('symbol');

// This useEffect is to check if some url query params have been specified
useEffect(() => {
if (!searchData) return;

const result = convertAPIResponseToTokens(
searchData?.result?.data as TokenAssetResponse[],
asset || ''
);

// if it is considered a native token, Token Atlas would have handled the request
// with showing the asset as a symbol rather than an contract address
const nativeOrGasToken = result.filter(
(token) => token.blockchain === chain && token.symbol === asset
);

if (nativeOrGasToken.length > 0) {
const clickedNativeToken = nativeOrGasToken[0];
if (asset || symbol) {
dispatch(
setSelectedToken({
id: clickedNativeToken?.id,
symbol: clickedNativeToken?.symbol,
address: clickedNativeToken?.contract,
decimals: clickedNativeToken?.decimals,
chainId: chainNameToChainIdTokensData(clickedNativeToken?.blockchain),
name: clickedNativeToken?.name,
icon: clickedNativeToken?.logo,
})
);
} else {
const clickedToken = result[0];
dispatch(
setSelectedToken({
id: clickedToken?.id,
symbol: clickedToken?.symbol,
address: clickedToken?.contract,
decimals: clickedToken?.decimals,
chainId: chainNameToChainIdTokensData(clickedToken?.blockchain),
name: clickedToken?.name,
icon: clickedToken?.logo,
id: Number(id),
symbol: symbol || '',
address: '',
decimals: undefined,
chainId: undefined,
name: asset || '',
icon: '',
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [asset, searchData]);

// This useEffect is to make sure that the default token is PLR token
useEffect(() => {
if (selectedToken === defaultToken) {
dispatch(setSelectedToken(defaultToken));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedToken]);
}, [asset, symbol]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

URL param id alone no longer selects a token

The guard if (asset || symbol) ignores the case where only id is provided (e.g. /token-atlas?id=102502677).
As a result the user lands on the default token instead of the requested one.

-if (asset || symbol) {
+if (id || asset || symbol) {

Also guard against id === null before Number(id) to avoid NaN sneaking into state.

📝 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 id = query.get('id');
const asset = query.get('asset');
const chain = query.get('blockchain');
// API call to search tokens and assets
const { data: searchData } = useGetSearchTokensQuery(
{
searchInput: asset || '',
filterBlockchains: chain || undefined,
},
{ skip: !asset && !chain }
);
const symbol = query.get('symbol');
// This useEffect is to check if some url query params have been specified
useEffect(() => {
if (!searchData) return;
const result = convertAPIResponseToTokens(
searchData?.result?.data as TokenAssetResponse[],
asset || ''
);
// if it is considered a native token, Token Atlas would have handled the request
// with showing the asset as a symbol rather than an contract address
const nativeOrGasToken = result.filter(
(token) => token.blockchain === chain && token.symbol === asset
);
if (nativeOrGasToken.length > 0) {
const clickedNativeToken = nativeOrGasToken[0];
if (asset || symbol) {
dispatch(
setSelectedToken({
id: clickedNativeToken?.id,
symbol: clickedNativeToken?.symbol,
address: clickedNativeToken?.contract,
decimals: clickedNativeToken?.decimals,
chainId: chainNameToChainIdTokensData(clickedNativeToken?.blockchain),
name: clickedNativeToken?.name,
icon: clickedNativeToken?.logo,
})
);
} else {
const clickedToken = result[0];
dispatch(
setSelectedToken({
id: clickedToken?.id,
symbol: clickedToken?.symbol,
address: clickedToken?.contract,
decimals: clickedToken?.decimals,
chainId: chainNameToChainIdTokensData(clickedToken?.blockchain),
name: clickedToken?.name,
icon: clickedToken?.logo,
id: Number(id),
symbol: symbol || '',
address: '',
decimals: undefined,
chainId: undefined,
name: asset || '',
icon: '',
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [asset, searchData]);
// This useEffect is to make sure that the default token is PLR token
useEffect(() => {
if (selectedToken === defaultToken) {
dispatch(setSelectedToken(defaultToken));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedToken]);
}, [asset, symbol]);
const id = query.get('id');
const asset = query.get('asset');
const symbol = query.get('symbol');
// This useEffect is to check if some url query params have been specified
useEffect(() => {
- if (asset || symbol) {
+ if (id || asset || symbol) {
dispatch(
setSelectedToken({
id: Number(id),
symbol: symbol || '',
address: '',
decimals: undefined,
chainId: undefined,
name: asset || '',
icon: '',
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [asset, symbol]);

Comment on lines 22 to 70
export const tokenInfoApi = createApi({
reducerPath: 'tokenInfoApi',
baseQuery: fetchBaseQuery({
baseUrl: isTestnet
? 'https://hifidata-nubpgwxpiq-uc.a.run.app'
: 'https://hifidata-7eu4izffpa-uc.a.run.app',
headers: {
'Content-Type': 'application/json',
},
? 'https://token-nubpgwxpiq-uc.a.run.app'
: 'https://token-7eu4izffpa-uc.a.run.app',
}),
{ maxRetries: 5 }
);

export const tokenMarketHistoryPair = createApi({
reducerPath: 'tokenMarketHistoryPair',
baseQuery: fetchBaseMobula,
endpoints: (builder) => ({
getTokenMarketHistoryPair: builder.query<
MarketHistoryPairData,
{
asset?: string;
symbol?: string;
blockchain: string;
period: string;
from: number;
to?: number;
amount?: number;
}
getTokenInfo: builder.query<
TokenAtlasInfoApiResponse,
{ id?: number; asset?: string; symbol: string }
>({
query: ({ asset, symbol, blockchain, period, from, to, amount }) => {
return {
url: `?${chainIdsQuery}&testnets=${String(isTestnet)}`,
method: 'POST',
body: {
path: 'market/history/pair',
params: {
asset,
symbol,
blockchain,
period,
from: from * 1000,
to: to ? to * 1000 : undefined,
amount: amount || undefined,
},
},
};
query: ({ id, asset, symbol }) => {
return `?${id ? `id=${id}` : ''}${asset ? `&asset=${asset}` : ''}&symbol=${symbol}&${chainIdsQuery}&testnets=${String(isTestnet)}`;
},
}),
}),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding error handling and retry logic.

The implementation removed the retry wrapper around fetchBaseQuery. In production environments, API requests often need retry logic for transient failures.

import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react';

// Create a retryable base query
const baseQueryWithRetry = retry(fetchBaseQuery({
  baseUrl: isTestnet
    ? 'https://token-nubpgwxpiq-uc.a.run.app'
    : 'https://token-7eu4izffpa-uc.a.run.app',
}), {
  maxRetries: 3,
});

export const tokenInfoApi = createApi({
  reducerPath: 'tokenInfoApi',
  baseQuery: baseQueryWithRetry,
  // rest of the code remains the same
});

Comment on lines 34 to 67
query: ({ id, asset, symbol }) => {
return `?${id ? `id=${id}` : ''}${asset ? `&asset=${asset}` : ''}&symbol=${symbol}&${chainIdsQuery}&testnets=${String(isTestnet)}`;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix URL parameter construction to avoid malformed URLs.

The current implementation might generate malformed URLs if certain parameters are undefined or falsy. Also, parameter values aren't properly encoded.

Improve the URL construction with proper encoding and parameter handling:

-      query: ({ id, asset, symbol }) => {
-        return `?${id ? `id=${id}` : ''}${asset ? `&asset=${asset}` : ''}&symbol=${symbol}&${chainIdsQuery}&testnets=${String(isTestnet)}`;
+      query: ({ id, asset, symbol }) => {
+        const params = new URLSearchParams();
+        if (id !== undefined) params.append('id', String(id));
+        if (asset) params.append('asset', asset);
+        params.append('symbol', symbol);
+        chainIds.forEach(id => params.append('chainIds', String(id)));
+        params.append('testnets', String(isTestnet));
+        return `?${params.toString()}`;
       },
📝 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
query: ({ id, asset, symbol }) => {
return `?${id ? `id=${id}` : ''}${asset ? `&asset=${asset}` : ''}&symbol=${symbol}&${chainIdsQuery}&testnets=${String(isTestnet)}`;
},
query: ({ id, asset, symbol }) => {
const params = new URLSearchParams();
if (id !== undefined) params.append('id', String(id));
if (asset) params.append('asset', asset);
params.append('symbol', symbol);
chainIds.forEach(id => params.append('chainIds', String(id)));
params.append('testnets', String(isTestnet));
return `?${params.toString()}`;
},

Comment on lines 59 to 99
query: ({ id, asset, symbol, from, to }) => {
const toParam = to !== undefined ? `&to=${from * 1000}` : '';
return `?${id ? `id=${id}` : ''}${asset ? `&asset=${asset}` : ''}&symbol=${symbol}&from=${from * 1000}${toParam}&${chainIdsQuery}&testnets=${String(isTestnet)}`;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix timestamp conversion bug and improve URL parameter handling.

There are two issues in this code:

  1. The toParam variable uses from instead of to when constructing the query parameter
  2. Similar URL construction issues as in the previous endpoint

Fix the timestamp conversion and improve URL parameter handling:

-      query: ({ id, asset, symbol, from, to }) => {
-        const toParam = to !== undefined ? `&to=${from * 1000}` : '';
-        return `?${id ? `id=${id}` : ''}${asset ? `&asset=${asset}` : ''}&symbol=${symbol}&from=${from * 1000}${toParam}&${chainIdsQuery}&testnets=${String(isTestnet)}`;
+      query: ({ id, asset, symbol, from, to }) => {
+        const params = new URLSearchParams();
+        if (id !== undefined) params.append('id', String(id));
+        if (asset) params.append('asset', asset);
+        params.append('symbol', symbol);
+        params.append('from', String(from * 1000));
+        if (to !== undefined) params.append('to', String(to * 1000));
+        chainIds.forEach(id => params.append('chainIds', String(id)));
+        params.append('testnets', String(isTestnet));
+        return `?${params.toString()}`;
       },
📝 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
query: ({ id, asset, symbol, from, to }) => {
const toParam = to !== undefined ? `&to=${from * 1000}` : '';
return `?${id ? `id=${id}` : ''}${asset ? `&asset=${asset}` : ''}&symbol=${symbol}&from=${from * 1000}${toParam}&${chainIdsQuery}&testnets=${String(isTestnet)}`;
},
query: ({ id, asset, symbol, from, to }) => {
const params = new URLSearchParams();
if (id !== undefined) params.append('id', String(id));
if (asset) params.append('asset', asset);
params.append('symbol', symbol);
params.append('from', String(from * 1000));
if (to !== undefined) params.append('to', String(to * 1000));
chainIds.forEach(id => params.append('chainIds', String(id)));
params.append('testnets', String(isTestnet));
return `?${params.toString()}`;
},

Comment on lines 35 to 36
backView: VIEW_TYPE.RECIPIENT
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix circular back navigation references.

Several views have themselves as their backView (RECIPIENT, CONFIRM, ERROR). This could create navigation loops when users try to go back.

For example, for the RECIPIENT view:

  RECIPIENT: {
    id: VIEW_TYPE.RECIPIENT,
    name: 'Recipient',
    view: 'recipientView',
-    backView: VIEW_TYPE.RECIPIENT
+    backView: VIEW_TYPE.EXCHANGE
  },

Similar changes should be applied to CONFIRM and ERROR views to ensure logical navigation flow.

Also applies to: 41-42, 59-60

Copy link
Contributor

@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: 3

♻️ Duplicate comments (2)
src/apps/emcd-swap/views/ExchangeView.tsx (2)

101-105: Use optional chaining for safer property access

When accessing nested properties from potentially null/undefined objects, use optional chaining to prevent potential runtime errors.

            <div className="text-sm text-color-3">
-              1 {currentCoinFrom && currentCoinFrom.title}{' '}
+              1 {currentCoinFrom?.title}{' '}
              <span className={'mb-1'}>≈</span>{' '}
              <span className={'text-color-1'}>
-                {rate} {currentCoinTo && currentCoinTo.title}
+                {rate} {currentCoinTo?.title}
              </span>
            </div>
🧰 Tools
🪛 Biome (1.9.4)

[error] 103-104: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


161-164: Update PDF document path to ensure proper access

The relative path ../documents/terms.pdf may not work correctly in production builds. Based on past review comments, a file named CryptoTerms.pdf was found in a different location.

-          <a href='../documents/terms.pdf' download='Crypto Swap (exchange) Terms and Conditions.pdf'
+          <a href='/documents/terms.pdf' download='Crypto Swap (exchange) Terms and Conditions.pdf'
             target='_blank' className='text-brand text-xs cursor-pointer'>
            правилами использования сервиса
          </a>

Consider either:

  1. Moving the PDF to your public folder and using an absolute path (as shown above)
  2. Importing the PDF file directly so your bundler can handle it correctly
🧹 Nitpick comments (9)
src/apps/emcd-swap/manifest.json (1)

4-4: Initialize translation scaffolding.

The translations object is currently empty. If you plan to support multiple locales, consider seeding default entries, e.g.:

"translations": {
  "en": {
    "title": "EMCD Swap",
    "description": "Cryptocurrency swap application allowing users to exchange digital assets."
  }
}
src/apps/emcd-swap/views/ExchangeView.tsx (3)

167-172: Extract conditional button styling to improve readability

The button has complex conditional styling that would be easier to maintain if extracted to a computed variable or function.

+        const isButtonDisabled = !amountTo.value || !amountFrom.value || amountTo.error || amountFrom.error || isFetchingEstimate;
+        const buttonClassName = `w-full min-h-7 text-color-1 text-sm border py-2 rounded-sm text-center border-bg-35 mt-10 transition-all ${isButtonDisabled ? 'bg-transparent' : 'bg-brand'}`;
+
         <button
-          className={`w-full min-h-7 text-color-1 text-sm border py-2 rounded-sm text-center border-bg-35 mt-10 transition-all ${!amountTo.value || !amountFrom.value || amountTo.error || amountFrom.error || isFetchingEstimate ? 'bg-transparent' : 'bg-brand'}`}
+          className={buttonClassName}
           onClick={submitForm}
+          disabled={isButtonDisabled}
         >
           Начать
         </button>

39-46: Add aria-label to help button for improved accessibility

For better accessibility, add an aria-label to the help button to clarify its purpose for screen readers.

        <Header title={'Обменивай легко и выгодно!'} />

-        <Button type={'shade'} onClick={handleButtonClick}>
+        <Button type={'shade'} onClick={handleButtonClick} aria-label="Open help information">
          Как это работает?
        </Button>

88-98: Add accessible label to info tooltip

The InfoIcon tooltip lacks an accessible label for screen readers. Add aria attributes to improve accessibility.

            <Tippy
              content={<span className={'text-xs text-color-3'}>Сейчас курс примерный <br/> Финальный курс на шаге обмена</span>}
              placement="bottom"
              arrow={true}
              delay={[0, 100]} // задержка на появление и скрытие
              interactive={true}
            >
-              <div className='cursor-pointer'>
+              <div className='cursor-pointer' role="button" aria-label="Exchange rate information">
                <InfoIcon />
              </div>
            </Tippy>
src/apps/emcd-swap/components/Select/Select.tsx (5)

32-36: Use optional chaining for the onChange callback

Use optional chaining for more concise code when calling the optional onChange callback.

  const handleOptionClick = (option: Option) => {
    setSelected(option);
-    onChange && onChange(option);
+    onChange?.(option);
    setIsOpen(false);
  };

38-49: Use useCallback for event handlers to prevent unnecessary re-renders

The handleClickOutside function is recreated on each render. Memoize it with useCallback to optimize performance.

+  import { useState, useRef, useEffect, useCallback } from "react";

-  const handleClickOutside = (event: MouseEvent) => {
+  const handleClickOutside = useCallback((event: MouseEvent) => {
    if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
      setIsOpen(false);
    }
-  };
+  }, []);

  useEffect(() => {
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
-  }, []);
+  }, [handleClickOutside]);

This follows the pattern found in other hooks like useComponentVisible in your codebase.


52-52: Make dropdown width configurable through props

The dropdown width is hardcoded to "w-36" (9rem), which might not be flexible enough for different use cases. Consider making it configurable through props.

interface CustomSelectProps {
  options: Option[];
  currentValue: Record<string, any> | null;
  placeholder: string;
  itemValue?: string;
  itemText?: string;
  withIcon?: boolean;
  itemIcon?: string;
  onChange?: (value: any) => void;
+ width?: string; // Custom width class
}

// In the component definition
const CustomSelect: React.FC<CustomSelectProps> = ({ 
  options, 
  placeholder, 
  currentValue, 
  itemValue = "id", 
  itemText = "name", 
  onChange, 
  withIcon, 
  itemIcon,
+ width = "w-36" 
}) => {
  // ...

  return (
-    <div ref={dropdownRef} className="relative w-36">
+    <div ref={dropdownRef} className={`relative ${width}`}>

24-28: Add null check when syncing with external state

The effect that syncs the component with external state doesn't handle the case when currentValue becomes null after previously being set. Add handling for this case.

  useEffect(() => {
-    if (currentValue) {
-      setSelected(currentValue);
-    }
+    setSelected(currentValue);
  }, [currentValue]);

This will ensure the component correctly reflects the external state even when it's reset to null.


6-17: Consider using more specific types for better type safety

The Option type is currently defined as Record<string, any>, which is very permissive. Consider using a more specific type to improve type safety.

-export type Option = Record<string, any>;
+export type Option = {
+  [key: string]: unknown;
+  id?: string | number;
+  name?: string;
+  title?: string;
+  icon_url?: string;
+};

interface CustomSelectProps {
-  options: Option[];
-  currentValue: Record<string, any> | null;
+  options: Option[];
+  currentValue: Option | null;
  placeholder: string;
  itemValue?: string;
  itemText?: string; // Поле для отображения текста (по умолчанию "name")
  withIcon?: boolean;
  itemIcon?: string;
-  onChange?: (value: any) => void; // Callback при выборе
+  onChange?: (value: Option) => void; // Callback при выборе
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7b6757c and c7ada18.

⛔ Files ignored due to path filters (5)
  • public/emcd-swap/documents/terms.pdf is excluded by !**/*.pdf
  • src/apps/emcd-swap/assets/cancelled.png is excluded by !**/*.png
  • src/apps/emcd-swap/assets/error.png is excluded by !**/*.png
  • src/apps/emcd-swap/assets/success.png is excluded by !**/*.png
  • src/apps/emcd-swap/icon.png is excluded by !**/*.png
📒 Files selected for processing (53)
  • src/apps/emcd-swap/api/coinsApi.tsx (1 hunks)
  • src/apps/emcd-swap/components/Button/Button.tsx (1 hunks)
  • src/apps/emcd-swap/components/Card/Card.tsx (1 hunks)
  • src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (1 hunks)
  • src/apps/emcd-swap/components/FAQ/FAQ.tsx (1 hunks)
  • src/apps/emcd-swap/components/FAQ/components/FAQItem.tsx (1 hunks)
  • src/apps/emcd-swap/components/FormInput/FormInput.tsx (1 hunks)
  • src/apps/emcd-swap/components/Header/Header.tsx (1 hunks)
  • src/apps/emcd-swap/components/Loader/Loader.tsx (1 hunks)
  • src/apps/emcd-swap/components/Modals/SupportModal.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/Select.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectActivator.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectOption.tsx (1 hunks)
  • src/apps/emcd-swap/components/Step/Step.tsx (1 hunks)
  • src/apps/emcd-swap/components/SwapTimer/SwapTimer.tsx (1 hunks)
  • src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/ArrowRightIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/CloseIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/ConfirmIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/CopyIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/InfoIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/LogoIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/SupportIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/SwapIcon.tsx (1 hunks)
  • src/apps/emcd-swap/constants/views.ts (1 hunks)
  • src/apps/emcd-swap/enums/status.enum.ts (1 hunks)
  • src/apps/emcd-swap/helpers/copy.helper.ts (1 hunks)
  • src/apps/emcd-swap/helpers/email-validator.helper.ts (1 hunks)
  • src/apps/emcd-swap/helpers/input-validator.helper.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useConfirmLogic.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useExchangeLogic.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useRecipientLogic.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useValidatedInput.ts (1 hunks)
  • src/apps/emcd-swap/index.tsx (1 hunks)
  • src/apps/emcd-swap/layouts/MainLayout.tsx (1 hunks)
  • src/apps/emcd-swap/manifest.json (1 hunks)
  • src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1 hunks)
  • src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (1 hunks)
  • src/apps/emcd-swap/styles/tailwindDeposit.css (1 hunks)
  • src/apps/emcd-swap/tailwind.deposit.config.js (1 hunks)
  • src/apps/emcd-swap/views/CancelledView.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/ConfirmView.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/ConfirmDepositForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/ExchangeDepositForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/WaitingDepositForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/ErrorView.tsx (1 hunks)
  • src/apps/emcd-swap/views/ExchangeView.tsx (1 hunks)
  • src/apps/emcd-swap/views/FaqView.tsx (1 hunks)
  • src/apps/emcd-swap/views/RecipientView.tsx (1 hunks)
  • src/apps/emcd-swap/views/SuccessView.tsx (1 hunks)
  • src/store.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/store.ts
🚧 Files skipped from review as they are similar to previous changes (49)
  • src/apps/emcd-swap/helpers/email-validator.helper.ts
  • src/apps/emcd-swap/components/icons/CopyIcon.tsx
  • src/apps/emcd-swap/components/icons/ConfirmIcon.tsx
  • src/apps/emcd-swap/components/icons/ArrowRightIcon.tsx
  • src/apps/emcd-swap/components/icons/SwapIcon.tsx
  • src/apps/emcd-swap/components/icons/LogoIcon.tsx
  • src/apps/emcd-swap/components/Loader/Loader.tsx
  • src/apps/emcd-swap/styles/tailwindDeposit.css
  • src/apps/emcd-swap/hooks/useConfirmLogic.ts
  • src/apps/emcd-swap/layouts/MainLayout.tsx
  • src/apps/emcd-swap/components/icons/InfoIcon.tsx
  • src/apps/emcd-swap/helpers/copy.helper.ts
  • src/apps/emcd-swap/hooks/useValidatedInput.ts
  • src/apps/emcd-swap/components/icons/CloseIcon.tsx
  • src/apps/emcd-swap/index.tsx
  • src/apps/emcd-swap/views/ErrorView.tsx
  • src/apps/emcd-swap/components/SwapTimer/SwapTimer.tsx
  • src/apps/emcd-swap/views/CancelledView.tsx
  • src/apps/emcd-swap/components/Card/Card.tsx
  • src/apps/emcd-swap/hooks/useExchangeLogic.ts
  • src/apps/emcd-swap/views/FaqView.tsx
  • src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx
  • src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx
  • src/apps/emcd-swap/components/Select/components/SelectOption.tsx
  • src/apps/emcd-swap/components/Step/Step.tsx
  • src/apps/emcd-swap/views/Confirm/ConfirmView.tsx
  • src/apps/emcd-swap/views/SuccessView.tsx
  • src/apps/emcd-swap/views/Confirm/components/forms/ExchangeDepositForm.tsx
  • src/apps/emcd-swap/components/icons/SupportIcon.tsx
  • src/apps/emcd-swap/components/FAQ/components/FAQItem.tsx
  • src/apps/emcd-swap/enums/status.enum.ts
  • src/apps/emcd-swap/components/FAQ/FAQ.tsx
  • src/apps/emcd-swap/components/Header/Header.tsx
  • src/apps/emcd-swap/components/Modals/SupportModal.tsx
  • src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx
  • src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts
  • src/apps/emcd-swap/components/FormInput/FormInput.tsx
  • src/apps/emcd-swap/helpers/input-validator.helper.ts
  • src/apps/emcd-swap/tailwind.deposit.config.js
  • src/apps/emcd-swap/views/RecipientView.tsx
  • src/apps/emcd-swap/views/Confirm/components/forms/ConfirmDepositForm.tsx
  • src/apps/emcd-swap/views/Confirm/components/forms/WaitingDepositForm.tsx
  • src/apps/emcd-swap/api/coinsApi.tsx
  • src/apps/emcd-swap/hooks/useRecipientLogic.ts
  • src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx
  • src/apps/emcd-swap/components/Button/Button.tsx
  • src/apps/emcd-swap/components/Select/components/SelectActivator.tsx
  • src/apps/emcd-swap/reducer/emcdSwapSlice.ts
  • src/apps/emcd-swap/constants/views.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/apps/emcd-swap/components/Select/Select.tsx (1)
src/apps/simpleswap/hooks/useComponentVisible.js (1)
  • handleClickOutside (7-14)
🪛 Biome (1.9.4)
src/apps/emcd-swap/components/Select/Select.tsx

[error] 35-38: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/apps/emcd-swap/views/ExchangeView.tsx

[error] 103-104: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 110-110: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (1)
src/apps/emcd-swap/manifest.json (1)

2-3: Manifest metadata updated correctly.

The title and description now accurately reflect the EMCD Swap application purpose. Great to see placeholders replaced.

Comment on lines +14 to +38
const ExchangeView = () => {
const {
coins,
currentCoinFrom,
currentCoinTo,
currentNetworkFrom,
currentNetworkTo,
networksFrom,
networksTo,
amountFrom,
amountTo,
rate,
isFetching,
isFetchingEstimate,
handleChangeAmountFrom,
handleChangeAmountTo,
changeCoinFrom,
changeCoinTo,
setCurrentNetworkFrom,
setCurrentNetworkTo,
handleButtonClick,
submitForm,
handleSwap,
} = useExchangeLogic()

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider adding error handling for API request failures

The component is using multiple fetch states (isFetching, isFetchingEstimate) but doesn't appear to handle API error states. Consider adding error handling to show appropriate messages to users when API requests fail.


🏁 Script executed:

#!/bin/bash
# Check if there's error handling in useExchangeLogic hook
rg -A 5 -B 5 "useExchangeLogic" --type=typescript

Length of output: 84


To locate the hook’s implementation and check for any error handling, let’s search for its declaration:


🏁 Script executed:

#!/bin/bash
# Locate the definition of useExchangeLogic
rg -n "export (const|function) useExchangeLogic"

Length of output: 138


Let’s inspect the hook’s implementation and look specifically for API calls and any error handling:


🏁 Script executed:

#!/bin/bash
# Display the first 200 lines of useExchangeLogic to inspect API calls and error handling
sed -n '1,200p' src/apps/emcd-swap/hooks/useExchangeLogic.ts

# Search for catch blocks in the hook
rg -n "catch" src/apps/emcd-swap/hooks/useExchangeLogic.ts

# Search for any error state variables or setError calls
rg -n "error" src/apps/emcd-swap/hooks/useExchangeLogic.ts

Length of output: 7311


Add API error handling to useExchangeLogic and ExchangeView

The current hook only tracks data and isFetching from your RTK-Query calls but doesn’t handle failures. Let’s enhance both the hook and the view:

• In src/apps/emcd-swap/hooks/useExchangeLogic.ts
– Destructure and expose RTK-Query’s error states:
```ts
const {
data,
isFetching,
isError: isCoinsError,
error: coinsError,
} = useGetSwapCoinsQuery();

const [
  triggerEstimate,
  {
    data: dataEstimate,
    isFetching: isFetchingEstimate,
    isError: isEstimateError,
    error: estimateError,
  },
] = useLazyGetEstimateQuery();
```

– Return isCoinsError, coinsError, isEstimateError, and estimateError alongside your other hook values.

• In src/apps/emcd-swap/views/ExchangeView.tsx
– Destructure the new error props from useExchangeLogic().
– Render user-friendly error messages or banners when either query fails. For example:
tsx {isCoinsError && ( <ErrorBanner message={coinsError?.message || 'Unable to load coins'} /> )} {isEstimateError && ( <ErrorBanner message={estimateError?.message || 'Failed to fetch estimate'} /> )}
– Optionally disable inputs or show a retry button until successful.

This ensures that API failures surface clearly to users and gives you a hook to implement retry logic or fallbacks.

🤖 Prompt for AI Agents
In src/apps/emcd-swap/hooks/useExchangeLogic.ts around the API query calls,
update the destructuring to include RTK-Query error states such as isError and
error for both useGetSwapCoinsQuery and useLazyGetEstimateQuery, then return
these error states from the hook. In src/apps/emcd-swap/views/ExchangeView.tsx
lines 14 to 38, destructure the new error states from useExchangeLogic and add
conditional rendering to display user-friendly error messages or banners when
isCoinsError or isEstimateError is true, optionally disabling inputs or adding
retry options to improve user experience during API failures.

Copy link
Contributor

@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 (2)
src/apps/emcd-swap/components/Select/components/SelectActivator.tsx (2)

16-20: 🛠️ Refactor suggestion

Add accessibility attributes to dropdown button

The button element should include ARIA attributes for better accessibility. Add aria-expanded to indicate the dropdown state and aria-haspopup to indicate it opens a menu.

    <button
      onClick={toggleDropdown}
+     aria-expanded={isOpen}
+     aria-haspopup="listbox"
      className="w-full outline-none rounded-sm bg-bg-8 border border-color-7 hover:border-brand px-3 py-2 text-left flex items-center justify-between"
      { ...buttonComponents }
    >

21-28: 🛠️ Refactor suggestion

Improve image alt text and null checks

The current implementation can result in empty alt text when option or itemText is null. Also, option[itemIcon] might not be a valid URL. Improve the image rendering with proper fallbacks and meaningful alt text.

      <div className={'flex items-center gap-x-2'}>
        {withIcon && (
          <div className={'w-4 h-4'}>
-            <img src={option && itemIcon ? option[itemIcon] : ''} alt={option && itemText ? option[itemText] : ''} />
+            {option && itemIcon && option[itemIcon] && (
+              <img 
+                src={option[itemIcon]} 
+                alt={option && itemText && option[itemText] ? `${option[itemText]} icon` : 'Option icon'} 
+              />
+            )}
          </div>
        )}
-        <span className='text-sm text-color-1'>{ option && itemText  ? option[itemText] : placeholder }</span>
+        <span className='text-sm text-color-1'>{ option && itemText && option[itemText] ? option[itemText] : placeholder }</span>
      </div>
🧹 Nitpick comments (6)
src/apps/emcd-swap/components/Select/components/SelectActivator.tsx (1)

14-14: Add a space after the colon in the component declaration.

The TypeScript syntax convention is to include a space after the colon when specifying types.

-const SelectActivator:React.FC<SelectActivatorProps> = ({ isOpen, toggleDropdown, withIcon, option, itemIcon, itemText, placeholder, ...buttonComponents }) => {
+const SelectActivator: React.FC<SelectActivatorProps> = ({ isOpen, toggleDropdown, withIcon, option, itemIcon, itemText, placeholder, ...buttonComponents }) => {
src/apps/emcd-swap/components/Select/Select.tsx (5)

13-13: Translate non-English comment to English for consistency.

Keep all comments in a consistent language (English) for better maintainability across teams.

-  itemText?: string; // Поле для отображения текста (по умолчанию "name")
+  itemText?: string; // Field for displaying text (default is "name")

19-28: Fix excessive and inconsistent indentation.

The parameter list has excessive indentation that doesn't match the rest of the file style. Adjust to match the standard indentation level.

const CustomSelect: React.FC<CustomSelectProps> = ({
-                                                     options,
-                                                     placeholder,
-                                                     currentValue,
-                                                     itemValue = "id",
-                                                     itemText = "name",
-                                                     onChange,
-                                                     withIcon,
-                                                     itemIcon,
-                                                   }) => {
+  options,
+  placeholder,
+  currentValue,
+  itemValue = "id",
+  itemText = "name",
+  onChange,
+  withIcon,
+  itemIcon,
}) => {

46-50: Use optional chaining for the onChange callback.

Replace the conditional check with optional chaining for cleaner code.

  const handleOptionClick = (option: Option) => {
    setSelected(option);
-    onChange && onChange(option);
+    onChange?.(option);
    setIsOpen(false);
  };

87-87: Translate non-English comment to English for consistency.

Keep all comments in English for better maintainability.

-      tabIndex={0} // чтобы фокус был на div
+      tabIndex={0} // to allow div to receive focus

6-6: Consider defining a more specific Option type.

Using Record<string, any> reduces type safety. Consider creating a more specific interface with the expected properties.

-export type Option = Record<string, any>;
+export interface Option {
+  [key: string]: any;
+  id?: string | number;
+  name?: string;
+  // Add other common properties you expect in options
+}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c7ada18 and 76f20fd.

📒 Files selected for processing (10)
  • config-overrides.js (1 hunks)
  • src/apps/emcd-swap/api/coinsApi.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/Select.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectActivator.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectOption.tsx (1 hunks)
  • src/apps/emcd-swap/hooks/useExchangeLogic.ts (1 hunks)
  • src/apps/emcd-swap/manifest.json (1 hunks)
  • src/apps/emcd-swap/tailwind.deposit.config.js (1 hunks)
  • src/apps/emcd-swap/views/ExchangeView.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • config-overrides.js
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/apps/emcd-swap/manifest.json
  • src/apps/emcd-swap/components/Select/components/SelectOption.tsx
  • src/apps/emcd-swap/tailwind.deposit.config.js
  • src/apps/emcd-swap/views/ExchangeView.tsx
  • src/apps/emcd-swap/hooks/useExchangeLogic.ts
  • src/apps/emcd-swap/api/coinsApi.tsx
  • src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
src/apps/emcd-swap/components/Select/Select.tsx

[error] 49-52: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Copy link
Contributor

@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: 4

♻️ Duplicate comments (1)
src/apps/emcd-swap/components/Select/Select.tsx (1)

102-113: 🛠️ Refactor suggestion

Remove duplicate ARIA attributes from SelectActivator

The role, aria-haspopup, and aria-expanded attributes are already defined on the parent div and shouldn't be duplicated on the SelectActivator component. This was also mentioned in previous reviews.

<SelectActivator
  isOpen={isOpen}
  option={selected}
  placeholder={placeholder}
  itemText={itemText}
  itemIcon={itemIcon}
  withIcon={withIcon}
- role="button"
- aria-haspopup="listbox"
- aria-expanded={isOpen}
  toggleDropdown={toggleDropdown}
/>
🧹 Nitpick comments (4)
src/apps/emcd-swap/components/Select/Select.tsx (4)

6-10: Improve type safety for the Option interface

The Option interface uses an index signature [key: string]: any which reduces type safety and allows any property to be added. Consider using a more specific interface or generic type that provides better type checking.

export interface Option {
-   [key: string]: any;
+   [key: string]: unknown;
    id?: string | number;
    name?: string;
+   // Add other expected properties with specific types
}

19-19: Translate comment to English for consistency

The comment "Callback при выборе" is in Russian. For code maintainability and consistency, all comments should be in English.

-  onChange?: (value: any) => void; // Callback при выборе
+  onChange?: (value: any) => void; // Callback when an option is selected

19-19: Improve type safety for onChange callback

The onChange callback uses any as a parameter type, which reduces type safety. Use the Option type instead to provide better type checking.

-  onChange?: (value: any) => void; // Callback при выборе
+  onChange?: (value: Option) => void; // Callback when an option is selected

89-89: Make the component width customizable

The width of the component is fixed at w-36 (9rem in Tailwind), which might not be flexible enough. Consider making it customizable via props.

// In CustomSelectProps interface:
+  className?: string; // Allow additional classes for styling

// In component props destructuring:
+  className,

// In the render method:
-  className="relative w-36"
+  className={`relative w-36 ${className || ''}`}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 76f20fd and 7728ec2.

📒 Files selected for processing (2)
  • src/apps/emcd-swap/components/Select/Select.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectActivator.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/apps/emcd-swap/components/Select/components/SelectActivator.tsx

Copy link
Contributor

@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

♻️ Duplicate comments (2)
src/apps/emcd-swap/components/Select/Select.tsx (2)

74-97: Well-implemented keyboard navigation

The keyboard handling now supports:

  • Escape to close the dropdown
  • Enter/Space to open/select
  • Arrow keys for navigation
  • Proper null checks for the options array
  • preventDefault to avoid scrolling

This is a significant improvement for accessibility.


125-136: Properly implemented ID for aria-controls

The SelectDropdown component now has the correct ID (select-listbox) that matches the aria-controls attribute, ensuring proper accessibility.

🧹 Nitpick comments (4)
src/apps/emcd-swap/components/Select/Select.tsx (4)

6-11: Consider making the Option interface more type-safe

The current Option interface uses an index signature [key: string]: any which makes it very permissive and weakens type safety. This could lead to unexpected runtime errors when accessing properties that might not exist.

Consider using a more specific interface without the index signature, or use generics to make the type more specific to the consumer's needs:

-export interface Option {
-   [key: string]: any;
-   id?: string | number;
-   name?: string;
-   icon_url?: string;
-}
+export interface Option<T extends string = string> {
+   id?: string | number;
+   name?: string;
+   icon_url?: string;
+   [key in T]?: any;
+}

This approach would still allow flexibility while providing better type safety.


67-72: Handle document reference safely for SSR compatibility

Direct references to document can cause issues in server-side rendering environments where document is not available.

Add a check for the existence of document before adding event listeners:

useEffect(() => {
-  document.addEventListener("mousedown", handleClickOutside);
-  return () => {
-    document.removeEventListener("mousedown", handleClickOutside);
-  };
+  if (typeof document !== 'undefined') {
+    document.addEventListener("mousedown", handleClickOutside);
+    return () => {
+      document.removeEventListener("mousedown", handleClickOutside);
+    };
+  }
+  return undefined;
}, []);

102-102: Consider more flexible width handling

The component applies a fixed width of w-36 which might limit its reusability in different contexts.

Make the width fully customizable through className by removing the fixed width:

-className={`relative w-36 ${className || ''}`}
+className={`relative ${className || ''}`}

This allows consumers to set any width they need through the className prop.


74-97: Consider extracting keyboard handling to a custom hook

The keyboard event handling logic is relatively complex and could potentially be reused in other similar components.

Extract the keyboard navigation logic to a custom hook:

// useDropdownKeyboardNavigation.ts
import { useState, useCallback } from 'react';

interface UseDropdownKeyboardNavigationProps<T> {
  isOpen: boolean;
  setIsOpen: (isOpen: boolean) => void;
  options: T[];
  onSelect: (option: T) => void;
}

export function useDropdownKeyboardNavigation<T>({
  isOpen,
  setIsOpen,
  options,
  onSelect,
}: UseDropdownKeyboardNavigationProps<T>) {
  const [highlightedIndex, setHighlightedIndex] = useState(0);

  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    if (e.key === "Escape" && isOpen) {
      setIsOpen(false);
    } else if (e.key === "Enter" || e.key === " ") {
      if (isOpen) {
        if (options.length > 0) {
          onSelect(options[highlightedIndex]);
        }
      } else {
        setIsOpen(true);
      }
      e.preventDefault();
    } else if (e.key === "ArrowDown" && isOpen) {
      if (options.length > 0) {
        setHighlightedIndex((prev) => (prev + 1) % options.length);
      }
      e.preventDefault();
    } else if (e.key === "ArrowUp" && isOpen) {
      if (options.length > 0) {
        setHighlightedIndex((prev) => (prev - 1 + options.length) % options.length);
      }
      e.preventDefault();
    }
  }, [isOpen, options, highlightedIndex, setIsOpen, onSelect]);

  return {
    highlightedIndex,
    setHighlightedIndex,
    handleKeyDown,
  };
}

This improves code organization and enables reuse across components.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7728ec2 and 194d204.

📒 Files selected for processing (2)
  • src/apps/emcd-swap/components/Select/Select.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/apps/emcd-swap/components/Select/components/SelectDropdown.tsx
🔇 Additional comments (2)
src/apps/emcd-swap/components/Select/Select.tsx (2)

40-51: Good implementation of the null check for currentValue

The effect correctly handles the case when currentValue is null by resetting both the selected state and the highlightedIndex.


99-114: Excellent implementation of ARIA attributes

The component correctly implements accessibility with appropriate ARIA attributes:

  • role="combobox"
  • aria-haspopup="listbox"
  • aria-expanded
  • aria-controls with matching ID
  • aria-activedescendant with proper null checks

Copy link
Contributor

@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: 7

♻️ Duplicate comments (4)
src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (1)

12-16: Fix state synchronization for empty or null values

The current implementation only updates stateValue when value is truthy, which means empty strings or null values won't properly synchronize with the parent component.

This issue was previously identified and remains unresolved. When value is an empty string or null, the input won't reflect these changes.

useEffect(() => {
-  if (value) {
+  if (value !== undefined) {
    setStateValue(value || '')
+  } else {
+    setStateValue('')
  }
}, [value]);
src/apps/emcd-swap/helpers/input-validator.helper.ts (1)

1-41: Properly refactored code that addresses previous comments.

This implementation effectively addresses the previous review comments by consolidating duplicate functions into a single amountValidator function and maintaining backward compatibility with aliases. The added type definition and comment documentation also improve code quality.

One minor suggestion for further improvement would be to add a better test for decimal numbers in the validation function. The current implementation using num.toString() might not handle decimal representations perfectly.

- if (isNaN(num) || value.trim() !== num.toString()) {
+ // Use regex to check for valid number format (integers or decimals)
+ if (isNaN(num) || !/^-?\d+(\.\d+)?$/.test(value.trim())) {
    return { valid: false, error: 'Введите корректное число' };
  }
src/apps/emcd-swap/api/coinsApi.tsx (2)

75-89: 🛠️ Refactor suggestion

Define and use interfaces for createSwap endpoint

Using any types reduces type safety. Define and use proper interfaces for the request and response.

+interface CreateSwapRequest {
+  // Define the properties based on your API contract
+  coin_from: string;
+  coin_to: string;
+  amount_from: number;
+  network_from: string;
+  network_to: string;
+  address_to: string;
+  tag_to?: string;
+}
+
+interface CreateSwapResponse {
+  swap_id: string;
+  status: number;
+  // Add other properties as needed
+}

-    createSwap: builder.mutation<any, any>({
+    createSwap: builder.mutation<CreateSwapResponse, CreateSwapRequest>({

54-74: 🛠️ Refactor suggestion

Add proper TypeScript types for getEstimate endpoint

The endpoint is using any types, which reduces type safety. Consider using the defined EstimateResponse and EstimateParams interfaces.

-    getEstimate: builder.query<any, any>({
+    getEstimate: builder.query<EstimateResponse, EstimateParams>({
🧹 Nitpick comments (14)
src/apps/emcd-swap/components/SwapTimer/SwapTimer.tsx (3)

12-22: Potential duplicate callback execution

The onComplete callback is called in two places, which could lead to it being called twice:

  1. Line 13: When remaining <= 0 at the beginning of useEffect
  2. Line 21: When the newly calculated value is <= 0 inside the interval callback

Consider this refactoring to avoid duplicate calls:

useEffect(() => {
  if (remaining <= 0) {
-   onComplete?.();
    return;
  }

  const interval = setInterval(() => {
    setRemaining(prev => {
      const newValue = prev - 1;
      if (newValue <= 0 && onComplete) {
        onComplete();
      }
      return newValue;
    });
  }, 1000);

  return () => clearInterval(interval);
}, [remaining, onComplete]);

9-9: Replace non-English comment

The comment "в секундах" appears to be in Russian. For better maintainability and international collaboration, consider using English comments throughout the codebase.

-  const [remaining, setRemaining] = useState(duration); // в секундах
+  const [remaining, setRemaining] = useState(duration); // in seconds

48-48: Remove extra empty line

There's an unnecessary double empty line before the export statement.

-

export default SwapTimer;
+
export default SwapTimer;
src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (1)

18-21: Consider type consistency in handleChange function

The component's interface accepts null as a possible return value for onChange, but the handleChange function always passes a string and never null.

Either update the interface to reflect that onChange only receives strings, or modify the handler to accommodate null values:

interface DefaultInputProps {
  value: string | null;
-  onChange: (value: string | null) => void;
+  onChange: (value: string) => void;
}

Or alternatively, if null values are needed:

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const newValue = e.target.value;
  setStateValue(newValue);
-  onChange(e.target.value);
+  onChange(newValue.length > 0 ? newValue : null);
}
src/apps/emcd-swap/components/Button/Button.tsx (3)

9-9: Consider refining the disabled prop type

The disabled prop accepts boolean | null, but this creates unnecessary complexity. The HTML button element's disabled attribute expects a boolean value.

-  disabled?: boolean | null;
+  disabled?: boolean;

63-63: Handle undefined className properly

The component concatenates className directly in the template string, which could result in "undefined" being appended to the class string if className is not provided.

-  <button onClick={onClick} type={buttonType} className={`w-full text-color-1 rounded-sm text-sm outline-none border font-medium border-transparent ${getSize()} ${getType()} ${className}`}>
+  <button onClick={onClick} type={buttonType} className={`w-full text-color-1 rounded-sm text-sm outline-none border font-medium border-transparent ${getSize()} ${getType()} ${className || ''}`}>

1-69: Consider adding unit tests for this component

This Button component is likely used throughout the application for important user interactions. Adding unit tests would help ensure it behaves as expected across different prop combinations.

I can help generate a basic test suite for this component if needed. It would cover rendering with different props and interaction testing.

src/apps/emcd-swap/helpers/email-validator.helper.spec.ts (1)

1-31: Good test coverage for email validation, but consider using English for descriptions.

The test suite provides comprehensive coverage for the isValidEmail function, testing various valid and invalid email formats. However, the test descriptions are written in Russian, which might not be consistent with the rest of the codebase.

Consider translating the test descriptions to English for consistency and to improve maintainability for developers who might not read Russian. Additionally, you could expand test coverage with these edge cases:

test('handles emails with special formats', () => {
  expect(isValidEmail('email@123.123.123.123')).toBe(true); // IP address as domain
  expect(isValidEmail('email@domain-with-dash.com')).toBe(true);
  expect(isValidEmail('very.long.email.address.with.many.parts@domain.com')).toBe(true);
  // Add more edge cases as needed
});

test('rejects emails with invalid formats', () => {
  expect(isValidEmail('email@domain..com')).toBe(false); // Double dots
  expect(isValidEmail('"email"@domain.com')).toBe(false); // Quoted strings
  // Add more invalid cases as needed
});
src/apps/emcd-swap/helpers/input-validator.helper.ts (1)

18-20: String comparison may not handle decimal numbers correctly.

The current validation using value.trim() !== num.toString() has an edge case when dealing with decimal numbers, as JavaScript's toString() may not maintain the exact string representation of the original input.

Consider using a regular expression to validate the number format instead:

- if (isNaN(num) || value.trim() !== num.toString()) {
+ // Use regex to check for valid number format (integers or decimals)
+ if (isNaN(num) || !/^-?\d+(\.\d+)?$/.test(value.trim())) {
    return { valid: false, error: 'Введите корректное число' };
  }

This approach would better handle cases like "1.0" (which becomes "1" after parseFloat().toString()).

src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (2)

1-1: Fix file path in comment

The file path in the comment doesn't match the actual file path. Update it to reflect the correct location.

-// src/components/ToastNotification.tsx
+// src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx

39-39: Consider adding position configuration

The ToastContainer has good configuration for autoClose, stacked, and limit, but you might want to add a position prop to control where toasts appear on the screen.

-  return <ToastContainer autoClose={1000} stacked limit={3} />
+  return <ToastContainer autoClose={1000} stacked limit={3} position="top-right" />
src/apps/emcd-swap/api/coinsApi.tsx (1)

45-52: Refactor duplicated error transformation logic

The same error transformation logic is duplicated across multiple endpoints. Consider extracting it to a shared function.

+const transformApiError = (response: { status: number; data: ErrorData }) => {
+  console.error('API Error:', response);
+  return {
+    status: response.status,
+    data: response.data,
+    message: response.data?.message || 'An error occurred'
+  };
+};

// Then in each endpoint:
-      transformErrorResponse: (response: { status: number; data: ErrorData }) => {
-        console.error('API Error:', response);
-        return {
-          status: response.status,
-          data: response.data,
-          message: response.data?.message || 'An error occurred'
-        };
-      },
+      transformErrorResponse: transformApiError,

Also applies to: 66-73, 81-88, 92-99, 110-117, 126-133

src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx (2)

33-35: Simplify toast dispatching

Consider using the useDispatch hook directly with showToast rather than creating an intermediate function.

-  const setToast = ({ message, type }: { message: string; type: ToastType }) => {
-    dispatch(showToast({ message, type }))
-  }

// Then update line 97:
-  <div onClick={() => copyToClipboard(formData.tag_to || '', setToast)}>
+  <div onClick={() => copyToClipboard(
+    formData.tag_to || '',
+    (toast) => dispatch(showToast(toast))
+  )}>

97-99: Add cursor-pointer class to the copy icon

The copy icon should visually indicate it's clickable by adding a cursor-pointer class.

-  <div onClick={() => copyToClipboard(formData.tag_to || '', setToast)}>
+  <div className="cursor-pointer" onClick={() => copyToClipboard(formData.tag_to || '', setToast)}>
   <CopyIcon />
  </div>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 194d204 and a7cceda.

📒 Files selected for processing (33)
  • config-overrides.js (1 hunks)
  • src/apps/emcd-swap/api/coinsApi.tsx (1 hunks)
  • src/apps/emcd-swap/components/Button/Button.tsx (1 hunks)
  • src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (1 hunks)
  • src/apps/emcd-swap/components/FAQ/components/FAQItem.tsx (1 hunks)
  • src/apps/emcd-swap/components/FormInput/FormInput.tsx (1 hunks)
  • src/apps/emcd-swap/components/Modals/SupportModal.tsx (1 hunks)
  • src/apps/emcd-swap/components/Select/Select.tsx (1 hunks)
  • src/apps/emcd-swap/components/SwapTimer/SwapTimer.tsx (1 hunks)
  • src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/ArrowRightIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/CloseIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/CopyIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/InfoIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/SupportIcon.tsx (1 hunks)
  • src/apps/emcd-swap/components/icons/SwapIcon.tsx (1 hunks)
  • src/apps/emcd-swap/helpers/copy.helper.ts (1 hunks)
  • src/apps/emcd-swap/helpers/crypto-address-validator.ts (1 hunks)
  • src/apps/emcd-swap/helpers/email-validator.helper.spec.ts (1 hunks)
  • src/apps/emcd-swap/helpers/email-validator.helper.ts (1 hunks)
  • src/apps/emcd-swap/helpers/input-validator.helper.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useExchangeLogic.ts (1 hunks)
  • src/apps/emcd-swap/hooks/useRecipientLogic.ts (1 hunks)
  • src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1 hunks)
  • src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (1 hunks)
  • src/apps/emcd-swap/styles/tailwindDeposit.css (1 hunks)
  • src/apps/emcd-swap/views/Confirm/ConfirmView.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/ExchangeDepositForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/Confirm/components/forms/WaitingDepositForm.tsx (1 hunks)
  • src/apps/emcd-swap/views/ErrorView.tsx (1 hunks)
  • src/apps/emcd-swap/views/RecipientView.tsx (1 hunks)
  • src/apps/emcd-swap/views/SuccessView.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • config-overrides.js
🚧 Files skipped from review as they are similar to previous changes (22)
  • src/apps/emcd-swap/components/icons/SwapIcon.tsx
  • src/apps/emcd-swap/components/icons/InfoIcon.tsx
  • src/apps/emcd-swap/components/icons/ArrowRightIcon.tsx
  • src/apps/emcd-swap/components/icons/CloseIcon.tsx
  • src/apps/emcd-swap/components/icons/CopyIcon.tsx
  • src/apps/emcd-swap/styles/tailwindDeposit.css
  • src/apps/emcd-swap/components/FAQ/components/FAQItem.tsx
  • src/apps/emcd-swap/views/Confirm/components/forms/ExchangeDepositForm.tsx
  • src/apps/emcd-swap/helpers/email-validator.helper.ts
  • src/apps/emcd-swap/components/Modals/SupportModal.tsx
  • src/apps/emcd-swap/views/SuccessView.tsx
  • src/apps/emcd-swap/components/icons/SupportIcon.tsx
  • src/apps/emcd-swap/views/ErrorView.tsx
  • src/apps/emcd-swap/views/Confirm/components/forms/WaitingDepositForm.tsx
  • src/apps/emcd-swap/hooks/useExchangeLogic.ts
  • src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts
  • src/apps/emcd-swap/views/Confirm/ConfirmView.tsx
  • src/apps/emcd-swap/components/FormInput/FormInput.tsx
  • src/apps/emcd-swap/views/RecipientView.tsx
  • src/apps/emcd-swap/hooks/useRecipientLogic.ts
  • src/apps/emcd-swap/reducer/emcdSwapSlice.ts
  • src/apps/emcd-swap/components/Select/Select.tsx
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/apps/emcd-swap/helpers/email-validator.helper.spec.ts (1)
src/apps/emcd-swap/helpers/email-validator.helper.ts (1)
  • isValidEmail (14-14)
src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (1)
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (2)
  • selectToastMessage (38-39)
  • selectToastType (41-42)
src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (1)
src/components/LandingPage/Input/index.jsx (1)
  • handleChange (58-60)
src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx (3)
src/apps/emcd-swap/reducer/emcdSwapSlice.ts (1)
  • DetailSwapFormData (23-38)
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (1)
  • ToastType (5-5)
src/apps/emcd-swap/helpers/copy.helper.ts (1)
  • copyToClipboard (30-30)
src/apps/emcd-swap/helpers/copy.helper.ts (2)
src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts (1)
  • ToastType (5-5)
src/apps/simpleswap/helpers/getClientLanguage.js (1)
  • navigator (3-3)
🔇 Additional comments (12)
src/apps/emcd-swap/components/SwapTimer/SwapTimer.tsx (1)

1-49: LGTM! Previous review comments have been addressed.

The component correctly implements the swap timer with the requested changes:

  • Reset timer when duration prop changes (lines 37-39)
  • Added onComplete callback and improved timer effect structure (lines 4-5, 8, 13, 20-22)
src/apps/emcd-swap/components/DefaultInput/DefaultInput.tsx (2)

9-9: LGTM - Props cleanup implemented

The previously unused loading prop has been removed as suggested in the previous review.


25-25: LGTM - Accessibility improvements implemented

Good job adding the aria-label attribute to improve accessibility.

src/apps/emcd-swap/components/Button/Button.tsx (1)

3-11: Previously identified issues are now fixed

Great job addressing the previous review comments:

  1. The monochrome type implementation is now included
  2. All size variants (xs, sm, md, lg, xl) are now properly implemented
  3. The form submission button support via buttonType prop has been added

The component is more complete and consistent now.

src/apps/emcd-swap/components/ToastNotification/ToastNotification.tsx (2)

15-37: LGTM! Good implementation of toast notifications

The useEffect implementation correctly handles different toast types with a switch statement, including a default case. The component also properly clears the toast message after displaying it.


7-7:

Details

❌ Incorrect review comment

Fix import path

The import path seems incorrect relative to the file location. Ensure the path correctly points to the toast slice.


🏁 Script executed:

#!/bin/bash
# Check if the import path is correct
ls -la src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts

Length of output: 155


Import path is correct

The path ../../reducer/emcdSwapToastSlice correctly resolves to src/apps/emcd-swap/reducer/emcdSwapToastSlice.ts. No changes are needed here.

Likely an incorrect or invalid review comment.

src/apps/emcd-swap/api/coinsApi.tsx (2)

7-31: Well-defined type interfaces

Good job defining interfaces for the API responses and requests. This provides better type safety and improves code maintainability.


36-41: Good environment variable usage

Properly using environment variables for the API base URL is a good practice, with a sensible fallback.

src/apps/emcd-swap/helpers/copy.helper.ts (2)

1-9: Good type definitions and i18n planning

The TODO comment and type definitions are well-structured. Having proper types for toast messages improves type safety.


11-27: Well-implemented clipboard functionality

The function properly handles both success and error cases, with appropriate feedback via toast notifications. The Promise return type is correct for this async function.

src/apps/emcd-swap/views/Confirm/components/forms/SendToWalletForm.tsx (2)

12-17: Well-defined props interface

The props interface is clearly defined with appropriate types and optionality markers.


19-113: Well-structured component with good conditional rendering

The component is well-structured with clear conditional rendering logic based on the active and confirm states. The styling approach with dynamic class names is clean and maintainable.

type?: 'shade' | 'main' | 'monochrome';
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
className?: string;
onClick?: () => void;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve onClick event handler typing

The current onClick type is () => void, but it should accept the React mouse event parameter for compatibility with form handling and event prevention.

-  onClick?: () => void;
+  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
🤖 Prompt for AI Agents
In src/apps/emcd-swap/components/Button/Button.tsx at line 8, update the onClick
prop type from a parameterless function to one that accepts a React mouse event
parameter. Change the type to (event: React.MouseEvent<HTMLButtonElement>) =>
void to ensure proper typing for event handling and compatibility with form
events and event prevention.

Comment on lines +63 to +65
<button onClick={onClick} type={buttonType} className={`w-full text-color-1 rounded-sm text-sm outline-none border font-medium border-transparent ${getSize()} ${getType()} ${className}`}>
{children}
</button>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility attributes to the button

The button component is missing important accessibility attributes that would improve usability for users with disabilities.

  return (
    <button 
      onClick={onClick} 
      type={buttonType} 
+     disabled={!!disabled}
+     aria-disabled={!!disabled}
      className={`w-full text-color-1 rounded-sm text-sm outline-none border font-medium border-transparent ${getSize()} ${getType()} ${className || ''}`}>
      {children}
    </button>
  );
🤖 Prompt for AI Agents
In src/apps/emcd-swap/components/Button/Button.tsx around lines 63 to 65, the
button element lacks accessibility attributes. Add appropriate ARIA attributes
such as aria-label or aria-labelledby to describe the button's purpose, and
ensure the button has a clear accessible name. Also verify that the button's
role and keyboard interaction are properly supported to improve usability for
users with disabilities.

Comment on lines +44 to +46
if (disabled) {
return ''
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve disabled state handling

When disabled is true, the getType() function returns an empty string, but there's no specific styling applied for the disabled state. This could confuse users as there's no visual indication that the button is disabled.

  const getType = () => {
    const classes = []

    if (disabled) {
-     return ''
+     return 'bg-gray-400 cursor-not-allowed opacity-60'
    }

    // rest of the function remains the same
📝 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
if (disabled) {
return ''
}
if (disabled) {
return 'bg-gray-400 cursor-not-allowed opacity-60'
}
🤖 Prompt for AI Agents
In src/apps/emcd-swap/components/Button/Button.tsx around lines 44 to 46, the
getType() function returns an empty string when disabled is true but does not
apply any specific styling for the disabled state. Update the function to return
a distinct type or class name for the disabled state and ensure the button
component applies corresponding styles to visually indicate it is disabled.

Comment on lines +1 to +12
export function isValidCryptoAddress(address: string): boolean {
if (!address) return false;
const trimmed = address.trim();

// Общие правила — длина от 26 до 64, допустимые символы (буквы/цифры)
if (trimmed.length < 26 || trimmed.length > 64) return false;

// Проверяем, что строка состоит только из латинских букв и цифр
if (!/^[A-Za-z0-9]+$/.test(trimmed)) return false;

return true;
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Function logic may be too restrictive for certain cryptocurrency addresses.

The current implementation has several potential issues:

  1. The length restriction (26-64 characters) might not accommodate all valid cryptocurrency addresses
  2. The character set limitation (only alphanumeric) excludes some valid cryptocurrency addresses
  3. No specific validation for different cryptocurrencies (BTC, ETH, etc.)

Consider implementing a more flexible approach that can handle different cryptocurrency address formats:

-export function isValidCryptoAddress(address: string): boolean {
-  if (!address) return false;
-  const trimmed = address.trim();
-
-  // Общие правила — длина от 26 до 64, допустимые символы (буквы/цифры)
-  if (trimmed.length < 26 || trimmed.length > 64) return false;
-
-  // Проверяем, что строка состоит только из латинских букв и цифр
-  if (!/^[A-Za-z0-9]+$/.test(trimmed)) return false;
-
-  return true;
-}
+export function isValidCryptoAddress(address: string, coinType?: string): boolean {
+  if (!address) return false;
+  const trimmed = address.trim();
+  
+  // Basic validation - prevent empty strings
+  if (trimmed.length === 0) return false;
+  
+  // Coin-specific validation
+  if (coinType) {
+    switch(coinType.toLowerCase()) {
+      case 'btc':
+        // Bitcoin addresses can be legacy (1), P2SH (3), or bech32 (bc1)
+        return /^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$|^(bc1)[a-z0-9]{39,59}$/.test(trimmed);
+      case 'eth':
+        // Ethereum addresses are 42 chars including '0x' prefix
+        return /^0x[a-fA-F0-9]{40}$/.test(trimmed);
+      // Add other cryptocurrencies as needed
+      default:
+        // Fall back to general validation
+    }
+  }
+  
+  // General validation for unknown coin types
+  // More permissive - allows for special characters found in some addresses
+  return trimmed.length >= 26 && trimmed.length <= 90;
+}

This implementation is more flexible and can be extended to support specific validation rules for different cryptocurrencies.

📝 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
export function isValidCryptoAddress(address: string): boolean {
if (!address) return false;
const trimmed = address.trim();
// Общие правила — длина от 26 до 64, допустимые символы (буквы/цифры)
if (trimmed.length < 26 || trimmed.length > 64) return false;
// Проверяем, что строка состоит только из латинских букв и цифр
if (!/^[A-Za-z0-9]+$/.test(trimmed)) return false;
return true;
}
export function isValidCryptoAddress(address: string, coinType?: string): boolean {
if (!address) return false;
const trimmed = address.trim();
// Basic validation - prevent empty strings
if (trimmed.length === 0) return false;
// Coin-specific validation
if (coinType) {
switch (coinType.toLowerCase()) {
case 'btc':
// Bitcoin addresses can be legacy (1), P2SH (3), or bech32 (bc1)
return /^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$|^(bc1)[a-z0-9]{39,59}$/.test(trimmed);
case 'eth':
// Ethereum addresses are 42 chars including '0x' prefix
return /^0x[a-fA-F0-9]{40}$/.test(trimmed);
// Add other cryptocurrencies as needed
default:
// Fall back to general validation
}
}
// General validation for unknown coin types
// More permissive - allows for special characters found in some addresses
return trimmed.length >= 26 && trimmed.length <= 90;
}
🤖 Prompt for AI Agents
In src/apps/emcd-swap/helpers/crypto-address-validator.ts lines 1 to 12, the
current validation is too restrictive by enforcing a fixed length range and only
allowing alphanumeric characters, which excludes valid cryptocurrency addresses
with different formats. To fix this, refactor the function to remove the strict
length and character set checks and instead implement a flexible validation
approach that can handle multiple cryptocurrency address formats, possibly by
adding specific validation logic for each supported cryptocurrency type (e.g.,
BTC, ETH) to accommodate their unique address rules.

Comment on lines +101 to +103
getSwap: builder.query<any, { swapID: string }>({
query: ({ swapID }) => `swap/${swapID}`,
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling to getSwap endpoint

This endpoint is missing error transformation logic that all other endpoints have.

    getSwap: builder.query<any, { swapID: string }>({
      query: ({ swapID }) => `swap/${swapID}`,
+      transformErrorResponse: (response: { status: number; data: ErrorData }) => {
+        console.error('API Error:', response);
+        return {
+          status: response.status,
+          data: response.data,
+          message: response.data?.message || 'An error occurred'
+        };
+      },
    }),

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/apps/emcd-swap/api/coinsApi.tsx around lines 101 to 103, the getSwap
endpoint lacks error transformation logic present in other endpoints. Add an
error handling or transformErrorResponse function to the getSwap builder.query
configuration to properly handle and transform errors returned from the API,
ensuring consistent error processing across all endpoints.

Comment on lines +90 to +100
getSwapStatus: builder.query<any, { swapID: string; status: number }>({
query: ({ swapID, status }) => `swap/${swapID}/${status}`,
transformErrorResponse: (response: { status: number; data: ErrorData }) => {
console.error('API Error:', response);
return {
status: response.status,
data: response.data,
message: response.data?.message || 'An error occurred'
};
},
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Define response type for getSwapStatus endpoint

Using any for the response type reduces type safety. Define and use a proper interface.

+interface SwapStatusResponse {
+  status: number;
+  // Add other properties based on the API response
+}

-    getSwapStatus: builder.query<any, { swapID: string; status: number }>({
+    getSwapStatus: builder.query<SwapStatusResponse, { swapID: string; status: number }>({
🤖 Prompt for AI Agents
In src/apps/emcd-swap/api/coinsApi.tsx around lines 90 to 100, the getSwapStatus
query uses 'any' as the response type, which reduces type safety. Define a
proper TypeScript interface representing the expected response structure from
this endpoint and replace 'any' with this interface in the query definition to
improve type safety and code clarity.

Comment on lines +17 to +20
setToast({
message: 'Текст успешно скопирован в буфер обмена',
type: 'success',
});
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Ensure consistency with ToastType from Redux slice

Your local ToastType definition might become inconsistent with the one in the Redux slice, which includes a null option.


🏁 Script executed:

#!/bin/bash
# Check if the ToastType in the Redux slice includes options not handled here
grep -r "export type ToastType" --include="*.ts" --include="*.tsx" src/apps/emcd-swap/

Length of output: 209


🏁 Script executed:

#!/bin/bash
# Show imports and ToastType usage in copy.helper.ts around lines 1-50
sed -n '1,50p' src/apps/emcd-swap/helpers/copy.helper.ts
echo "-----"
grep -R "ToastType" -n src/apps/emcd-swap/helpers/copy.helper.ts

Length of output: 964


Use the central ToastType from the Redux slice in your copy helper

Your local ToastType definition in copy.helper.ts is missing the null option added in emcdSwapToastSlice.ts. To keep types consistent and avoid drift:

• File to update: src/apps/emcd-swap/helpers/copy.helper.ts
• Remove the local ToastType alias and import it from the slice.
• Adjust the import path as needed to point at emcdSwapToastSlice.ts.

Suggested diff:

--- a/src/apps/emcd-swap/helpers/copy.helper.ts
+++ b/src/apps/emcd-swap/helpers/copy.helper.ts
@@
-// TODO: once i18n is configured, replace the hard-coded messages below with t('copy.success') / t('copy.error')
-type ToastType = 'success' | 'error' | 'info' | 'warning';
+import type { ToastType } from '../reducer/emcdSwapToastSlice';

 interface ToastMessage {
   message: string;
   type: ToastType;
 }

This ensures your helper stays in sync with the slice’s type (which now includes null).

📝 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
setToast({
message: 'Текст успешно скопирован в буфер обмена',
type: 'success',
});
// src/apps/emcd-swap/helpers/copy.helper.ts
-import type ToastType = 'success' | 'error' | 'info' | 'warning';
+import type { ToastType } from '../reducer/emcdSwapToastSlice';
interface ToastMessage {
message: string;
type: ToastType;
}
🤖 Prompt for AI Agents
In src/apps/emcd-swap/helpers/copy.helper.ts around lines 17 to 20, the
ToastType used locally is inconsistent with the ToastType defined in the Redux
slice emcdSwapToastSlice.ts, which includes a null option. To fix this, remove
the local ToastType definition in copy.helper.ts and import the ToastType
directly from the Redux slice file, adjusting the import path as necessary. This
will ensure type consistency and prevent drift between the helper and the slice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants