-
Notifications
You must be signed in to change notification settings - Fork 160
feat(tokenselector): 7 - implement SelectTokenModal with enhanced token selection #6527
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: feat/token-selector-6
Are you sure you want to change the base?
Changes from 19 commits
7063b6a
06e4157
62d9df0
0a36b49
24260ca
b3f48f1
8288b94
8c81595
bc32977
bcd4762
49a3fe6
6587732
b863b55
601257c
b3783a7
32d9b94
b01ecc7
0436f9c
6ad1e2a
7b8d9ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import { ReactNode } from 'react' | ||
|
|
||
| import { ChainInfo } from '@cowprotocol/cow-sdk' | ||
| import { TokenListCategory } from '@cowprotocol/tokens' | ||
|
|
||
| import { SelectTokenModalContent } from './SelectTokenModalContent' | ||
| import * as styledEl from './styled' | ||
|
|
||
| import { LpTokenListsWidget } from '../../containers/LpTokenListsWidget' | ||
| import { ChainsToSelectState, TokenSelectionHandler } from '../../types' | ||
| import { ChainsSelector } from '../ChainsSelector' | ||
|
|
||
| type TokenListCategoryState = [TokenListCategory[] | null, (category: TokenListCategory[] | null) => void] | ||
|
|
||
| export interface TokenColumnContentProps { | ||
| displayLpTokenLists?: boolean | ||
| account: string | undefined | ||
| inputValue: string | ||
| onSelectToken: TokenSelectionHandler | ||
| openPoolPage(poolAddress: string): void | ||
| disableErc20?: boolean | ||
| tokenListCategoryState: TokenListCategoryState | ||
| isRouteAvailable: boolean | undefined | ||
| chainsToSelect?: ChainsToSelectState | ||
| onSelectChain: (chain: ChainInfo) => void | ||
| children: ReactNode | ||
| } | ||
|
|
||
| export function TokenColumnContent({ | ||
| displayLpTokenLists, | ||
| account, | ||
| inputValue, | ||
| onSelectToken, | ||
| openPoolPage, | ||
| disableErc20, | ||
| tokenListCategoryState, | ||
| isRouteAvailable, | ||
| chainsToSelect, | ||
| onSelectChain, | ||
| children, | ||
| }: TokenColumnContentProps): ReactNode { | ||
| if (displayLpTokenLists) { | ||
| return ( | ||
| <LpTokenListsWidget | ||
| account={account} | ||
| search={inputValue} | ||
| onSelectToken={onSelectToken} | ||
| openPoolPage={openPoolPage} | ||
| disableErc20={disableErc20} | ||
| tokenListCategoryState={tokenListCategoryState} | ||
| > | ||
| {children} | ||
| </LpTokenListsWidget> | ||
| ) | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <LegacyChainSelector chainsToSelect={chainsToSelect} onSelectChain={onSelectChain} /> | ||
| <SelectTokenModalContent isRouteAvailable={isRouteAvailable}>{children}</SelectTokenModalContent> | ||
| </> | ||
| ) | ||
| } | ||
|
|
||
| interface LegacyChainSelectorProps { | ||
| chainsToSelect: ChainsToSelectState | undefined | ||
| onSelectChain: (chain: ChainInfo) => void | ||
| } | ||
|
|
||
| function LegacyChainSelector({ chainsToSelect, onSelectChain }: LegacyChainSelectorProps): ReactNode { | ||
| if (!chainsToSelect?.chains?.length) { | ||
| return null | ||
| } | ||
|
|
||
| return ( | ||
| <styledEl.LegacyChainsWrapper> | ||
| <ChainsSelector | ||
| isLoading={chainsToSelect.isLoading || false} | ||
| chains={chainsToSelect.chains} | ||
| defaultChainId={chainsToSelect.defaultChainId} | ||
| onSelectChain={onSelectChain} | ||
| /> | ||
| </styledEl.LegacyChainsWrapper> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import { ReactNode, useMemo, useState } from 'react' | ||
|
|
||
| import { BackButton } from '@cowprotocol/ui' | ||
|
|
||
| import { SettingsIcon } from 'modules/trade/pure/Settings' | ||
|
|
||
| import * as styledEl from './styled' | ||
|
|
||
| import { SelectTokenContext } from '../../types' | ||
|
|
||
| import type { SelectTokenModalProps } from './types' | ||
|
|
||
| export function useSelectTokenContext(props: SelectTokenModalProps): SelectTokenContext { | ||
| const { | ||
| selectedToken, | ||
| balancesState, | ||
| unsupportedTokens, | ||
| permitCompatibleTokens, | ||
| onSelectToken, | ||
| onTokenListItemClick, | ||
| account, | ||
| tokenListTags, | ||
| } = props | ||
|
|
||
| return useMemo( | ||
| () => ({ | ||
| balancesState, | ||
| selectedToken, | ||
| onSelectToken, | ||
| onTokenListItemClick, | ||
| unsupportedTokens, | ||
| permitCompatibleTokens, | ||
| tokenListTags, | ||
| isWalletConnected: !!account, | ||
| }), | ||
| [ | ||
| balancesState, | ||
| selectedToken, | ||
| onSelectToken, | ||
| onTokenListItemClick, | ||
| unsupportedTokens, | ||
| permitCompatibleTokens, | ||
| tokenListTags, | ||
| account, | ||
| ], | ||
| ) | ||
| } | ||
|
|
||
| export function useTokenSearchInput(defaultInputValue = ''): [string, (value: string) => void, string] { | ||
| const [inputValue, setInputValue] = useState<string>(defaultInputValue) | ||
|
|
||
| return [inputValue, setInputValue, inputValue.trim()] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Memoize, maybe?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got this assessment on this, let me know if we should add it on this branch or not:
|
||
| } | ||
|
|
||
| interface TitleBarActionsProps { | ||
| showManageButton: boolean | ||
| onDismiss(): void | ||
| onOpenManageWidget(): void | ||
| title: string | ||
| } | ||
|
|
||
| export function TitleBarActions({ | ||
| showManageButton, | ||
| onDismiss, | ||
| onOpenManageWidget, | ||
| title, | ||
| }: TitleBarActionsProps): ReactNode { | ||
| return ( | ||
| <styledEl.TitleBar> | ||
| <styledEl.TitleGroup> | ||
| <BackButton onClick={onDismiss} /> | ||
| <styledEl.ModalTitle>{title}</styledEl.ModalTitle> | ||
| </styledEl.TitleGroup> | ||
| {showManageButton && ( | ||
| <styledEl.TitleActions> | ||
| <styledEl.TitleActionButton | ||
| id="token-selector-title-manage-button" | ||
| onClick={onOpenManageWidget} | ||
| aria-label="Manage token lists" | ||
| title="Manage token lists" | ||
|
Comment on lines
+79
to
+80
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should they be marked for translation?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but addressed in branch 10 (05327ab9e) |
||
| > | ||
| <SettingsIcon /> | ||
| </styledEl.TitleActionButton> | ||
| </styledEl.TitleActions> | ||
| )} | ||
| </styledEl.TitleBar> | ||
| ) | ||
|
Comment on lines
+62
to
+87
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Localize the “Manage token lists” labels (aria-label/title). 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,21 @@ | ||
| import { BalancesState } from '@cowprotocol/balances-and-allowances' | ||
| import { CHAIN_INFO } from '@cowprotocol/common-const' | ||
| import { getRandomInt } from '@cowprotocol/common-utils' | ||
| import { SupportedChainId, ChainInfo } from '@cowprotocol/cow-sdk' | ||
| import { BigNumber } from '@ethersproject/bignumber' | ||
|
|
||
| import styled from 'styled-components/macro' | ||
|
|
||
| import { allTokensMock, favoriteTokensMock } from '../../mocks' | ||
| import { mapChainInfo } from '../../utils/mapChainInfo' | ||
|
|
||
| import { SelectTokenModal, SelectTokenModalProps } from './index' | ||
|
|
||
| const Wrapper = styled.div` | ||
| max-height: 90vh; | ||
| margin: 20px auto; | ||
| display: flex; | ||
| width: 450px; | ||
| width: 520px; | ||
| ` | ||
|
|
||
| const unsupportedTokens = {} | ||
|
|
@@ -26,6 +28,20 @@ const balances = allTokensMock.reduce<BalancesState['values']>((acc, token) => { | |
| return acc | ||
| }, {}) | ||
|
|
||
| const chainsMock: ChainInfo[] = [ | ||
| SupportedChainId.MAINNET, | ||
| SupportedChainId.BASE, | ||
| SupportedChainId.ARBITRUM_ONE, | ||
| ].reduce<ChainInfo[]>((acc, id) => { | ||
| const info = CHAIN_INFO[id] | ||
|
|
||
| if (info) { | ||
| acc.push(mapChainInfo(id, info)) | ||
| } | ||
|
|
||
| return acc | ||
| }, []) | ||
|
|
||
| const defaultProps: SelectTokenModalProps = { | ||
| tokenListTags: {}, | ||
| account: undefined, | ||
|
|
@@ -35,7 +51,11 @@ const defaultProps: SelectTokenModalProps = { | |
| favoriteTokens: favoriteTokensMock, | ||
| areTokensLoading: false, | ||
| areTokensFromBridge: false, | ||
| chainsToSelect: undefined, | ||
| chainsToSelect: { | ||
| chains: chainsMock, | ||
| isLoading: false, | ||
| defaultChainId: SupportedChainId.MAINNET, | ||
| }, | ||
|
Comment on lines
+54
to
+58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.cosmos.tsx | head -80Repository: cowprotocol/cowswap Length of output: 2790 🏁 Script executed: cd apps/cowswap-frontend && grep -n "SEPOLIA\|MAINNET" src/modules/tokensList/pure/SelectTokenModal/index.cosmos.tsxRepository: cowprotocol/cowswap Length of output: 238 🏁 Script executed: cd apps/cowswap-frontend && head -50 src/modules/tokensList/pure/SelectTokenModal/index.tsxRepository: cowprotocol/cowswap Length of output: 1499 🏁 Script executed: cd apps/cowswap-frontend && grep -B5 -A5 "balancesState:" src/modules/tokensList/pure/SelectTokenModal/index.cosmos.tsxRepository: cowprotocol/cowswap Length of output: 325 🏁 Script executed: cd apps/cowswap-frontend && sed -n '31,43p' src/modules/tokensList/pure/SelectTokenModal/index.cosmos.tsxRepository: cowprotocol/cowswap Length of output: 326 Fix chain ID mismatch in fixture: defaultChainId (MAINNET) vs balancesState.chainId (SEPOLIA). Also applies to: 66-67 🤖 Prompt for AI Agents |
||
| onSelectChain(chain: ChainInfo) { | ||
| console.log('onSelectChain', chain) | ||
| }, | ||
|
|
@@ -48,6 +68,7 @@ const defaultProps: SelectTokenModalProps = { | |
| }, | ||
| selectedToken, | ||
| isRouteAvailable: true, | ||
| modalTitle: 'Swap from', | ||
| recentTokens: favoriteTokensMock.slice(0, 2), | ||
| selectedTargetChainId: SupportedChainId.SEPOLIA, | ||
| onSelectToken() { | ||
|
|
||
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.
isRouteAvailableis ignored whendisplayLpTokenListsis true.Right now the “route not supported” message (SelectTokenModalContent) is only applied in the non-LP branch; if
isRouteAvailable === falseduring LP view, the warning won’t render. Consider wrappingchildrenwithSelectTokenModalContentin both branches (or documenting why LP mode is exempt).🤖 Prompt for AI Agents