From 5d22bef43df21b6dbca9e13767cd9726883ecc64 Mon Sep 17 00:00:00 2001 From: mikasackermn Date: Sat, 30 Dec 2023 11:01:10 +0000 Subject: [PATCH] feat: update filter tokens interface in widget --- .../src/hooks/useSyncStoresWithConfig.ts | 51 ++--- widget/embedded/src/index.ts | 2 + widget/embedded/src/store/slices/data.ts | 12 +- widget/embedded/src/types/config.ts | 19 +- widget/embedded/src/utils/configs.ts | 41 ++++ .../components/MultiList/MultiList.styles.ts | 11 +- .../src/components/MultiList/MultiList.tsx | 1 + .../components/MultiSelect/MultiSelect.tsx | 3 +- .../MultiSelect/MultiSelect.types.ts | 7 +- .../TokensPanel/TokensPanel.List.tsx | 8 +- .../components/TokensPanel/TokensPanel.tsx | 199 ++++++++++++++++-- .../TokensPanel/TokensPanel.types.ts | 10 +- .../DefaultChainAndToken.tsx | 13 +- .../SupportedTokens/SupportedTokens.tsx | 89 ++++---- widget/playground/src/store/config.ts | 6 +- widget/playground/src/utils/configs.ts | 25 +++ 16 files changed, 382 insertions(+), 115 deletions(-) diff --git a/widget/embedded/src/hooks/useSyncStoresWithConfig.ts b/widget/embedded/src/hooks/useSyncStoresWithConfig.ts index 839f5ee7ba..9611be6ac5 100644 --- a/widget/embedded/src/hooks/useSyncStoresWithConfig.ts +++ b/widget/embedded/src/hooks/useSyncStoresWithConfig.ts @@ -1,9 +1,13 @@ import type { Asset } from 'rango-sdk'; -import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'; +import { useEffect, useLayoutEffect, useRef } from 'react'; import { useAppStore } from '../store/AppStore'; import { useQuoteStore } from '../store/quote'; +import { + isBlockchainExcludedInConfig, + isTokenExcludedInConfig, +} from '../utils/configs'; import { tokensAreEqual } from '../utils/wallets'; export function useSyncStoresWithConfig() { @@ -27,22 +31,10 @@ export function useSyncStoresWithConfig() { const { setAffiliateRef, setAffiliatePercent, setAffiliateWallets } = useAppStore(); - const fromTokensConfig = useMemo( - () => config?.from?.tokens, - [config?.from?.tokens] - ); - const fromBlockchainsConfig = useMemo( - () => config?.from?.blockchains, - [config?.from?.blockchains] - ); - const toTokensConfig = useMemo( - () => config?.to?.tokens, - [config?.to?.tokens] - ); - const toBlockchainsConfig = useMemo( - () => config?.to?.blockchains, - [config?.to?.blockchains] - ); + const fromTokensConfig = config?.from?.tokens; + const fromBlockchainsConfig = config?.from?.blockchains; + const toTokensConfig = config?.to?.tokens; + const toBlockchainsConfig = config?.to?.blockchains; const prevConfigFromToken = useRef(undefined); const prevConfigToToken = useRef(undefined); const prevConfigFromBlockchain = useRef(undefined); @@ -90,30 +82,21 @@ export function useSyncStoresWithConfig() { ]); useEffect(() => { - if (fromToken && fromTokensConfig) { - if (!fromTokensConfig.some((token) => tokensAreEqual(token, fromToken))) { - setFromToken({ token: null }); - } + if (isTokenExcludedInConfig(fromToken, fromTokensConfig)) { + setFromToken({ token: null }); } - if (fromBlockchain && fromBlockchainsConfig) { - if (!fromBlockchainsConfig.includes(fromBlockchain.name)) { - setFromBlockchain(null); - } + if (isBlockchainExcludedInConfig(fromBlockchain, fromBlockchainsConfig)) { + setFromBlockchain(null); } }, [fromTokensConfig, fromBlockchainsConfig]); useEffect(() => { - if (toToken && toTokensConfig) { - if (!toTokensConfig.some((token) => tokensAreEqual(token, toToken))) { - setToToken({ token: null }); - } + if (isTokenExcludedInConfig(toToken, toTokensConfig)) { + setFromToken({ token: null }); } - - if (toBlockchain && toBlockchainsConfig) { - if (!toBlockchainsConfig.includes(toBlockchain.name)) { - setToBlockchain(null); - } + if (isBlockchainExcludedInConfig(toBlockchain, toBlockchainsConfig)) { + setToBlockchain(null); } }, [toTokensConfig, toBlockchainsConfig]); diff --git a/widget/embedded/src/index.ts b/widget/embedded/src/index.ts index e8ee2b1fac..8330a6d07a 100644 --- a/widget/embedded/src/index.ts +++ b/widget/embedded/src/index.ts @@ -2,6 +2,7 @@ import type { WidgetProps } from './containers/Widget'; import type { ConnectedWallet } from './store/wallets'; import type { BlockchainAndTokenConfig, + Tokens, WidgetColors, WidgetColorsKeys, WidgetConfig, @@ -86,6 +87,7 @@ export type { StepOutputRevealedEvent, HandleWalletsUpdate, ConnectedWallet, + Tokens, }; export { Widget, diff --git a/widget/embedded/src/store/slices/data.ts b/widget/embedded/src/store/slices/data.ts index d4460a0994..1af5c6f3d3 100644 --- a/widget/embedded/src/store/slices/data.ts +++ b/widget/embedded/src/store/slices/data.ts @@ -6,6 +6,7 @@ import type { StateCreator } from 'zustand'; import { httpService as sdk } from '../../services/httpService'; import { containsText } from '../../utils/common'; +import { isTokenExcludedInConfig } from '../../utils/configs'; import { sortLiquiditySourcesByGroupTitle } from '../../utils/settings'; import { tokensAreEqual } from '../../utils/wallets'; @@ -81,21 +82,18 @@ export const createDataSlice: StateCreator< } const config = get().config; - const supportedTokensFromConfig = + const supportedTokensConfig = (options.type === 'source' ? config.from?.tokens : config.to?.tokens) ?? - []; + {}; const blockchains = get().blockchains({ type: options.type, }); const list = tokensFromState .filter((token) => { - // If there is a list of tokens in config, we only keep them. if ( - supportedTokensFromConfig.length > 0 && - !supportedTokensFromConfig.some((asset) => { - return tokensAreEqual(asset, token); - }) + supportedTokensConfig && + isTokenExcludedInConfig(token, supportedTokensConfig) ) { return false; } diff --git a/widget/embedded/src/types/config.ts b/widget/embedded/src/types/config.ts index f53c8b09aa..52f5397565 100644 --- a/widget/embedded/src/types/config.ts +++ b/widget/embedded/src/types/config.ts @@ -67,6 +67,19 @@ export type WidgetAffiliate = { wallets?: { [key: string]: string }; }; +/** + * `Tokens` + * + * @property {boolean} isExcluded - This is a boolean property capable of removing tokens from all or a blockchain. + * @property {Asset[]} tokens - The `tokens` property is an array of `Asset` objects that + * you could use that to limit tokens to some limited ones. + */ + +export type Tokens = { + isExcluded: boolean; + tokens: Asset[]; +}; + /** * `BlockchainAndTokenConfig` * @@ -75,8 +88,8 @@ export type WidgetAffiliate = { * which is selected. e.g. {blockchain: 'BSC', symbol: 'BNB', address: null} * @property {string[]} blockchains - An optional array of strings representing the supported * blockchains. e.g. ['BSC','ETHEREUM'] - * @property {Asset[]} tokens - The `tokens` property is an optional array of `Asset` objects that - * you could use that to limit tokens to some limited ones. + * @property {Asset[] |{ [blockchain: string]: Tokens }} tokens - The `tokens` property is an optional object of `blockchain` objects or array of `Asset` objects + * that you could use that to limit tokens to some limited ones. * @property {Asset} pinnedTokens - The `pinnedTokens` property is an optional array of `Asset` objects that * you could use to pin tokens of your choice to the top of the token list. */ @@ -84,8 +97,8 @@ export type BlockchainAndTokenConfig = { blockchain?: string; token?: Asset; blockchains?: string[]; - tokens?: Asset[]; pinnedTokens?: Asset[]; + tokens?: Asset[] | { [blockchain: string]: Tokens }; }; /** diff --git a/widget/embedded/src/utils/configs.ts b/widget/embedded/src/utils/configs.ts index 92d6ba0d7e..276c32f227 100644 --- a/widget/embedded/src/utils/configs.ts +++ b/widget/embedded/src/utils/configs.ts @@ -1,10 +1,21 @@ +import type { Tokens } from '../types'; +import type { Asset, BlockchainMeta, Token } from 'rango-sdk'; + import { RANGO_PUBLIC_API_KEY } from '../constants'; +import { tokensAreEqual } from './wallets'; + export interface Configs { API_KEY: string; BASE_URL?: string; } +type TokensConfig = + | Asset[] + | { + [blockchain: string]: Tokens; + }; + let configs: Configs = { API_KEY: RANGO_PUBLIC_API_KEY, }; @@ -38,3 +49,33 @@ export const DEFAULT_SECONDARY_RADIUS = 25; export const DEFAULT_FONT_FAMILY = 'Roboto'; export const THEME_CLASS_NAME_PREFIX = `theme-widget`; + +export const isTokenExcludedInConfig = ( + token: Token | null, + tokensConfig?: TokensConfig +) => { + let result = false; + if (tokensConfig && token) { + if (Array.isArray(tokensConfig)) { + result = !tokensConfig.some((asset) => tokensAreEqual(asset, token)); + } else if (!Array.isArray(tokensConfig) && tokensConfig[token.blockchain]) { + result = tokensConfig[token.blockchain].tokens.some((asset) => + tokensAreEqual(asset, token) + ); + const isExcluded = tokensConfig[token.blockchain].isExcluded; + return (!isExcluded && !result) || (isExcluded && result); + } + } + return result; +}; + +export const isBlockchainExcludedInConfig = ( + blockchain: BlockchainMeta | null, + blockchainsConfig?: string[] +) => { + return ( + blockchain && + blockchainsConfig && + !blockchainsConfig.includes(blockchain.name) + ); +}; diff --git a/widget/playground/src/components/MultiList/MultiList.styles.ts b/widget/playground/src/components/MultiList/MultiList.styles.ts index d3ef775734..cf7c978de9 100644 --- a/widget/playground/src/components/MultiList/MultiList.styles.ts +++ b/widget/playground/src/components/MultiList/MultiList.styles.ts @@ -19,8 +19,15 @@ export const SelectButton = styled('div', { }); export const SelectDeselectText = styled(Typography, { - '&:hover': { - color: '$secondary500', + variants: { + disabled: { + true: {}, + false: { + '&:hover': { + color: '$secondary500', + }, + }, + }, }, }); export const CheckList = styled('div', { diff --git a/widget/playground/src/components/MultiList/MultiList.tsx b/widget/playground/src/components/MultiList/MultiList.tsx index df863e060b..b492a37bc8 100644 --- a/widget/playground/src/components/MultiList/MultiList.tsx +++ b/widget/playground/src/components/MultiList/MultiList.tsx @@ -179,6 +179,7 @@ export function MultiList(props: MultiListPropTypes) { {isAllCategorySelected ? 'Deselect all' : 'Select all'} diff --git a/widget/playground/src/components/MultiSelect/MultiSelect.tsx b/widget/playground/src/components/MultiSelect/MultiSelect.tsx index a13b8b80e2..5489facbbe 100644 --- a/widget/playground/src/components/MultiSelect/MultiSelect.tsx +++ b/widget/playground/src/components/MultiSelect/MultiSelect.tsx @@ -60,7 +60,7 @@ export function MultiSelect(props: MuliSelectPropTypes) { )} - + {showNextModal && ( @@ -87,6 +87,7 @@ export function MultiSelect(props: MuliSelectPropTypes) { ) : ( { props.onChange(selectedTokens, pinnedTokens); onBack(); diff --git a/widget/playground/src/components/MultiSelect/MultiSelect.types.ts b/widget/playground/src/components/MultiSelect/MultiSelect.types.ts index f69ad855ed..0e768f35c4 100644 --- a/widget/playground/src/components/MultiSelect/MultiSelect.types.ts +++ b/widget/playground/src/components/MultiSelect/MultiSelect.types.ts @@ -1,4 +1,5 @@ import type { TokenType } from '../TokensPanel/TokensPanel.types'; +import type { Tokens } from '@rango-dev/widget-embedded'; export interface CommonListProps { type: 'Blockchains' | 'Bridges' | 'DEXs' | 'Wallets'; @@ -11,7 +12,11 @@ interface TokensListProps { type: 'Tokens'; list: TokenType[]; selectedBlockchains: string[]; - onChange: (selectedTokens?: TokenType[], pinnedTokens?: TokenType[]) => void; + onChange: ( + selectedTokens?: { [blockchain: string]: Tokens }, + pinnedTokens?: TokenType[] + ) => void; + tokensConfig: { [blockchain: string]: Tokens }; } export type MuliSelectPropTypes = (TokensListProps | CommonListProps) & { value?: string[]; diff --git a/widget/playground/src/components/TokensPanel/TokensPanel.List.tsx b/widget/playground/src/components/TokensPanel/TokensPanel.List.tsx index 0666617da3..1db47c257f 100644 --- a/widget/playground/src/components/TokensPanel/TokensPanel.List.tsx +++ b/widget/playground/src/components/TokensPanel/TokensPanel.List.tsx @@ -37,6 +37,7 @@ export function TokensList(props: TokensListProps) { showSelectedTokens, setShowSelectedTokens, isAllSelected, + isExcluded, } = props; const [searchValue, setSearchValue] = useState(''); const [virtualList, setVirtualList] = useState(list); @@ -123,13 +124,14 @@ export function TokensList(props: TokensListProps) { {isAllSelected ? 'Deselect all' : 'Select all'}
- Selected Tokens + {isExcluded ? 'Excluded' : 'Included'} Tokens toggleTokenSelection(virtualList[index])} end={ <> - {virtualList[index].checked && ( + {((virtualList[index].checked && !isExcluded) || + (!virtualList[index].checked && isExcluded)) && ( <> { e.stopPropagation(); diff --git a/widget/playground/src/components/TokensPanel/TokensPanel.tsx b/widget/playground/src/components/TokensPanel/TokensPanel.tsx index 750f6fef79..ae4329491a 100644 --- a/widget/playground/src/components/TokensPanel/TokensPanel.tsx +++ b/widget/playground/src/components/TokensPanel/TokensPanel.tsx @@ -1,6 +1,8 @@ import type { PropTypes, TokenType } from './TokensPanel.types'; +import type { Tokens } from '@rango-dev/widget-embedded'; +import type { Asset } from 'rango-sdk'; -import { ChainsIcon, Divider, Typography } from '@rango-dev/ui'; +import { ChainsIcon, Checkbox, Divider, Typography } from '@rango-dev/ui'; import React, { useState } from 'react'; import { tokensAreEqual } from '../../utils/common'; @@ -26,14 +28,117 @@ export function TokensPanel(props: PropTypes) { list: listProps, selectedBlockchains: selectedBlockchainsProps, onChange, + tokensConfig, } = props; const [selectedBlockchain, setSelectedBlockchain] = useState( selectedBlockchainsProps[0] ); + const [list, setList] = useState(listProps); + + const [supportedTokenList, setSupportedTokenList] = useState( + tokensConfig || {} + ); const [showSelectedTokens, setShowSelectedTokens] = useState(false); + const getTokens = ( + selected: boolean, + allTokensInBlockchain: Asset[], + token?: TokenType + ) => { + if (!token) { + return selected ? allTokensInBlockchain : []; + } + const { blockchain, symbol, address } = token; + if (!selected) { + return [ + ...supportedTokenList[blockchain].tokens.filter( + (t) => !tokensAreEqual(t, token) + ), + ]; + } + return [ + ...supportedTokenList[blockchain].tokens, + { symbol, address, blockchain }, + ]; + }; + + /* + * This function is designed to operate in two scenarios: + * + * 1. Activating the checkbox by clicking on it. + * 2. Choosing "Select All" for each blockchain. + * + * If the token has been dispatched, it indicates that the checkbox has been selected. + * Conversely, if the token has not been dispatched, it implies that another one option has been chosen. + * The resulting output fulfills our configuration requirements. + */ + + const makeSupportedTokenList = ( + blockchain: string, + tokens: { + [blockchain: string]: Tokens; + }, + selected: boolean, + token?: TokenType + ) => { + const allTokensInBlockchain = list + .filter((item) => item.blockchain === blockchain) + .map(({ symbol, blockchain, address }) => ({ + symbol, + blockchain, + address, + })); + if (supportedTokenList[blockchain]) { + const blockchainTokens = supportedTokenList[blockchain].tokens; + if (!supportedTokenList[blockchain].isExcluded) { + /* + * This condition is for when the select option is true and show that all tokens have been selected. + * If there is a token, it means that one token has been added to the rest of the tokens, so its lenght is equal to all the tokens of that blockchain. + * If there is no token, it means that select all is selected done + */ + if ( + selected && + (blockchainTokens.length + 1 === allTokensInBlockchain.length || + !token) + ) { + const { [blockchain]: deletedKey, ...otherKeys } = tokens; + return otherKeys; + } + } + + return { + ...tokens, + [blockchain]: { + ...tokens[blockchain], + tokens: getTokens(selected, allTokensInBlockchain, token), + }, + }; + } + + return { + ...tokens, + [blockchain]: { + isExcluded: false, + tokens: + !token && !selected + ? [] + : allTokensInBlockchain.filter((t) => !tokensAreEqual(t, token)), + }, + }; + }; + const handleChange = (token: TokenType, type: 'checked' | 'pinned') => { + if (type === 'checked') { + setSupportedTokenList( + makeSupportedTokenList( + selectedBlockchain, + supportedTokenList, + !token.checked, + token + ) + ); + } setList((prev) => prev.map((item) => { if (tokensAreEqual(token, item)) { @@ -47,6 +152,16 @@ export function TokensPanel(props: PropTypes) { }; const handleSelectDeselectInBlockchain = (selected: boolean) => { + const tokenList = { + ...makeSupportedTokenList( + selectedBlockchain, + supportedTokenList, + selected + ), + }; + + setSupportedTokenList(tokenList); + setList((prev) => prev.map((item) => { if (item.blockchain === selectedBlockchain) { @@ -56,23 +171,57 @@ export function TokensPanel(props: PropTypes) { }) ); }; + const notAllTokensSelected = list.some((item) => !item.checked); - const handleAllSelectDeselect = (selected: boolean) => () => { - setList((prev) => - prev.map((item) => { - return { ...item, checked: selected }; - }) - ); + const handleResetTokens = () => { + if (notAllTokensSelected) { + setSupportedTokenList({}); + setList((prev) => + prev.map((item) => { + return { ...item, checked: true }; + }) + ); + } }; - const notAllTokensSelected = list.some((item) => !item.checked); - const handleConfirmAllList = () => { - const allChecked = list.filter((item) => item.checked); - const allSelected = listProps.length === allChecked.length; const allPinned = list.filter((item) => item.pinned); + onChange( + supportedTokenList && !Object.keys(supportedTokenList).length + ? undefined + : supportedTokenList, + allPinned + ); + }; - onChange(allSelected ? undefined : allChecked, allPinned); + const onExcludedChange = () => { + const allTokensInBlockchain = list.filter( + (item) => selectedBlockchain === item.blockchain + ); + const tokenList = { + ...supportedTokenList, + [selectedBlockchain]: { + isExcluded: supportedTokenList[selectedBlockchain] + ? !supportedTokenList[selectedBlockchain].isExcluded + : true, + tokens: allTokensInBlockchain + .filter((t) => !t.checked) + .map(({ symbol, blockchain, address }) => ({ + symbol, + blockchain, + address, + })), + }, + }; + setList((prev) => + prev.map((item) => { + if (item.blockchain === selectedBlockchain) { + return { ...item, checked: !item.checked }; + } + return item; + }) + ); + setSupportedTokenList(tokenList); }; return ( @@ -85,9 +234,13 @@ export function TokensPanel(props: PropTypes) { Supported Tokens
- - - {notAllTokensSelected ? 'Select all Tokens' : 'Deselect all Tokens'} + + + Reset Tokens @@ -104,9 +257,25 @@ export function TokensPanel(props: PropTypes) { ))} + + Exclude {selectedBlockchain} Tokens + + } + /> + token.blockchain === selectedBlockchain)} onChange={handleChange} + isExcluded={supportedTokenList[selectedBlockchain]?.isExcluded || false} setShowSelectedTokens={setShowSelectedTokens} showSelectedTokens={showSelectedTokens} isAllSelected={getItemCountLabel(selectedBlockchain, list) === 'All'} diff --git a/widget/playground/src/components/TokensPanel/TokensPanel.types.ts b/widget/playground/src/components/TokensPanel/TokensPanel.types.ts index 996f19e5ab..7ea341ca41 100644 --- a/widget/playground/src/components/TokensPanel/TokensPanel.types.ts +++ b/widget/playground/src/components/TokensPanel/TokensPanel.types.ts @@ -1,10 +1,15 @@ +import type { Tokens } from '@rango-dev/widget-embedded'; import type { Token } from 'rango-sdk'; -export type TokenType = Token & { checked: boolean; pinned: boolean }; +export type TokenType = Token & { checked?: boolean; pinned?: boolean }; export interface PropTypes { list: TokenType[]; selectedBlockchains: string[]; - onChange: (selectedTokens?: TokenType[], pinnedTokens?: TokenType[]) => void; + onChange: ( + items?: { [blockchain: string]: Tokens }, + pinnedTokens?: TokenType[] + ) => void; + tokensConfig?: { [blockchain: string]: Tokens }; } export interface TokensListProps { @@ -14,6 +19,7 @@ export interface TokensListProps { setShowSelectedTokens: (show: boolean) => void; list: TokenType[]; isAllSelected: boolean; + isExcluded: boolean; } export interface BlockchainProps { diff --git a/widget/playground/src/containers/DefaultChainAndToken/DefaultChainAndToken.tsx b/widget/playground/src/containers/DefaultChainAndToken/DefaultChainAndToken.tsx index 40e2d2d8a3..9b18bffaf0 100644 --- a/widget/playground/src/containers/DefaultChainAndToken/DefaultChainAndToken.tsx +++ b/widget/playground/src/containers/DefaultChainAndToken/DefaultChainAndToken.tsx @@ -1,4 +1,5 @@ import type { Type } from '../../types'; +import type { tokensConfigType } from '../../utils/configs'; import { ChainsIcon, Divider, Tooltip } from '@rango-dev/ui'; import React, { useState } from 'react'; @@ -9,6 +10,7 @@ import { SingleList } from '../../components/SingleList'; import { useConfigStore } from '../../store/config'; import { useMetaStore } from '../../store/meta'; import { tokensAreEqual, tokenToString } from '../../utils/common'; +import { isTokenExcludedInConfig } from '../../utils/configs'; import { ModalState } from '../FunctionalLayout/FunctionalLayout.types'; export function DefaultChainAndToken({ type }: { type: Type }) { @@ -23,13 +25,12 @@ export function DefaultChainAndToken({ type }: { type: Type }) { } = useMetaStore(); const selectedType = type === 'Source' ? from : to; - const configTokens = selectedType?.tokens; + const tokensConfig = selectedType?.tokens as tokensConfigType; const filteredTokens = selectedType?.blockchain - ? tokens.filter( - (token) => - token.blockchain === selectedType.blockchain && - (!configTokens || configTokens.some((t) => tokensAreEqual(token, t))) - ) + ? tokens.filter((token) => { + const isToken = isTokenExcludedInConfig(token, tokensConfig); + return token.blockchain === selectedType.blockchain && !isToken; + }) : []; const chainValue = blockchains.find( (chain) => chain.name === selectedType?.blockchain diff --git a/widget/playground/src/containers/SupportedTokens/SupportedTokens.tsx b/widget/playground/src/containers/SupportedTokens/SupportedTokens.tsx index e8902a8ec3..bef85ca520 100644 --- a/widget/playground/src/containers/SupportedTokens/SupportedTokens.tsx +++ b/widget/playground/src/containers/SupportedTokens/SupportedTokens.tsx @@ -1,4 +1,5 @@ import type { Type } from '../../types'; +import type { tokensConfigType } from '../../utils/configs'; import { ChainsIcon } from '@rango-dev/ui'; import React from 'react'; @@ -7,6 +8,7 @@ import { MultiSelect } from '../../components/MultiSelect/MultiSelect'; import { useConfigStore } from '../../store/config'; import { useMetaStore } from '../../store/meta'; import { tokensAreEqual } from '../../utils/common'; +import { isTokenExcludedInConfig } from '../../utils/configs'; export function SupportedTokens({ type }: { type: Type }) { const { @@ -21,61 +23,66 @@ export function SupportedTokens({ type }: { type: Type }) { const selectedType = type === 'Source' ? from : to; - const configTokens = selectedType?.tokens; + const tokensConfig = selectedType?.tokens as tokensConfigType; const pinnedTokens = selectedType?.pinnedTokens; - const seletedBlockchains = selectedType?.blockchains; - const allTokens = seletedBlockchains - ? tokens.filter((token) => seletedBlockchains.includes(token.blockchain)) + const selectedBlockchains = selectedType?.blockchains; + const allTokens = selectedBlockchains + ? tokens.filter((token) => selectedBlockchains.includes(token.blockchain)) : tokens; const defaultBlockchains = - seletedBlockchains || blockchains.map((chain) => chain.name); + selectedBlockchains || blockchains.map((chain) => chain.name); + + const tokensWithCheck = allTokens.map((token) => { + const isToken = isTokenExcludedInConfig(token, tokensConfig); + const isExclude = + tokensConfig && + tokensConfig[token.blockchain] && + tokensConfig[token.blockchain].isExcluded; - const list = allTokens.map((token) => { return { ...token, checked: - !configTokens || configTokens.some((ct) => tokensAreEqual(ct, token)), + !tokensConfig || (!isExclude && !isToken) || (isExclude && isToken), pinned: !!pinnedTokens && pinnedTokens.some((ct) => tokensAreEqual(ct, token)), }; }); - const isAllTokens = !configTokens || configTokens.length === list.length; + const checkedTokens = tokensWithCheck.filter((item) => item.checked); + + const isAllTokens = + !tokensConfig || checkedTokens.length === tokensWithCheck.length; return ( - } - selectedBlockchains={defaultBlockchains} - value={ - isAllTokens - ? undefined - : defaultBlockchains.filter((blockchain) => - configTokens?.some((token) => token.blockchain === blockchain) - ) - } - list={list} - onChange={(selectedTokens, pinnedTokens) => { - onChangePinnedTokens( - pinnedTokens?.map(({ symbol, blockchain, address }) => ({ - symbol, - blockchain, - address, - })), - type - ); - onChangeTokens( - selectedTokens?.map(({ symbol, blockchain, address }) => ({ - symbol, - blockchain, - address, - })), - type - ); - onChangeToken(undefined, type); - }} - /> + <> + } + selectedBlockchains={defaultBlockchains} + value={ + isAllTokens + ? undefined + : defaultBlockchains.filter((blockchain) => + checkedTokens?.some((token) => token.blockchain === blockchain) + ) + } + tokensConfig={tokensConfig || {}} + list={tokensWithCheck} + onChange={(selectedTokens, pinnedTokens) => { + onChangePinnedTokens( + pinnedTokens?.map(({ symbol, blockchain, address }) => ({ + symbol, + blockchain, + address, + })), + type + ); + onChangeTokens(selectedTokens, type); + onChangeToken(undefined, type); + }} + /> + ); } diff --git a/widget/playground/src/store/config.ts b/widget/playground/src/store/config.ts index d93c7a1e13..70645d8f90 100644 --- a/widget/playground/src/store/config.ts +++ b/widget/playground/src/store/config.ts @@ -2,6 +2,7 @@ import type { Type } from '../types'; import type { WalletType } from '@rango-dev/wallets-shared'; import type { ProviderInterface, + Tokens, WidgetColors, WidgetColorsKeys, WidgetConfig, @@ -25,8 +26,11 @@ interface ConfigState { onChangeWallets: (wallets?: (WalletType | ProviderInterface)[]) => void; onChangeSources: (sources?: string[]) => void; onChangeBlockChains: (chains?: string[], type?: Type) => void; - onChangeTokens: (tokens?: Asset[], type?: Type) => void; onChangePinnedTokens: (tokens?: Asset[], type?: Type) => void; + onChangeTokens: ( + tokens?: { [blockchain: string]: Tokens }, + type?: Type + ) => void; onChangeBooleansConfig: ( name: | 'multiWallets' diff --git a/widget/playground/src/utils/configs.ts b/widget/playground/src/utils/configs.ts index be822aeb49..ca815f0b43 100644 --- a/widget/playground/src/utils/configs.ts +++ b/widget/playground/src/utils/configs.ts @@ -1,3 +1,8 @@ +import type { Tokens } from '@rango-dev/widget-embedded'; +import type { Token } from 'rango-sdk'; + +import { tokensAreEqual } from './common'; + interface Configs { API_KEY: string; WC_PROJECT_ID: string; @@ -18,3 +23,23 @@ const configs: Configs = { export function getConfig(name: keyof Configs) { return configs[name]; } + +export type tokensConfigType = + | { + [blockchain: string]: Tokens; + } + | undefined; +export const isTokenExcludedInConfig = ( + token: Token, + tokensConfig?: tokensConfigType +) => { + let result = false; + if (tokensConfig && tokensConfig[token.blockchain]) { + result = tokensConfig[token.blockchain].tokens.some((asset) => + tokensAreEqual(asset, token) + ); + const isExcluded = tokensConfig[token.blockchain].isExcluded; + return (!isExcluded && !result) || (isExcluded && result); + } + return result; +};