mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
Move "Import NFTs" to Modal (#19806)
* moved import nft to modal * fixed modal state * updated port-nft-popup * updated onChange for import nft modal * updated tests * updated tests * updated tests * added story and updated spec file * updated spec file * updated spec file * updated spec file for import-nft * added focus to form field * added autofocus to tokenId
This commit is contained in:
parent
7bdd76a4ad
commit
5bc0ba7f3a
@ -1162,9 +1162,6 @@
|
||||
"ui/hooks/useUserPreferencedCurrency.test.js",
|
||||
"ui/index.js",
|
||||
"ui/index.test.js",
|
||||
"ui/pages/add-nft/add-nft.js",
|
||||
"ui/pages/add-nft/add-nft.test.js",
|
||||
"ui/pages/add-nft/index.js",
|
||||
"ui/pages/asset/asset.js",
|
||||
"ui/pages/asset/components/asset-breadcrumb.js",
|
||||
"ui/pages/asset/components/asset-navigation.js",
|
||||
|
@ -38,9 +38,11 @@ describe('Import ERC1155 NFT', function () {
|
||||
await driver.clickElement({ text: 'Import NFT', tag: 'button' });
|
||||
|
||||
// Enter a valid NFT that belongs to user and check success message appears
|
||||
await driver.fill('[data-testid="address"]', contractAddress);
|
||||
await driver.fill('[data-testid="token-id"]', '1');
|
||||
await driver.clickElement({ text: 'Add', tag: 'button' });
|
||||
await driver.fill('#address', contractAddress);
|
||||
await driver.fill('#token-id', '1');
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-nfts-modal-import-button"]',
|
||||
);
|
||||
|
||||
const newNftNotification = await driver.findVisibleElement({
|
||||
text: 'NFT was successfully added!',
|
||||
@ -86,14 +88,15 @@ describe('Import ERC1155 NFT', function () {
|
||||
await driver.clickElement({ text: 'Import NFT', tag: 'button' });
|
||||
|
||||
// Enter an NFT that not belongs to user with a valid address and an invalid token id
|
||||
await driver.fill('[data-testid="address"]', contractAddress);
|
||||
await driver.fill('[data-testid="token-id"]', '4');
|
||||
await driver.clickElement({ text: 'Add', tag: 'button' });
|
||||
|
||||
await driver.fill('#address', contractAddress);
|
||||
await driver.fill('#token-id', '4');
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-nfts-modal-import-button"]',
|
||||
);
|
||||
// Check error message appears
|
||||
const invalidNftNotification = await driver.findElement({
|
||||
text: 'NFT can’t be added as the ownership details do not match. Make sure you have entered correct information.',
|
||||
tag: 'h6',
|
||||
tag: 'p',
|
||||
});
|
||||
assert.equal(await invalidNftNotification.isDisplayed(), true);
|
||||
},
|
||||
|
@ -38,9 +38,11 @@ describe('Import NFT', function () {
|
||||
await driver.clickElement({ text: 'Import NFT', tag: 'button' });
|
||||
|
||||
// Enter a valid NFT that belongs to user and check success message appears
|
||||
await driver.fill('[data-testid="address"]', contractAddress);
|
||||
await driver.fill('[data-testid="token-id"]', '1');
|
||||
await driver.clickElement({ text: 'Add', tag: 'button' });
|
||||
await driver.fill('#address', contractAddress);
|
||||
await driver.fill('#token-id', '1');
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-nfts-modal-import-button"]',
|
||||
);
|
||||
|
||||
const newNftNotification = await driver.findElement({
|
||||
text: 'NFT was successfully added!',
|
||||
@ -85,14 +87,16 @@ describe('Import NFT', function () {
|
||||
await driver.clickElement({ text: 'Import NFT', tag: 'button' });
|
||||
|
||||
// Enter an NFT that not belongs to user with a valid address and an invalid token id
|
||||
await driver.fill('[data-testid="address"]', contractAddress);
|
||||
await driver.fill('[data-testid="token-id"]', '2');
|
||||
await driver.clickElement({ text: 'Add', tag: 'button' });
|
||||
await driver.fill('#address', contractAddress);
|
||||
await driver.fill('#token-id', '2');
|
||||
await driver.clickElement(
|
||||
'[data-testid="import-nfts-modal-import-button"]',
|
||||
);
|
||||
|
||||
// Check error message appears
|
||||
const invalidNftNotification = await driver.findElement({
|
||||
text: 'NFT can’t be added as the ownership details do not match. Make sure you have entered correct information.',
|
||||
tag: 'h6',
|
||||
tag: 'p',
|
||||
});
|
||||
assert.equal(await invalidNftNotification.isDisplayed(), true);
|
||||
},
|
||||
|
@ -7,12 +7,9 @@ import Typography from '../../../ui/typography';
|
||||
import { TypographyVariant } from '../../../../helpers/constants/design-system';
|
||||
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import {
|
||||
ADD_NFT_ROUTE,
|
||||
ASSET_ROUTE,
|
||||
} from '../../../../helpers/constants/routes';
|
||||
import { ASSET_ROUTE } from '../../../../helpers/constants/routes';
|
||||
import { getNfts } from '../../../../ducks/metamask/metamask';
|
||||
import { ignoreTokens } from '../../../../store/actions';
|
||||
import { ignoreTokens, showImportNftsModal } from '../../../../store/actions';
|
||||
import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-utils';
|
||||
|
||||
const ConvertTokenToNFTModal = ({ hideModal, tokenAddress }) => {
|
||||
@ -39,10 +36,7 @@ const ConvertTokenToNFTModal = ({ hideModal, tokenAddress }) => {
|
||||
pathname: `${ASSET_ROUTE}/${tokenAddress}/${tokenId}`,
|
||||
});
|
||||
} else {
|
||||
history.push({
|
||||
pathname: ADD_NFT_ROUTE,
|
||||
state: { tokenAddress },
|
||||
});
|
||||
dispatch(showImportNftsModal());
|
||||
}
|
||||
hideModal();
|
||||
}}
|
||||
|
@ -1,6 +1,4 @@
|
||||
.nfts-detection-notice {
|
||||
margin: 16px 16px 0 16px;
|
||||
|
||||
&__message {
|
||||
position: relative;
|
||||
padding: 0.75rem 0.75rem 1rem 0.75rem !important;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import NftsItems from '../nfts-items';
|
||||
@ -19,13 +18,14 @@ import { EXPERIMENTAL_ROUTE } from '../../../helpers/constants/routes';
|
||||
import {
|
||||
checkAndUpdateAllNftsOwnershipStatus,
|
||||
detectNfts,
|
||||
showImportNftsModal,
|
||||
} from '../../../store/actions';
|
||||
import { useNftsCollections } from '../../../hooks/useNftsCollections';
|
||||
import { Box, ButtonLink, IconName, Text } from '../../component-library';
|
||||
import NftsDetectionNotice from '../nfts-detection-notice';
|
||||
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
|
||||
|
||||
export default function NftsTab({ onAddNFT }) {
|
||||
export default function NftsTab() {
|
||||
const useNftDetection = useSelector(getUseNftDetection);
|
||||
const isMainnet = useSelector(getIsMainnet);
|
||||
const history = useHistory();
|
||||
@ -60,7 +60,11 @@ export default function NftsTab({ onAddNFT }) {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{isMainnet && !useNftDetection ? <NftsDetectionNotice /> : null}{' '}
|
||||
{isMainnet && !useNftDetection ? (
|
||||
<Box padding={4}>
|
||||
<NftsDetectionNotice />
|
||||
</Box>
|
||||
) : null}
|
||||
<Box
|
||||
padding={12}
|
||||
display={Display.Flex}
|
||||
@ -90,7 +94,6 @@ export default function NftsTab({ onAddNFT }) {
|
||||
</Text>
|
||||
<ButtonLink
|
||||
size={Size.MD}
|
||||
data-testid="import-nft-button"
|
||||
href={ZENDESK_URLS.NFT_TOKENS}
|
||||
externalLink
|
||||
>
|
||||
@ -113,7 +116,9 @@ export default function NftsTab({ onAddNFT }) {
|
||||
size={Size.MD}
|
||||
data-testid="import-nft-button"
|
||||
startIconName={IconName.Add}
|
||||
onClick={onAddNFT}
|
||||
onClick={() => {
|
||||
dispatch(showImportNftsModal());
|
||||
}}
|
||||
>
|
||||
{t('importNFT')}
|
||||
</ButtonLink>
|
||||
@ -149,7 +154,3 @@ export default function NftsTab({ onAddNFT }) {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
NftsTab.propTypes = {
|
||||
onAddNFT: PropTypes.func.isRequired,
|
||||
};
|
||||
|
@ -150,7 +150,6 @@ const render = ({
|
||||
selectedAddress,
|
||||
chainId = '0x1',
|
||||
useNftDetection,
|
||||
onAddNFT = jest.fn(),
|
||||
}) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
@ -170,7 +169,7 @@ const render = ({
|
||||
nftsDropdownState,
|
||||
},
|
||||
});
|
||||
return renderWithProvider(<NftsTab onAddNFT={onAddNFT} />, store);
|
||||
return renderWithProvider(<NftsTab />, store);
|
||||
};
|
||||
|
||||
describe('NFT Items', () => {
|
||||
@ -295,16 +294,5 @@ describe('NFT Items', () => {
|
||||
expect(historyPushMock).toHaveBeenCalledTimes(1);
|
||||
expect(historyPushMock).toHaveBeenCalledWith(EXPERIMENTAL_ROUTE);
|
||||
});
|
||||
it('should render a link "Import NFTs" when some NFTs are present, which, when clicked calls the passed in onAddNFT method', () => {
|
||||
const onAddNFTStub = jest.fn();
|
||||
render({
|
||||
selectedAddress: ACCOUNT_1,
|
||||
nfts: NFTS,
|
||||
onAddNFT: onAddNFTStub,
|
||||
});
|
||||
expect(onAddNFTStub).toHaveBeenCalledTimes(0);
|
||||
fireEvent.click(screen.queryByText('Import NFT'));
|
||||
expect(onAddNFTStub).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
286
ui/components/multichain/import-nfts-modal/import-nfts-modal.js
Normal file
286
ui/components/multichain/import-nfts-modal/import-nfts-modal.js
Normal file
@ -0,0 +1,286 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { isValidHexAddress } from '@metamask/controller-utils';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes';
|
||||
import {
|
||||
AlignItems,
|
||||
Display,
|
||||
FlexDirection,
|
||||
IconColor,
|
||||
JustifyContent,
|
||||
Severity,
|
||||
Size,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
|
||||
import {
|
||||
addNftVerifyOwnership,
|
||||
getTokenStandardAndDetails,
|
||||
ignoreTokens,
|
||||
setNewNftAddedMessage,
|
||||
updateNftDropDownState,
|
||||
} from '../../../store/actions';
|
||||
import {
|
||||
getCurrentChainId,
|
||||
getIsMainnet,
|
||||
getSelectedAddress,
|
||||
getUseNftDetection,
|
||||
} from '../../../selectors';
|
||||
import { getNftsDropdownState } from '../../../ducks/metamask/metamask';
|
||||
import NftsDetectionNotice from '../../app/nfts-detection-notice';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||
import { AssetType } from '../../../../shared/constants/transaction';
|
||||
import {
|
||||
MetaMetricsEventName,
|
||||
MetaMetricsTokenEventSource,
|
||||
} from '../../../../shared/constants/metametrics';
|
||||
import {
|
||||
IconName,
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
ModalHeader,
|
||||
Modal,
|
||||
ButtonPrimary,
|
||||
ButtonSecondary,
|
||||
Box,
|
||||
FormTextField,
|
||||
Label,
|
||||
Icon,
|
||||
IconSize,
|
||||
BannerAlert,
|
||||
} from '../../component-library';
|
||||
import Tooltip from '../../ui/tooltip';
|
||||
|
||||
export const ImportNftsModal = ({ onClose }) => {
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const useNftDetection = useSelector(getUseNftDetection);
|
||||
const isMainnet = useSelector(getIsMainnet);
|
||||
const nftsDropdownState = useSelector(getNftsDropdownState);
|
||||
const selectedAddress = useSelector(getSelectedAddress);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const addressEnteredOnImportTokensPage =
|
||||
history?.location?.state?.addressEnteredOnImportTokensPage;
|
||||
const contractAddressToConvertFromTokenToNft =
|
||||
history?.location?.state?.tokenAddress;
|
||||
|
||||
const [nftAddress, setNftAddress] = useState(
|
||||
addressEnteredOnImportTokensPage ??
|
||||
contractAddressToConvertFromTokenToNft ??
|
||||
'',
|
||||
);
|
||||
const [tokenId, setTokenId] = useState('');
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
const [nftAddFailed, setNftAddFailed] = useState(false);
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
|
||||
const handleAddNft = async () => {
|
||||
try {
|
||||
await dispatch(addNftVerifyOwnership(nftAddress, tokenId));
|
||||
const newNftDropdownState = {
|
||||
...nftsDropdownState,
|
||||
[selectedAddress]: {
|
||||
...nftsDropdownState?.[selectedAddress],
|
||||
[chainId]: {
|
||||
...nftsDropdownState?.[selectedAddress]?.[chainId],
|
||||
[nftAddress]: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(updateNftDropDownState(newNftDropdownState));
|
||||
} catch (error) {
|
||||
const { message } = error;
|
||||
dispatch(setNewNftAddedMessage(message));
|
||||
setNftAddFailed(true);
|
||||
return;
|
||||
}
|
||||
if (contractAddressToConvertFromTokenToNft) {
|
||||
await dispatch(
|
||||
ignoreTokens({
|
||||
tokensToIgnore: contractAddressToConvertFromTokenToNft,
|
||||
dontShowLoadingIndicator: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
dispatch(setNewNftAddedMessage('success'));
|
||||
|
||||
const tokenDetails = await getTokenStandardAndDetails(
|
||||
nftAddress,
|
||||
null,
|
||||
tokenId.toString(),
|
||||
);
|
||||
|
||||
trackEvent({
|
||||
event: MetaMetricsEventName.TokenAdded,
|
||||
category: 'Wallet',
|
||||
sensitiveProperties: {
|
||||
token_contract_address: nftAddress,
|
||||
token_symbol: tokenDetails?.symbol,
|
||||
tokenId: tokenId.toString(),
|
||||
asset_type: AssetType.NFT,
|
||||
token_standard: tokenDetails?.standard,
|
||||
source_connection_method: MetaMetricsTokenEventSource.Custom,
|
||||
},
|
||||
});
|
||||
|
||||
history.push(DEFAULT_ROUTE);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const validateAndSetAddress = (val) => {
|
||||
setDisabled(!isValidHexAddress(val) || !tokenId);
|
||||
setNftAddress(val);
|
||||
};
|
||||
|
||||
const validateAndSetTokenId = (val) => {
|
||||
setDisabled(!isValidHexAddress(nftAddress) || !val || isNaN(Number(val)));
|
||||
setTokenId(val);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen
|
||||
onClose={() => {
|
||||
onClose();
|
||||
}}
|
||||
className="import-nfts-modal"
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader
|
||||
onClose={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('importNFT')}
|
||||
</ModalHeader>
|
||||
<Box>
|
||||
{isMainnet && !useNftDetection ? (
|
||||
<Box marginTop={6}>
|
||||
<NftsDetectionNotice />
|
||||
</Box>
|
||||
) : null}
|
||||
{nftAddFailed && (
|
||||
<Box marginTop={6}>
|
||||
<BannerAlert
|
||||
severity={Severity.Danger}
|
||||
onClose={() => setNftAddFailed(false)}
|
||||
closeButtonProps={{ 'data-testid': 'add-nft-error-close' }}
|
||||
>
|
||||
{t('nftAddFailedMessage')}
|
||||
</BannerAlert>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
display={Display.Flex}
|
||||
flexDirection={FlexDirection.Column}
|
||||
gap={6}
|
||||
marginTop={6}
|
||||
marginBottom={6}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
display={Display.Flex}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
alignItems={AlignItems.flexEnd}
|
||||
>
|
||||
<Box display={Display.Flex} alignItems={AlignItems.center}>
|
||||
<Label htmlFor="address">{t('address')}</Label>
|
||||
<Tooltip
|
||||
title={t('importNFTAddressToolTip')}
|
||||
position="bottom"
|
||||
>
|
||||
<Icon
|
||||
name={IconName.Info}
|
||||
size={IconSize.Sm}
|
||||
marginLeft={1}
|
||||
color={IconColor.iconAlternative}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<FormTextField
|
||||
autoFocus
|
||||
dataTestId="address"
|
||||
id="address"
|
||||
placeholder="0x..."
|
||||
value={nftAddress}
|
||||
onChange={(e) => {
|
||||
validateAndSetAddress(e.target.value);
|
||||
setNftAddFailed(false);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box
|
||||
display={Display.Flex}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
alignItems={AlignItems.flexEnd}
|
||||
>
|
||||
<Box display={Display.Flex} alignItems={AlignItems.center}>
|
||||
<Label htmlFor="token-id">{t('tokenId')}</Label>
|
||||
<Tooltip
|
||||
title={t('importNFTTokenIdToolTip')}
|
||||
position="bottom"
|
||||
>
|
||||
<Icon
|
||||
name={IconName.Info}
|
||||
size={IconSize.Sm}
|
||||
marginLeft={1}
|
||||
color={IconColor.iconAlternative}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<FormTextField
|
||||
autoFocus
|
||||
dataTestId="token-id"
|
||||
id="token-id"
|
||||
placeholder={t('nftTokenIdPlaceholder')}
|
||||
value={tokenId}
|
||||
onChange={(e) => {
|
||||
validateAndSetTokenId(e.target.value);
|
||||
setNftAddFailed(false);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
display={Display.Flex}
|
||||
flexDirection={FlexDirection.Row}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
gap={4}
|
||||
paddingTop={4}
|
||||
paddingBottom={4}
|
||||
>
|
||||
<ButtonSecondary
|
||||
size={Size.LG}
|
||||
onClick={() => onClose()}
|
||||
block
|
||||
className="import-nfts-modal__cancel-button"
|
||||
>
|
||||
{t('cancel')}
|
||||
</ButtonSecondary>
|
||||
<ButtonPrimary
|
||||
size={Size.LG}
|
||||
onClick={() => handleAddNft()}
|
||||
disabled={disabled}
|
||||
block
|
||||
data-testid="import-nfts-modal-import-button"
|
||||
>
|
||||
{t('import')}
|
||||
</ButtonPrimary>
|
||||
</Box>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
ImportNftsModal.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from '../../../store/store';
|
||||
import testData from '../../../../.storybook/test-data';
|
||||
import { CHAIN_IDS } from '../../../../shared/constants/network';
|
||||
import { ImportNftsModal } from '.';
|
||||
|
||||
const createStore = (chainId = CHAIN_IDS.MAINNET, useTokenDetection = true) => {
|
||||
return configureStore({
|
||||
...testData,
|
||||
metamask: {
|
||||
...testData.metamask,
|
||||
useTokenDetection,
|
||||
providerConfig: { chainId },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Components/Multichain/ImportNftsModal',
|
||||
component: ImportNftsModal,
|
||||
argTypes: {
|
||||
onClose: {
|
||||
action: 'onClose',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <ImportNftsModal {...args} />;
|
||||
DefaultStory.decorators = [
|
||||
(Story) => (
|
||||
<Provider store={createStore()}>
|
||||
<Story />
|
||||
</Provider>
|
||||
),
|
||||
];
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
@ -3,16 +3,16 @@ import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { renderWithProvider } from '../../../test/jest/rendering';
|
||||
import mockState from '../../../test/data/mock-state.json';
|
||||
import { DEFAULT_ROUTE } from '../../helpers/constants/routes';
|
||||
import { renderWithProvider } from '../../../../test/jest/rendering';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes';
|
||||
import {
|
||||
addNftVerifyOwnership,
|
||||
ignoreTokens,
|
||||
setNewNftAddedMessage,
|
||||
updateNftDropDownState,
|
||||
} from '../../store/actions';
|
||||
import AddNft from '.';
|
||||
} from '../../../store/actions';
|
||||
import { ImportNftsModal } from '.';
|
||||
|
||||
const VALID_ADDRESS = '0x312BE6a98441F9F6e3F6246B13CA19701e0AC3B9';
|
||||
const INVALID_ADDRESS = 'aoinsafasdfa';
|
||||
@ -33,7 +33,7 @@ jest.mock('react-router-dom', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../../store/actions.ts', () => ({
|
||||
jest.mock('../../../store/actions.ts', () => ({
|
||||
addNftVerifyOwnership: jest
|
||||
.fn()
|
||||
.mockReturnValue(jest.fn().mockResolvedValue()),
|
||||
@ -47,56 +47,72 @@ jest.mock('../../store/actions.ts', () => ({
|
||||
.mockReturnValue(jest.fn().mockResolvedValue()),
|
||||
}));
|
||||
|
||||
describe('AddNft', () => {
|
||||
describe('ImportNftsModal', () => {
|
||||
const store = configureMockStore([thunk])(mockState);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should enable the "Add" button when valid entries are input into both Address and TokenId fields', () => {
|
||||
const { getByTestId, getByText } = renderWithProvider(<AddNft />, store);
|
||||
expect(getByText('Add')).not.toBeEnabled();
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
it('should enable the "Import" button when valid entries are input into both Address and TokenId fields', () => {
|
||||
const { getByText, getByPlaceholderText } = renderWithProvider(
|
||||
<ImportNftsModal />,
|
||||
store,
|
||||
);
|
||||
expect(getByText('Import')).not.toBeEnabled();
|
||||
const addressInput = getByPlaceholderText('0x...');
|
||||
const tokenIdInput = getByPlaceholderText('Enter the token id');
|
||||
fireEvent.change(addressInput, {
|
||||
target: { value: VALID_ADDRESS },
|
||||
});
|
||||
fireEvent.change(getByTestId('token-id'), {
|
||||
fireEvent.change(tokenIdInput, {
|
||||
target: { value: VALID_TOKENID },
|
||||
});
|
||||
expect(getByText('Add')).toBeEnabled();
|
||||
expect(getByText('Import')).toBeEnabled();
|
||||
});
|
||||
|
||||
it('should not enable the "Add" button when an invalid entry is input into one or both Address and TokenId fields', () => {
|
||||
const { getByTestId, getByText } = renderWithProvider(<AddNft />, store);
|
||||
expect(getByText('Add')).not.toBeEnabled();
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
it('should not enable the "Import" button when an invalid entry is input into one or both Address and TokenId fields', () => {
|
||||
const { getByText, getByPlaceholderText } = renderWithProvider(
|
||||
<ImportNftsModal />,
|
||||
store,
|
||||
);
|
||||
expect(getByText('Import')).not.toBeEnabled();
|
||||
const addressInput = getByPlaceholderText('0x...');
|
||||
const tokenIdInput = getByPlaceholderText('Enter the token id');
|
||||
fireEvent.change(addressInput, {
|
||||
target: { value: INVALID_ADDRESS },
|
||||
});
|
||||
fireEvent.change(getByTestId('token-id'), {
|
||||
fireEvent.change(tokenIdInput, {
|
||||
target: { value: VALID_TOKENID },
|
||||
});
|
||||
expect(getByText('Add')).not.toBeEnabled();
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
expect(getByText('Import')).not.toBeEnabled();
|
||||
fireEvent.change(addressInput, {
|
||||
target: { value: VALID_ADDRESS },
|
||||
});
|
||||
expect(getByText('Add')).toBeEnabled();
|
||||
fireEvent.change(getByTestId('token-id'), {
|
||||
expect(getByText('Import')).toBeEnabled();
|
||||
fireEvent.change(tokenIdInput, {
|
||||
target: { value: INVALID_TOKENID },
|
||||
});
|
||||
expect(getByText('Add')).not.toBeEnabled();
|
||||
expect(getByText('Import')).not.toBeEnabled();
|
||||
});
|
||||
|
||||
it('should call addNftVerifyOwnership, updateNftDropDownState, setNewNftAddedMessage, and ignoreTokens action with correct values (tokenId should not be in scientific notation)', async () => {
|
||||
const { getByTestId, getByText } = renderWithProvider(<AddNft />, store);
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
const onClose = jest.fn();
|
||||
const { getByPlaceholderText, getByText } = renderWithProvider(
|
||||
<ImportNftsModal onClose={onClose} />,
|
||||
store,
|
||||
);
|
||||
const addressInput = getByPlaceholderText('0x...');
|
||||
const tokenIdInput = getByPlaceholderText('Enter the token id');
|
||||
fireEvent.change(addressInput, {
|
||||
target: { value: VALID_ADDRESS },
|
||||
});
|
||||
const LARGE_TOKEN_ID = Number.MAX_SAFE_INTEGER + 1;
|
||||
fireEvent.change(getByTestId('token-id'), {
|
||||
fireEvent.change(tokenIdInput, {
|
||||
target: { value: LARGE_TOKEN_ID },
|
||||
});
|
||||
|
||||
fireEvent.click(getByText('Add'));
|
||||
fireEvent.click(getByText('Import'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addNftVerifyOwnership).toHaveBeenCalledWith(
|
||||
@ -127,16 +143,21 @@ describe('AddNft', () => {
|
||||
jest.fn().mockRejectedValue(new Error('error')),
|
||||
);
|
||||
|
||||
const { getByTestId, getByText } = renderWithProvider(<AddNft />, store);
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
const { getByTestId, getByText, getByPlaceholderText } = renderWithProvider(
|
||||
<ImportNftsModal />,
|
||||
store,
|
||||
);
|
||||
const addressInput = getByPlaceholderText('0x...');
|
||||
const tokenIdInput = getByPlaceholderText('Enter the token id');
|
||||
fireEvent.change(addressInput, {
|
||||
target: { value: VALID_ADDRESS },
|
||||
});
|
||||
const LARGE_TOKEN_ID = Number.MAX_SAFE_INTEGER + 1;
|
||||
fireEvent.change(getByTestId('token-id'), {
|
||||
fireEvent.change(tokenIdInput, {
|
||||
target: { value: LARGE_TOKEN_ID },
|
||||
});
|
||||
|
||||
fireEvent.click(getByText('Add'));
|
||||
fireEvent.click(getByText('Import'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setNewNftAddedMessage).toHaveBeenCalledWith('error');
|
||||
@ -148,19 +169,23 @@ describe('AddNft', () => {
|
||||
});
|
||||
|
||||
it('should route to default route when cancel button is clicked', () => {
|
||||
const { queryByTestId } = renderWithProvider(<AddNft />, store);
|
||||
const onClose = jest.fn();
|
||||
const { getByText } = renderWithProvider(
|
||||
<ImportNftsModal onClose={onClose} />,
|
||||
store,
|
||||
);
|
||||
|
||||
const cancelButton = queryByTestId('page-container-footer-cancel');
|
||||
const cancelButton = getByText('Cancel');
|
||||
fireEvent.click(cancelButton);
|
||||
|
||||
expect(useHistory().push).toHaveBeenCalledWith(DEFAULT_ROUTE);
|
||||
});
|
||||
|
||||
it('should route to default route when close button is clicked', () => {
|
||||
const { queryByLabelText } = renderWithProvider(<AddNft />, store);
|
||||
const onClose = jest.fn();
|
||||
renderWithProvider(<ImportNftsModal onClose={onClose} />, store);
|
||||
|
||||
const closeButton = queryByLabelText('close');
|
||||
fireEvent.click(closeButton);
|
||||
fireEvent.click(document.querySelector('button[aria-label="Close"]'));
|
||||
|
||||
expect(useHistory().push).toHaveBeenCalledWith(DEFAULT_ROUTE);
|
||||
});
|
1
ui/components/multichain/import-nfts-modal/index.js
Normal file
1
ui/components/multichain/import-nfts-modal/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { ImportNftsModal } from './import-nfts-modal';
|
@ -15,3 +15,4 @@ export { ProductTour } from './product-tour-popover';
|
||||
export { AccountDetails } from './account-details';
|
||||
export { CreateAccount } from './create-account';
|
||||
export { ImportAccount } from './import-account';
|
||||
export { ImportNftsModal } from './import-nfts-modal';
|
||||
|
@ -5,6 +5,7 @@
|
||||
* unintended overrides.
|
||||
**/
|
||||
@import 'address-copy-button/index';
|
||||
@import 'import-nfts-modal/index';
|
||||
@import 'account-list-item/index';
|
||||
@import 'account-list-item-menu/index';
|
||||
@import 'account-list-menu/index';
|
||||
|
@ -26,6 +26,7 @@ interface AppState {
|
||||
values?: { address?: string | null };
|
||||
} | null;
|
||||
networkDropdownOpen: boolean;
|
||||
importNftsModalOpen: boolean;
|
||||
accountDetail: {
|
||||
subview?: string;
|
||||
accountExport?: string;
|
||||
@ -94,6 +95,7 @@ const initialState: AppState = {
|
||||
alertMessage: null,
|
||||
qrCodeData: null,
|
||||
networkDropdownOpen: false,
|
||||
importNftsModalOpen: false,
|
||||
accountDetail: {
|
||||
privateKey: '',
|
||||
},
|
||||
@ -163,6 +165,17 @@ export default function reduceApp(
|
||||
networkDropdownOpen: false,
|
||||
};
|
||||
|
||||
case actionConstants.IMPORT_NFTS_MODAL_OPEN:
|
||||
return {
|
||||
...appState,
|
||||
importNftsModalOpen: true,
|
||||
};
|
||||
|
||||
case actionConstants.IMPORT_NFTS_MODAL_CLOSE:
|
||||
return {
|
||||
...appState,
|
||||
importNftsModalOpen: false,
|
||||
};
|
||||
// alert methods
|
||||
case actionConstants.ALERT_OPEN:
|
||||
return {
|
||||
|
@ -66,7 +66,6 @@ const SMART_TRANSACTION_STATUS_ROUTE = '/swaps/smart-transaction-status';
|
||||
const AWAITING_SWAP_ROUTE = '/swaps/awaiting-swap';
|
||||
const SWAPS_ERROR_ROUTE = '/swaps/swaps-error';
|
||||
const SWAPS_MAINTENANCE_ROUTE = '/swaps/maintenance';
|
||||
const ADD_NFT_ROUTE = '/add-nft';
|
||||
|
||||
const ONBOARDING_ROUTE = '/onboarding';
|
||||
const ONBOARDING_REVIEW_SRP_ROUTE = '/onboarding/review-recovery-phrase';
|
||||
@ -273,7 +272,6 @@ export {
|
||||
SWAPS_ERROR_ROUTE,
|
||||
SWAPS_MAINTENANCE_ROUTE,
|
||||
SMART_TRANSACTION_STATUS_ROUTE,
|
||||
ADD_NFT_ROUTE,
|
||||
ONBOARDING_ROUTE,
|
||||
ONBOARDING_HELP_US_IMPROVE_ROUTE,
|
||||
ONBOARDING_CREATE_PASSWORD_ROUTE,
|
||||
|
@ -1,206 +0,0 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { isValidHexAddress } from '@metamask/controller-utils';
|
||||
import { useI18nContext } from '../../hooks/useI18nContext';
|
||||
import { DEFAULT_ROUTE } from '../../helpers/constants/routes';
|
||||
import {
|
||||
DISPLAY,
|
||||
FONT_WEIGHT,
|
||||
TypographyVariant,
|
||||
} from '../../helpers/constants/design-system';
|
||||
|
||||
import Box from '../../components/ui/box';
|
||||
import Typography from '../../components/ui/typography';
|
||||
import ActionableMessage from '../../components/ui/actionable-message';
|
||||
import PageContainer from '../../components/ui/page-container';
|
||||
import {
|
||||
addNftVerifyOwnership,
|
||||
getTokenStandardAndDetails,
|
||||
ignoreTokens,
|
||||
setNewNftAddedMessage,
|
||||
updateNftDropDownState,
|
||||
} from '../../store/actions';
|
||||
import FormField from '../../components/ui/form-field';
|
||||
import {
|
||||
getCurrentChainId,
|
||||
getIsMainnet,
|
||||
getSelectedAddress,
|
||||
getUseNftDetection,
|
||||
} from '../../selectors';
|
||||
import { getNftsDropdownState } from '../../ducks/metamask/metamask';
|
||||
import NftsDetectionNotice from '../../components/app/nfts-detection-notice';
|
||||
import { MetaMetricsContext } from '../../contexts/metametrics';
|
||||
import { AssetType } from '../../../shared/constants/transaction';
|
||||
import {
|
||||
MetaMetricsEventName,
|
||||
MetaMetricsTokenEventSource,
|
||||
} from '../../../shared/constants/metametrics';
|
||||
import {
|
||||
ButtonIcon,
|
||||
IconName,
|
||||
ButtonIconSize,
|
||||
} from '../../components/component-library';
|
||||
|
||||
export default function AddNft() {
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const useNftDetection = useSelector(getUseNftDetection);
|
||||
const isMainnet = useSelector(getIsMainnet);
|
||||
const nftsDropdownState = useSelector(getNftsDropdownState);
|
||||
const selectedAddress = useSelector(getSelectedAddress);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const addressEnteredOnImportTokensPage =
|
||||
history?.location?.state?.addressEnteredOnImportTokensPage;
|
||||
const contractAddressToConvertFromTokenToNft =
|
||||
history?.location?.state?.tokenAddress;
|
||||
|
||||
const [nftAddress, setNftAddress] = useState(
|
||||
addressEnteredOnImportTokensPage ??
|
||||
contractAddressToConvertFromTokenToNft ??
|
||||
'',
|
||||
);
|
||||
const [tokenId, setTokenId] = useState('');
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
const [nftAddFailed, setNftAddFailed] = useState(false);
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
|
||||
const handleAddNft = async () => {
|
||||
try {
|
||||
await dispatch(addNftVerifyOwnership(nftAddress, tokenId));
|
||||
const newNftDropdownState = {
|
||||
...nftsDropdownState,
|
||||
[selectedAddress]: {
|
||||
...nftsDropdownState?.[selectedAddress],
|
||||
[chainId]: {
|
||||
...nftsDropdownState?.[selectedAddress]?.[chainId],
|
||||
[nftAddress]: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(updateNftDropDownState(newNftDropdownState));
|
||||
} catch (error) {
|
||||
const { message } = error;
|
||||
dispatch(setNewNftAddedMessage(message));
|
||||
setNftAddFailed(true);
|
||||
return;
|
||||
}
|
||||
if (contractAddressToConvertFromTokenToNft) {
|
||||
await dispatch(
|
||||
ignoreTokens({
|
||||
tokensToIgnore: contractAddressToConvertFromTokenToNft,
|
||||
dontShowLoadingIndicator: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
dispatch(setNewNftAddedMessage('success'));
|
||||
|
||||
const tokenDetails = await getTokenStandardAndDetails(
|
||||
nftAddress,
|
||||
null,
|
||||
tokenId.toString(),
|
||||
);
|
||||
|
||||
trackEvent({
|
||||
event: MetaMetricsEventName.TokenAdded,
|
||||
category: 'Wallet',
|
||||
sensitiveProperties: {
|
||||
token_contract_address: nftAddress,
|
||||
token_symbol: tokenDetails?.symbol,
|
||||
tokenId: tokenId.toString(),
|
||||
asset_type: AssetType.NFT,
|
||||
token_standard: tokenDetails?.standard,
|
||||
source_connection_method: MetaMetricsTokenEventSource.Custom,
|
||||
},
|
||||
});
|
||||
|
||||
history.push(DEFAULT_ROUTE);
|
||||
};
|
||||
|
||||
const validateAndSetAddress = (val) => {
|
||||
setDisabled(!isValidHexAddress(val) || !tokenId);
|
||||
setNftAddress(val);
|
||||
};
|
||||
|
||||
const validateAndSetTokenId = (val) => {
|
||||
setDisabled(!isValidHexAddress(nftAddress) || !val || isNaN(Number(val)));
|
||||
setTokenId(val);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title={t('importNFT')}
|
||||
onSubmit={() => {
|
||||
handleAddNft();
|
||||
}}
|
||||
submitText={t('add')}
|
||||
onCancel={() => {
|
||||
history.push(DEFAULT_ROUTE);
|
||||
}}
|
||||
onClose={() => {
|
||||
history.push(DEFAULT_ROUTE);
|
||||
}}
|
||||
disabled={disabled}
|
||||
contentComponent={
|
||||
<Box>
|
||||
{isMainnet && !useNftDetection ? <NftsDetectionNotice /> : null}
|
||||
{nftAddFailed && (
|
||||
<Box marginLeft={4} marginRight={4}>
|
||||
<ActionableMessage
|
||||
type="danger"
|
||||
useIcon
|
||||
iconFillColor="var(--color-error-default)"
|
||||
message={
|
||||
<Box display={DISPLAY.INLINE_FLEX}>
|
||||
<Typography
|
||||
variant={TypographyVariant.H7}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
marginTop={0}
|
||||
>
|
||||
{t('nftAddFailedMessage')}
|
||||
</Typography>
|
||||
<ButtonIcon
|
||||
className="add-nft__close"
|
||||
iconName={IconName.Close}
|
||||
size={ButtonIconSize.Sm}
|
||||
ariaLabel={t('close')}
|
||||
data-testid="add-nft-error-close"
|
||||
onClick={() => setNftAddFailed(false)}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box margin={4}>
|
||||
<FormField
|
||||
dataTestId="address"
|
||||
titleText={t('address')}
|
||||
placeholder="0x..."
|
||||
value={nftAddress}
|
||||
onChange={(val) => {
|
||||
validateAndSetAddress(val);
|
||||
setNftAddFailed(false);
|
||||
}}
|
||||
tooltipText={t('importNFTAddressToolTip')}
|
||||
autoFocus
|
||||
/>
|
||||
<FormField
|
||||
dataTestId="token-id"
|
||||
titleText={t('tokenId')}
|
||||
placeholder={t('nftTokenIdPlaceholder')}
|
||||
value={tokenId}
|
||||
onChange={(val) => {
|
||||
validateAndSetTokenId(val);
|
||||
setNftAddFailed(false);
|
||||
}}
|
||||
tooltipText={t('importNFTTokenIdToolTip')}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { default } from './add-nft';
|
@ -62,7 +62,6 @@ import {
|
||||
BUILD_QUOTE_ROUTE,
|
||||
VIEW_QUOTE_ROUTE,
|
||||
CONFIRMATION_V_NEXT_ROUTE,
|
||||
ADD_NFT_ROUTE,
|
||||
ONBOARDING_SECURE_YOUR_WALLET_ROUTE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
CONFIRM_INSTITUTIONAL_FEATURE_CONNECT,
|
||||
@ -879,11 +878,7 @@ export default class Home extends PureComponent {
|
||||
name={this.context.t('nfts')}
|
||||
tabKey="nfts"
|
||||
>
|
||||
<NftsTab
|
||||
onAddNFT={() => {
|
||||
history.push(ADD_NFT_ROUTE);
|
||||
}}
|
||||
/>
|
||||
<NftsTab />
|
||||
</Tab>
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
} from '../../helpers/utils/util';
|
||||
import { tokenInfoGetter } from '../../helpers/utils/token-util';
|
||||
import {
|
||||
ADD_NFT_ROUTE,
|
||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||
SECURITY_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
@ -60,6 +59,11 @@ class ImportToken extends Component {
|
||||
*/
|
||||
clearPendingTokens: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Clear the list of pending tokens. Called when closing the modal.
|
||||
*/
|
||||
showImportNftsModal: PropTypes.func,
|
||||
|
||||
/**
|
||||
* The list of already added tokens.
|
||||
*/
|
||||
@ -314,21 +318,15 @@ class ImportToken extends Component {
|
||||
nftAddressError: this.context.t('nftAddressError', [
|
||||
<a
|
||||
className="import-token__nft-address-error-link"
|
||||
onClick={() =>
|
||||
this.props.history.push({
|
||||
pathname: ADD_NFT_ROUTE,
|
||||
state: {
|
||||
addressEnteredOnImportTokensPage: this.state.customAddress,
|
||||
},
|
||||
})
|
||||
}
|
||||
onClick={() => {
|
||||
this.props.showImportNftsModal();
|
||||
}}
|
||||
key="nftAddressError"
|
||||
>
|
||||
{this.context.t('importNFTPage')}
|
||||
</a>,
|
||||
]),
|
||||
});
|
||||
|
||||
break;
|
||||
case isMainnetToken && !isMainnetNetwork:
|
||||
this.setState({
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
setPendingTokens,
|
||||
clearPendingTokens,
|
||||
getTokenStandardAndDetails,
|
||||
showImportNftsModal,
|
||||
} from '../../store/actions';
|
||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||
import { getProviderConfig } from '../../ducks/metamask/metamask';
|
||||
@ -58,6 +59,7 @@ const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
setPendingTokens: (tokens) => dispatch(setPendingTokens(tokens)),
|
||||
clearPendingTokens: () => dispatch(clearPendingTokens()),
|
||||
showImportNftsModal: () => dispatch(showImportNftsModal()),
|
||||
getTokenStandardAndDetails: (address, selectedAddress) =>
|
||||
getTokenStandardAndDetails(address, selectedAddress, null),
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
/** Please import your files in alphabetical order **/
|
||||
@import 'add-nft/index';
|
||||
@import 'keyring-snaps/index';
|
||||
@import 'import-token/index';
|
||||
@import 'asset/asset';
|
||||
|
@ -19,7 +19,6 @@ import PermissionsConnect from '../permissions-connect';
|
||||
import RestoreVaultPage from '../keychains/restore-vault';
|
||||
import RevealSeedConfirmation from '../keychains/reveal-seed';
|
||||
import ImportTokenPage from '../import-token';
|
||||
import AddNftPage from '../add-nft';
|
||||
import ConfirmImportTokenPage from '../confirm-import-token';
|
||||
import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token';
|
||||
import CreateAccountPage from '../create-account/create-account.component';
|
||||
@ -33,6 +32,7 @@ import {
|
||||
AccountListMenu,
|
||||
NetworkListMenu,
|
||||
AccountDetails,
|
||||
ImportNftsModal,
|
||||
} from '../../components/multichain';
|
||||
import UnlockPage from '../unlock-page';
|
||||
import Alerts from '../../components/app/alerts';
|
||||
@ -80,7 +80,6 @@ import {
|
||||
CONFIRMATION_V_NEXT_ROUTE,
|
||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||
ONBOARDING_ROUTE,
|
||||
ADD_NFT_ROUTE,
|
||||
ONBOARDING_UNLOCK_ROUTE,
|
||||
TOKEN_DETAILS,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
@ -162,6 +161,8 @@ export default class Routes extends Component {
|
||||
isNetworkMenuOpen: PropTypes.bool,
|
||||
toggleNetworkMenu: PropTypes.func,
|
||||
accountDetailsAddress: PropTypes.string,
|
||||
isImportNftsModalOpen: PropTypes.bool.isRequired,
|
||||
hideImportNftsModal: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -284,7 +285,6 @@ export default class Routes extends Component {
|
||||
component={ImportTokenPage}
|
||||
exact
|
||||
/>
|
||||
<Authenticated path={ADD_NFT_ROUTE} component={AddNftPage} exact />
|
||||
<Authenticated
|
||||
path={CONFIRM_IMPORT_TOKEN_ROUTE}
|
||||
component={ConfirmImportTokenPage}
|
||||
@ -525,6 +525,8 @@ export default class Routes extends Component {
|
||||
toggleNetworkMenu,
|
||||
accountDetailsAddress,
|
||||
location,
|
||||
isImportNftsModalOpen,
|
||||
hideImportNftsModal,
|
||||
} = this.props;
|
||||
const loadMessage =
|
||||
loadingMessage || isNetworkLoading
|
||||
@ -583,6 +585,9 @@ export default class Routes extends Component {
|
||||
{accountDetailsAddress ? (
|
||||
<AccountDetails address={accountDetailsAddress} />
|
||||
) : null}
|
||||
{isImportNftsModalOpen ? (
|
||||
<ImportNftsModal onClose={() => hideImportNftsModal()} />
|
||||
) : null}
|
||||
<Box className="main-container-wrapper">
|
||||
{isLoading ? <Loading loadingMessage={loadMessage} /> : null}
|
||||
{!isLoading && isNetworkLoading ? <LoadingNetwork /> : null}
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
} from '../../selectors';
|
||||
import {
|
||||
lockMetamask,
|
||||
hideImportNftsModal,
|
||||
setCurrentCurrency,
|
||||
setLastActiveTime,
|
||||
setMouseUserState,
|
||||
@ -63,6 +64,7 @@ function mapStateToProps(state) {
|
||||
isAccountMenuOpen: state.metamask.isAccountMenuOpen,
|
||||
isNetworkMenuOpen: state.metamask.isNetworkMenuOpen,
|
||||
accountDetailsAddress: state.appState.accountDetailsAddress,
|
||||
isImportNftsModalOpen: state.appState.importNftsModalOpen,
|
||||
};
|
||||
}
|
||||
|
||||
@ -77,6 +79,7 @@ function mapDispatchToProps(dispatch) {
|
||||
prepareToLeaveSwaps: () => dispatch(prepareToLeaveSwaps()),
|
||||
toggleAccountMenu: () => dispatch(toggleAccountMenu()),
|
||||
toggleNetworkMenu: () => dispatch(toggleNetworkMenu()),
|
||||
hideImportNftsModal: () => dispatch(hideImportNftsModal()),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ export const QR_CODE_DETECTED = 'UI_QR_CODE_DETECTED';
|
||||
// network dropdown open
|
||||
export const NETWORK_DROPDOWN_OPEN = 'UI_NETWORK_DROPDOWN_OPEN';
|
||||
export const NETWORK_DROPDOWN_CLOSE = 'UI_NETWORK_DROPDOWN_CLOSE';
|
||||
export const IMPORT_NFTS_MODAL_OPEN = 'UI_IMPORT_NFTS_MODAL_OPEN';
|
||||
export const IMPORT_NFTS_MODAL_CLOSE = 'UI_IMPORT_NFTS_MODAL_CLOSE';
|
||||
// remote state
|
||||
export const UPDATE_METAMASK_STATE = 'UPDATE_METAMASK_STATE';
|
||||
export const SELECTED_ADDRESS_CHANGED = 'SELECTED_ADDRESS_CHANGED';
|
||||
|
@ -2402,6 +2402,18 @@ export function hideModal(): Action {
|
||||
};
|
||||
}
|
||||
|
||||
export function showImportNftsModal(): Action {
|
||||
return {
|
||||
type: actionConstants.IMPORT_NFTS_MODAL_OPEN,
|
||||
};
|
||||
}
|
||||
|
||||
export function hideImportNftsModal(): Action {
|
||||
return {
|
||||
type: actionConstants.IMPORT_NFTS_MODAL_CLOSE,
|
||||
};
|
||||
}
|
||||
|
||||
export function closeCurrentNotificationWindow(): ThunkAction<
|
||||
void,
|
||||
MetaMaskReduxState,
|
||||
|
Loading…
Reference in New Issue
Block a user