import { Checkout, TokenFilterTypes, TokenInfo } from '@imtbl/checkout-sdk'; import { Box, ButtCon, Drawer, FramedIcon, FramedImage, MenuItem, SmartClone, TextInput, VerticalMenu, } from '@biom3/react'; import { Dispatch, type MouseEventHandler, useCallback, useEffect, useMemo, useState, } from 'react'; import { Environment } from '@imtbl/config'; import { useTranslation } from 'react-i18next'; import type { StrongCheckoutWidgetsConfig } from '../../../lib/withDefaultWidgetConfig'; import { AddTokensAction, AddTokensActions, AddTokensState, } from '../context/AddTokensContext'; import { useError } from '../hooks/useError'; import { getDefaultTokenImage, getTokenImageByAddress, isNativeToken, } from '../../../lib/utils'; import { AddTokensErrorTypes } from '../types'; import { TokenImage } from '../../../components/TokenImage/TokenImage'; import { PULSE_SHADOW } from '../utils/animation'; import { TOKEN_PRIORITY_ORDER } from '../../../lib/squid/config'; export interface TokenDrawerMenuProps { checkout: Checkout; config: StrongCheckoutWidgetsConfig; toTokenAddress?: string; addTokensState: AddTokensState; addTokensDispatch: Dispatch; } export function TokenDrawerMenu({ checkout, config, toTokenAddress, addTokensState, addTokensDispatch, }: TokenDrawerMenuProps) { const { showErrorHandover } = useError(config.environment); const [visible, setVisible] = useState(false); const [searchValue, setSearchValue] = useState(''); const defaultTokenImage = getDefaultTokenImage( checkout?.config.environment, config.theme, ); const { t } = useTranslation(); const setSelectedToken = (token: TokenInfo | undefined) => { addTokensDispatch({ payload: { type: AddTokensActions.SET_SELECTED_TOKEN, selectedToken: token, }, }); }; const handleTokenChange = useCallback( (token: TokenInfo) => { setSelectedToken(token); setVisible(false); setSearchValue(''); }, [addTokensState.id], ); const isSelected = useCallback( (token: TokenInfo) => token.address === addTokensState.selectedToken?.address, [addTokensState.selectedToken], ); const tokenChoiceOptions = useMemo( () => addTokensState.allowedTokens?.filter((token) => { if (!searchValue) return true; return token.symbol.toLowerCase().startsWith(searchValue.toLowerCase()); }), [ addTokensState.allowedTokens, addTokensState.tokens, handleTokenChange, isSelected, defaultTokenImage, searchValue, ], ); const handleTokenIconClick = useCallback< MouseEventHandler >(() => { setVisible(!visible); }, [visible]); const handleDrawerClose = useCallback(() => { setVisible(false); setSearchValue(''); }, [setVisible, setSearchValue]); useEffect(() => { if (!checkout || addTokensState.tokens != null) return; (async () => { try { const tokenResponse = await checkout.getTokenAllowList({ type: TokenFilterTypes.SWAP, chainId: checkout.config.l2ChainId, }); if (tokenResponse?.tokens.length > 0) { const updatedTokens = tokenResponse.tokens.map((token) => { if (isNativeToken(token.address)) { return { ...token, icon: getTokenImageByAddress( checkout.config.environment as Environment, token.symbol, ), }; } return token; }); updatedTokens.sort((a, b) => { const aIndex = TOKEN_PRIORITY_ORDER.findIndex( (token) => token === a.symbol, ); const bIndex = TOKEN_PRIORITY_ORDER.findIndex( (token) => token === b.symbol, ); // If both tokens are not in the priority list, sort by symbol if (aIndex === -1 && bIndex === -1) { return a.symbol.localeCompare(b.symbol); } // If only one token is in the priority list, sort it first if (aIndex === -1) { return 1; } if (bIndex === -1) { return -1; } // If both tokens are in the priority list, sort by index return aIndex < bIndex ? -1 : 1; }); if (toTokenAddress) { const preselectedToken = updatedTokens.find( (token) => token.address?.toLowerCase() === toTokenAddress.toLowerCase(), ); if (preselectedToken) { setSelectedToken(preselectedToken); } } addTokensDispatch({ payload: { type: AddTokensActions.SET_ALLOWED_TOKENS, allowedTokens: updatedTokens, }, }); } } catch (error) { showErrorHandover(AddTokensErrorTypes.SERVICE_BREAKDOWN); } })(); }, [addTokensState.tokens, checkout, toTokenAddress]); return ( {addTokensState.selectedToken ? ( } > div:first-of-type': { boxShadow: ({ base }) => `0 0 0 ${base.border.size[200]} ${base.color.text.body.primary}`, }, }} > )} padded emphasized circularFrame sx={{ mb: 'base.spacing.x1', }} /> ) : ( )} { setSearchValue(event.target.value); }} > {tokenChoiceOptions && tokenChoiceOptions.length > 0 && tokenChoiceOptions.map((token) => ( handleTokenChange(token)} selected={isSelected(token)} emphasized > )} emphasized={false} /> {token.symbol} {token.symbol !== token.name && ( {token.name} )} ))} ); }