Feat/collectibles the return (#12970)
* Wire collectibles frontend UI with controller datafeature/default_network_editable
parent
c03b6dd19b
commit
1b6e58c417
After Width: | Height: | Size: 59 KiB |
@ -0,0 +1,251 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { useDispatch, useSelector } from 'react-redux'; |
||||
import { useHistory } from 'react-router-dom'; |
||||
import { getTokenTrackerLink } from '@metamask/etherscan-link'; |
||||
import Box from '../../ui/box'; |
||||
import Card from '../../ui/card'; |
||||
import Typography from '../../ui/typography/typography'; |
||||
import { |
||||
COLORS, |
||||
TYPOGRAPHY, |
||||
FONT_WEIGHT, |
||||
JUSTIFY_CONTENT, |
||||
FLEX_DIRECTION, |
||||
OVERFLOW_WRAP, |
||||
DISPLAY, |
||||
} from '../../../helpers/constants/design-system'; |
||||
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||
import { |
||||
getAssetImageURL, |
||||
isEqualCaseInsensitive, |
||||
shortenAddress, |
||||
} from '../../../helpers/utils/util'; |
||||
import { |
||||
getCurrentChainId, |
||||
getIpfsGateway, |
||||
getRpcPrefsForCurrentProvider, |
||||
getSelectedIdentity, |
||||
} from '../../../selectors'; |
||||
import AssetNavigation from '../../../pages/asset/components/asset-navigation'; |
||||
import { getCollectibleContracts } from '../../../ducks/metamask/metamask'; |
||||
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; |
||||
import { removeAndIgnoreCollectible } from '../../../store/actions'; |
||||
import { |
||||
GOERLI_CHAIN_ID, |
||||
KOVAN_CHAIN_ID, |
||||
MAINNET_CHAIN_ID, |
||||
POLYGON_CHAIN_ID, |
||||
RINKEBY_CHAIN_ID, |
||||
ROPSTEN_CHAIN_ID, |
||||
} from '../../../../shared/constants/network'; |
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util'; |
||||
import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; |
||||
import CollectibleOptions from '../collectible-options/collectible-options'; |
||||
|
||||
export default function CollectibleDetails({ collectible }) { |
||||
const { image, name, description, address, tokenId } = collectible; |
||||
const t = useI18nContext(); |
||||
const history = useHistory(); |
||||
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider); |
||||
const ipfsGateway = useSelector(getIpfsGateway); |
||||
const collectibleContracts = useSelector(getCollectibleContracts); |
||||
const currentNetwork = useSelector(getCurrentChainId); |
||||
|
||||
const collectibleContractName = collectibleContracts.find( |
||||
({ address: contractAddress }) => |
||||
isEqualCaseInsensitive(contractAddress, address), |
||||
)?.name; |
||||
const selectedAccountName = useSelector( |
||||
(state) => getSelectedIdentity(state).name, |
||||
); |
||||
const collectibleImageURL = getAssetImageURL(image, ipfsGateway); |
||||
const dispatch = useDispatch(); |
||||
|
||||
const onRemove = () => { |
||||
dispatch(removeAndIgnoreCollectible(address, tokenId)); |
||||
history.push(DEFAULT_ROUTE); |
||||
}; |
||||
|
||||
const getOpenSeaLink = () => { |
||||
switch (currentNetwork) { |
||||
case MAINNET_CHAIN_ID: |
||||
return `https://opensea.io/assets/${address}/${tokenId}`; |
||||
case POLYGON_CHAIN_ID: |
||||
return `https://opensea.io/assets/matic/${address}/${tokenId}`; |
||||
case GOERLI_CHAIN_ID: |
||||
case KOVAN_CHAIN_ID: |
||||
case ROPSTEN_CHAIN_ID: |
||||
case RINKEBY_CHAIN_ID: |
||||
return `https://testnets.opensea.io/assets/${address}/${tokenId}`; |
||||
default: |
||||
return null; |
||||
} |
||||
}; |
||||
|
||||
const openSeaLink = getOpenSeaLink(); |
||||
return ( |
||||
<> |
||||
<AssetNavigation |
||||
accountName={selectedAccountName} |
||||
assetName={collectibleContractName} |
||||
onBack={() => history.push(DEFAULT_ROUTE)} |
||||
optionsButton={ |
||||
<CollectibleOptions |
||||
onViewOnOpensea={ |
||||
openSeaLink |
||||
? () => global.platform.openTab({ url: openSeaLink }) |
||||
: null |
||||
} |
||||
onRemove={onRemove} |
||||
/> |
||||
} |
||||
/> |
||||
<Box className="collectible-details"> |
||||
<div className="collectible-details__top-section"> |
||||
<Card |
||||
padding={0} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
className="collectible-details__card" |
||||
> |
||||
<img |
||||
className="collectible-details__image" |
||||
src={collectibleImageURL} |
||||
/> |
||||
</Card> |
||||
<Box |
||||
flexDirection={FLEX_DIRECTION.COLUMN} |
||||
className="collectible-details__top-section__info" |
||||
> |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
variant={TYPOGRAPHY.H4} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
boxProps={{ margin: 0, marginBottom: 4 }} |
||||
> |
||||
{name} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H5} |
||||
boxProps={{ margin: 0, marginBottom: 4 }} |
||||
overflowWrap={OVERFLOW_WRAP.BREAK_WORD} |
||||
> |
||||
{`#${tokenId}`} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
variant={TYPOGRAPHY.H6} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
className="collectible-details__description" |
||||
boxProps={{ margin: 0, marginBottom: 2 }} |
||||
> |
||||
{t('description')} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.UI4} |
||||
variant={TYPOGRAPHY.H6} |
||||
boxProps={{ margin: 0 }} |
||||
> |
||||
{description} |
||||
</Typography> |
||||
</Box> |
||||
</div> |
||||
<Box> |
||||
<Box display={DISPLAY.FLEX} flexDirection={FLEX_DIRECTION.ROW}> |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
variant={TYPOGRAPHY.H6} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
boxProps={{ |
||||
margin: 0, |
||||
marginBottom: 4, |
||||
marginRight: 2, |
||||
}} |
||||
className="collectible-details__link-title" |
||||
> |
||||
{t('source')} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.PRIMARY1} |
||||
variant={TYPOGRAPHY.H6} |
||||
boxProps={{ |
||||
margin: 0, |
||||
marginBottom: 4, |
||||
}} |
||||
overflowWrap={OVERFLOW_WRAP.BREAK_WORD} |
||||
> |
||||
<a |
||||
target="_blank" |
||||
href={collectibleImageURL} |
||||
rel="noopener noreferrer" |
||||
className="collectible-details__image-link" |
||||
> |
||||
{image} |
||||
</a> |
||||
</Typography> |
||||
</Box> |
||||
<Box display={DISPLAY.FLEX} flexDirection={FLEX_DIRECTION.ROW}> |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
variant={TYPOGRAPHY.H6} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
boxProps={{ |
||||
margin: 0, |
||||
marginBottom: 4, |
||||
marginRight: 2, |
||||
}} |
||||
className="collectible-details__link-title" |
||||
> |
||||
{t('contractAddress')} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H6} |
||||
overflowWrap={OVERFLOW_WRAP.BREAK_WORD} |
||||
boxProps={{ |
||||
margin: 0, |
||||
marginBottom: 4, |
||||
}} |
||||
> |
||||
<a |
||||
target="_blank" |
||||
className="collectible-details__contract-link" |
||||
href={getTokenTrackerLink( |
||||
address, |
||||
currentNetwork, |
||||
null, |
||||
null, |
||||
rpcPrefs, |
||||
)} |
||||
rel="noopener noreferrer" |
||||
> |
||||
{getEnvironmentType() === ENVIRONMENT_TYPE_POPUP |
||||
? shortenAddress(address) |
||||
: address} |
||||
</a> |
||||
</Typography> |
||||
</Box> |
||||
</Box> |
||||
</Box> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
CollectibleDetails.propTypes = { |
||||
collectible: PropTypes.shape({ |
||||
address: PropTypes.string.isRequired, |
||||
tokenId: PropTypes.string.isRequired, |
||||
name: PropTypes.string, |
||||
description: PropTypes.string, |
||||
image: PropTypes.string, |
||||
standard: PropTypes.string, |
||||
imageThumbnail: PropTypes.string, |
||||
imagePreview: PropTypes.string, |
||||
creator: PropTypes.shape({ |
||||
address: PropTypes.string, |
||||
config: PropTypes.string, |
||||
profile_img_url: PropTypes.string, |
||||
}), |
||||
}), |
||||
}; |
@ -0,0 +1,31 @@ |
||||
import React from 'react'; |
||||
import CollectibleDetails from './collectible-details'; |
||||
|
||||
export default { |
||||
title: 'Components/App/CollectiblesDetail', |
||||
id: __filename, |
||||
argTypes: { |
||||
collectible: { |
||||
control: 'object', |
||||
}, |
||||
}, |
||||
}; |
||||
|
||||
const collectible = { |
||||
name: 'Catnip Spicywright', |
||||
tokenId: '1124157', |
||||
address: '0x06012c8cf97bead5deae237070f9587f8e7a266d', |
||||
image: './catnip-spicywright.png', |
||||
description: |
||||
"Good day. My name is Catnip Spicywight, which got me teased a lot in high school. If I want to put low fat mayo all over my hamburgers, I shouldn't have to answer to anyone about it, am I right? One time I beat Arlene in an arm wrestle.", |
||||
}; |
||||
|
||||
export const DefaultStory = () => { |
||||
return <CollectibleDetails collectible={collectible} />; |
||||
}; |
||||
|
||||
DefaultStory.storyName = 'Default'; |
||||
|
||||
DefaultStory.args = { |
||||
collectible, |
||||
}; |
@ -0,0 +1,72 @@ |
||||
$card-width-break-large: 224px; |
||||
$link-title-width: 160px; |
||||
$spacer-break-large: 24px; |
||||
$spacer-break-small: 16px; |
||||
|
||||
.collectible-details { |
||||
padding: 0 $spacer-break-small; |
||||
|
||||
@media screen and (min-width: $break-large) { |
||||
padding: 0 $spacer-break-large; |
||||
} |
||||
|
||||
&__top-section { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin-bottom: $spacer-break-small; |
||||
|
||||
@media screen and (min-width: $break-large) { |
||||
margin-bottom: $spacer-break-large; |
||||
flex-direction: row; |
||||
} |
||||
|
||||
&__info { |
||||
@media screen and (min-width: $break-large) { |
||||
max-width: calc(100% - #{$card-width-break-large} - #{$spacer-break-large}); |
||||
flex: 0 0 calc(100% - #{$card-width-break-large} - #{$spacer-break-large}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
&__card { |
||||
overflow: hidden; |
||||
margin-bottom: $spacer-break-small; |
||||
|
||||
@media screen and (min-width: $break-large) { |
||||
margin-right: $spacer-break-large; |
||||
margin-bottom: 0; |
||||
max-width: $card-width-break-large; |
||||
flex: 0 0 $card-width-break-large; |
||||
height: $card-width-break-large; |
||||
} |
||||
} |
||||
|
||||
&__image { |
||||
width: 100%; |
||||
|
||||
@media screen and (min-width: $break-large) { |
||||
width: $card-width-break-large; |
||||
} |
||||
} |
||||
|
||||
&__address { |
||||
overflow-wrap: break-word; |
||||
} |
||||
|
||||
&__contract-link, |
||||
&__image-link { |
||||
color: $primary-1; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
word-break: break-all; |
||||
|
||||
&:hover { |
||||
color: $primary-3; |
||||
} |
||||
} |
||||
|
||||
&__link-title { |
||||
flex: 0 0 $link-title-width; |
||||
max-width: 0 0 $link-title-width; |
||||
} |
||||
} |
@ -0,0 +1,61 @@ |
||||
import React, { useContext, useState } from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
|
||||
import { I18nContext } from '../../../contexts/i18n'; |
||||
import { Menu, MenuItem } from '../../ui/menu'; |
||||
|
||||
const CollectibleOptions = ({ onRemove, onViewOnOpensea }) => { |
||||
const t = useContext(I18nContext); |
||||
const [ |
||||
collectibleOptionsButtonElement, |
||||
setCollectibleOptionsButtonElement, |
||||
] = useState(null); |
||||
const [collectibleOptionsOpen, setCollectibleOptionsOpen] = useState(false); |
||||
|
||||
return ( |
||||
<> |
||||
<button |
||||
className="fas fa-ellipsis-v collectible-options__button" |
||||
data-testid="collectible-options__button" |
||||
onClick={() => setCollectibleOptionsOpen(true)} |
||||
ref={setCollectibleOptionsButtonElement} |
||||
/> |
||||
{collectibleOptionsOpen ? ( |
||||
<Menu |
||||
anchorElement={collectibleOptionsButtonElement} |
||||
onHide={() => setCollectibleOptionsOpen(false)} |
||||
> |
||||
{onViewOnOpensea ? ( |
||||
<MenuItem |
||||
iconClassName="fas fa-qrcode" |
||||
data-testid="collectible-options__view-on-opensea" |
||||
onClick={() => { |
||||
setCollectibleOptionsOpen(false); |
||||
onViewOnOpensea(); |
||||
}} |
||||
> |
||||
{t('viewOnOpensea')} |
||||
</MenuItem> |
||||
) : null} |
||||
<MenuItem |
||||
iconClassName="fas fa-trash-alt collectible-options__icon" |
||||
data-testid="collectible-options__hide" |
||||
onClick={() => { |
||||
setCollectibleOptionsOpen(false); |
||||
onRemove(); |
||||
}} |
||||
> |
||||
{t('removeNFT')} |
||||
</MenuItem> |
||||
</Menu> |
||||
) : null} |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
CollectibleOptions.propTypes = { |
||||
onRemove: PropTypes.func.isRequired, |
||||
onViewOnOpensea: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
export default CollectibleOptions; |
@ -0,0 +1,12 @@ |
||||
.collectible-options { |
||||
&__button { |
||||
font-size: $font-size-paragraph; |
||||
color: $Black-100; |
||||
background-color: inherit; |
||||
padding: 2px 0 2px 8px; |
||||
} |
||||
|
||||
&__icon { |
||||
font-weight: 900; |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
export { default } from './collectibles-detection-notice'; |
@ -0,0 +1,31 @@ |
||||
.collectibles-detection-notice { |
||||
&__message { |
||||
position: relative; |
||||
padding: 0 1rem 1rem 1rem !important; |
||||
|
||||
&__close-button { |
||||
background-color: transparent; |
||||
|
||||
&::after { |
||||
position: absolute; |
||||
content: '\00D7'; |
||||
font-size: 29px; |
||||
font-weight: 200; |
||||
color: $black; |
||||
background-color: transparent; |
||||
top: 0; |
||||
right: 12px; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
|
||||
a.collectibles-detection-notice__message__link { |
||||
@include H6; |
||||
|
||||
width: 60%; |
||||
padding: 0; |
||||
justify-content: flex-start; |
||||
font-weight: bold; |
||||
} |
||||
} |
||||
} |
@ -1,148 +0,0 @@ |
||||
import React, { useState } from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import Box from '../../ui/box'; |
||||
import Button from '../../ui/button'; |
||||
import Typography from '../../ui/typography/typography'; |
||||
import { |
||||
COLORS, |
||||
TYPOGRAPHY, |
||||
TEXT_ALIGN, |
||||
JUSTIFY_CONTENT, |
||||
FLEX_DIRECTION, |
||||
ALIGN_ITEMS, |
||||
DISPLAY, |
||||
BLOCK_SIZES, |
||||
SIZES, |
||||
FLEX_WRAP, |
||||
} from '../../../helpers/constants/design-system'; |
||||
import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; |
||||
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util'; |
||||
|
||||
export default function CollectiblesItems({ onAddNFT, onRefreshList }) { |
||||
const t = useI18nContext(); |
||||
const collections = {}; |
||||
const defaultDropdownState = {}; |
||||
|
||||
Object.keys(collections).forEach((key) => { |
||||
defaultDropdownState[key] = true; |
||||
}); |
||||
|
||||
const [dropdownState, setDropdownState] = useState(defaultDropdownState); |
||||
const width = |
||||
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP |
||||
? BLOCK_SIZES.ONE_THIRD |
||||
: BLOCK_SIZES.ONE_SIXTH; |
||||
return ( |
||||
<div className="collectibles-items"> |
||||
<Box padding={[4, 6, 4, 6]} flexDirection={FLEX_DIRECTION.COLUMN}> |
||||
<> |
||||
{Object.keys(collections).map((key, index) => { |
||||
const { icon, collectibles } = collections[key]; |
||||
const isExpanded = dropdownState[key]; |
||||
|
||||
return ( |
||||
<div key={`collection-${index}`}> |
||||
<Box |
||||
marginTop={4} |
||||
marginBottom={4} |
||||
display={DISPLAY.FLEX} |
||||
alignItems={ALIGN_ITEMS.CENTER} |
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN} |
||||
> |
||||
<Box alignItems={ALIGN_ITEMS.CENTER}> |
||||
<img width="28" src={icon} /> |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
variant={TYPOGRAPHY.H4} |
||||
margin={[0, 0, 0, 2]} |
||||
> |
||||
{`${key} (${collectibles.length})`} |
||||
</Typography> |
||||
</Box> |
||||
<Box alignItems={ALIGN_ITEMS.FLEX_END}> |
||||
<i |
||||
className={`fa fa-lg fa-chevron-${ |
||||
isExpanded ? 'down' : 'right' |
||||
}`}
|
||||
onClick={() => { |
||||
setDropdownState((_dropdownState) => ({ |
||||
..._dropdownState, |
||||
[key]: !isExpanded, |
||||
})); |
||||
}} |
||||
/> |
||||
</Box> |
||||
</Box> |
||||
{isExpanded ? ( |
||||
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP}> |
||||
{collectibles.map((collectible, i) => { |
||||
return ( |
||||
<Box width={width} padding={2} key={`collectible-${i}`}> |
||||
<Box |
||||
borderRadius={SIZES.MD} |
||||
backgroundColor={collectible.backgroundColor} |
||||
> |
||||
<img src={collectible.icon} /> |
||||
</Box> |
||||
</Box> |
||||
); |
||||
})} |
||||
</Box> |
||||
) : null} |
||||
</div> |
||||
); |
||||
})} |
||||
<Box |
||||
marginTop={6} |
||||
flexDirection={FLEX_DIRECTION.COLUMN} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H5} |
||||
align={TEXT_ALIGN.CENTER} |
||||
> |
||||
{t('missingNFT')} |
||||
</Typography> |
||||
<Box |
||||
alignItems={ALIGN_ITEMS.CENTER} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
> |
||||
<Box justifyContent={JUSTIFY_CONTENT.FLEX_END}> |
||||
<Button |
||||
type="link" |
||||
onClick={onRefreshList} |
||||
style={{ padding: '4px' }} |
||||
> |
||||
{t('refreshList')} |
||||
</Button> |
||||
</Box> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H4} |
||||
align={TEXT_ALIGN.CENTER} |
||||
> |
||||
{t('or')} |
||||
</Typography> |
||||
<Box justifyContent={JUSTIFY_CONTENT.FLEX_START}> |
||||
<Button |
||||
type="link" |
||||
onClick={onAddNFT} |
||||
style={{ padding: '4px' }} |
||||
> |
||||
{t('addNFTLowerCase')} |
||||
</Button> |
||||
</Box> |
||||
</Box> |
||||
</Box> |
||||
</> |
||||
</Box> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
CollectiblesItems.propTypes = { |
||||
onAddNFT: PropTypes.func.isRequired, |
||||
onRefreshList: PropTypes.func.isRequired, |
||||
}; |
@ -0,0 +1,164 @@ |
||||
import React, { useState } from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { useSelector } from 'react-redux'; |
||||
import { useHistory } from 'react-router-dom'; |
||||
import Box from '../../ui/box'; |
||||
import Typography from '../../ui/typography/typography'; |
||||
import { |
||||
COLORS, |
||||
TYPOGRAPHY, |
||||
JUSTIFY_CONTENT, |
||||
FLEX_DIRECTION, |
||||
ALIGN_ITEMS, |
||||
DISPLAY, |
||||
BLOCK_SIZES, |
||||
FLEX_WRAP, |
||||
} from '../../../helpers/constants/design-system'; |
||||
import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; |
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util'; |
||||
import { getIpfsGateway } from '../../../selectors'; |
||||
import { ASSET_ROUTE } from '../../../helpers/constants/routes'; |
||||
import { getAssetImageURL } from '../../../helpers/utils/util'; |
||||
|
||||
const width = |
||||
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP |
||||
? BLOCK_SIZES.ONE_THIRD |
||||
: BLOCK_SIZES.ONE_SIXTH; |
||||
export default function CollectiblesItems({ collections = {} }) { |
||||
const defaultDropdownState = {}; |
||||
const ipfsGateway = useSelector(getIpfsGateway); |
||||
|
||||
Object.keys(collections).forEach((key) => { |
||||
defaultDropdownState[key] = true; |
||||
}); |
||||
const history = useHistory(); |
||||
|
||||
const [dropdownState, setDropdownState] = useState(defaultDropdownState); |
||||
return ( |
||||
<div className="collectibles-items"> |
||||
<Box padding={[6, 4]} flexDirection={FLEX_DIRECTION.COLUMN}> |
||||
<> |
||||
{Object.keys(collections).map((key, index) => { |
||||
const { |
||||
collectibles, |
||||
collectionName, |
||||
collectionImage, |
||||
} = collections[key]; |
||||
|
||||
const isExpanded = dropdownState[key]; |
||||
return ( |
||||
<div |
||||
className="collectibles-items__item" |
||||
key={`collection-${index}`} |
||||
onClick={() => { |
||||
setDropdownState((_dropdownState) => ({ |
||||
..._dropdownState, |
||||
[key]: !isExpanded, |
||||
})); |
||||
}} |
||||
> |
||||
<Box |
||||
marginBottom={2} |
||||
display={DISPLAY.FLEX} |
||||
alignItems={ALIGN_ITEMS.CENTER} |
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN} |
||||
className="collectibles-items__item__accordion-title" |
||||
> |
||||
<Box |
||||
alignItems={ALIGN_ITEMS.CENTER} |
||||
className="collectibles-items__item__collection-header" |
||||
> |
||||
{collectionImage ? ( |
||||
<img |
||||
src={collectionImage} |
||||
className="collectibles-items__item__collection-image" |
||||
/> |
||||
) : ( |
||||
<div className="collectibles-items__item__collection-image-alt"> |
||||
{collectionName[0]} |
||||
</div> |
||||
)} |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
variant={TYPOGRAPHY.H5} |
||||
margin={[0, 0, 0, 2]} |
||||
> |
||||
{`${collectionName} (${collectibles.length})`} |
||||
</Typography> |
||||
</Box> |
||||
<Box alignItems={ALIGN_ITEMS.FLEX_END}> |
||||
<i |
||||
className={`fa fa-chevron-${ |
||||
isExpanded ? 'down' : 'right' |
||||
}`}
|
||||
/> |
||||
</Box> |
||||
</Box> |
||||
{isExpanded ? ( |
||||
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}> |
||||
{collectibles.map((collectible, i) => { |
||||
const { |
||||
image, |
||||
address, |
||||
tokenId, |
||||
backgroundColor, |
||||
} = collectible; |
||||
const collectibleImage = getAssetImageURL( |
||||
image, |
||||
ipfsGateway, |
||||
); |
||||
return ( |
||||
<Box width={width} key={`collectible-${i}`}> |
||||
<div |
||||
className="collectibles-items__image__wrapper" |
||||
style={{ |
||||
backgroundColor, |
||||
}} |
||||
> |
||||
<img |
||||
onClick={() => |
||||
history.push( |
||||
`${ASSET_ROUTE}/${address}/${tokenId}`, |
||||
) |
||||
} |
||||
className="collectibles-items__image" |
||||
src={collectibleImage} |
||||
/> |
||||
</div> |
||||
</Box> |
||||
); |
||||
})} |
||||
</Box> |
||||
) : null} |
||||
</div> |
||||
); |
||||
})} |
||||
</> |
||||
</Box> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
CollectiblesItems.propTypes = { |
||||
collections: PropTypes.shape({ |
||||
collectibles: PropTypes.arrayOf( |
||||
PropTypes.shape({ |
||||
address: PropTypes.string.isRequired, |
||||
tokenId: PropTypes.string.isRequired, |
||||
name: PropTypes.string, |
||||
description: PropTypes.string, |
||||
image: PropTypes.string, |
||||
standard: PropTypes.string, |
||||
imageThumbnail: PropTypes.string, |
||||
imagePreview: PropTypes.string, |
||||
creator: PropTypes.shape({ |
||||
address: PropTypes.string, |
||||
config: PropTypes.string, |
||||
profile_img_url: PropTypes.string, |
||||
}), |
||||
}), |
||||
), |
||||
collectionImage: PropTypes.string, |
||||
collectionName: PropTypes.string, |
||||
}), |
||||
}; |
@ -1 +1 @@ |
||||
export { default } from './collectibles-items.component'; |
||||
export { default } from './collectibles-items'; |
||||
|
@ -0,0 +1,41 @@ |
||||
.collectibles-items { |
||||
&__image__wrapper { |
||||
border-radius: 4px; |
||||
width: 100%; |
||||
display: flex; |
||||
justify-content: center; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
&__image { |
||||
border-radius: 4px; |
||||
width: 100%; |
||||
height: 100%; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
&__item { |
||||
margin-bottom: 24px; |
||||
|
||||
&__accordion-title { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
&__collection-image { |
||||
width: 32px; |
||||
height: 32px; |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
&__collection-image-alt { |
||||
border-radius: 50%; |
||||
width: 32px; |
||||
height: 32px; |
||||
padding: 8px; |
||||
background: $ui-4; |
||||
color: $ui-white; |
||||
text-align: center; |
||||
line-height: 1; |
||||
} |
||||
} |
||||
} |
@ -1,90 +0,0 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import Box from '../../ui/box'; |
||||
import Button from '../../ui/button'; |
||||
import Typography from '../../ui/typography/typography'; |
||||
import NewCollectiblesNotice from '../new-collectibles-notice'; |
||||
import CollectiblesItems from '../collectibles-items'; |
||||
import { |
||||
COLORS, |
||||
TYPOGRAPHY, |
||||
TEXT_ALIGN, |
||||
JUSTIFY_CONTENT, |
||||
FLEX_DIRECTION, |
||||
FONT_WEIGHT, |
||||
} from '../../../helpers/constants/design-system'; |
||||
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||
|
||||
export default function CollectiblesTab({ onAddNFT }) { |
||||
const collectibles = []; |
||||
const newNFTsDetected = false; |
||||
const t = useI18nContext(); |
||||
|
||||
return ( |
||||
<div className="collectibles-tab"> |
||||
{collectibles.length > 0 ? ( |
||||
<CollectiblesItems |
||||
onAddNFT={onAddNFT} |
||||
onRefreshList={() => { |
||||
console.log('refreshing collectibles'); |
||||
}} |
||||
/> |
||||
) : ( |
||||
<Box padding={[6, 12, 6, 12]}> |
||||
{newNFTsDetected ? <NewCollectiblesNotice /> : null} |
||||
<Box justifyContent={JUSTIFY_CONTENT.CENTER}> |
||||
<img src="./images/no-nfts.svg" /> |
||||
</Box> |
||||
<Box |
||||
marginTop={4} |
||||
marginBottom={12} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
flexDirection={FLEX_DIRECTION.COLUMN} |
||||
> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H4} |
||||
align={TEXT_ALIGN.CENTER} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
> |
||||
{t('noNFTs')} |
||||
</Typography> |
||||
<Button |
||||
type="link" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
href="https://metamask.zendesk.com/hc/en-us/articles/360058238591-NFT-tokens-in-MetaMask-wallet" |
||||
style={{ padding: 0, fontSize: '1rem' }} |
||||
> |
||||
{t('learnMore')} |
||||
</Button> |
||||
</Box> |
||||
<Box |
||||
marginBottom={4} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
flexDirection={FLEX_DIRECTION.COLUMN} |
||||
> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H5} |
||||
align={TEXT_ALIGN.CENTER} |
||||
> |
||||
{t('missingNFT')} |
||||
</Typography> |
||||
<Button |
||||
type="link" |
||||
onClick={onAddNFT} |
||||
style={{ padding: 0, fontSize: '1rem' }} |
||||
> |
||||
{t('addNFT')} |
||||
</Button> |
||||
</Box> |
||||
</Box> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
CollectiblesTab.propTypes = { |
||||
onAddNFT: PropTypes.func.isRequired, |
||||
}; |
@ -0,0 +1,162 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { useDispatch, useSelector } from 'react-redux'; |
||||
import { useHistory } from 'react-router-dom'; |
||||
import Box from '../../ui/box'; |
||||
import Button from '../../ui/button'; |
||||
import Typography from '../../ui/typography/typography'; |
||||
import CollectiblesDetectionNotice from '../collectibles-detection-notice'; |
||||
import CollectiblesItems from '../collectibles-items'; |
||||
import { |
||||
COLORS, |
||||
TYPOGRAPHY, |
||||
TEXT_ALIGN, |
||||
JUSTIFY_CONTENT, |
||||
FLEX_DIRECTION, |
||||
FONT_WEIGHT, |
||||
ALIGN_ITEMS, |
||||
} from '../../../helpers/constants/design-system'; |
||||
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||
import { |
||||
getCollectibles, |
||||
getCollectibleContracts, |
||||
getCollectiblesDetectionNoticeDismissed, |
||||
} from '../../../ducks/metamask/metamask'; |
||||
import { getIsMainnet, getUseCollectibleDetection } from '../../../selectors'; |
||||
import { EXPERIMENTAL_ROUTE } from '../../../helpers/constants/routes'; |
||||
import { detectCollectibles } from '../../../store/actions'; |
||||
|
||||
export default function CollectiblesTab({ onAddNFT }) { |
||||
const collectibles = useSelector(getCollectibles); |
||||
const collectibleContracts = useSelector(getCollectibleContracts); |
||||
const useCollectibleDetection = useSelector(getUseCollectibleDetection); |
||||
const isMainnet = useSelector(getIsMainnet); |
||||
const collectibleDetectionNoticeDismissed = useSelector( |
||||
getCollectiblesDetectionNoticeDismissed, |
||||
); |
||||
const history = useHistory(); |
||||
const t = useI18nContext(); |
||||
const dispatch = useDispatch(); |
||||
|
||||
const collections = {}; |
||||
collectibles.forEach((collectible) => { |
||||
if (collections[collectible.address]) { |
||||
collections[collectible.address].collectibles.push(collectible); |
||||
} else { |
||||
const collectionContract = collectibleContracts.find( |
||||
({ address }) => address === collectible.address, |
||||
); |
||||
collections[collectible.address] = { |
||||
collectionName: collectionContract?.name || collectible.name, |
||||
collectionImage: |
||||
collectionContract?.logo || collectible.collectionImage, |
||||
collectibles: [collectible], |
||||
}; |
||||
} |
||||
}); |
||||
|
||||
const onEnableAutoDetect = () => { |
||||
history.push(EXPERIMENTAL_ROUTE); |
||||
}; |
||||
|
||||
return ( |
||||
<div className="collectibles-tab"> |
||||
{collectibles.length > 0 ? ( |
||||
<CollectiblesItems collections={collections} /> |
||||
) : ( |
||||
<Box padding={[6, 12, 6, 12]}> |
||||
{isMainnet && |
||||
!useCollectibleDetection && |
||||
!collectibleDetectionNoticeDismissed ? ( |
||||
<CollectiblesDetectionNotice /> |
||||
) : null} |
||||
<Box justifyContent={JUSTIFY_CONTENT.CENTER}> |
||||
<img src="./images/no-nfts.svg" /> |
||||
</Box> |
||||
<Box |
||||
marginTop={4} |
||||
marginBottom={12} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
flexDirection={FLEX_DIRECTION.COLUMN} |
||||
> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H4} |
||||
align={TEXT_ALIGN.CENTER} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
> |
||||
{t('noNFTs')} |
||||
</Typography> |
||||
<Button |
||||
type="link" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
href="https://metamask.zendesk.com/hc/en-us/articles/360058238591-NFT-tokens-in-MetaMask-wallet" |
||||
style={{ padding: 0, fontSize: '1rem' }} |
||||
> |
||||
{t('learnMoreUpperCase')} |
||||
</Button> |
||||
</Box> |
||||
</Box> |
||||
)} |
||||
<Box |
||||
marginBottom={4} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
flexDirection={FLEX_DIRECTION.COLUMN} |
||||
> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H5} |
||||
align={TEXT_ALIGN.CENTER} |
||||
> |
||||
{t('missingNFT')} |
||||
</Typography> |
||||
<Box |
||||
alignItems={ALIGN_ITEMS.CENTER} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
> |
||||
{isMainnet ? ( |
||||
<> |
||||
<Box |
||||
className="collectibles-tab__link" |
||||
justifyContent={JUSTIFY_CONTENT.FLEX_END} |
||||
> |
||||
{useCollectibleDetection ? ( |
||||
<Button |
||||
type="link" |
||||
onClick={() => dispatch(detectCollectibles())} |
||||
> |
||||
{t('refreshList')} |
||||
</Button> |
||||
) : ( |
||||
<Button type="link" onClick={onEnableAutoDetect}> |
||||
{t('enableAutoDetect')} |
||||
</Button> |
||||
)} |
||||
</Box> |
||||
<Typography |
||||
color={COLORS.UI3} |
||||
variant={TYPOGRAPHY.H4} |
||||
align={TEXT_ALIGN.CENTER} |
||||
> |
||||
{t('or')} |
||||
</Typography> |
||||
</> |
||||
) : null} |
||||
<Box |
||||
justifyContent={JUSTIFY_CONTENT.FLEX_START} |
||||
className="collectibles-tab__link" |
||||
> |
||||
<Button type="link" onClick={onAddNFT}> |
||||
{t('importNFTs')} |
||||
</Button> |
||||
</Box> |
||||
</Box> |
||||
</Box> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
CollectiblesTab.propTypes = { |
||||
onAddNFT: PropTypes.func.isRequired, |
||||
}; |
@ -0,0 +1,300 @@ |
||||
import React from 'react'; |
||||
import { fireEvent, screen } from '@testing-library/react'; |
||||
import reactRouterDom from 'react-router-dom'; |
||||
import configureStore from '../../../store/store'; |
||||
import { renderWithProvider } from '../../../../test/jest/rendering'; |
||||
import { EXPERIMENTAL_ROUTE } from '../../../helpers/constants/routes'; |
||||
import { setBackgroundConnection } from '../../../../test/jest'; |
||||
import CollectiblesTab from '.'; |
||||
|
||||
const COLLECTIBLES = [ |
||||
{ |
||||
address: '0x495f947276749Ce646f68AC8c248420045cb7b5e', |
||||
tokenId: |
||||
'58076532811975507823669075598676816378162417803895263482849101575514658701313', |
||||
name: 'Punk #4', |
||||
creator: { |
||||
user: { |
||||
username: null, |
||||
}, |
||||
profile_img_url: null, |
||||
address: '0x806627172af48bd5b0765d3449a7def80d6576ff', |
||||
config: '', |
||||
}, |
||||
description: 'Red Mohawk bam!', |
||||
image: |
||||
'https://lh3.googleusercontent.com/BdxvLseXcfl57BiuQcQYdJ64v-aI8din7WPk0Pgo3qQFhAUH-B6i-dCqqc_mCkRIzULmwzwecnohLhrcH8A9mpWIZqA7ygc52Sr81hE', |
||||
standard: 'ERC1155', |
||||
}, |
||||
{ |
||||
address: '0x495f947276749Ce646f68AC8c248420045cb7b5e', |
||||
tokenId: |
||||
'58076532811975507823669075598676816378162417803895263482849101574415147073537', |
||||
name: 'Punk #3', |
||||
creator: { |
||||
user: { |
||||
username: null, |
||||
}, |
||||
profile_img_url: null, |
||||
address: '0x806627172af48bd5b0765d3449a7def80d6576ff', |
||||
config: '', |
||||
}, |
||||
description: 'Clown PUNK!!!', |
||||
image: |
||||
'https://lh3.googleusercontent.com/H7VrxaalZv4PF1B8U7ADuc8AfuqTVyzmMEDQ5OXKlx0Tqu5XiwsKYj4j_pAF6wUJjLMQbSN_0n3fuj84lNyRhFW9hyrxqDfY1IiQEQ', |
||||
standard: 'ERC1155', |
||||
}, |
||||
{ |
||||
address: '0x495f947276749Ce646f68AC8c248420045cb7b5e', |
||||
tokenId: |
||||
'58076532811975507823669075598676816378162417803895263482849101573315635445761', |
||||
name: 'Punk #2', |
||||
creator: { |
||||
user: { |
||||
username: null, |
||||
}, |
||||
profile_img_url: null, |
||||
address: '0x806627172af48bd5b0765d3449a7def80d6576ff', |
||||
config: '', |
||||
}, |
||||
description: 'Got glasses and black hair!', |
||||
image: |
||||
'https://lh3.googleusercontent.com/CHNTSlKB_Gob-iwTq8jcag6XwBkTqBMLt_vEKeBv18Q4AoPFAEPceqK6mRzkad2s5djx6CT5zbGQwDy81WwtNzViK5dQbG60uAWv', |
||||
standard: 'ERC1155', |
||||
}, |
||||
{ |
||||
address: '0x495f947276749Ce646f68AC8c248420045cb7b5e', |
||||
tokenId: |
||||
'58076532811975507823669075598676816378162417803895263482849101572216123817985', |
||||
name: 'Punk #1', |
||||
creator: { |
||||
user: { |
||||
username: null, |
||||
}, |
||||
profile_img_url: null, |
||||
address: '0x806627172af48bd5b0765d3449a7def80d6576ff', |
||||
config: '', |
||||
}, |
||||
image: |
||||
'https://lh3.googleusercontent.com/4jfPi-nQNWCUXD5qVNVWX7LX2UufU_elEJcvICFlsTdcBXv70asnDEOlI8oKECZxlXq1wseeIXMwmP5tLyOUxMKk', |
||||
standard: 'ERC1155', |
||||
}, |
||||
{ |
||||
address: '0x495f947276749Ce646f68AC8c248420045cb7b5e', |
||||
tokenId: |
||||
'58076532811975507823669075598676816378162417803895263482849101571116612190209', |
||||
name: 'Punk #4651', |
||||
creator: { |
||||
user: { |
||||
username: null, |
||||
}, |
||||
profile_img_url: null, |
||||
address: '0x806627172af48bd5b0765d3449a7def80d6576ff', |
||||
config: '', |
||||
}, |
||||
image: |
||||
'https://lh3.googleusercontent.com/BdxvLseXcfl57BiuQcQYdJ64v-aI8din7WPk0Pgo3qQFhAUH-B6i-dCqqc_mCkRIzULmwzwecnohLhrcH8A9mpWIZqA7ygc52Sr81hE', |
||||
standard: 'ERC1155', |
||||
}, |
||||
{ |
||||
address: '0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414', |
||||
tokenId: '1', |
||||
name: 'MUNK #1', |
||||
description: null, |
||||
image: 'ipfs://QmTSZUNt8AKyDabkyXXXP4oHWDnaVXgNdXoJGEyaYzLbeL', |
||||
standard: 'ERC721', |
||||
}, |
||||
{ |
||||
address: '0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414', |
||||
tokenId: '2', |
||||
name: 'MUNK #2', |
||||
description: null, |
||||
image: 'ipfs://QmTSZUNt8AKyDabkyXXXP4oHWDnaVXgNdXoJGEyaYzLbeL', |
||||
standard: 'ERC721', |
||||
}, |
||||
{ |
||||
address: '0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414', |
||||
tokenId: '3', |
||||
name: 'MUNK #3', |
||||
description: null, |
||||
image: 'ipfs://QmTSZUNt8AKyDabkyXXXP4oHWDnaVXgNdXoJGEyaYzLbeL', |
||||
standard: 'ERC721', |
||||
}, |
||||
]; |
||||
|
||||
const COLLECTIBLES_CONTRACTS = [ |
||||
{ |
||||
address: '0x495f947276749Ce646f68AC8c248420045cb7b5e', |
||||
name: 'PUNKS', |
||||
symbol: 'PNKS', |
||||
schemaName: 'ERC1155', |
||||
}, |
||||
{ |
||||
address: '0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414', |
||||
name: 'Munks', |
||||
symbol: 'MNKS', |
||||
}, |
||||
]; |
||||
|
||||
const ACCOUNT_1 = '0x123'; |
||||
const ACCOUNT_2 = '0x456'; |
||||
|
||||
const render = ({ |
||||
collectibleContracts = [], |
||||
collectibles = [], |
||||
selectedAddress, |
||||
chainId = '0x1', |
||||
collectiblesDetectionNoticeDismissed = false, |
||||
useCollectibleDetection, |
||||
onAddNFT = jest.fn(), |
||||
}) => { |
||||
const store = configureStore({ |
||||
metamask: { |
||||
allCollectibles: { |
||||
[ACCOUNT_1]: { |
||||
[chainId]: collectibles, |
||||
}, |
||||
}, |
||||
allCollectibleContracts: { |
||||
[ACCOUNT_1]: { |
||||
[chainId]: collectibleContracts, |
||||
}, |
||||
}, |
||||
provider: { chainId }, |
||||
selectedAddress, |
||||
collectiblesDetectionNoticeDismissed, |
||||
useCollectibleDetection, |
||||
}, |
||||
}); |
||||
return renderWithProvider(<CollectiblesTab onAddNFT={onAddNFT} />, store); |
||||
}; |
||||
|
||||
describe('Collectible Items', () => { |
||||
const detectCollectiblesStub = jest.fn(); |
||||
const setCollectiblesDetectionNoticeDismissedStub = jest.fn(); |
||||
setBackgroundConnection({ |
||||
setCollectiblesDetectionNoticeDismissed: setCollectiblesDetectionNoticeDismissedStub, |
||||
detectCollectibles: detectCollectiblesStub, |
||||
}); |
||||
const historyPushMock = jest.fn(); |
||||
|
||||
jest |
||||
.spyOn(reactRouterDom, 'useHistory') |
||||
.mockImplementation() |
||||
.mockReturnValue({ push: historyPushMock }); |
||||
|
||||
afterEach(() => { |
||||
jest.clearAllMocks(); |
||||
}); |
||||
|
||||
describe('Collectibles Detection Notice', () => { |
||||
it('should render the Collectibles Detection Notice when currently selected network is Mainnet and currently selected account has no collectibles', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_2, |
||||
collectibles: COLLECTIBLES, |
||||
}); |
||||
expect(screen.queryByText('New! NFT detection')).toBeInTheDocument(); |
||||
}); |
||||
it('should not render the Collectibles Detection Notice when currently selected network is Mainnet and currently selected account has collectibles', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_1, |
||||
collectibles: COLLECTIBLES, |
||||
}); |
||||
expect(screen.queryByText('New! NFT detection')).not.toBeInTheDocument(); |
||||
}); |
||||
it('should take user to the experimental settings tab in setings when user clicks "Turn on NFT detection in Settings"', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_2, |
||||
collectibles: COLLECTIBLES, |
||||
}); |
||||
fireEvent.click(screen.queryByText('Turn on NFT detection in Settings')); |
||||
expect(historyPushMock).toHaveBeenCalledTimes(1); |
||||
expect(historyPushMock).toHaveBeenCalledWith(EXPERIMENTAL_ROUTE); |
||||
}); |
||||
it('should not render the Collectibles Detection Notice when currently selected network is Mainnet and currently selected account has no collectibles but use collectible autodetection preference is set to true', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_1, |
||||
collectibles: COLLECTIBLES, |
||||
useCollectibleDetection: true, |
||||
}); |
||||
expect(screen.queryByText('New! NFT detection')).not.toBeInTheDocument(); |
||||
}); |
||||
it('should not render the Collectibles Detection Notice when currently selected network is Mainnet and currently selected account has no collectibles but user has dismissed the notice before', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_1, |
||||
collectibles: COLLECTIBLES, |
||||
collectiblesDetectionNoticeDismissed: true, |
||||
}); |
||||
expect(screen.queryByText('New! NFT detection')).not.toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should call setCollectibesDetectionNoticeDismissed when users clicks "X"', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_2, |
||||
collectibles: COLLECTIBLES, |
||||
}); |
||||
expect( |
||||
setCollectiblesDetectionNoticeDismissedStub, |
||||
).not.toHaveBeenCalled(); |
||||
fireEvent.click( |
||||
screen.queryByTestId('collectibles-detection-notice-close'), |
||||
); |
||||
expect(setCollectiblesDetectionNoticeDismissedStub).toHaveBeenCalled(); |
||||
}); |
||||
}); |
||||
|
||||
describe('Collections', () => { |
||||
it('should render the name of the collections and number of collectibles in each collection if current account/chainId combination has collectibles', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_1, |
||||
collectibles: COLLECTIBLES, |
||||
collectibleContracts: COLLECTIBLES_CONTRACTS, |
||||
}); |
||||
expect(screen.queryByText('PUNKS (5)')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Munks (3)')).toBeInTheDocument(); |
||||
}); |
||||
it('should not render collections if current account/chainId combination has collectibles', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_2, |
||||
collectibles: COLLECTIBLES, |
||||
collectibleContracts: COLLECTIBLES_CONTRACTS, |
||||
}); |
||||
expect(screen.queryByText('PUNKS (5)')).not.toBeInTheDocument(); |
||||
expect(screen.queryByText('Munks (3)')).not.toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
describe('Collectibles options', () => { |
||||
it('should render a link "Refresh list" when some collectibles are present and collectible auto-detection preference is set to true, which, when clicked calls a method DetectCollectibles', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_1, |
||||
collectibles: COLLECTIBLES, |
||||
useCollectibleDetection: true, |
||||
}); |
||||
expect(detectCollectiblesStub).not.toHaveBeenCalled(); |
||||
fireEvent.click(screen.queryByText('Refresh list')); |
||||
expect(detectCollectiblesStub).toHaveBeenCalled(); |
||||
}); |
||||
|
||||
it('should render a link "Enable Autodetect" when some collectibles are present and collectible auto-detection preference is set to false, which, when clicked sends user to the experimental tab of settings', () => { |
||||
render({ |
||||
selectedAddress: ACCOUNT_1, |
||||
collectibles: COLLECTIBLES, |
||||
}); |
||||
expect(historyPushMock).toHaveBeenCalledTimes(0); |
||||
fireEvent.click(screen.queryByText('Enable Autodetect')); |
||||
expect(historyPushMock).toHaveBeenCalledTimes(1); |
||||
expect(historyPushMock).toHaveBeenCalledWith(EXPERIMENTAL_ROUTE); |
||||
}); |
||||
it('should render a link "Import NFTs" when some collectibles are present, which, when clicked calls the passed in onAddNFT method', () => { |
||||
const onAddNFTStub = jest.fn(); |
||||
render({ |
||||
selectedAddress: ACCOUNT_1, |
||||
collectibles: COLLECTIBLES, |
||||
onAddNFT: onAddNFTStub, |
||||
}); |
||||
expect(onAddNFTStub).toHaveBeenCalledTimes(0); |
||||
fireEvent.click(screen.queryByText('Import NFTs')); |
||||
expect(onAddNFTStub).toHaveBeenCalledTimes(1); |
||||
}); |
||||
}); |
||||
}); |
@ -1 +1 @@ |
||||
export { default } from './collectibles-tab.component'; |
||||
export { default } from './collectibles-tab'; |
||||
|
@ -0,0 +1,8 @@ |
||||
.collectibles-tab { |
||||
&__link { |
||||
a { |
||||
padding: 4px; |
||||
font-size: 1rem; |
||||
} |
||||
} |
||||
} |
@ -1 +0,0 @@ |
||||
export { default } from './new-collectibles-notice.component'; |
@ -1 +1 @@ |
||||
export { default } from './add-collectible.component'; |
||||
export { default } from './add-collectible'; |
||||
|
Loading…
Reference in new issue