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