1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-29 23:58:06 +01:00
metamask-extension/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js

335 lines
11 KiB
JavaScript
Raw Normal View History

2020-11-03 00:41:28 +01:00
import React, {
useState,
useCallback,
useEffect,
useContext,
useRef,
} from 'react';
2021-06-03 18:08:37 +02:00
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { isEqual } from 'lodash';
import { I18nContext } from '../../../contexts/i18n';
import SearchableItemList from '../searchable-item-list';
import PulseLoader from '../../../components/ui/pulse-loader';
import UrlIcon from '../../../components/ui/url-icon';
2023-06-15 20:17:21 +02:00
import {
Icon,
IconName,
IconSize,
} from '../../../components/component-library';
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
2021-06-03 18:08:37 +02:00
import ImportToken from '../import-token';
import {
isHardwareWallet,
getHardwareWalletType,
getCurrentChainId,
getRpcPrefsForCurrentProvider,
} from '../../../selectors/selectors';
import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps';
import { getURLHostName } from '../../../helpers/utils/util';
import {
getSmartTransactionsOptInStatus,
getSmartTransactionsEnabled,
getCurrentSmartTransactionsEnabled,
} from '../../../ducks/swaps/swaps';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
2020-10-06 20:28:38 +02:00
2020-11-03 00:41:28 +01:00
export default function DropdownSearchList({
2020-10-06 20:28:38 +02:00
searchListClassName,
itemsToSearch,
selectPlaceHolderText,
fuseSearchKeys,
defaultToAll,
maxListItems,
onSelect,
startingItem,
onOpen,
onClose,
className = '',
externallySelectedItem,
selectorClosedClassName,
loading,
hideRightLabels,
hideItemIf,
listContainerClassName,
2021-06-03 18:08:37 +02:00
shouldSearchForImports,
2020-10-06 20:28:38 +02:00
}) {
const t = useContext(I18nContext);
const [isOpen, setIsOpen] = useState(false);
2021-06-03 18:08:37 +02:00
const [isImportTokenModalOpen, setIsImportTokenModalOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState(startingItem);
2021-06-03 18:08:37 +02:00
const [tokenForImport, setTokenForImport] = useState(null);
const [searchQuery, setSearchQuery] = useState('');
2021-06-03 18:08:37 +02:00
const hardwareWalletUsed = useSelector(isHardwareWallet);
const hardwareWalletType = useSelector(getHardwareWalletType);
const chainId = useSelector(getCurrentChainId);
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
const smartTransactionsOptInStatus = useSelector(
getSmartTransactionsOptInStatus,
);
const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled);
const currentSmartTransactionsEnabled = useSelector(
getCurrentSmartTransactionsEnabled,
);
2021-06-03 18:08:37 +02:00
const trackEvent = useContext(MetaMetricsContext);
2021-06-03 18:08:37 +02:00
2020-10-23 23:08:46 +02:00
const close = useCallback(() => {
setIsOpen(false);
onClose?.();
}, [onClose]);
2020-10-23 23:08:46 +02:00
2020-11-03 00:41:28 +01:00
const onClickItem = useCallback(
(item) => {
onSelect?.(item);
setSelectedItem(item);
close();
2020-11-03 00:41:28 +01:00
},
[onSelect, close],
);
2020-10-06 20:28:38 +02:00
2021-06-03 18:08:37 +02:00
const onOpenImportTokenModalClick = (item) => {
setTokenForImport(item);
setIsImportTokenModalOpen(true);
};
/* istanbul ignore next */
2021-06-03 18:08:37 +02:00
const onImportTokenClick = () => {
trackEvent({
event: 'Token Imported',
category: MetaMetricsEventCategory.Swaps,
sensitiveProperties: {
symbol: tokenForImport?.symbol,
address: tokenForImport?.address,
chain_id: chainId,
is_hardware_wallet: hardwareWalletUsed,
hardware_wallet_type: hardwareWalletType,
stx_enabled: smartTransactionsEnabled,
current_stx_enabled: currentSmartTransactionsEnabled,
stx_user_opt_in: smartTransactionsOptInStatus,
},
});
2021-06-03 18:08:37 +02:00
// Only when a user confirms import of a token, we add it and show it in a dropdown.
onSelect?.(tokenForImport);
setSelectedItem(tokenForImport);
setTokenForImport(null);
close();
};
const onImportTokenCloseClick = () => {
setIsImportTokenModalOpen(false);
close();
};
2020-10-06 20:28:38 +02:00
const onClickSelector = useCallback(() => {
if (!isOpen) {
setIsOpen(true);
onOpen?.();
2020-10-06 20:28:38 +02:00
}
}, [isOpen, onOpen]);
2020-10-06 20:28:38 +02:00
const prevExternallySelectedItemRef = useRef();
2020-10-06 20:28:38 +02:00
useEffect(() => {
prevExternallySelectedItemRef.current = externallySelectedItem;
});
const prevExternallySelectedItem = prevExternallySelectedItemRef.current;
2020-10-06 20:28:38 +02:00
useEffect(() => {
2020-11-03 00:41:28 +01:00
if (
externallySelectedItem &&
!isEqual(externallySelectedItem, selectedItem)
) {
setSelectedItem(externallySelectedItem);
2020-10-06 20:28:38 +02:00
} else if (prevExternallySelectedItem && !externallySelectedItem) {
setSelectedItem(null);
2020-10-06 20:28:38 +02:00
}
}, [externallySelectedItem, selectedItem, prevExternallySelectedItem]);
2020-10-06 20:28:38 +02:00
const onKeyUp = (e) => {
if (e.key === 'Escape') {
close();
} else if (e.key === 'Enter') {
onClickSelector(e);
}
};
2021-06-03 18:08:37 +02:00
const blockExplorerLink =
rpcPrefs.blockExplorerUrl ??
SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[chainId] ??
null;
const blockExplorerHostName = getURLHostName(blockExplorerLink);
2021-06-03 18:08:37 +02:00
const importTokenProps = {
onImportTokenCloseClick,
onImportTokenClick,
setIsImportTokenModalOpen,
tokenForImport,
};
2020-10-06 20:28:38 +02:00
return (
<div
2020-10-06 20:28:38 +02:00
className={classnames('dropdown-search-list', className)}
data-testid="dropdown-search-list"
2020-10-06 20:28:38 +02:00
onClick={onClickSelector}
onKeyUp={onKeyUp}
tabIndex="0"
2020-10-06 20:28:38 +02:00
>
2021-06-03 18:08:37 +02:00
{tokenForImport && isImportTokenModalOpen && (
<ImportToken {...importTokenProps} />
)}
2020-10-06 20:28:38 +02:00
{!isOpen && (
<div
2020-11-03 00:41:28 +01:00
className={classnames(
'dropdown-search-list__selector-closed-container',
selectorClosedClassName,
)}
2020-10-06 20:28:38 +02:00
>
<div className="dropdown-search-list__selector-closed">
2020-11-03 00:41:28 +01:00
{selectedItem?.iconUrl && (
<UrlIcon
url={selectedItem.iconUrl}
className="dropdown-search-list__selector-closed-icon"
name={selectedItem?.symbol}
/>
)}
{!selectedItem?.iconUrl && (
<div className="dropdown-search-list__default-dropdown-icon" />
)}
2020-10-06 20:28:38 +02:00
<div className="dropdown-search-list__labels">
<div className="dropdown-search-list__item-labels">
<span
2020-11-03 00:41:28 +01:00
className={classnames(
'dropdown-search-list__closed-primary-label',
{
2022-07-31 20:26:40 +02:00
'dropdown-search-list__select-default':
!selectedItem?.symbol,
2020-11-03 00:41:28 +01:00
},
)}
>
{selectedItem?.symbol || selectPlaceHolderText}
2020-10-06 20:28:38 +02:00
</span>
</div>
</div>
</div>
2023-06-15 20:17:21 +02:00
<Icon name={IconName.ArrowDown} size={IconSize.Xs} marginRight={3} />
2020-10-06 20:28:38 +02:00
</div>
)}
{isOpen && (
<>
<SearchableItemList
itemsToSearch={loading ? [] : itemsToSearch}
Placeholder={() =>
/* istanbul ignore next */
2020-11-03 00:41:28 +01:00
loading ? (
2020-10-06 20:28:38 +02:00
<div className="dropdown-search-list__loading-item">
<PulseLoader />
<div className="dropdown-search-list__loading-item-text-container">
2020-11-03 00:41:28 +01:00
<span className="dropdown-search-list__loading-item-text">
{t('swapFetchingTokens')}
</span>
2020-10-06 20:28:38 +02:00
</div>
</div>
2020-11-03 00:41:28 +01:00
) : (
2020-10-06 20:28:38 +02:00
<div className="dropdown-search-list__placeholder">
{t('swapBuildQuotePlaceHolderText', [searchQuery])}
{blockExplorerLink && (
<div
tabIndex="0"
className="searchable-item-list__item searchable-item-list__item--add-token"
key="searchable-item-list-item-last"
>
<ActionableMessage
2023-06-15 20:17:21 +02:00
message={t('addTokenByContractAddress', [
2021-06-03 18:08:37 +02:00
<a
key="dropdown-search-list__etherscan-link"
onClick={() => {
trackEvent({
event: 'Clicked Block Explorer Link',
category: MetaMetricsEventCategory.Swaps,
properties: {
link_type: 'Token Tracker',
action: 'Verify Contract Address',
block_explorer_domain: blockExplorerHostName,
},
});
2021-06-03 18:08:37 +02:00
global.platform.openTab({
url: blockExplorerLink,
});
}}
target="_blank"
rel="noopener noreferrer"
>
{blockExplorerHostName}
2021-06-03 18:08:37 +02:00
</a>,
])}
/>
</div>
)}
2020-10-06 20:28:38 +02:00
</div>
)
2020-11-03 00:41:28 +01:00
}
searchPlaceholderText={t('swapSearchNameOrAddress')}
2020-10-06 20:28:38 +02:00
fuseSearchKeys={fuseSearchKeys}
defaultToAll={defaultToAll}
onClickItem={onClickItem}
2021-06-03 18:08:37 +02:00
onOpenImportTokenModalClick={onOpenImportTokenModalClick}
2020-10-06 20:28:38 +02:00
maxListItems={maxListItems}
2020-11-03 00:41:28 +01:00
className={classnames(
'dropdown-search-list__token-container',
searchListClassName,
{
'dropdown-search-list--open': isOpen,
},
)}
2020-10-06 20:28:38 +02:00
hideRightLabels={hideRightLabels}
hideItemIf={hideItemIf}
listContainerClassName={listContainerClassName}
2021-06-03 18:08:37 +02:00
shouldSearchForImports={shouldSearchForImports}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
2020-10-06 20:28:38 +02:00
/>
<div
className="dropdown-search-list__close-area"
data-testid="dropdown-search-list__close-area"
2020-10-06 20:28:38 +02:00
onClick={(event) => {
event.stopPropagation();
setIsOpen(false);
onClose?.();
2020-10-06 20:28:38 +02:00
}}
/>
</>
)}
</div>
);
2020-10-06 20:28:38 +02:00
}
DropdownSearchList.propTypes = {
itemsToSearch: PropTypes.array,
onSelect: PropTypes.func,
searchListClassName: PropTypes.string,
2020-11-03 00:41:28 +01:00
fuseSearchKeys: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
weight: PropTypes.number,
}),
),
2020-10-06 20:28:38 +02:00
defaultToAll: PropTypes.bool,
maxListItems: PropTypes.number,
startingItem: PropTypes.object,
onOpen: PropTypes.func,
onClose: PropTypes.func,
className: PropTypes.string,
externallySelectedItem: PropTypes.object,
loading: PropTypes.bool,
selectPlaceHolderText: PropTypes.string,
selectorClosedClassName: PropTypes.string,
hideRightLabels: PropTypes.bool,
hideItemIf: PropTypes.func,
listContainerClassName: PropTypes.string,
2021-06-03 18:08:37 +02:00
shouldSearchForImports: PropTypes.bool,
};