mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-24 19:10:22 +01:00
Added IPFS toggle (#20172)
* added ipfs toggle * added placeholder valur * fixed snapshot * updated tests * updated spec file * hide input if toggle is disabled * updated e2e tests for nft image * fixed view-ercc-1155 spec * updated e2e tests for nfts * added ipfs toggle modal * updated UI for ipfs * updated tests * updated classname * added placeholder image * lint fix * removed prop ipfsEnabled and used with selector * fixed ui for ipfs toggle * updated test * updated test to handle cases * nit fix * ensure default image height match nft image
This commit is contained in:
parent
e27a9e4344
commit
4c37448c97
13
app/_locales/en/messages.json
generated
13
app/_locales/en/messages.json
generated
@ -2054,6 +2054,16 @@
|
||||
"invalidSeedPhraseCaseSensitive": {
|
||||
"message": "Invalid input! Secret Recovery Phrase is case sensitive."
|
||||
},
|
||||
"ipfsToggleModalDescriptionOne": {
|
||||
"message": "We use third-party services to show images of your NFTs stored on IPFS, display information related to ENS addresses entered in your browser's address bar, and fetch icons for different tokens. Your IP address may be exposed to these services when you’re using them."
|
||||
},
|
||||
"ipfsToggleModalDescriptionTwo": {
|
||||
"message": "Selecting Confirm turns on IPFS resolution. You can turn it off in $1 at any time.",
|
||||
"description": "$1 is the method to turn off ipfs"
|
||||
},
|
||||
"ipfsToggleModalSettings": {
|
||||
"message": "Settings > Security and privacy"
|
||||
},
|
||||
"jazzAndBlockies": {
|
||||
"message": "Jazzicons and Blockies are two different styles of unique icons that help you identify an account at a glance."
|
||||
},
|
||||
@ -3821,6 +3831,9 @@
|
||||
"showMore": {
|
||||
"message": "Show more"
|
||||
},
|
||||
"showNft": {
|
||||
"message": "Show NFT"
|
||||
},
|
||||
"showPermissions": {
|
||||
"message": "Show permissions"
|
||||
},
|
||||
|
BIN
app/images/default_nft.png
Normal file
BIN
app/images/default_nft.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
@ -93,7 +93,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
await driver.clickElement({ text: 'NFTs', tag: 'button' });
|
||||
await driver.findElement({ text: 'TestDappNFTs (3)' });
|
||||
const nftsListItemsFirstCheck = await driver.findElements(
|
||||
'.nft-item__item',
|
||||
'.nft-item__container',
|
||||
);
|
||||
assert.equal(nftsListItemsFirstCheck.length, 3);
|
||||
|
||||
@ -119,7 +119,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
await driver.clickElement({ text: 'NFTs', tag: 'button' });
|
||||
await driver.findElement({ text: 'TestDappNFTs (6)' });
|
||||
const nftsListItemsSecondCheck = await driver.findElements(
|
||||
'.nft-item__item',
|
||||
'.nft-item__container',
|
||||
);
|
||||
assert.equal(nftsListItemsSecondCheck.length, 6);
|
||||
},
|
||||
@ -215,7 +215,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
await driver.clickElement({ text: 'NFTs', tag: 'button' });
|
||||
await driver.findElement({ text: 'TestDappNFTs (5)' });
|
||||
const nftsListItemsSecondCheck = await driver.findElements(
|
||||
'.nft-item__item',
|
||||
'.nft-item__container',
|
||||
);
|
||||
|
||||
assert.equal(nftsListItemsSecondCheck.length, 5);
|
||||
|
@ -58,7 +58,7 @@ describe('Import ERC1155 NFT', function () {
|
||||
assert.equal(await importedERC1155.isDisplayed(), true);
|
||||
|
||||
const importedERC1155Image = await driver.findVisibleElement(
|
||||
'.nft-item__item',
|
||||
'.nft-item__container',
|
||||
);
|
||||
assert.equal(await importedERC1155Image.isDisplayed(), true);
|
||||
},
|
||||
|
@ -56,7 +56,7 @@ describe('Import NFT', function () {
|
||||
text: 'TestDappNFTs',
|
||||
});
|
||||
const importedNftImage = await driver.findElement(
|
||||
'.nft-item__item-image',
|
||||
'.nft-item__container',
|
||||
);
|
||||
assert.equal(await importedNft.isDisplayed(), true);
|
||||
assert.equal(await importedNftImage.isDisplayed(), true);
|
||||
|
@ -32,7 +32,7 @@ describe('Remove ERC1155 NFT', function () {
|
||||
// Open the details page and click remove nft button
|
||||
await driver.clickElement('[data-testid="home__nfts-tab"]');
|
||||
const importedNftImage = await driver.findVisibleElement(
|
||||
'.nft-item__item',
|
||||
'.nft-item__container',
|
||||
);
|
||||
await importedNftImage.click();
|
||||
await driver.clickElement('[data-testid="nft-options__button"]');
|
||||
|
@ -31,7 +31,7 @@ describe('Remove NFT', function () {
|
||||
|
||||
// Open the details and click remove nft button
|
||||
await driver.clickElement('[data-testid="home__nfts-tab"]');
|
||||
await driver.clickElement('.nft-item__item-image');
|
||||
await driver.clickElement('.nft-item__container');
|
||||
await driver.clickElement('[data-testid="nft-options__button"]');
|
||||
await driver.clickElement('[data-testid="nft-item-remove"]');
|
||||
|
||||
|
@ -31,7 +31,7 @@ describe('Send NFT', function () {
|
||||
|
||||
// Fill the send NFT form and confirm the transaction
|
||||
await driver.clickElement('[data-testid="home__nfts-tab"]');
|
||||
await driver.clickElement('.nft-item__item-image');
|
||||
await driver.clickElement('.nft-item__container');
|
||||
await driver.clickElement({ text: 'Send', tag: 'button' });
|
||||
await driver.fill(
|
||||
'input[placeholder="Enter public address (0x) or ENS name"]',
|
||||
|
@ -34,7 +34,7 @@ describe('View ERC1155 NFT details', function () {
|
||||
// Click to open the NFT details page and check displayed account
|
||||
await driver.clickElement('[data-testid="home__nfts-tab"]');
|
||||
const importedNftImage = await driver.findVisibleElement(
|
||||
'.nft-item__item',
|
||||
'.nft-item__container',
|
||||
);
|
||||
await importedNftImage.click();
|
||||
const detailsPageAccount = await driver.findElement(
|
||||
@ -54,7 +54,7 @@ describe('View ERC1155 NFT details', function () {
|
||||
'This is a collection of Rock NFTs.',
|
||||
);
|
||||
|
||||
const nftImage = await driver.findElement('.nft-item__item-image');
|
||||
const nftImage = await driver.findElement('.nft-item__container');
|
||||
assert.equal(await nftImage.isDisplayed(), true);
|
||||
|
||||
const nftImageSource = await driver.findElement(
|
||||
|
@ -33,7 +33,7 @@ describe('View NFT details', function () {
|
||||
|
||||
// Click to open the NFT details page and check title
|
||||
await driver.clickElement('[data-testid="home__nfts-tab"]');
|
||||
await driver.clickElement('.nft-item__item-image');
|
||||
await driver.clickElement('.nft-item__container');
|
||||
|
||||
const detailsPageTitle = await driver.findElement('.asset-breadcrumb');
|
||||
assert.equal(
|
||||
@ -53,7 +53,7 @@ describe('View NFT details', function () {
|
||||
'Test Dapp NFTs for testing.',
|
||||
);
|
||||
|
||||
const nftImage = await driver.findElement('.nft-item__item-image');
|
||||
const nftImage = await driver.findElement('.nft-item__container');
|
||||
assert.equal(await nftImage.isDisplayed(), true);
|
||||
|
||||
const nftImageSource = await driver.findElement(
|
||||
|
@ -3,58 +3,29 @@
|
||||
exports[`NFT Default Image should match snapshot with all provided props 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="box nft-default nft-default--clickable box--display-flex box--flex-direction-row box--width-full box--background-color-background-alternative box--rounded-lg"
|
||||
class="mm-box nft-default mm-box--display-flex mm-box--rounded-lg"
|
||||
data-testid="nft-default-image"
|
||||
tabindex="0"
|
||||
>
|
||||
<h6
|
||||
class="mm-box mm-text nft-default__text mm-text--body-sm mm-text--ellipsis mm-text--text-align-center mm-box--color-text-default"
|
||||
>
|
||||
NFT Name
|
||||
|
||||
<br />
|
||||
#
|
||||
123
|
||||
</h6>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NFT Default Image should match snapshot with missing clickable prop 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="box nft-default box--display-flex box--flex-direction-row box--width-full box--background-color-background-alternative box--rounded-lg"
|
||||
class="mm-box nft-default mm-box--display-flex mm-box--rounded-lg"
|
||||
data-testid="nft-default-image"
|
||||
tabindex="0"
|
||||
>
|
||||
<h6
|
||||
class="mm-box mm-text nft-default__text mm-text--body-sm mm-text--ellipsis mm-text--text-align-center mm-box--color-text-default"
|
||||
>
|
||||
NFT Name
|
||||
|
||||
<br />
|
||||
#
|
||||
123
|
||||
</h6>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NFT Default Image should render with no props 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="box nft-default box--display-flex box--flex-direction-row box--width-full box--background-color-background-alternative box--rounded-lg"
|
||||
class="mm-box nft-default mm-box--display-flex mm-box--rounded-lg"
|
||||
data-testid="nft-default-image"
|
||||
tabindex="0"
|
||||
>
|
||||
<h6
|
||||
class="mm-box mm-text nft-default__text mm-text--body-sm mm-text--ellipsis mm-text--text-align-center mm-box--color-text-default"
|
||||
>
|
||||
[unknownCollection]
|
||||
|
||||
<br />
|
||||
#
|
||||
</h6>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
@ -1,17 +1,12 @@
|
||||
.nft-default {
|
||||
padding-top: 100%;
|
||||
position: relative;
|
||||
background-image: url('/images/default_nft.png');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
&--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__text {
|
||||
overflow: hidden;
|
||||
&__button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: calc(100% - 32px);
|
||||
bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
Display,
|
||||
AlignItems,
|
||||
BlockSize,
|
||||
JustifyContent,
|
||||
TextVariant,
|
||||
BorderRadius,
|
||||
TextAlign,
|
||||
BackgroundColor,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { Text } from '../../component-library';
|
||||
import Box from '../../ui/box/box';
|
||||
import { ButtonLink, Box } from '../../component-library';
|
||||
import { showIpfsModal } from '../../../store/actions';
|
||||
import { getIpfsGateway } from '../../../selectors';
|
||||
|
||||
export default function NftDefaultImage({
|
||||
name,
|
||||
tokenId,
|
||||
className,
|
||||
clickable = false,
|
||||
}) {
|
||||
export default function NftDefaultImage({ className, clickable }) {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
const isIpfsEnabled = useSelector(getIpfsGateway);
|
||||
|
||||
return (
|
||||
<Box
|
||||
tabIndex={0}
|
||||
@ -32,32 +28,25 @@ export default function NftDefaultImage({
|
||||
display={Display.Flex}
|
||||
alignItems={AlignItems.Center}
|
||||
justifyContent={JustifyContent.Center}
|
||||
backgroundColor={BackgroundColor.backgroundAlternative}
|
||||
width={BlockSize.Full}
|
||||
borderRadius={BorderRadius.LG}
|
||||
>
|
||||
<Text
|
||||
variant={TextVariant.bodySm}
|
||||
textAlign={TextAlign.Center}
|
||||
ellipsis
|
||||
as="h6"
|
||||
className="nft-default__text"
|
||||
>
|
||||
{name ?? t('unknownCollection')} <br /> #{tokenId}
|
||||
</Text>
|
||||
{!isIpfsEnabled && (
|
||||
<ButtonLink
|
||||
block
|
||||
className="nft-default__button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
dispatch(showIpfsModal());
|
||||
}}
|
||||
>
|
||||
{t('show')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
NftDefaultImage.propTypes = {
|
||||
/**
|
||||
* The name of the NFT collection if not supplied will default to "Unnamed collection"
|
||||
*/
|
||||
name: PropTypes.string,
|
||||
/**
|
||||
* The token id of the nft
|
||||
*/
|
||||
tokenId: PropTypes.string,
|
||||
/**
|
||||
* Controls the css class for the cursor hover
|
||||
*/
|
||||
|
@ -5,21 +5,10 @@ export default {
|
||||
title: 'Components/App/NftDefaultImage',
|
||||
|
||||
argTypes: {
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
tokenId: {
|
||||
control: 'text',
|
||||
},
|
||||
clickable: {
|
||||
control: 'boolean',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
name: null,
|
||||
tokenId: '12345',
|
||||
clickable: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => (
|
||||
|
@ -1,72 +1,54 @@
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import NftDefaultImage from '.';
|
||||
|
||||
describe('NFT Default Image', () => {
|
||||
const mockShowIpfsModal = jest.fn();
|
||||
jest.mock('../../../store/actions.ts', () => ({
|
||||
showIpfsModal: () => mockShowIpfsModal,
|
||||
}));
|
||||
|
||||
const store = configureStore()(mockState);
|
||||
|
||||
it('should render with no props', () => {
|
||||
const { container } = renderWithProvider(<NftDefaultImage />);
|
||||
const { container } = renderWithProvider(<NftDefaultImage />, store);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot with all provided props', () => {
|
||||
const props = {
|
||||
name: 'NFT Name',
|
||||
tokenId: '123',
|
||||
clickable: true,
|
||||
clickable: false,
|
||||
};
|
||||
|
||||
const { container } = renderWithProvider(<NftDefaultImage {...props} />);
|
||||
const { container } = renderWithProvider(
|
||||
<NftDefaultImage {...props} />,
|
||||
store,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot with missing clickable prop', () => {
|
||||
const props = {
|
||||
name: 'NFT Name',
|
||||
tokenId: '123',
|
||||
};
|
||||
|
||||
const { container } = renderWithProvider(<NftDefaultImage {...props} />);
|
||||
const { container } = renderWithProvider(<NftDefaultImage />, store);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render NFT name', () => {
|
||||
const props = {
|
||||
name: 'NFT Name',
|
||||
};
|
||||
|
||||
const { queryByText } = renderWithProvider(<NftDefaultImage {...props} />);
|
||||
|
||||
const nftElement = queryByText(`${props.name} #`);
|
||||
|
||||
expect(nftElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render NFT name and tokenId', () => {
|
||||
const props = {
|
||||
name: 'NFT Name',
|
||||
tokenId: '123',
|
||||
};
|
||||
|
||||
const { queryByText } = renderWithProvider(<NftDefaultImage {...props} />);
|
||||
|
||||
const nftElement = queryByText(`${props.name} #${props.tokenId}`);
|
||||
|
||||
expect(nftElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render component with clickable class when clickable is false', () => {
|
||||
const { container } = renderWithProvider(
|
||||
<NftDefaultImage name="NFT Name" tokenId="123" clickable={false} />,
|
||||
<NftDefaultImage clickable={false} />,
|
||||
store,
|
||||
);
|
||||
expect(container.firstChild).not.toHaveClass('nft-default--clickable');
|
||||
});
|
||||
|
||||
it('renders component with clickable class when clickable is true', () => {
|
||||
const { container } = renderWithProvider(
|
||||
<NftDefaultImage name="NFT Name" tokenId="123" clickable />,
|
||||
<NftDefaultImage clickable />,
|
||||
store,
|
||||
);
|
||||
expect(container.firstChild).toHaveClass('nft-default--clickable');
|
||||
});
|
||||
|
70
ui/components/app/nft-default-image/toggle-ipfs-modal.js
Normal file
70
ui/components/app/nft-default-image/toggle-ipfs-modal.js
Normal file
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import {
|
||||
Box,
|
||||
ButtonPrimary,
|
||||
Modal,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Text,
|
||||
} from '../../component-library';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { hideIpfsModal, setIpfsGateway } from '../../../store/actions';
|
||||
import { IPFS_DEFAULT_GATEWAY_URL } from '../../../../shared/constants/network';
|
||||
import {
|
||||
Size,
|
||||
TextColor,
|
||||
TextVariant,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
|
||||
export const ToggleIpfsModal = ({ onClose }) => {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<Modal isOpen onClose={onClose} className="toggle-ipfs-modal">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader onClose={onClose}>{t('showNft')}</ModalHeader>
|
||||
<Box className="toggle-ipfs-modal" marginTop={6}>
|
||||
<Text variant={TextVariant.bodyMd} color={TextColor.textAlternative}>
|
||||
{t('ipfsToggleModalDescriptionOne')}
|
||||
</Text>
|
||||
<Text
|
||||
variant={TextVariant.bodyMd}
|
||||
color={TextColor.textAlternative}
|
||||
marginTop={6}
|
||||
>
|
||||
{t('ipfsToggleModalDescriptionTwo', [
|
||||
<Text
|
||||
variant={TextVariant.bodyMdBold}
|
||||
color={TextColor.textAlternative}
|
||||
as="span"
|
||||
key="span"
|
||||
>
|
||||
{t('ipfsToggleModalSettings')},
|
||||
</Text>,
|
||||
])}
|
||||
</Text>
|
||||
</Box>
|
||||
<ButtonPrimary
|
||||
block
|
||||
marginTop={9}
|
||||
onClick={() => {
|
||||
dispatch(setIpfsGateway(IPFS_DEFAULT_GATEWAY_URL));
|
||||
dispatch(hideIpfsModal());
|
||||
}}
|
||||
size={Size.LG}
|
||||
>
|
||||
{t('confirm')}
|
||||
</ButtonPrimary>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
ToggleIpfsModal.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
@ -56,7 +56,7 @@ exports[`NFT Details should match minimal props and state snapshot 1`] = `
|
||||
alt="MUNK #1 1"
|
||||
class="mm-box nft-item__item nft-item__item-image mm-box--display-block mm-box--justify-content-center"
|
||||
data-testid="nft-image"
|
||||
src="https://bafybeiclzx7zfjvuiuwobn5ip3ogc236bjqfjzoblumf4pau4ep6dqramu.ipfs.dweb.link"
|
||||
src="ipfs://QmTSZUNt8AKyDabkyXXXP4oHWDnaVXgNdXoJGEyaYzLbeL"
|
||||
/>
|
||||
<div
|
||||
class="mm-box mm-badge-wrapper__badge-container"
|
||||
@ -73,7 +73,7 @@ exports[`NFT Details should match minimal props and state snapshot 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="box nft-details__info box--flex-direction-column box--justify-content-space-between box--display-flex"
|
||||
class="box nft-details__info box--margin-top-4 box--flex-direction-column box--justify-content-space-between box--display-flex"
|
||||
>
|
||||
<div>
|
||||
<h4
|
||||
|
@ -188,7 +188,8 @@ export default function NftDetails({ nft }) {
|
||||
>
|
||||
<Box className="nft-details__nft-item">
|
||||
<NftItem
|
||||
src={image ? nftImageURL : ''}
|
||||
nftImageURL={nftImageURL}
|
||||
src={image}
|
||||
alt={image ? nftImageAlt : ''}
|
||||
name={name}
|
||||
tokenId={tokenId}
|
||||
@ -199,6 +200,7 @@ export default function NftDetails({ nft }) {
|
||||
<Box
|
||||
flexDirection={FlexDirection.Column}
|
||||
className="nft-details__info"
|
||||
marginTop={4}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
>
|
||||
<div>
|
||||
|
@ -52,6 +52,7 @@ export default function NftsItems({
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const currentChain = useSelector(getCurrentNetwork);
|
||||
const t = useI18nContext();
|
||||
const ipfsGateway = useSelector(getIpfsGateway);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@ -86,7 +87,6 @@ export default function NftsItems({
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
const ipfsGateway = useSelector(getIpfsGateway);
|
||||
const history = useHistory();
|
||||
|
||||
const renderCollectionImage = (collectionImage, collectionName) => {
|
||||
@ -169,9 +169,13 @@ export default function NftsItems({
|
||||
{isExpanded ? (
|
||||
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}>
|
||||
{nfts.map((nft, i) => {
|
||||
const { image, address, tokenId, name } = nft;
|
||||
const nftImage = getAssetImageURL(image, ipfsGateway);
|
||||
const { image, address, tokenId, name, imageOriginal } = nft;
|
||||
const nftImage = getAssetImageURL(imageOriginal, ipfsGateway);
|
||||
const nftImageAlt = getNftImageAlt(nft);
|
||||
const nftImageURL = imageOriginal?.includes('ipfs')
|
||||
? nftImage
|
||||
: image;
|
||||
|
||||
const handleImageClick = () =>
|
||||
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`);
|
||||
return (
|
||||
@ -182,8 +186,9 @@ export default function NftsItems({
|
||||
className="nfts-items__item-wrapper"
|
||||
>
|
||||
<NftItem
|
||||
src={nftImage}
|
||||
nftImageURL={nftImageURL}
|
||||
alt={nftImageAlt}
|
||||
src={image}
|
||||
name={name}
|
||||
tokenId={tokenId}
|
||||
networkName={currentChain.nickname}
|
||||
|
@ -80,8 +80,12 @@ describe('NFTs Item Component', () => {
|
||||
);
|
||||
|
||||
const nftImages = queryAllByTestId('nft-image');
|
||||
|
||||
fireEvent.click(nftImages[0]);
|
||||
const nftDefaultImages = queryAllByTestId('nft-default-image');
|
||||
if (nftImages.length) {
|
||||
fireEvent.click(nftImages[0]);
|
||||
} else {
|
||||
fireEvent.click(nftDefaultImages[0]);
|
||||
}
|
||||
|
||||
const firstNft = nfts[0];
|
||||
const nftRoute = `/asset/${firstNft.address}/${firstNft.tokenId}`;
|
||||
|
@ -15,7 +15,10 @@ import {
|
||||
Display,
|
||||
JustifyContent,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { getTestNetworkBackgroundColor } from '../../../selectors';
|
||||
import {
|
||||
getIpfsGateway,
|
||||
getTestNetworkBackgroundColor,
|
||||
} from '../../../selectors';
|
||||
|
||||
export const NftItem = ({
|
||||
alt,
|
||||
@ -25,9 +28,12 @@ export const NftItem = ({
|
||||
networkSrc,
|
||||
tokenId,
|
||||
onClick,
|
||||
clickable = false,
|
||||
clickable,
|
||||
nftImageURL,
|
||||
}) => {
|
||||
const testNetworkBackgroundColor = useSelector(getTestNetworkBackgroundColor);
|
||||
const isIpfsEnabled = useSelector(getIpfsGateway);
|
||||
const isIpfsURL = nftImageURL?.includes('ipfs:');
|
||||
return (
|
||||
<Box
|
||||
className="nft-item__container"
|
||||
@ -58,7 +64,7 @@ export const NftItem = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
{src ? (
|
||||
{isIpfsEnabled ? (
|
||||
<Box
|
||||
className="nft-item__item nft-item__item-image"
|
||||
data-testid="nft-image"
|
||||
@ -69,13 +75,27 @@ export const NftItem = ({
|
||||
justifyContent={JustifyContent.center}
|
||||
/>
|
||||
) : (
|
||||
<NftDefaultImage
|
||||
className="nft-item__default-image"
|
||||
data-testid="nft-default-image"
|
||||
name={name}
|
||||
tokenId={tokenId}
|
||||
clickable={clickable}
|
||||
/>
|
||||
<>
|
||||
{isIpfsURL ? (
|
||||
<NftDefaultImage
|
||||
className="nft-item__default-image"
|
||||
data-testid="nft-default-image"
|
||||
name={name}
|
||||
tokenId={tokenId}
|
||||
clickable={clickable}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
className="nft-item__item nft-item__item-image"
|
||||
data-testid="nft-image"
|
||||
as="img"
|
||||
src={src}
|
||||
alt={alt}
|
||||
display={Display.Block}
|
||||
justifyContent={JustifyContent.center}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</BadgeWrapper>
|
||||
</Box>
|
||||
@ -91,4 +111,5 @@ NftItem.propTypes = {
|
||||
tokenId: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
clickable: PropTypes.bool,
|
||||
nftImageURL: PropTypes.string,
|
||||
};
|
||||
|
@ -1,13 +1,28 @@
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import configureStore from '../../../store/store';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import { NftItem } from '.';
|
||||
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
},
|
||||
});
|
||||
|
||||
const noIpfsStore = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
ipfsGateway: '',
|
||||
},
|
||||
});
|
||||
|
||||
describe('NftItem component', () => {
|
||||
const store = configureStore()(mockState);
|
||||
jest.mock('../../../store/actions.ts', () => ({
|
||||
getTokenStandardAndDetails: jest.fn().mockResolvedValue(),
|
||||
}));
|
||||
describe('render', () => {
|
||||
const props = {
|
||||
alt: 'Test Alt',
|
||||
@ -18,6 +33,7 @@ describe('NftItem component', () => {
|
||||
networkSrc: 'test-network-src',
|
||||
tokenId: '1',
|
||||
onClick: jest.fn(),
|
||||
nftImageURL: '',
|
||||
};
|
||||
|
||||
it('renders correctly with an image source', () => {
|
||||
@ -29,10 +45,13 @@ describe('NftItem component', () => {
|
||||
expect(getByTestId('nft-image')).toHaveAttribute('src', 'test-src');
|
||||
});
|
||||
|
||||
it('renders correctly with default image when no image source is provided', () => {
|
||||
it('renders correctly with default image when no ipfs is off and no image is provided', () => {
|
||||
const { getByTestId, queryByTestId } = renderWithProvider(
|
||||
<NftItem {...props} src="" />,
|
||||
store,
|
||||
<NftItem
|
||||
{...props}
|
||||
nftImageURL="ipfs://QmTSZUNt8AKyDabkyXXXP4oHWDnaVXgNdXoJGEyaYzLbeL"
|
||||
/>,
|
||||
noIpfsStore,
|
||||
);
|
||||
|
||||
expect(queryByTestId('nft-image')).not.toBeInTheDocument();
|
||||
|
@ -27,6 +27,7 @@ interface AppState {
|
||||
} | null;
|
||||
networkDropdownOpen: boolean;
|
||||
importNftsModalOpen: boolean;
|
||||
showIpfsModalOpen: boolean;
|
||||
accountDetail: {
|
||||
subview?: string;
|
||||
accountExport?: string;
|
||||
@ -96,6 +97,7 @@ const initialState: AppState = {
|
||||
qrCodeData: null,
|
||||
networkDropdownOpen: false,
|
||||
importNftsModalOpen: false,
|
||||
showIpfsModalOpen: false,
|
||||
accountDetail: {
|
||||
privateKey: '',
|
||||
},
|
||||
@ -176,6 +178,19 @@ export default function reduceApp(
|
||||
...appState,
|
||||
importNftsModalOpen: false,
|
||||
};
|
||||
|
||||
case actionConstants.SHOW_IPFS_MODAL_OPEN:
|
||||
return {
|
||||
...appState,
|
||||
showIpfsModalOpen: true,
|
||||
};
|
||||
|
||||
case actionConstants.SHOW_IPFS_MODAL_CLOSE:
|
||||
return {
|
||||
...appState,
|
||||
showIpfsModalOpen: false,
|
||||
};
|
||||
|
||||
// alert methods
|
||||
case actionConstants.ALERT_OPEN:
|
||||
return {
|
||||
|
@ -117,6 +117,7 @@ import DeprecatedTestNetworks from '../../components/ui/deprecated-test-networks
|
||||
import NewNetworkInfo from '../../components/ui/new-network-info/new-network-info';
|
||||
import { ThemeType } from '../../../shared/constants/preferences';
|
||||
import { Box } from '../../components/component-library';
|
||||
import { ToggleIpfsModal } from '../../components/app/nft-default-image/toggle-ipfs-modal';
|
||||
|
||||
export default class Routes extends Component {
|
||||
static propTypes = {
|
||||
@ -159,6 +160,8 @@ export default class Routes extends Component {
|
||||
accountDetailsAddress: PropTypes.string,
|
||||
isImportNftsModalOpen: PropTypes.bool.isRequired,
|
||||
hideImportNftsModal: PropTypes.func.isRequired,
|
||||
isIpfsModalOpen: PropTypes.bool.isRequired,
|
||||
hideIpfsModal: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -515,6 +518,8 @@ export default class Routes extends Component {
|
||||
location,
|
||||
isImportNftsModalOpen,
|
||||
hideImportNftsModal,
|
||||
isIpfsModalOpen,
|
||||
hideIpfsModal,
|
||||
} = this.props;
|
||||
const loadMessage =
|
||||
loadingMessage || isNetworkLoading
|
||||
@ -576,6 +581,9 @@ export default class Routes extends Component {
|
||||
{isImportNftsModalOpen ? (
|
||||
<ImportNftsModal onClose={() => hideImportNftsModal()} />
|
||||
) : null}
|
||||
{isIpfsModalOpen ? (
|
||||
<ToggleIpfsModal onClose={() => hideIpfsModal()} />
|
||||
) : null}
|
||||
<Box className="main-container-wrapper">
|
||||
{isLoading ? <Loading loadingMessage={loadMessage} /> : null}
|
||||
{!isLoading && isNetworkLoading ? <LoadingNetwork /> : null}
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
import {
|
||||
lockMetamask,
|
||||
hideImportNftsModal,
|
||||
hideIpfsModal,
|
||||
setCurrentCurrency,
|
||||
setLastActiveTime,
|
||||
setMouseUserState,
|
||||
@ -65,6 +66,7 @@ function mapStateToProps(state) {
|
||||
isNetworkMenuOpen: state.metamask.isNetworkMenuOpen,
|
||||
accountDetailsAddress: state.appState.accountDetailsAddress,
|
||||
isImportNftsModalOpen: state.appState.importNftsModalOpen,
|
||||
isIpfsModalOpen: state.appState.showIpfsModalOpen,
|
||||
};
|
||||
}
|
||||
|
||||
@ -80,6 +82,7 @@ function mapDispatchToProps(dispatch) {
|
||||
toggleAccountMenu: () => dispatch(toggleAccountMenu()),
|
||||
toggleNetworkMenu: () => dispatch(toggleNetworkMenu()),
|
||||
hideImportNftsModal: () => dispatch(hideImportNftsModal()),
|
||||
hideIpfsModal: () => dispatch(hideIpfsModal()),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -381,6 +381,55 @@ exports[`Security Tab should match snapshot 1`] = `
|
||||
The IPFS gateway makes it possible to access and view data hosted by third parties. You can add a custom IPFS gateway or continue using the default.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="settings-page__content-item"
|
||||
>
|
||||
<label
|
||||
class="toggle-button toggle-button--on"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
|
||||
>
|
||||
<div
|
||||
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
|
||||
>
|
||||
<div
|
||||
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
|
||||
/>
|
||||
<div
|
||||
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
|
||||
>
|
||||
<div
|
||||
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
|
||||
type="checkbox"
|
||||
value="dweb.link"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="toggle-button__status"
|
||||
>
|
||||
<span
|
||||
class="toggle-button__label-off"
|
||||
>
|
||||
Off
|
||||
</span>
|
||||
<span
|
||||
class="toggle-button__label-on"
|
||||
>
|
||||
On
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="settings-page__content-item"
|
||||
>
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
MetaMetricsEventKeyType,
|
||||
MetaMetricsEventName,
|
||||
} from '../../../../shared/constants/metametrics';
|
||||
import { IPFS_DEFAULT_GATEWAY_URL } from '../../../../shared/constants/network';
|
||||
import {
|
||||
AUTO_DETECT_TOKEN_LEARN_MORE_LINK,
|
||||
COINGECKO_LINK,
|
||||
@ -68,6 +69,7 @@ export default class SecurityTab extends PureComponent {
|
||||
ipfsGateway: this.props.ipfsGateway,
|
||||
ipfsGatewayError: '',
|
||||
srpQuizModalVisible: false,
|
||||
ipfsToggle: false,
|
||||
};
|
||||
|
||||
settingsRefCounter = 0;
|
||||
@ -320,7 +322,7 @@ export default class SecurityTab extends PureComponent {
|
||||
this.props;
|
||||
|
||||
const handleIpfsGatewaySave = (gateway) => {
|
||||
const url = new URL(addUrlProtocolPrefix(gateway));
|
||||
const url = gateway ? new URL(addUrlProtocolPrefix(gateway)) : '';
|
||||
const { host } = url;
|
||||
|
||||
this.props.setIpfsGateway(host);
|
||||
@ -355,6 +357,11 @@ export default class SecurityTab extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
const handleIpfsToggle = (url) => {
|
||||
url?.length < 1
|
||||
? handleIpfsGatewayChange(IPFS_DEFAULT_GATEWAY_URL)
|
||||
: handleIpfsGatewayChange('');
|
||||
};
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[5]}
|
||||
@ -368,17 +375,31 @@ export default class SecurityTab extends PureComponent {
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-page__content-item">
|
||||
<div className="settings-page__content-item-col">
|
||||
<TextField
|
||||
type="text"
|
||||
value={this.state.ipfsGateway}
|
||||
onChange={(e) => handleIpfsGatewayChange(e.target.value)}
|
||||
error={ipfsGatewayError}
|
||||
fullWidth
|
||||
margin="dense"
|
||||
/>
|
||||
</div>
|
||||
<ToggleButton
|
||||
value={this.state.ipfsGateway}
|
||||
onToggle={(value) => {
|
||||
handleIpfsToggle(value);
|
||||
this.setState({ ipfsToggle: Boolean(value) });
|
||||
}}
|
||||
offLabel={t('off')}
|
||||
onLabel={t('on')}
|
||||
/>
|
||||
</div>
|
||||
{!this.state.ipfsToggle && (
|
||||
<div className="settings-page__content-item">
|
||||
<div className="settings-page__content-item-col">
|
||||
<TextField
|
||||
type="text"
|
||||
disabled={!this.state.ipfsGateway}
|
||||
value={this.state.ipfsGateway}
|
||||
onChange={(e) => handleIpfsGatewayChange(e.target.value)}
|
||||
error={ipfsGatewayError}
|
||||
fullWidth
|
||||
margin="dense"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="settings-page__content-item"
|
||||
ref={this.settingsRefs[9]}
|
||||
|
@ -11,6 +11,8 @@ 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';
|
||||
export const SHOW_IPFS_MODAL_OPEN = 'UI_IPFS_MODAL_OPEN';
|
||||
export const SHOW_IPFS_MODAL_CLOSE = 'UI_IPFS_MODAL_CLOSE';
|
||||
// remote state
|
||||
export const UPDATE_METAMASK_STATE = 'UPDATE_METAMASK_STATE';
|
||||
export const SELECTED_ADDRESS_CHANGED = 'SELECTED_ADDRESS_CHANGED';
|
||||
|
@ -2427,6 +2427,17 @@ export function hideImportNftsModal(): Action {
|
||||
};
|
||||
}
|
||||
|
||||
export function showIpfsModal(): Action {
|
||||
return {
|
||||
type: actionConstants.SHOW_IPFS_MODAL_OPEN,
|
||||
};
|
||||
}
|
||||
|
||||
export function hideIpfsModal(): Action {
|
||||
return {
|
||||
type: actionConstants.SHOW_IPFS_MODAL_CLOSE,
|
||||
};
|
||||
}
|
||||
export function closeCurrentNotificationWindow(): ThunkAction<
|
||||
void,
|
||||
MetaMaskReduxState,
|
||||
|
Loading…
Reference in New Issue
Block a user