-
Notifications
You must be signed in to change notification settings - Fork 12
Emcd swap #297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Emcd swap #297
Conversation
WalkthroughA 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
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
Poem
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
npm warn config production Use Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit 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. Note ⚡️ Faster reviews with cachingCodeRabbit 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 ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 suggestionGlobal store is reused across tests – consider isolating state per test
beforeEachmutates the singletonstorethat 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 issuePossible typo in test-ID breaks the assertion
queryByTestId('select-token-car')is missing the trailing “d” that is present everywhere else (select-token-card).
BecausequeryByTestIdreturnsnullon 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 10Increasing 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 propertiesInstead 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
setSwapChainandsetReceiveChainare 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 logicThe 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.tokenListis created with an inlinefiltereach 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 neitherisLoadingnorisFetchingare 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
isGraphErroringflag (removed earlier) or handlingisError/errorfrom 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 reusabilityThe component looks good overall, but I have two suggestions:
- 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">
- 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 componentWhile 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 maintainabilityThe 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 annotationsThe 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 accessibilityFor 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 reusabilityThe 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 flexibilityThe 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 styleThe 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 localizationThe component structure is clean, but a few improvements could be made:
- Add TypeScript type annotation for the component (React.FC)
- 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 stylesThere 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 importThe 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 APIThe hook lacks documentation and could benefit from additional functionality.
- 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, ) {
- 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, }
- 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 expansionThe 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 commentsThere 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 fallbacksThe image element doesn't have proper error handling if the image fails to load. Additionally, the conditional check for
itemIconseems redundant sincewithIconis 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 designThe current interface could be improved for better type safety and clarity. Consider making
withIconanditemIconproperties 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 commentThe comment indicates this file is at
src/components/ToastNotification.tsx, but the actual path issrc/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 pathsUsing 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 configurationThe 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 typeThe 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 | undefinedsrc/apps/emcd-swap/components/SwapTimer/SwapTimer.tsx (3)
20-25: Improve time formatting for better user experienceThe 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 feedbackThe 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 linesThere 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 rulesThe 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 requirementsThe 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
validwhen 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 formattingThe 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 supportThe 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 divThe 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 consistencyAdd 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 nullFor 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 slashThere'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 fileThe 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 FAQFor 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 usinganytype in TypeScriptReplace the
anytype with a more specific type for better type safety.- setFormData((prev: any) => ({ + setFormData((prev: {name: string; email: string; text: string}) => ({
50-54: Enhance form validationThe 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 stylingThe inputs use many utility classes with
!importantflags (!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 likeclassnamesfor conditional classesThe 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 localizationThe 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 validationThe current number validation using
parseFloatis 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 valueAdding 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 messagesThe 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 supportThe component doesn't handle a disabled state. Consider adding a
disabledprop 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 stringThe component accepts
nullvalues but always passes string values in theonChangecallback. 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 stringThe 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 stringSimilar to FormInput, this component accepts
nullvalues but always passes string values in theonChangecallback. 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 variableThe 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 internationalizationThe 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 definitionThere should be no space between
React.FCand 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 scanningThe 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 schemeThe 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 colorsFor 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:
- Maintain a single source of truth for your brand colors
- Enable easier theming and dark mode switching
- Share colors between Tailwind and non-Tailwind CSS if needed
Example implementation:
- Create a colors.css file with variables
- Import it in your main CSS file
- 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.typeThen 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.reducersrc/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 valuesThis 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 buttonsThere are multiple copy buttons with repeated logic and styling. Consider extracting this to a reusable component.
Create a reusable
CopyButtoncomponent: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 commentThere'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 callbackThe current implementation manually checks for the existence of the
onChangecallback 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 constraintThe 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
statusfield indetailSwapFormDatais defined as potentially null, but thesetDetailSwapStatusreducer 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
⛔ Files ignored due to path filters (22)
package-lock.jsonis excluded by!**/package-lock.jsonsrc/apps/emcd-swap/assets/cancelled.pngis excluded by!**/*.pngsrc/apps/emcd-swap/assets/error.pngis excluded by!**/*.pngsrc/apps/emcd-swap/assets/success.pngis excluded by!**/*.pngsrc/apps/emcd-swap/documents/CryptoTerms.pdfis excluded by!**/*.pdfsrc/apps/emcd-swap/icon.pngis excluded by!**/*.pngsrc/apps/pillarx-app/components/MediaGridCollection/tests/__snapshots__/DisplayCollectionImage.test.tsx.snapis excluded by!**/*.snapsrc/apps/pillarx-app/components/PointsTile/test/__snapshots__/PointsTile.test.tsx.snapis excluded by!**/*.snapsrc/apps/pillarx-app/components/PortfolioOverview/test/__snapshots__/PortfolioOverview.test.tsx.snapis excluded by!**/*.snapsrc/apps/pillarx-app/components/TokenMarketDataRow/tests/__snapshots__/LeftColumnTokenMarketDataRow.test.tsx.snapis excluded by!**/*.snapsrc/apps/pillarx-app/components/TokenMarketDataRow/tests/__snapshots__/RightColumnTokenMarketDataRow.test.tsx.snapis excluded by!**/*.snapsrc/apps/pillarx-app/components/TokensWithMarketDataTile/test/__snapshots__/TokensWithMarketDataTile.test.tsx.snapis excluded by!**/*.snapsrc/apps/pillarx-app/images/token-market-data-copy.pngis excluded by!**/*.pngsrc/apps/the-exchange/components/CardsSwap/test/__snapshots__/CardSwap.test.tsx.snapis excluded by!**/*.snapsrc/apps/the-exchange/components/DropdownTokensList/test/__snapshots__/DropdownTokensList.test.tsx.snapis excluded by!**/*.snapsrc/apps/the-exchange/components/EnterAmount/test/__snapshots__/EnterAmount.test.tsx.snapis excluded by!**/*.snapsrc/apps/the-exchange/components/SelectToken/test/__snapshots__/SelectToken.test.tsx.snapis excluded by!**/*.snapsrc/apps/the-exchange/components/TokenListItem/test/__snapshots__/TokenListItem.test.tsx.snapis excluded by!**/*.snapsrc/apps/token-atlas/components/TokenGraphColumn/test/__snapshots__/TokenGraphColumn.test.tsx.snapis excluded by!**/*.snapsrc/apps/token-atlas/components/TokensSlider/test/__snapshots__/TokenSlider.test.tsx.snapis excluded by!**/*.snapsrc/components/BottomMenu/__snapshots__/BottomMenu.test.tsx.snapis excluded by!**/*.snapsrc/components/Form/Select/__snapshots__/Select.test.tsx.snapis 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 (
isLoadingandallowed) after the removal of theisAnimatedstate. 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=tsxLength 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" -nLength of output: 664
No references to
wallet-portfolio-balanceID – removal is safeI’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.datainstead of using optional chaining withblockchainListData?.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 10Length of output: 7092
Direct data access is consistent and safe
Verified that ChainCard, TokenInfoHorizontal, and HorizontalToken all guard on
isSuccessbefore accessingblockchainListData.data. RTK Query guaranteesdatais populated whenisSuccessis true, so optional chaining here is redundant.src/apps/pillarx-app/components/HorizontalToken/HorizontalToken.tsx (1)
47-47: Removed optional chaining when accessing blockchain dataConsistent with the changes in TokenInfoHorizontal, this file also directly accesses
blockchainListData.datainstead 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 formatThe top-level key in the mock trending tokens data has been changed from
resulttodata, aligning with the updated API response format.
33-33: Simplified blockchain naming in test dataSpecific blockchain names have been replaced with generic placeholders (
blockchain1andblockchain2) to match the simplified token selection logic in the component.Also applies to: 40-40
134-134: Updated expected chainId value for token selectionThe expected
chainIdin the token selection test has been changed from a specific value toundefinedto 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 logicThe 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: ''andchainId: 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 ofselectedTokencan handle these fallbacks without errors or unintended behavior:• In
src/apps/token-atlas/index.tsx, we doconst nativeToken = getNativeAssetForChainId(selectedToken.chainId || 0);– Confirm that passing
0(whenchainIdis undefined) yields a sensible default/native token or is explicitly guarded.
• Also inindex.tsx, we includechainId: selectedToken.chainIdin analytics and payloads. Ensure downstream systems tolerate
undefined(or adjust to a valid default).If any consumer expects a non-nullable
chainIdoraddress, 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
.datainstead 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
.resultstructure: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
.dataAccess VerifiedThe code now uniformly uses
.datainstead of.resultto access blockchain list data, matching the updated API response format. A project-wide search of all.ts/.tsxfiles found no remaining.resultreferences, 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
waitForimport from@testing-library/reactaligns 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
setReceiveChainandsetSwapChainimports aligns with the simplified token selection flow where chain resets during token list opening have been removed.
55-63: LGTM: Function simplification.The
handleOpenTokenListfunction 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
SwitchCardsButtonhas 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
TokenMarketHistorywhich reflects the new data structure being used in the component.
50-52: LGTM: Updated state selector type.The type for
tokenDataGraphhas been appropriately updated toTokenMarketHistory | undefinedto match the new data structure.
95-125: LGTM: Simplified data access pattern.The code now directly accesses the
price_historyproperty fromtokenDataGraphrather 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_historyarray, making the code more readable.
147-155: LGTM: Direct data mapping.The chart data preparation now directly maps from the flattened data structure, using
timestampandpriceUSDproperties from each data point in theprice_historyarray.src/apps/token-atlas/components/TokenGraphColumn/test/TokenGraphColumn.test.tsx (4)
21-22: LGTM: Updated imports.The type import has been updated to include
TokenMarketHistoryinstead 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
TokenMarketHistorytype with a streamlinedprice_historyarray, 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.priceinstead of a previously hardcoded value, improving test accuracy.src/apps/pillarx-app/components/TokensVerticalList/TokensVerticalList.tsx (1)
22-37: Code simplification looks goodThe 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 changesThe removal of the
mb-4class 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 implementedThe 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 correctlyThe test now properly uses individual token properties instead of a single token object, consistent with component API changes.
14-27: Test fixture properly updatedThe renderer test has been correctly updated to use the new prop structure.
29-44: Test assertions properly updatedTest 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 updatedAll 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 logicThe 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 tokenMoving the
setSearchTokendispatch 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 modelThe import change aligns with the broader refactoring of token market data handling in the app.
26-31: LGTM: Updated mock data structureThe mock data structure has been properly updated to use the new
TokenMarketHistorytype with aprice_historyarray of timestamped price points, which matches the type definition in the relevant code snippets.
81-81: LGTM: Updated test case for no price historyThe 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 importRemoving
useAppDispatchis consistent with the refactor to remove side effects and API-driven token selection logic from this component.
41-41: LGTM: Simplified container CSS classReplacing
min-h-[200px]andh-fullwith a fixedh-[200px]creates more predictable layout behavior.
59-66: LGTM: Simplified EnterAmount component usageRemoving the
tokenBalanceprop fromEnterAmountaligns with the broader refactoring to fetch token balances asynchronously via hooks from@etherspot/transaction-kitinstead of using static token properties.src/apps/the-exchange/components/TokenListItem/TokenListItem.tsx (3)
9-16: LGTM: Refactored props structureBreaking down the single
tokenobject 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 propsThe TokenLogo component props have been correctly updated to match the new property structure.
49-51: LGTM: Improved chain name displayUsing
chainNameDataCompatibilityensures 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 goodSwitching to
TokenMarketHistorycleans 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 copyThe 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:searchTokenfield now requires a string valueThe type has been changed from optional to required, removing the possibility of
undefinedvalues. 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 requirementsSetting the initial state to an empty string instead of
undefinedensures 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 fromstring | undefinedtostringThis change to the
setSearchTokenreducer 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 reducerThe 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 importThis 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 assetsThe test tokens now omit the
balanceandpriceproperties, which aligns with the component changes that now obtain this data dynamically from hooks rather than through props.
62-65: Added useEtherspotPrices mockThis 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 mockThe 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 mockThis 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 setupThese 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 valueThe 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 importsThe imports now use
TokenMarketHistoryinstead ofMarketHistoryPairData, reflecting the API refactoring mentioned in the PR summary. This change ensures type consistency with the updated API responses.
47-47: Updated tokenDataGraph typeThe state selector now correctly casts to the new
TokenMarketHistorytype, ensuring proper type checking when accessing data properties.
176-182: Simplified price display logicThe 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 stylingThe conditional now checks for the existence of
tokenDataGraph?.price_historyinstead 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 hooksThe 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 importThis hook provides access to wallet balances across different chains, replacing the removed tokenBalance prop with dynamic balance retrieval.
34-34: Added processEth utility importThis 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 accessThe 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 logicThe 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 logicSimilar 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 functionThis 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 fetchingThe 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 displayThe 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 hadidattributes 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
TokenItemhas 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-toastifypackage 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 tsxLength 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-toastifydependency is correctly added topackage.jsonand is in use by the newToastNotificationcomponent:
- 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 identifiersChanging 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 faderule lives insrc/apps/emcd-swap/styles/tailwindDeposit.css, so you don’t need to add an inline<style>block inLoader.tsx. Ensure that your global styles (includingtailwindDeposit.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 enumThe 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 approachThe 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 componentThe 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 importsThe 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-designedThe 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 importsImports are well-organized and grouped logically.
32-67: Well-structured component with good user flowThe 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 interfaceThe FAQItemData interface is appropriately typed with clear property names.
src/apps/emcd-swap/views/ExchangeView.tsx (1)
109-113: 🛠️ Refactor suggestionUse 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 suggestionPotential 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 changeAdditionally, 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_towhile line 96 referencesformData.network_from. This seems inconsistent - should it benetwork_toif 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=typescriptLength 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 -B1Length 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 -B1Length of output: 7978
No change needed:
network_fromis correct here.In ConfirmDepositForm,
formData.address_tois the deposit address on the source network, so showingformData.network_fromis intentional. Thenetwork_tofield is only used in ExchangeDepositForm for cross‐chain swaps.Likely an incorrect or invalid review comment.
src/apps/token-atlas/index.tsx
Outdated
| 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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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]); |
src/apps/token-atlas/api/token.ts
Outdated
| 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)}`; | ||
| }, | ||
| }), | ||
| }), | ||
| }); |
There was a problem hiding this comment.
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
});
src/apps/token-atlas/api/token.ts
Outdated
| query: ({ id, asset, symbol }) => { | ||
| return `?${id ? `id=${id}` : ''}${asset ? `&asset=${asset}` : ''}&symbol=${symbol}&${chainIdsQuery}&testnets=${String(isTestnet)}`; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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()}`; | |
| }, |
src/apps/token-atlas/api/token.ts
Outdated
| 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)}`; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix timestamp conversion bug and improve URL parameter handling.
There are two issues in this code:
- The
toParamvariable usesfrominstead oftowhen constructing the query parameter - 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.
| 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()}`; | |
| }, |
| backView: VIEW_TYPE.RECIPIENT | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this 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 accessWhen 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 accessThe relative path
../documents/terms.pdfmay not work correctly in production builds. Based on past review comments, a file namedCryptoTerms.pdfwas 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:
- Moving the PDF to your public folder and using an absolute path (as shown above)
- 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
translationsobject 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 readabilityThe 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 accessibilityFor 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 tooltipThe 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 callbackUse 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-rendersThe
handleClickOutsidefunction 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
useComponentVisiblein your codebase.
52-52: Make dropdown width configurable through propsThe 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 stateThe effect that syncs the component with external state doesn't handle the case when
currentValuebecomes 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 safetyThe
Optiontype is currently defined asRecord<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
⛔ Files ignored due to path filters (5)
public/emcd-swap/documents/terms.pdfis excluded by!**/*.pdfsrc/apps/emcd-swap/assets/cancelled.pngis excluded by!**/*.pngsrc/apps/emcd-swap/assets/error.pngis excluded by!**/*.pngsrc/apps/emcd-swap/assets/success.pngis excluded by!**/*.pngsrc/apps/emcd-swap/icon.pngis 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
titleanddescriptionnow accurately reflect the EMCD Swap application purpose. Great to see placeholders replaced.
| 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() | ||
|
|
There was a problem hiding this comment.
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=typescriptLength 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.tsLength 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.
There was a problem hiding this 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 suggestionAdd 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 suggestionImprove 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
📒 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)
There was a problem hiding this 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 suggestionRemove duplicate ARIA attributes from SelectActivator
The
role,aria-haspopup, andaria-expandedattributes 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 interfaceThe
Optioninterface uses an index signature[key: string]: anywhich 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 consistencyThe 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 callbackThe
onChangecallback usesanyas a parameter type, which reduces type safety. Use theOptiontype 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 customizableThe 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
📒 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
There was a problem hiding this 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 navigationThe 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-controlsThe SelectDropdown component now has the correct ID (
select-listbox) that matches thearia-controlsattribute, ensuring proper accessibility.
🧹 Nitpick comments (4)
src/apps/emcd-swap/components/Select/Select.tsx (4)
6-11: Consider making the Option interface more type-safeThe current Option interface uses an index signature
[key: string]: anywhich 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 compatibilityDirect references to
documentcan cause issues in server-side rendering environments wheredocumentis not available.Add a check for the existence of
documentbefore 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 handlingThe component applies a fixed width of
w-36which 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 hookThe 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
📒 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 currentValueThe effect correctly handles the case when
currentValueisnullby resetting both theselectedstate and thehighlightedIndex.
99-114: Excellent implementation of ARIA attributesThe 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
There was a problem hiding this 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 valuesThe current implementation only updates
stateValuewhenvalueis 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
valueis 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
amountValidatorfunction 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 suggestionDefine and use interfaces for createSwap endpoint
Using
anytypes 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 suggestionAdd proper TypeScript types for getEstimate endpoint
The endpoint is using
anytypes, 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 executionThe
onCompletecallback is called in two places, which could lead to it being called twice:
- Line 13: When
remaining <= 0at the beginning of useEffect- 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 commentThe 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 lineThere'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 functionThe component's interface accepts
nullas a possible return value foronChange, but thehandleChangefunction always passes a string and nevernull.Either update the interface to reflect that
onChangeonly 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 typeThe
disabledprop acceptsboolean | null, but this creates unnecessary complexity. The HTML button element'sdisabledattribute expects a boolean value.- disabled?: boolean | null; + disabled?: boolean;
63-63: Handle undefined className properlyThe component concatenates
classNamedirectly in the template string, which could result in "undefined" being appended to the class string ifclassNameis 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 componentThis 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
isValidEmailfunction, 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'stoString()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 commentThe 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 configurationThe 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 logicThe 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 dispatchingConsider using the
useDispatchhook directly withshowToastrather 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 iconThe 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
📒 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 implementedThe previously unused
loadingprop has been removed as suggested in the previous review.
25-25: LGTM - Accessibility improvements implementedGood job adding the
aria-labelattribute to improve accessibility.src/apps/emcd-swap/components/Button/Button.tsx (1)
3-11: Previously identified issues are now fixedGreat job addressing the previous review comments:
- The monochrome type implementation is now included
- All size variants (xs, sm, md, lg, xl) are now properly implemented
- 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 notificationsThe 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.tsLength of output: 155
Import path is correct
The path
../../reducer/emcdSwapToastSlicecorrectly resolves tosrc/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 interfacesGood job defining interfaces for the API responses and requests. This provides better type safety and improves code maintainability.
36-41: Good environment variable usageProperly 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 planningThe TODO comment and type definitions are well-structured. Having proper types for toast messages improves type safety.
11-27: Well-implemented clipboard functionalityThe 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 interfaceThe props interface is clearly defined with appropriate types and optionality markers.
19-113: Well-structured component with good conditional renderingThe 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; |
There was a problem hiding this comment.
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.
| <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> |
There was a problem hiding this comment.
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.
| if (disabled) { | ||
| return '' | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| 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 |
There was a problem hiding this comment.
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:
- The length restriction (26-64 characters) might not accommodate all valid cryptocurrency addresses
- The character set limitation (only alphanumeric) excludes some valid cryptocurrency addresses
- 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.
| 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.
| getSwap: builder.query<any, { swapID: string }>({ | ||
| query: ({ swapID }) => `swap/${swapID}`, | ||
| }), |
There was a problem hiding this comment.
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.
| 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' | ||
| }; | ||
| }, | ||
| }), |
There was a problem hiding this comment.
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.
| setToast({ | ||
| message: 'Текст успешно скопирован в буфер обмена', | ||
| type: 'success', | ||
| }); |
There was a problem hiding this comment.
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.tsLength 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.
| 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.
Description
How Has This Been Tested?
Screenshots (if appropriate):
Types of changes
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Style
Tests
Chores