mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
NFT: Replaced all the instances of collectibles with NFTs (#17741)
* replaced all the instances of collectibles with nfts * updated actions * updated e2e seeder * updated confirm Approve test * updated test dapp change * updated test dapp change * nit fix * nit fix * updated casing and snapshots * updated casinG * added migrations * updated ,igration * updated 078.test * updated tests for 078 migration * updated migration * updated 078 index.js
This commit is contained in:
parent
cf49761d71
commit
33cc8d587a
2
app/_locales/el/messages.json
generated
2
app/_locales/el/messages.json
generated
@ -2109,7 +2109,7 @@
|
||||
"message": "Το “$1” προστέθηκε με επιτυχία!"
|
||||
},
|
||||
"newNftAddedMessage": {
|
||||
"message": "Το Collectible προστέθηκε με επιτυχία!"
|
||||
"message": "Το Nft προστέθηκε με επιτυχία!"
|
||||
},
|
||||
"newPassword": {
|
||||
"message": "Νέος Κωδικός Πρόσβασης (ελάχιστο 8 χαρακτήρες)"
|
||||
|
6
app/_locales/fr/messages.json
generated
6
app/_locales/fr/messages.json
generated
@ -1570,7 +1570,7 @@
|
||||
"message": "page Importer des NFT"
|
||||
},
|
||||
"importNFTTokenIdToolTip": {
|
||||
"message": "L’ID d’un collectible est un identifiant unique puisqu’il n’y a pas deux NFT identiques. Encore une fois, sur OpenSea, ce numéro se trouve dans la section « Détails ». Prenez-en note ou copiez-le dans votre presse-papiers."
|
||||
"message": "L’ID d’un NFT est un identifiant unique puisqu’il n’y a pas deux NFT identiques. Encore une fois, sur OpenSea, ce numéro se trouve dans la section « Détails ». Prenez-en note ou copiez-le dans votre presse-papiers."
|
||||
},
|
||||
"importNFTs": {
|
||||
"message": "Importer des NFT"
|
||||
@ -2109,7 +2109,7 @@
|
||||
"message": "« $1 » a été ajouté avec succès !"
|
||||
},
|
||||
"newNftAddedMessage": {
|
||||
"message": "Le collectible a été ajouté avec succès !"
|
||||
"message": "Le NFT a été ajouté avec succès !"
|
||||
},
|
||||
"newPassword": {
|
||||
"message": "Nouveau mot de passe (min 8 caractères)"
|
||||
@ -4157,7 +4157,7 @@
|
||||
"description": "Shown when the protocol is unknown by the extension. $1 is the protocol code."
|
||||
},
|
||||
"unsendableAsset": {
|
||||
"message": "L’envoi de jetons collectibles (ERC-721) n’est pas pris en charge actuellement",
|
||||
"message": "L’envoi de jetons NFTs (ERC-721) n’est pas pris en charge actuellement",
|
||||
"description": "This is an error message we show the user if they attempt to send an NFT asset type, for which currently don't support sending"
|
||||
},
|
||||
"unverifiedContractAddressMessage": {
|
||||
|
6
app/_locales/tl/messages.json
generated
6
app/_locales/tl/messages.json
generated
@ -1570,7 +1570,7 @@
|
||||
"message": "I-import ang pahina ng NFT"
|
||||
},
|
||||
"importNFTTokenIdToolTip": {
|
||||
"message": "Ang ID ng collectible ay isang natatanging pagkakakilanlan dahil walang dalawang NFT ang magkatulad. Muli, sa OpenSea ang numerong ito ay nasa ilalim ng 'Mga Detalye'. Itala ito, o kopyahin ito sa iyong clipboard."
|
||||
"message": "Ang ID ng NFT ay isang natatanging pagkakakilanlan dahil walang dalawang NFT ang magkatulad. Muli, sa OpenSea ang numerong ito ay nasa ilalim ng 'Mga Detalye'. Itala ito, o kopyahin ito sa iyong clipboard."
|
||||
},
|
||||
"importNFTs": {
|
||||
"message": "I-import ang mga NFT"
|
||||
@ -2109,7 +2109,7 @@
|
||||
"message": "Ang “$1” matagumpay na naidagdag!"
|
||||
},
|
||||
"newNftAddedMessage": {
|
||||
"message": "Ang collectible ay tagumpay na naidagdag!"
|
||||
"message": "Ang NFT ay tagumpay na naidagdag!"
|
||||
},
|
||||
"newPassword": {
|
||||
"message": "Bagong password (min na 8 char)"
|
||||
@ -4157,7 +4157,7 @@
|
||||
"description": "Shown when the protocol is unknown by the extension. $1 is the protocol code."
|
||||
},
|
||||
"unsendableAsset": {
|
||||
"message": "Ang pagpapadala ng collectible (ERC-721) token ay kasalukuyang hindi magagamit",
|
||||
"message": "Ang pagpapadala ng NFT (ERC-721) token ay kasalukuyang hindi magagamit",
|
||||
"description": "This is an error message we show the user if they attempt to send an NFT asset type, for which currently don't support sending"
|
||||
},
|
||||
"unverifiedContractAddressMessage": {
|
||||
|
2
app/_locales/zh_TW/messages.json
generated
2
app/_locales/zh_TW/messages.json
generated
@ -984,7 +984,7 @@
|
||||
"description": "The next nonce according to MetaMask's internal logic"
|
||||
},
|
||||
"nftTokenIdPlaceholder": {
|
||||
"message": "Enter the collectible ID"
|
||||
"message": "Enter the NFT ID"
|
||||
},
|
||||
"noAccountsFound": {
|
||||
"message": "指定的搜尋條件找不到帳戶"
|
||||
|
@ -38,14 +38,14 @@ export default class AppStateController extends EventEmitter {
|
||||
recoveryPhraseReminderHasBeenShown: false,
|
||||
recoveryPhraseReminderLastShown: new Date().getTime(),
|
||||
outdatedBrowserWarningLastShown: new Date().getTime(),
|
||||
collectiblesDetectionNoticeDismissed: false,
|
||||
nftsDetectionNoticeDismissed: false,
|
||||
showTestnetMessageInDropdown: true,
|
||||
showPortfolioTooltip: true,
|
||||
showBetaHeader: isBeta(),
|
||||
trezorModel: null,
|
||||
...initState,
|
||||
qrHardware: {},
|
||||
collectiblesDropdownState: {},
|
||||
nftsDropdownState: {},
|
||||
usedNetworks: {
|
||||
'0x1': true,
|
||||
'0x5': true,
|
||||
@ -330,13 +330,13 @@ export default class AppStateController extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for the `collectiblesDropdownState` property
|
||||
* A setter for the `nftsDropdownState` property
|
||||
*
|
||||
* @param collectiblesDropdownState
|
||||
* @param nftsDropdownState
|
||||
*/
|
||||
updateCollectibleDropDownState(collectiblesDropdownState) {
|
||||
updateNftDropDownState(nftsDropdownState) {
|
||||
this.store.updateState({
|
||||
collectiblesDropdownState,
|
||||
nftsDropdownState,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ function getMockController() {
|
||||
return mcState;
|
||||
}
|
||||
|
||||
const jsonData = `{"preferences":{"frequentRpcListDetail":[{"chainId":"0x539","nickname":"Localhost 8545","rpcPrefs":{},"rpcUrl":"http://localhost:8545","ticker":"ETH"},{"chainId":"0x38","nickname":"Binance Smart Chain Mainnet","rpcPrefs":{"blockExplorerUrl":"https://bscscan.com"},"rpcUrl":"https://bsc-dataseed1.binance.org","ticker":"BNB"},{"chainId":"0x61","nickname":"Binance Smart Chain Testnet","rpcPrefs":{"blockExplorerUrl":"https://testnet.bscscan.com"},"rpcUrl":"https://data-seed-prebsc-1-s1.binance.org:8545","ticker":"tBNB"},{"chainId":"0x89","nickname":"Polygon Mainnet","rpcPrefs":{"blockExplorerUrl":"https://polygonscan.com"},"rpcUrl":"https://polygon-rpc.com","ticker":"MATIC"}],"useBlockie":false,"useNonceField":false,"usePhishDetect":true,"dismissSeedBackUpReminder":false,"useTokenDetection":false,"useCollectibleDetection":false,"openSeaEnabled":false,"advancedGasFee":null,"featureFlags":{"sendHexData":true,"showIncomingTransactions":true},"knownMethodData":{},"currentLocale":"en","forgottenPassword":false,"preferences":{"hideZeroBalanceTokens":false,"showFiatInTestnets":false,"showTestNetworks":true,"useNativeCurrencyAsPrimaryCurrency":true},"ipfsGateway":"dweb.link","infuraBlocked":false,"ledgerTransportType":"webhid","theme":"light","customNetworkListEnabled":false,"textDirection":"auto"},"addressBook":{"addressBook":{"0x61":{"0x42EB768f2244C8811C63729A21A3569731535f06":{"address":"0x42EB768f2244C8811C63729A21A3569731535f06","chainId":"0x61","isEns":false,"memo":"","name":""}}}}}`;
|
||||
const jsonData = `{"preferences":{"frequentRpcListDetail":[{"chainId":"0x539","nickname":"Localhost 8545","rpcPrefs":{},"rpcUrl":"http://localhost:8545","ticker":"ETH"},{"chainId":"0x38","nickname":"Binance Smart Chain Mainnet","rpcPrefs":{"blockExplorerUrl":"https://bscscan.com"},"rpcUrl":"https://bsc-dataseed1.binance.org","ticker":"BNB"},{"chainId":"0x61","nickname":"Binance Smart Chain Testnet","rpcPrefs":{"blockExplorerUrl":"https://testnet.bscscan.com"},"rpcUrl":"https://data-seed-prebsc-1-s1.binance.org:8545","ticker":"tBNB"},{"chainId":"0x89","nickname":"Polygon Mainnet","rpcPrefs":{"blockExplorerUrl":"https://polygonscan.com"},"rpcUrl":"https://polygon-rpc.com","ticker":"MATIC"}],"useBlockie":false,"useNonceField":false,"usePhishDetect":true,"dismissSeedBackUpReminder":false,"useTokenDetection":false,"useNftDetection":false,"openSeaEnabled":false,"advancedGasFee":null,"featureFlags":{"sendHexData":true,"showIncomingTransactions":true},"knownMethodData":{},"currentLocale":"en","forgottenPassword":false,"preferences":{"hideZeroBalanceTokens":false,"showFiatInTestnets":false,"showTestNetworks":true,"useNativeCurrencyAsPrimaryCurrency":true},"ipfsGateway":"dweb.link","infuraBlocked":false,"ledgerTransportType":"webhid","theme":"light","customNetworkListEnabled":false,"textDirection":"auto"},"addressBook":{"addressBook":{"0x61":{"0x42EB768f2244C8811C63729A21A3569731535f06":{"address":"0x42EB768f2244C8811C63729A21A3569731535f06","chainId":"0x61","isEns":false,"memo":"","name":""}}}}}`;
|
||||
|
||||
describe('BackupController', function () {
|
||||
const getBackupController = () => {
|
||||
|
@ -758,7 +758,7 @@ export default class MetaMetricsController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all of the collectibles/NFTs the user
|
||||
* Returns an array of all of the NFTs the user
|
||||
* possesses across all networks and accounts.
|
||||
*
|
||||
* @param {object} allNfts
|
||||
@ -771,7 +771,7 @@ export default class MetaMetricsController {
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the number of unique collectible/NFT addresses the user
|
||||
* Returns the number of unique NFT addresses the user
|
||||
* possesses across all networks and accounts.
|
||||
*
|
||||
* @param {object} allNfts
|
||||
|
@ -151,7 +151,7 @@ export default class PreferencesController {
|
||||
/**
|
||||
* Setter for the `useNftDetection` property
|
||||
*
|
||||
* @param {boolean} useNftDetection - Whether or not the user prefers to autodetect collectibles.
|
||||
* @param {boolean} useNftDetection - Whether or not the user prefers to autodetect NFTs.
|
||||
*/
|
||||
setUseNftDetection(useNftDetection) {
|
||||
this.store.updateState({ useNftDetection });
|
||||
@ -169,7 +169,7 @@ export default class PreferencesController {
|
||||
/**
|
||||
* Setter for the `openSeaEnabled` property
|
||||
*
|
||||
* @param {boolean} openSeaEnabled - Whether or not the user prefers to use the OpenSea API for collectibles data.
|
||||
* @param {boolean} openSeaEnabled - Whether or not the user prefers to use the OpenSea API for NFTs data.
|
||||
*/
|
||||
setOpenSeaEnabled(openSeaEnabled) {
|
||||
this.store.updateState({
|
||||
|
@ -960,8 +960,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
const { txReceipt } = txMeta;
|
||||
|
||||
// if this is a transferFrom method generated from within the app it may be a collectible transfer transaction
|
||||
// in which case we will want to check and update ownership status of the transferred collectible.
|
||||
// if this is a transferFrom method generated from within the app it may be an NFT transfer transaction
|
||||
// in which case we will want to check and update ownership status of the transferred NFT.
|
||||
if (
|
||||
txMeta.type === TransactionType.tokenMethodTransferFrom &&
|
||||
txMeta.txParams !== undefined
|
||||
@ -982,19 +982,17 @@ export default class MetamaskController extends EventEmitter {
|
||||
const { allNfts } = this.nftController.state;
|
||||
|
||||
const chainIdAsDecimal = hexToDecimal(chainId);
|
||||
// check if its a known collectible
|
||||
const knownCollectible = allNfts?.[userAddress]?.[
|
||||
chainIdAsDecimal
|
||||
]?.find(
|
||||
// check if its a known NFT
|
||||
const knownNft = allNfts?.[userAddress]?.[chainIdAsDecimal]?.find(
|
||||
({ address, tokenId }) =>
|
||||
isEqualCaseInsensitive(address, contractAddress) &&
|
||||
tokenId === transactionDataTokenId,
|
||||
);
|
||||
|
||||
// if it is we check and update ownership status.
|
||||
if (knownCollectible) {
|
||||
if (knownNft) {
|
||||
this.nftController.checkAndUpdateSingleNftOwnershipStatus(
|
||||
knownCollectible,
|
||||
knownNft,
|
||||
false,
|
||||
{ userAddress, chainId: chainIdAsDecimal },
|
||||
);
|
||||
@ -1904,10 +1902,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
appStateController.setShowPortfolioTooltip.bind(appStateController),
|
||||
setShowBetaHeader:
|
||||
appStateController.setShowBetaHeader.bind(appStateController),
|
||||
updateCollectibleDropDownState:
|
||||
appStateController.updateCollectibleDropDownState.bind(
|
||||
appStateController,
|
||||
),
|
||||
updateNftDropDownState:
|
||||
appStateController.updateNftDropDownState.bind(appStateController),
|
||||
setFirstTimeUsedNetwork:
|
||||
appStateController.setFirstTimeUsedNetwork.bind(appStateController),
|
||||
// EnsController
|
||||
@ -2144,7 +2140,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
detectTokensController,
|
||||
),
|
||||
|
||||
// DetectCollectibleController
|
||||
// DetectNftController
|
||||
detectNfts: process.env.NFTS_V1
|
||||
? nftDetectionController.detectNfts.bind(nftDetectionController)
|
||||
: null,
|
||||
|
31
app/scripts/migrations/078.js
Normal file
31
app/scripts/migrations/078.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
const version = 78;
|
||||
|
||||
/**
|
||||
* Remove collectiblesDropdownState and collectiblesDetectionNoticeDismissed:.
|
||||
*/
|
||||
export default {
|
||||
version,
|
||||
async migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData);
|
||||
versionedData.meta.version = version;
|
||||
const state = versionedData.data;
|
||||
const newState = transformState(state);
|
||||
versionedData.data = newState;
|
||||
return versionedData;
|
||||
},
|
||||
};
|
||||
|
||||
function transformState(state) {
|
||||
if (
|
||||
state?.AppStateController?.collectiblesDetectionNoticeDismissed !==
|
||||
undefined
|
||||
) {
|
||||
delete state.AppStateController.collectiblesDetectionNoticeDismissed;
|
||||
}
|
||||
if (state?.metamask?.collectiblesDropdownState !== undefined) {
|
||||
delete state.metamask.collectiblesDropdownState;
|
||||
}
|
||||
return state;
|
||||
}
|
173
app/scripts/migrations/078.test.js
Normal file
173
app/scripts/migrations/078.test.js
Normal file
@ -0,0 +1,173 @@
|
||||
import migration78 from './078';
|
||||
|
||||
describe('migration #78', () => {
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 77,
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migration78.migrate(oldStorage);
|
||||
expect(newStorage.meta).toStrictEqual({
|
||||
version: 78,
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the "collectiblesDetectionNoticeDismissed"', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 77,
|
||||
},
|
||||
data: {
|
||||
AppStateController: {
|
||||
collectiblesDetectionNoticeDismissed: false,
|
||||
bar: 'baz',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migration78.migrate(oldStorage);
|
||||
expect(newStorage).toStrictEqual({
|
||||
meta: {
|
||||
version: 78,
|
||||
},
|
||||
data: {
|
||||
AppStateController: {
|
||||
bar: 'baz',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the "collectiblesDropdownState"', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 77,
|
||||
},
|
||||
data: {
|
||||
metamask: {
|
||||
isInitialized: true,
|
||||
isUnlocked: true,
|
||||
isAccountMenuOpen: false,
|
||||
identities: {
|
||||
'0x00000': {
|
||||
address: '0x00000',
|
||||
lastSelected: 1675966229118,
|
||||
name: 'Account 1',
|
||||
},
|
||||
'0x00001': {
|
||||
address: '0x00001',
|
||||
name: 'Account 2',
|
||||
},
|
||||
},
|
||||
collectiblesDropdownState: {},
|
||||
qrHardware: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migration78.migrate(oldStorage);
|
||||
expect(newStorage).toStrictEqual({
|
||||
meta: {
|
||||
version: 78,
|
||||
},
|
||||
data: {
|
||||
metamask: {
|
||||
isInitialized: true,
|
||||
isUnlocked: true,
|
||||
isAccountMenuOpen: false,
|
||||
identities: {
|
||||
'0x00000': {
|
||||
address: '0x00000',
|
||||
lastSelected: 1675966229118,
|
||||
name: 'Account 1',
|
||||
},
|
||||
'0x00001': {
|
||||
address: '0x00001',
|
||||
name: 'Account 2',
|
||||
},
|
||||
},
|
||||
qrHardware: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should make no changes if "collectiblesDetectionNoticeDismissed" never existed', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 77,
|
||||
},
|
||||
data: {
|
||||
AppStateController: {
|
||||
bar: 'baz',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migration78.migrate(oldStorage);
|
||||
expect(newStorage).toStrictEqual({
|
||||
meta: {
|
||||
version: 78,
|
||||
},
|
||||
data: {
|
||||
AppStateController: {
|
||||
bar: 'baz',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should make no changes if "collectiblesDropdownState" never existed', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 77,
|
||||
},
|
||||
data: {
|
||||
metamask: {
|
||||
isInitialized: true,
|
||||
isUnlocked: true,
|
||||
isAccountMenuOpen: false,
|
||||
identities: {
|
||||
'0x00000': {
|
||||
address: '0x00000',
|
||||
lastSelected: 1675966229118,
|
||||
name: 'Account 1',
|
||||
},
|
||||
'0x00001': {
|
||||
address: '0x00001',
|
||||
name: 'Account 2',
|
||||
},
|
||||
},
|
||||
qrHardware: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migration78.migrate(oldStorage);
|
||||
expect(newStorage).toStrictEqual({
|
||||
meta: {
|
||||
version: 78,
|
||||
},
|
||||
data: {
|
||||
metamask: {
|
||||
isInitialized: true,
|
||||
isUnlocked: true,
|
||||
isAccountMenuOpen: false,
|
||||
identities: {
|
||||
'0x00000': {
|
||||
address: '0x00000',
|
||||
lastSelected: 1675966229118,
|
||||
name: 'Account 1',
|
||||
},
|
||||
'0x00001': {
|
||||
address: '0x00001',
|
||||
name: 'Account 2',
|
||||
},
|
||||
},
|
||||
qrHardware: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
@ -81,6 +81,7 @@ import m074 from './074';
|
||||
import m075 from './075';
|
||||
import m076 from './076';
|
||||
import m077 from './077';
|
||||
import m078 from './078';
|
||||
|
||||
const migrations = [
|
||||
m002,
|
||||
@ -159,6 +160,7 @@ const migrations = [
|
||||
m075,
|
||||
m076,
|
||||
m077,
|
||||
m078,
|
||||
];
|
||||
|
||||
export default migrations;
|
||||
|
@ -1149,7 +1149,7 @@
|
||||
"ui/hooks/useApproveTransaction.js",
|
||||
"ui/hooks/useAssetDetails.js",
|
||||
"ui/hooks/useAssetDetails.test.js",
|
||||
"ui/hooks/useCollectiblesCollections.js",
|
||||
"ui/hooks/useNftsCollections.js",
|
||||
"ui/hooks/useCopyToClipboard.js",
|
||||
"ui/hooks/useCurrencyDisplay.js",
|
||||
"ui/hooks/useCurrencyDisplay.test.js",
|
||||
|
@ -261,7 +261,7 @@
|
||||
"maxBaseFee": "75",
|
||||
"priorityFee": "2"
|
||||
},
|
||||
"collectiblesDropdownState": {
|
||||
"nftsDropdownState": {
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||
"0x5": {
|
||||
"0x495f947276749Ce646f68AC8c248420045cb7b5e": false
|
||||
|
@ -115,7 +115,7 @@ function defaultFixture() {
|
||||
},
|
||||
AppStateController: {
|
||||
browserEnvironment: {},
|
||||
collectiblesDropdownState: {},
|
||||
nftsDropdownState: {},
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
defaultHomeActiveTabName: null,
|
||||
fullScreenGasPollTokens: [],
|
||||
@ -281,7 +281,7 @@ function onboardingFixture() {
|
||||
data: {
|
||||
AppStateController: {
|
||||
browserEnvironment: {},
|
||||
collectiblesDropdownState: {},
|
||||
nftsDropdownState: {},
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
defaultHomeActiveTabName: null,
|
||||
fullScreenGasPollTokens: [],
|
||||
@ -411,7 +411,7 @@ class FixtureBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
withCollectiblesController(data) {
|
||||
withNftsController(data) {
|
||||
merge(
|
||||
this.fixture.data.NftController
|
||||
? this.fixture.data.NftController
|
||||
|
@ -4,7 +4,7 @@ const { SMART_CONTRACTS } = require('../seeder/smart-contracts');
|
||||
const FixtureBuilder = require('../fixture-builder');
|
||||
|
||||
describe('Import NFT', function () {
|
||||
const smartContract = SMART_CONTRACTS.COLLECTIBLES;
|
||||
const smartContract = SMART_CONTRACTS.NFTS;
|
||||
const ganacheOptions = {
|
||||
accounts: [
|
||||
{
|
||||
@ -54,7 +54,7 @@ describe('Import NFT', function () {
|
||||
text: 'TestDappCollectibles',
|
||||
});
|
||||
const importedNftImage = await driver.findElement(
|
||||
'.collectibles-items__item-image',
|
||||
'.nfts-items__item-image',
|
||||
);
|
||||
assert.equal(await importedNft.isDisplayed(), true);
|
||||
assert.equal(await importedNftImage.isDisplayed(), true);
|
||||
|
@ -4,7 +4,7 @@ const { SMART_CONTRACTS } = require('../seeder/smart-contracts');
|
||||
const FixtureBuilder = require('../fixture-builder');
|
||||
|
||||
describe('Send NFT', function () {
|
||||
const smartContract = SMART_CONTRACTS.COLLECTIBLES;
|
||||
const smartContract = SMART_CONTRACTS.NFTS;
|
||||
const ganacheOptions = {
|
||||
accounts: [
|
||||
{
|
||||
@ -40,7 +40,7 @@ describe('Send NFT', function () {
|
||||
await driver.clickElement({ text: 'Add', tag: 'button' });
|
||||
|
||||
// Fill the send NFT form and confirm the transaction
|
||||
await driver.clickElement('.collectibles-items__item-image');
|
||||
await driver.clickElement('.nfts-items__item-image');
|
||||
await driver.clickElement({ text: 'Send', tag: 'button' });
|
||||
await driver.fill(
|
||||
'input[placeholder="Search, public address (0x), or ENS"]',
|
||||
@ -49,7 +49,7 @@ describe('Send NFT', function () {
|
||||
await driver.clickElement({ text: 'Next', tag: 'button' });
|
||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||
|
||||
// When transaction complete, check the send nft item is displayed in activity tab
|
||||
// When transaction complete, check the send NFT is displayed in activity tab
|
||||
await driver.wait(async () => {
|
||||
const confirmedTxes = await driver.findElements(
|
||||
'.transaction-list__completed-transactions .transaction-list-item',
|
||||
|
@ -44,7 +44,7 @@ class GanacheSeeder {
|
||||
|
||||
await contract.deployTransaction.wait();
|
||||
|
||||
if (contractName === SMART_CONTRACTS.COLLECTIBLES) {
|
||||
if (contractName === SMART_CONTRACTS.NFTS) {
|
||||
const transaction = await contract.mintCollectibles(1, {
|
||||
from: fromAddress,
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ const hstFactory = {
|
||||
abi: hstAbi,
|
||||
};
|
||||
|
||||
const collectiblesFactory = {
|
||||
const nftsFactory = {
|
||||
bytecode: collectiblesBytecode,
|
||||
abi: collectiblesAbi,
|
||||
};
|
||||
@ -42,7 +42,7 @@ const multisigFactory = {
|
||||
|
||||
const SMART_CONTRACTS = {
|
||||
HST: 'hst',
|
||||
COLLECTIBLES: 'collectibles',
|
||||
NFTS: 'nfts',
|
||||
PIGGYBANK: 'piggybank',
|
||||
FAILING: 'failing',
|
||||
MULTISIG: 'multisig',
|
||||
@ -50,7 +50,7 @@ const SMART_CONTRACTS = {
|
||||
|
||||
const contractConfiguration = {
|
||||
[SMART_CONTRACTS.HST]: hstFactory,
|
||||
[SMART_CONTRACTS.COLLECTIBLES]: collectiblesFactory,
|
||||
[SMART_CONTRACTS.NFTS]: nftsFactory,
|
||||
[SMART_CONTRACTS.PIGGYBANK]: piggybankFactory,
|
||||
[SMART_CONTRACTS.FAILING]: failingContract,
|
||||
[SMART_CONTRACTS.MULTISIG]: multisigFactory,
|
||||
|
@ -3,8 +3,8 @@ const { convertToHexValue, withFixtures } = require('../helpers');
|
||||
const { SMART_CONTRACTS } = require('../seeder/smart-contracts');
|
||||
const FixtureBuilder = require('../fixture-builder');
|
||||
|
||||
describe('Collectibles', function () {
|
||||
const smartContract = SMART_CONTRACTS.COLLECTIBLES;
|
||||
describe('NFTs', function () {
|
||||
const smartContract = SMART_CONTRACTS.NFTS;
|
||||
const ganacheOptions = {
|
||||
accounts: [
|
||||
{
|
||||
|
@ -350,7 +350,7 @@ ContractDetailsModal.propTypes = {
|
||||
*/
|
||||
siteImage: PropTypes.string,
|
||||
/**
|
||||
* The token id of the collectible
|
||||
* The token id of the NFT
|
||||
*/
|
||||
tokenId: PropTypes.string,
|
||||
/**
|
||||
|
@ -8,10 +8,10 @@ import { TypographyVariant } from '../../../../helpers/constants/design-system';
|
||||
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import {
|
||||
ADD_COLLECTIBLE_ROUTE,
|
||||
ADD_NFT_ROUTE,
|
||||
ASSET_ROUTE,
|
||||
} from '../../../../helpers/constants/routes';
|
||||
import { getCollectibles } from '../../../../ducks/metamask/metamask';
|
||||
import { getNfts } from '../../../../ducks/metamask/metamask';
|
||||
import { ignoreTokens } from '../../../../store/actions';
|
||||
import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-utils';
|
||||
|
||||
@ -19,7 +19,7 @@ const ConvertTokenToNFTModal = ({ hideModal, tokenAddress }) => {
|
||||
const history = useHistory();
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
const allNfts = useSelector(getCollectibles);
|
||||
const allNfts = useSelector(getNfts);
|
||||
const tokenAddedAsNFT = allNfts.find(({ address }) =>
|
||||
isEqualCaseInsensitive(address, tokenAddress),
|
||||
);
|
||||
@ -40,7 +40,7 @@ const ConvertTokenToNFTModal = ({ hideModal, tokenAddress }) => {
|
||||
});
|
||||
} else {
|
||||
history.push({
|
||||
pathname: ADD_COLLECTIBLE_ROUTE,
|
||||
pathname: ADD_NFT_ROUTE,
|
||||
state: { tokenAddress },
|
||||
});
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Collectible Default Image should match snapshot with all provided props 1`] = `
|
||||
exports[`NFT Default Image should match snapshot with all provided props 1`] = `
|
||||
<div>
|
||||
<button
|
||||
class="collectible-default collectible-default--clickable"
|
||||
data-testid="collectible-default-image"
|
||||
class="nft-default nft-default--clickable"
|
||||
data-testid="nft-default-image"
|
||||
tabindex="0"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography collectible-default__text typography--h6 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography nft-default__text typography--h6 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||
>
|
||||
Collectible Name
|
||||
NFT Name
|
||||
|
||||
<br />
|
||||
#
|
||||
@ -20,17 +20,17 @@ exports[`Collectible Default Image should match snapshot with all provided props
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Collectible Default Image should match snapshot with missing image click handler 1`] = `
|
||||
exports[`NFT Default Image should match snapshot with missing image click handler 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="collectible-default"
|
||||
data-testid="collectible-default-image"
|
||||
class="nft-default"
|
||||
data-testid="nft-default-image"
|
||||
tabindex="0"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography collectible-default__text typography--h6 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography nft-default__text typography--h6 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||
>
|
||||
Collectible Name
|
||||
NFT Name
|
||||
|
||||
<br />
|
||||
#
|
||||
@ -40,15 +40,15 @@ exports[`Collectible Default Image should match snapshot with missing image clic
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Collectible Default Image should render with no props 1`] = `
|
||||
exports[`NFT Default Image should render with no props 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="collectible-default"
|
||||
data-testid="collectible-default-image"
|
||||
class="nft-default"
|
||||
data-testid="nft-default-image"
|
||||
tabindex="0"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography collectible-default__text typography--h6 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography nft-default__text typography--h6 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||
>
|
||||
[unknownCollection]
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
.collectible-default {
|
||||
.nft-default {
|
||||
background-color: var(--color-background-alternative);
|
||||
padding-top: 100%; // retains 1:1 aspect ratio
|
||||
position: relative;
|
||||
|
@ -5,43 +5,36 @@ import Typography from '../../ui/typography';
|
||||
import { TypographyVariant } from '../../../helpers/constants/design-system';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
|
||||
export default function CollectibleDefaultImage({
|
||||
name,
|
||||
tokenId,
|
||||
handleImageClick,
|
||||
}) {
|
||||
export default function NftDefaultImage({ name, tokenId, handleImageClick }) {
|
||||
const t = useI18nContext();
|
||||
const Tag = handleImageClick ? 'button' : 'div';
|
||||
return (
|
||||
<Tag
|
||||
tabIndex={0}
|
||||
data-testid="collectible-default-image"
|
||||
className={classnames('collectible-default', {
|
||||
'collectible-default--clickable': handleImageClick,
|
||||
data-testid="nft-default-image"
|
||||
className={classnames('nft-default', {
|
||||
'nft-default--clickable': handleImageClick,
|
||||
})}
|
||||
onClick={handleImageClick}
|
||||
>
|
||||
<Typography
|
||||
variant={TypographyVariant.H6}
|
||||
className="collectible-default__text"
|
||||
>
|
||||
<Typography variant={TypographyVariant.H6} className="nft-default__text">
|
||||
{name ?? t('unknownCollection')} <br /> #{tokenId}
|
||||
</Typography>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
CollectibleDefaultImage.propTypes = {
|
||||
NftDefaultImage.propTypes = {
|
||||
/**
|
||||
* The name of the collectible collection if not supplied will default to "Unnamed collection"
|
||||
* The name of the NFT collection if not supplied will default to "Unnamed collection"
|
||||
*/
|
||||
name: PropTypes.string,
|
||||
/**
|
||||
* The token id of the collectible
|
||||
* The token id of the nft
|
||||
*/
|
||||
tokenId: PropTypes.string,
|
||||
/**
|
||||
* The click handler for the collectible default image
|
||||
* The click handler for the NFT default image
|
||||
*/
|
||||
handleImageClick: PropTypes.func,
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import CollectibleDefaultImage from '.';
|
||||
import NftDefaultImage from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/CollectibleDefaultImage',
|
||||
title: 'Components/App/NftDefaultImage',
|
||||
|
||||
argTypes: {
|
||||
name: {
|
||||
@ -24,7 +24,7 @@ export default {
|
||||
|
||||
export const DefaultStory = (args) => (
|
||||
<div style={{ width: 200, height: 200 }}>
|
||||
<CollectibleDefaultImage {...args} />
|
||||
<NftDefaultImage {...args} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -32,11 +32,11 @@ DefaultStory.storyName = 'Default';
|
||||
|
||||
export const HandleImageClick = (args) => (
|
||||
<div style={{ width: 200, height: 200 }}>
|
||||
<CollectibleDefaultImage {...args} />
|
||||
<NftDefaultImage {...args} />
|
||||
</div>
|
||||
);
|
||||
|
||||
HandleImageClick.args = {
|
||||
// eslint-disable-next-line no-alert
|
||||
handleImageClick: () => window.alert('CollectibleDefaultImage clicked!'),
|
||||
handleImageClick: () => window.alert('NftDefaultImage clicked!'),
|
||||
};
|
||||
|
@ -1,69 +1,61 @@
|
||||
import React from 'react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import CollectibleDefaultImage from '.';
|
||||
import NftDefaultImage from '.';
|
||||
|
||||
describe('Collectible Default Image', () => {
|
||||
describe('NFT Default Image', () => {
|
||||
it('should render with no props', () => {
|
||||
const { container } = renderWithProvider(<CollectibleDefaultImage />);
|
||||
const { container } = renderWithProvider(<NftDefaultImage />);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot with all provided props', () => {
|
||||
const props = {
|
||||
name: 'Collectible Name',
|
||||
name: 'NFT Name',
|
||||
tokenId: '123',
|
||||
handleImageClick: jest.fn(),
|
||||
};
|
||||
|
||||
const { container } = renderWithProvider(
|
||||
<CollectibleDefaultImage {...props} />,
|
||||
);
|
||||
const { container } = renderWithProvider(<NftDefaultImage {...props} />);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot with missing image click handler', () => {
|
||||
const props = {
|
||||
name: 'Collectible Name',
|
||||
name: 'NFT Name',
|
||||
tokenId: '123',
|
||||
};
|
||||
|
||||
const { container } = renderWithProvider(
|
||||
<CollectibleDefaultImage {...props} />,
|
||||
);
|
||||
const { container } = renderWithProvider(<NftDefaultImage {...props} />);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render collectible name', () => {
|
||||
it('should render NFT name', () => {
|
||||
const props = {
|
||||
name: 'Collectible Name',
|
||||
name: 'NFT Name',
|
||||
};
|
||||
|
||||
const { queryByText } = renderWithProvider(
|
||||
<CollectibleDefaultImage {...props} />,
|
||||
);
|
||||
const { queryByText } = renderWithProvider(<NftDefaultImage {...props} />);
|
||||
|
||||
const collectibleElement = queryByText(`${props.name} #`);
|
||||
const nftElement = queryByText(`${props.name} #`);
|
||||
|
||||
expect(collectibleElement).toBeInTheDocument();
|
||||
expect(nftElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render collectible name and tokenId', () => {
|
||||
it('should render NFT name and tokenId', () => {
|
||||
const props = {
|
||||
name: 'Collectible Name',
|
||||
name: 'NFT Name',
|
||||
tokenId: '123',
|
||||
};
|
||||
|
||||
const { queryByText } = renderWithProvider(
|
||||
<CollectibleDefaultImage {...props} />,
|
||||
);
|
||||
const { queryByText } = renderWithProvider(<NftDefaultImage {...props} />);
|
||||
|
||||
const collectibleElement = queryByText(`${props.name} #${props.tokenId}`);
|
||||
const nftElement = queryByText(`${props.name} #${props.tokenId}`);
|
||||
|
||||
expect(collectibleElement).toBeInTheDocument();
|
||||
expect(nftElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle image click', () => {
|
||||
@ -72,11 +64,11 @@ describe('Collectible Default Image', () => {
|
||||
};
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDefaultImage {...props} />,
|
||||
<NftDefaultImage {...props} />,
|
||||
);
|
||||
|
||||
const collectibleImageElement = queryByTestId('collectible-default-image');
|
||||
fireEvent.click(collectibleImageElement);
|
||||
const nftImageElement = queryByTestId('nft-default-image');
|
||||
fireEvent.click(nftImageElement);
|
||||
|
||||
expect(props.handleImageClick).toHaveBeenCalled();
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Collectible Details should match minimal props and state snapshot 1`] = `
|
||||
exports[`NFT Details should match minimal props and state snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="asset-navigation"
|
||||
@ -26,8 +26,8 @@ exports[`Collectible Details should match minimal props and state snapshot 1`] =
|
||||
<div>
|
||||
<button
|
||||
aria-label="NFT Options"
|
||||
class="box mm-button-icon mm-button-icon--size-lg collectible-options__button box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default box--background-color-transparent box--rounded-lg"
|
||||
data-testid="collectible-options__button"
|
||||
class="box mm-button-icon mm-button-icon--size-lg nft-options__button box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default box--background-color-transparent box--rounded-lg"
|
||||
data-testid="nft-options__button"
|
||||
>
|
||||
<div
|
||||
class="box mm-icon mm-icon--size-lg box--flex-direction-row box--color-inherit"
|
||||
@ -37,22 +37,22 @@ exports[`Collectible Details should match minimal props and state snapshot 1`] =
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="box collectible-details box--flex-direction-row"
|
||||
class="box nft-details box--flex-direction-row"
|
||||
>
|
||||
<div
|
||||
class="collectible-details__top-section"
|
||||
class="nft-details__top-section"
|
||||
>
|
||||
<div
|
||||
class="box collectible-details__card box--flex-direction-row box--justify-content-center box--background-color-background-default box--rounded-md box--border-style-solid box--border-color-border-muted box--border-width-1 box--display-flex"
|
||||
class="box nft-details__card box--flex-direction-row box--justify-content-center box--background-color-background-default box--rounded-md box--border-style-solid box--border-color-border-muted box--border-width-1 box--display-flex"
|
||||
>
|
||||
<img
|
||||
alt="MUNK #1 1"
|
||||
class="collectible-details__image"
|
||||
class="nft-details__image"
|
||||
src="https://bafybeiclzx7zfjvuiuwobn5ip3ogc236bjqfjzoblumf4pau4ep6dqramu.ipfs.dweb.link"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="box collectible-details__info box--flex-direction-column box--justify-content-space-between box--display-flex"
|
||||
class="box nft-details__info box--flex-direction-column box--justify-content-space-between box--display-flex"
|
||||
>
|
||||
<div>
|
||||
<h4
|
||||
@ -71,8 +71,8 @@ exports[`Collectible Details should match minimal props and state snapshot 1`] =
|
||||
class="box box--display-flex box--flex-direction-row box--width-1/2"
|
||||
>
|
||||
<button
|
||||
class="button btn--rounded btn-primary collectible-details__send-button"
|
||||
data-testid="collectible-send-button"
|
||||
class="button btn--rounded btn-primary nft-details__send-button"
|
||||
data-testid="nft-send-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -88,12 +88,12 @@ exports[`Collectible Details should match minimal props and state snapshot 1`] =
|
||||
class="box box--display-flex box--flex-direction-row"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-right-2 box--margin-bottom-4 box--flex-direction-row typography collectible-details__link-title typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
class="box box--margin-top-1 box--margin-right-2 box--margin-bottom-4 box--flex-direction-row typography nft-details__link-title typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
Source
|
||||
</h6>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-4 box--flex-direction-row typography collectible-details__image-source typography--h6 typography--weight-normal typography--style-normal typography--color-primary-default"
|
||||
class="box box--margin-top-1 box--margin-bottom-4 box--flex-direction-row typography nft-details__image-source typography--h6 typography--weight-normal typography--style-normal typography--color-primary-default"
|
||||
>
|
||||
<a
|
||||
href="https://bafybeiclzx7zfjvuiuwobn5ip3ogc236bjqfjzoblumf4pau4ep6dqramu.ipfs.dweb.link"
|
||||
@ -109,12 +109,12 @@ exports[`Collectible Details should match minimal props and state snapshot 1`] =
|
||||
class="box box--display-flex box--flex-direction-row"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-right-2 box--margin-bottom-4 box--flex-direction-row typography collectible-details__link-title typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
class="box box--margin-top-1 box--margin-right-2 box--margin-bottom-4 box--flex-direction-row typography nft-details__link-title typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
Contract address
|
||||
</h6>
|
||||
<div
|
||||
class="box collectible-details__contract-wrapper box--display-flex box--flex-direction-row"
|
||||
class="box nft-details__contract-wrapper box--display-flex box--flex-direction-row"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-4 box--flex-direction-row typography typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative typography--overflowwrap-break-word"
|
||||
@ -122,7 +122,7 @@ exports[`Collectible Details should match minimal props and state snapshot 1`] =
|
||||
0xDc7...6414
|
||||
</h6>
|
||||
<div
|
||||
class="collectible-details__tooltip-wrapper"
|
||||
class="nft-details__tooltip-wrapper"
|
||||
>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-1"
|
||||
@ -134,8 +134,8 @@ exports[`Collectible Details should match minimal props and state snapshot 1`] =
|
||||
>
|
||||
<button
|
||||
aria-label="copy"
|
||||
class="box mm-button-icon mm-button-icon--size-lg collectible-details__contract-copy-button box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-alternative box--background-color-transparent box--rounded-lg"
|
||||
data-testid="collectible-address-copy"
|
||||
class="box mm-button-icon mm-button-icon--size-lg nft-details__contract-copy-button box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-alternative box--background-color-transparent box--rounded-lg"
|
||||
data-testid="nft-address-copy"
|
||||
>
|
||||
<div
|
||||
class="box mm-icon mm-icon--size-lg box--flex-direction-row box--color-inherit"
|
||||
|
@ -3,7 +3,7 @@ $link-title-width: 160px;
|
||||
$spacer-break-large: 24px;
|
||||
$spacer-break-small: 16px;
|
||||
|
||||
.collectible-details {
|
||||
.nft-details {
|
||||
padding: 0 $spacer-break-small;
|
||||
|
||||
@include screen-sm-min {
|
||||
|
@ -23,24 +23,24 @@ import {
|
||||
getAssetImageURL,
|
||||
shortenAddress,
|
||||
} from '../../../helpers/utils/util';
|
||||
import { getCollectibleImageAlt } from '../../../helpers/utils/nfts';
|
||||
import { getNftImageAlt } from '../../../helpers/utils/nfts';
|
||||
import {
|
||||
getCurrentChainId,
|
||||
getIpfsGateway,
|
||||
getSelectedIdentity,
|
||||
} from '../../../selectors';
|
||||
import AssetNavigation from '../../../pages/asset/components/asset-navigation';
|
||||
import { getCollectibleContracts } from '../../../ducks/metamask/metamask';
|
||||
import { getNftContracts } from '../../../ducks/metamask/metamask';
|
||||
import { DEFAULT_ROUTE, SEND_ROUTE } from '../../../helpers/constants/routes';
|
||||
import {
|
||||
checkAndUpdateSingleNftOwnershipStatus,
|
||||
removeAndIgnoreNft,
|
||||
setRemoveCollectibleMessage,
|
||||
setRemoveNftMessage,
|
||||
} from '../../../store/actions';
|
||||
import { CHAIN_IDS } from '../../../../shared/constants/network';
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
||||
import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app';
|
||||
import CollectibleOptions from '../nft-options/nft-options';
|
||||
import NftOptions from '../nft-options/nft-options';
|
||||
import Button from '../../ui/button';
|
||||
import { startNewDraftTransaction } from '../../../ducks/send';
|
||||
import InfoTooltip from '../../ui/info-tooltip';
|
||||
@ -51,11 +51,11 @@ import {
|
||||
AssetType,
|
||||
TokenStandard,
|
||||
} from '../../../../shared/constants/transaction';
|
||||
import CollectibleDefaultImage from '../nft-default-image';
|
||||
import NftDefaultImage from '../nft-default-image';
|
||||
import { ButtonIcon, ICON_NAMES } from '../../component-library';
|
||||
import Tooltip from '../../ui/tooltip';
|
||||
|
||||
export default function CollectibleDetails({ collectible }) {
|
||||
export default function NftDetails({ nft }) {
|
||||
const {
|
||||
image,
|
||||
imageOriginal,
|
||||
@ -67,28 +67,24 @@ export default function CollectibleDetails({ collectible }) {
|
||||
isCurrentlyOwned,
|
||||
lastSale,
|
||||
imageThumbnail,
|
||||
} = collectible;
|
||||
} = nft;
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const ipfsGateway = useSelector(getIpfsGateway);
|
||||
const collectibleContracts = useSelector(getCollectibleContracts);
|
||||
const nftContracts = useSelector(getNftContracts);
|
||||
const currentNetwork = useSelector(getCurrentChainId);
|
||||
const [addressCopied, handleAddressCopy] = useCopyToClipboard();
|
||||
|
||||
const collectibleContractName = collectibleContracts.find(
|
||||
({ address: contractAddress }) =>
|
||||
isEqualCaseInsensitive(contractAddress, address),
|
||||
const nftContractName = nftContracts.find(({ address: contractAddress }) =>
|
||||
isEqualCaseInsensitive(contractAddress, address),
|
||||
)?.name;
|
||||
const selectedAccountName = useSelector(
|
||||
(state) => getSelectedIdentity(state).name,
|
||||
);
|
||||
const collectibleImageAlt = getCollectibleImageAlt(collectible);
|
||||
const collectibleImageURL = getAssetImageURL(
|
||||
imageOriginal ?? image,
|
||||
ipfsGateway,
|
||||
);
|
||||
const isDataURI = collectibleImageURL.startsWith('data:');
|
||||
const nftImageAlt = getNftImageAlt(nft);
|
||||
const nftImageURL = getAssetImageURL(imageOriginal ?? image, ipfsGateway);
|
||||
const isDataURI = nftImageURL.startsWith('data:');
|
||||
const formattedTimestamp = formatDate(
|
||||
new Date(lastSale?.event_timestamp).getTime(),
|
||||
'M/d/y',
|
||||
@ -96,16 +92,16 @@ export default function CollectibleDetails({ collectible }) {
|
||||
|
||||
const onRemove = () => {
|
||||
dispatch(removeAndIgnoreNft(address, tokenId));
|
||||
dispatch(setRemoveCollectibleMessage('success'));
|
||||
dispatch(setRemoveNftMessage('success'));
|
||||
history.push(DEFAULT_ROUTE);
|
||||
};
|
||||
|
||||
const prevCollectible = usePrevious(collectible);
|
||||
const prevNft = usePrevious(nft);
|
||||
useEffect(() => {
|
||||
if (!isEqual(prevCollectible, collectible)) {
|
||||
checkAndUpdateSingleNftOwnershipStatus(collectible);
|
||||
if (!isEqual(prevNft, nft)) {
|
||||
checkAndUpdateSingleNftOwnershipStatus(nft);
|
||||
}
|
||||
}, [collectible, prevCollectible]);
|
||||
}, [nft, prevNft]);
|
||||
|
||||
const getOpenSeaLink = () => {
|
||||
switch (currentNetwork) {
|
||||
@ -129,7 +125,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
await dispatch(
|
||||
startNewDraftTransaction({
|
||||
type: AssetType.NFT,
|
||||
details: collectible,
|
||||
details: nft,
|
||||
}),
|
||||
);
|
||||
history.push(SEND_ROUTE);
|
||||
@ -149,8 +145,8 @@ export default function CollectibleDetails({ collectible }) {
|
||||
type="primary"
|
||||
onClick={onSend}
|
||||
disabled={sendDisabled}
|
||||
className="collectible-details__send-button"
|
||||
data-testid="collectible-send-button"
|
||||
className="nft-details__send-button"
|
||||
data-testid="nft-send-button"
|
||||
>
|
||||
{t('send')}
|
||||
</Button>
|
||||
@ -165,10 +161,10 @@ export default function CollectibleDetails({ collectible }) {
|
||||
<>
|
||||
<AssetNavigation
|
||||
accountName={selectedAccountName}
|
||||
assetName={collectibleContractName}
|
||||
assetName={nftContractName}
|
||||
onBack={() => history.push(DEFAULT_ROUTE)}
|
||||
optionsButton={
|
||||
<CollectibleOptions
|
||||
<NftOptions
|
||||
onViewOnOpensea={
|
||||
openSeaLink
|
||||
? () => global.platform.openTab({ url: openSeaLink })
|
||||
@ -178,26 +174,26 @@ export default function CollectibleDetails({ collectible }) {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Box className="collectible-details">
|
||||
<div className="collectible-details__top-section">
|
||||
<Box className="nft-details">
|
||||
<div className="nft-details__top-section">
|
||||
<Card
|
||||
padding={0}
|
||||
justifyContent={JustifyContent.center}
|
||||
className="collectible-details__card"
|
||||
className="nft-details__card"
|
||||
>
|
||||
{image ? (
|
||||
<img
|
||||
className="collectible-details__image"
|
||||
src={collectibleImageURL}
|
||||
alt={collectibleImageAlt}
|
||||
className="nft-details__image"
|
||||
src={nftImageURL}
|
||||
alt={nftImageAlt}
|
||||
/>
|
||||
) : (
|
||||
<CollectibleDefaultImage name={name} tokenId={tokenId} />
|
||||
<NftDefaultImage name={name} tokenId={tokenId} />
|
||||
)}
|
||||
</Card>
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="collectible-details__info"
|
||||
className="nft-details__info"
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
>
|
||||
<div>
|
||||
@ -224,7 +220,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
color={TextColor.textDefault}
|
||||
variant={TypographyVariant.H6}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
className="collectible-details__description"
|
||||
className="nft-details__description"
|
||||
boxProps={{ margin: 0, marginBottom: 2 }}
|
||||
>
|
||||
{t('description')}
|
||||
@ -255,14 +251,14 @@ export default function CollectibleDetails({ collectible }) {
|
||||
marginBottom: 4,
|
||||
marginRight: 2,
|
||||
}}
|
||||
className="collectible-details__link-title"
|
||||
className="nft-details__link-title"
|
||||
>
|
||||
{t('lastSold')}
|
||||
</Typography>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
className="collectible-details__contract-wrapper"
|
||||
className="nft-details__contract-wrapper"
|
||||
>
|
||||
<Typography
|
||||
color={TextColor.textAlternative}
|
||||
@ -284,14 +280,14 @@ export default function CollectibleDetails({ collectible }) {
|
||||
marginBottom: 4,
|
||||
marginRight: 2,
|
||||
}}
|
||||
className="collectible-details__link-title"
|
||||
className="nft-details__link-title"
|
||||
>
|
||||
{t('lastPriceSold')}
|
||||
</Typography>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
className="collectible-details__contract-wrapper"
|
||||
className="nft-details__contract-wrapper"
|
||||
>
|
||||
<Typography
|
||||
color={TextColor.textAlternative}
|
||||
@ -315,7 +311,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
marginBottom: 4,
|
||||
marginRight: 2,
|
||||
}}
|
||||
className="collectible-details__link-title"
|
||||
className="nft-details__link-title"
|
||||
>
|
||||
{t('source')}
|
||||
</Typography>
|
||||
@ -325,21 +321,21 @@ export default function CollectibleDetails({ collectible }) {
|
||||
margin: 0,
|
||||
marginBottom: 4,
|
||||
}}
|
||||
className="collectible-details__image-source"
|
||||
className="nft-details__image-source"
|
||||
color={
|
||||
isDataURI ? TextColor.textDefault : TextColor.primaryDefault
|
||||
}
|
||||
>
|
||||
{isDataURI ? (
|
||||
<>{collectibleImageURL}</>
|
||||
<>{nftImageURL}</>
|
||||
) : (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={collectibleImageURL}
|
||||
title={collectibleImageURL}
|
||||
href={nftImageURL}
|
||||
title={nftImageURL}
|
||||
>
|
||||
{collectibleImageURL}
|
||||
{nftImageURL}
|
||||
</a>
|
||||
)}
|
||||
</Typography>
|
||||
@ -355,7 +351,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
marginBottom: 4,
|
||||
marginRight: 2,
|
||||
}}
|
||||
className="collectible-details__link-title"
|
||||
className="nft-details__link-title"
|
||||
>
|
||||
{t('link')}
|
||||
</Typography>
|
||||
@ -365,7 +361,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
margin: 0,
|
||||
marginBottom: 4,
|
||||
}}
|
||||
className="collectible-details__image-source"
|
||||
className="nft-details__image-source"
|
||||
color={
|
||||
isDataURI ? TextColor.textDefault : TextColor.primaryDefault
|
||||
}
|
||||
@ -373,8 +369,8 @@ export default function CollectibleDetails({ collectible }) {
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={collectibleImageURL}
|
||||
title={collectibleImageURL}
|
||||
href={nftImageURL}
|
||||
title={nftImageURL}
|
||||
>
|
||||
{imageThumbnail}
|
||||
</a>
|
||||
@ -391,14 +387,14 @@ export default function CollectibleDetails({ collectible }) {
|
||||
marginBottom: 4,
|
||||
marginRight: 2,
|
||||
}}
|
||||
className="collectible-details__link-title"
|
||||
className="nft-details__link-title"
|
||||
>
|
||||
{t('contractAddress')}
|
||||
</Typography>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
className="collectible-details__contract-wrapper"
|
||||
className="nft-details__contract-wrapper"
|
||||
>
|
||||
<Typography
|
||||
color={TextColor.textAlternative}
|
||||
@ -409,7 +405,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
{shortenAddress(address)}
|
||||
</Typography>
|
||||
<Tooltip
|
||||
wrapperClassName="collectible-details__tooltip-wrapper"
|
||||
wrapperClassName="nft-details__tooltip-wrapper"
|
||||
position="bottom"
|
||||
title={
|
||||
addressCopied ? t('copiedExclamation') : t('copyToClipboard')
|
||||
@ -418,8 +414,8 @@ export default function CollectibleDetails({ collectible }) {
|
||||
<ButtonIcon
|
||||
ariaLabel="copy"
|
||||
color={IconColor.iconAlternative}
|
||||
className="collectible-details__contract-copy-button"
|
||||
data-testid="collectible-address-copy"
|
||||
className="nft-details__contract-copy-button"
|
||||
data-testid="nft-address-copy"
|
||||
onClick={() => {
|
||||
handleAddressCopy(address);
|
||||
}}
|
||||
@ -443,8 +439,8 @@ export default function CollectibleDetails({ collectible }) {
|
||||
);
|
||||
}
|
||||
|
||||
CollectibleDetails.propTypes = {
|
||||
collectible: PropTypes.shape({
|
||||
NftDetails.propTypes = {
|
||||
nft: PropTypes.shape({
|
||||
address: PropTypes.string.isRequired,
|
||||
tokenId: PropTypes.string.isRequired,
|
||||
isCurrentlyOwned: PropTypes.bool,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import CollectibleDetails from './nft-details';
|
||||
import NftDetails from './nft-details';
|
||||
|
||||
const collectible = {
|
||||
const nft = {
|
||||
name: 'Catnip Spicywright',
|
||||
tokenId: '1124157',
|
||||
address: '0x06012c8cf97bead5deae237070f9587f8e7a266d',
|
||||
@ -16,31 +16,31 @@ const collectible = {
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Components/App/CollectiblesDetail',
|
||||
title: 'Components/App/NftsDetail',
|
||||
|
||||
argTypes: {
|
||||
collectible: {
|
||||
nft: {
|
||||
control: 'object',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
collectible,
|
||||
nft,
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => {
|
||||
return <CollectibleDetails {...args} />;
|
||||
return <NftDetails {...args} />;
|
||||
};
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
export const NoImage = (args) => {
|
||||
return <CollectibleDetails {...args} />;
|
||||
return <NftDetails {...args} />;
|
||||
};
|
||||
|
||||
NoImage.args = {
|
||||
collectible: {
|
||||
...collectible,
|
||||
nft: {
|
||||
...nft,
|
||||
image: undefined,
|
||||
},
|
||||
};
|
||||
|
@ -10,9 +10,9 @@ import { DEFAULT_ROUTE, SEND_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { AssetType } from '../../../../shared/constants/transaction';
|
||||
import {
|
||||
removeAndIgnoreNft,
|
||||
setRemoveCollectibleMessage,
|
||||
setRemoveNftMessage,
|
||||
} from '../../../store/actions';
|
||||
import CollectibleDetails from './nft-details';
|
||||
import NftDetails from './nft-details';
|
||||
|
||||
jest.mock('copy-to-clipboard');
|
||||
|
||||
@ -36,17 +36,17 @@ jest.mock('../../../store/actions.ts', () => ({
|
||||
...jest.requireActual('../../../store/actions.ts'),
|
||||
checkAndUpdateSingleNftOwnershipStatus: jest.fn().mockReturnValue(jest.fn()),
|
||||
removeAndIgnoreNft: jest.fn().mockReturnValue(jest.fn()),
|
||||
setRemoveCollectibleMessage: jest.fn().mockReturnValue(jest.fn()),
|
||||
setRemoveNftMessage: jest.fn().mockReturnValue(jest.fn()),
|
||||
}));
|
||||
|
||||
describe('Collectible Details', () => {
|
||||
describe('NFT Details', () => {
|
||||
const mockStore = configureMockStore([thunk])(mockState);
|
||||
|
||||
const collectibles =
|
||||
const nfts =
|
||||
mockState.metamask.allNftContracts[mockState.metamask.selectedAddress][5];
|
||||
|
||||
const props = {
|
||||
collectible: collectibles[5],
|
||||
nft: nfts[5],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@ -55,7 +55,7 @@ describe('Collectible Details', () => {
|
||||
|
||||
it('should match minimal props and state snapshot', () => {
|
||||
const { container } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
@ -64,7 +64,7 @@ describe('Collectible Details', () => {
|
||||
|
||||
it(`should route to '/' route when the back button is clicked`, () => {
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
@ -75,51 +75,51 @@ describe('Collectible Details', () => {
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(DEFAULT_ROUTE);
|
||||
});
|
||||
|
||||
it(`should call removeAndIgnoreNft with proper collectible details and route to '/' when removing collectible`, () => {
|
||||
it(`should call removeAndIgnoreNFT with proper nft details and route to '/' when removing nft`, () => {
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const removeCollectibleButton = queryByTestId('collectible-item-remove');
|
||||
fireEvent.click(removeCollectibleButton);
|
||||
const removeNftButton = queryByTestId('nft-item-remove');
|
||||
fireEvent.click(removeNftButton);
|
||||
|
||||
expect(removeAndIgnoreNft).toHaveBeenCalledWith(
|
||||
collectibles[5].address,
|
||||
collectibles[5].tokenId,
|
||||
nfts[5].address,
|
||||
nfts[5].tokenId,
|
||||
);
|
||||
expect(setRemoveCollectibleMessage).toHaveBeenCalledWith('success');
|
||||
expect(setRemoveNftMessage).toHaveBeenCalledWith('success');
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(DEFAULT_ROUTE);
|
||||
});
|
||||
|
||||
it('should copy collectible address', async () => {
|
||||
it('should copy nft address', async () => {
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const copyAddressButton = queryByTestId('collectible-address-copy');
|
||||
const copyAddressButton = queryByTestId('nft-address-copy');
|
||||
fireEvent.click(copyAddressButton);
|
||||
|
||||
expect(copyToClipboard).toHaveBeenCalledWith(collectibles[5].address);
|
||||
expect(copyToClipboard).toHaveBeenCalledWith(nfts[5].address);
|
||||
});
|
||||
|
||||
it('should navigate to draft transaction send route with ERC721 data', async () => {
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const collectibleSendButton = queryByTestId('collectible-send-button');
|
||||
fireEvent.click(collectibleSendButton);
|
||||
const nftSendButton = queryByTestId('nft-send-button');
|
||||
fireEvent.click(nftSendButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(startNewDraftTransaction).toHaveBeenCalledWith({
|
||||
type: AssetType.NFT,
|
||||
details: collectibles[5],
|
||||
details: nfts[5],
|
||||
});
|
||||
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(SEND_ROUTE);
|
||||
@ -127,18 +127,18 @@ describe('Collectible Details', () => {
|
||||
});
|
||||
|
||||
it('should not render send button if isCurrentlyOwned is false', () => {
|
||||
const sixthCollectibleProps = {
|
||||
collectible: collectibles[6],
|
||||
const sixthNftProps = {
|
||||
nft: nfts[6],
|
||||
};
|
||||
collectibles[6].isCurrentlyOwned = false;
|
||||
nfts[6].isCurrentlyOwned = false;
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...sixthCollectibleProps} />,
|
||||
<NftDetails {...sixthNftProps} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const collectibleSendButton = queryByTestId('collectible-send-button');
|
||||
expect(collectibleSendButton).not.toBeInTheDocument();
|
||||
const nftSendButton = queryByTestId('nft-send-button');
|
||||
expect(nftSendButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe(`Alternative Networks' OpenSea Links`, () => {
|
||||
@ -146,24 +146,24 @@ describe('Collectible Details', () => {
|
||||
global.platform = { openTab: jest.fn() };
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const openOpenSea = queryByTestId('collectible-options__view-on-opensea');
|
||||
const openOpenSea = queryByTestId('nft-options__view-on-opensea');
|
||||
fireEvent.click(openOpenSea);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.platform.openTab).toHaveBeenCalledWith({
|
||||
url: `https://testnets.opensea.io/assets/${collectibles[5].address}/${collectibles[5].tokenId}`,
|
||||
url: `https://testnets.opensea.io/assets/${nfts[5].address}/${nfts[5].tokenId}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should open tab to mainnet opensea url with collectible info', async () => {
|
||||
it('should open tab to mainnet opensea url with nft info', async () => {
|
||||
global.platform = { openTab: jest.fn() };
|
||||
|
||||
const mainnetState = {
|
||||
@ -178,24 +178,24 @@ describe('Collectible Details', () => {
|
||||
const mainnetMockStore = configureMockStore([thunk])(mainnetState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
mainnetMockStore,
|
||||
);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const openOpenSea = queryByTestId('collectible-options__view-on-opensea');
|
||||
const openOpenSea = queryByTestId('nft-options__view-on-opensea');
|
||||
fireEvent.click(openOpenSea);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.platform.openTab).toHaveBeenCalledWith({
|
||||
url: `https://opensea.io/assets/${collectibles[5].address}/${collectibles[5].tokenId}`,
|
||||
url: `https://opensea.io/assets/${nfts[5].address}/${nfts[5].tokenId}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should open tab to polygon opensea url with collectible info', async () => {
|
||||
it('should open tab to polygon opensea url with nft info', async () => {
|
||||
const polygonState = {
|
||||
...mockState,
|
||||
metamask: {
|
||||
@ -208,24 +208,24 @@ describe('Collectible Details', () => {
|
||||
const polygonMockStore = configureMockStore([thunk])(polygonState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
polygonMockStore,
|
||||
);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const openOpenSea = queryByTestId('collectible-options__view-on-opensea');
|
||||
const openOpenSea = queryByTestId('nft-options__view-on-opensea');
|
||||
fireEvent.click(openOpenSea);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.platform.openTab).toHaveBeenCalledWith({
|
||||
url: `https://opensea.io/assets/matic/${collectibles[5].address}/${collectibles[5].tokenId}`,
|
||||
url: `https://opensea.io/assets/matic/${nfts[5].address}/${nfts[5].tokenId}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should open tab to sepolia opensea url with collectible info', async () => {
|
||||
it('should open tab to sepolia opensea url with nft info', async () => {
|
||||
const sepoliaState = {
|
||||
...mockState,
|
||||
metamask: {
|
||||
@ -238,19 +238,19 @@ describe('Collectible Details', () => {
|
||||
const sepoliaMockStore = configureMockStore([thunk])(sepoliaState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
sepoliaMockStore,
|
||||
);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const openOpenSea = queryByTestId('collectible-options__view-on-opensea');
|
||||
const openOpenSea = queryByTestId('nft-options__view-on-opensea');
|
||||
fireEvent.click(openOpenSea);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.platform.openTab).toHaveBeenCalledWith({
|
||||
url: `https://testnets.opensea.io/assets/${collectibles[5].address}/${collectibles[5].tokenId}`,
|
||||
url: `https://testnets.opensea.io/assets/${nfts[5].address}/${nfts[5].tokenId}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -270,14 +270,14 @@ describe('Collectible Details', () => {
|
||||
);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleDetails {...props} />,
|
||||
<NftDetails {...props} />,
|
||||
randomNetworkMockStore,
|
||||
);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const openOpenSea = queryByTestId('collectible-options__view-on-opensea');
|
||||
const openOpenSea = queryByTestId('nft-options__view-on-opensea');
|
||||
await waitFor(() => {
|
||||
expect(openOpenSea).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
.collectible-options {
|
||||
.nft-options {
|
||||
&__button {
|
||||
padding: 2px 0 2px 8px;
|
||||
}
|
||||
|
@ -6,34 +6,34 @@ import { Menu, MenuItem } from '../../ui/menu';
|
||||
import { ButtonIcon, ICON_NAMES } from '../../component-library';
|
||||
import { Color } from '../../../helpers/constants/design-system';
|
||||
|
||||
const CollectibleOptions = ({ onRemove, onViewOnOpensea }) => {
|
||||
const NftOptions = ({ onRemove, onViewOnOpensea }) => {
|
||||
const t = useContext(I18nContext);
|
||||
const [collectibleOptionsOpen, setCollectibleOptionsOpen] = useState(false);
|
||||
const [nftOptionsOpen, setNftOptionsOpen] = useState(false);
|
||||
const ref = useRef(false);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<ButtonIcon
|
||||
iconName={ICON_NAMES.MORE_VERTICAL}
|
||||
className="collectible-options__button"
|
||||
data-testid="collectible-options__button"
|
||||
onClick={() => setCollectibleOptionsOpen(true)}
|
||||
className="nft-options__button"
|
||||
data-testid="nft-options__button"
|
||||
onClick={() => setNftOptionsOpen(true)}
|
||||
color={Color.textDefault}
|
||||
ariaLabel={t('nftOptions')}
|
||||
/>
|
||||
|
||||
{collectibleOptionsOpen ? (
|
||||
{nftOptionsOpen ? (
|
||||
<Menu
|
||||
data-testid="close-collectible-options-menu"
|
||||
data-testid="close-nft-options-menu"
|
||||
anchorElement={ref.current}
|
||||
onHide={() => setCollectibleOptionsOpen(false)}
|
||||
onHide={() => setNftOptionsOpen(false)}
|
||||
>
|
||||
{onViewOnOpensea ? (
|
||||
<MenuItem
|
||||
iconName={ICON_NAMES.EXPORT}
|
||||
data-testid="collectible-options__view-on-opensea"
|
||||
data-testid="nft-options__view-on-opensea"
|
||||
onClick={() => {
|
||||
setCollectibleOptionsOpen(false);
|
||||
setNftOptionsOpen(false);
|
||||
onViewOnOpensea();
|
||||
}}
|
||||
>
|
||||
@ -42,9 +42,9 @@ const CollectibleOptions = ({ onRemove, onViewOnOpensea }) => {
|
||||
) : null}
|
||||
<MenuItem
|
||||
iconName={ICON_NAMES.TRASH}
|
||||
data-testid="collectible-item-remove"
|
||||
data-testid="nft-item-remove"
|
||||
onClick={() => {
|
||||
setCollectibleOptionsOpen(false);
|
||||
setNftOptionsOpen(false);
|
||||
onRemove();
|
||||
}}
|
||||
>
|
||||
@ -56,9 +56,9 @@ const CollectibleOptions = ({ onRemove, onViewOnOpensea }) => {
|
||||
);
|
||||
};
|
||||
|
||||
CollectibleOptions.propTypes = {
|
||||
NftOptions.propTypes = {
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
onViewOnOpensea: PropTypes.func,
|
||||
};
|
||||
|
||||
export default CollectibleOptions;
|
||||
export default NftOptions;
|
||||
|
@ -1,42 +1,36 @@
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import CollectibleOptions from './nft-options';
|
||||
import NftOptions from './nft-options';
|
||||
|
||||
describe('Collectible Options Component', () => {
|
||||
describe('NFT Options Component', () => {
|
||||
const props = {
|
||||
onRemove: jest.fn(),
|
||||
onViewOnOpensea: jest.fn(),
|
||||
};
|
||||
|
||||
it('should expand collectible options menu`', async () => {
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleOptions {...props} />,
|
||||
);
|
||||
it('should expand NFT options menu`', async () => {
|
||||
const { queryByTestId } = renderWithProvider(<NftOptions {...props} />);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
|
||||
expect(queryByTestId('collectible-item-remove')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('nft-item-remove')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByTestId('collectible-item-remove')).toBeInTheDocument();
|
||||
expect(queryByTestId('nft-item-remove')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should expand and close menu options when clicked`', async () => {
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleOptions {...props} />,
|
||||
);
|
||||
const { queryByTestId } = renderWithProvider(<NftOptions {...props} />);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const closeOptionMenuButton = queryByTestId(
|
||||
'close-collectible-options-menu',
|
||||
);
|
||||
const closeOptionMenuButton = queryByTestId('close-nft-options-menu');
|
||||
|
||||
fireEvent.click(closeOptionMenuButton);
|
||||
|
||||
@ -44,37 +38,33 @@ describe('Collectible Options Component', () => {
|
||||
});
|
||||
|
||||
it('should click onRemove handler and close option menu', () => {
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleOptions {...props} />,
|
||||
);
|
||||
const { queryByTestId } = renderWithProvider(<NftOptions {...props} />);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const removeCollectibleButton = queryByTestId('collectible-item-remove');
|
||||
const removeNftButton = queryByTestId('nft-item-remove');
|
||||
|
||||
fireEvent.click(removeCollectibleButton);
|
||||
fireEvent.click(removeNftButton);
|
||||
|
||||
expect(props.onRemove).toHaveBeenCalled();
|
||||
expect(removeCollectibleButton).not.toBeInTheDocument();
|
||||
expect(removeNftButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should click onViewOnOpensea handler and close option menu', () => {
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<CollectibleOptions {...props} />,
|
||||
);
|
||||
const { queryByTestId } = renderWithProvider(<NftOptions {...props} />);
|
||||
|
||||
const openOptionMenuButton = queryByTestId('collectible-options__button');
|
||||
const removeCollectibleButton = queryByTestId('collectible-item-remove');
|
||||
const openOptionMenuButton = queryByTestId('nft-options__button');
|
||||
const removeNftButton = queryByTestId('nft-item-remove');
|
||||
|
||||
fireEvent.click(openOptionMenuButton);
|
||||
|
||||
const openOpenSea = queryByTestId('collectible-options__view-on-opensea');
|
||||
const openOpenSea = queryByTestId('nft-options__view-on-opensea');
|
||||
|
||||
fireEvent.click(openOpenSea);
|
||||
|
||||
expect(props.onViewOnOpensea).toHaveBeenCalled();
|
||||
expect(removeCollectibleButton).not.toBeInTheDocument();
|
||||
expect(removeNftButton).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
.collectibles-detection-notice {
|
||||
.nfts-detection-notice {
|
||||
margin: 16px 16px 0 16px;
|
||||
|
||||
&__message {
|
||||
@ -13,7 +13,7 @@
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
a.collectibles-detection-notice__message__link {
|
||||
a.nfts-detection-notice__message__link {
|
||||
@include H7;
|
||||
|
||||
width: 100%;
|
||||
|
@ -14,13 +14,13 @@ import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import Button from '../../ui/button';
|
||||
import { EXPERIMENTAL_ROUTE } from '../../../helpers/constants/routes';
|
||||
|
||||
export default function CollectiblesDetectionNotice() {
|
||||
export default function NftsDetectionNotice() {
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<Box className="collectibles-detection-notice">
|
||||
<Dialog type="message" className="collectibles-detection-notice__message">
|
||||
<Box className="nfts-detection-notice">
|
||||
<Dialog type="message" className="nfts-detection-notice__message">
|
||||
<Box display={DISPLAY.FLEX}>
|
||||
<Box paddingTop={1}>
|
||||
<i
|
||||
@ -54,7 +54,7 @@ export default function CollectiblesDetectionNotice() {
|
||||
e.preventDefault();
|
||||
history.push(`${EXPERIMENTAL_ROUTE}#autodetect-nfts`);
|
||||
}}
|
||||
className="collectibles-detection-notice__message__link"
|
||||
className="nfts-detection-notice__message__link"
|
||||
>
|
||||
{t('selectNFTPrivacyPreference')}
|
||||
</Button>
|
||||
|
@ -1,4 +1,4 @@
|
||||
.collectibles-items {
|
||||
.nfts-items {
|
||||
&__collection {
|
||||
margin-bottom: 24px;
|
||||
|
||||
|
@ -25,13 +25,13 @@ import {
|
||||
} from '../../../selectors';
|
||||
import { ASSET_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { getAssetImageURL } from '../../../helpers/utils/util';
|
||||
import { getCollectibleImageAlt } from '../../../helpers/utils/nfts';
|
||||
import { updateCollectibleDropDownState } from '../../../store/actions';
|
||||
import { getNftImageAlt } from '../../../helpers/utils/nfts';
|
||||
import { updateNftDropDownState } from '../../../store/actions';
|
||||
import { usePrevious } from '../../../hooks/usePrevious';
|
||||
import { getCollectiblesDropdownState } from '../../../ducks/metamask/metamask';
|
||||
import { getNftsDropdownState } from '../../../ducks/metamask/metamask';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import CollectibleDefaultImage from '../nft-default-image';
|
||||
import { Icon, ICON_NAMES } from '../../component-library';
|
||||
import NftDefaultImage from '../nft-default-image';
|
||||
|
||||
const width =
|
||||
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
|
||||
@ -40,13 +40,13 @@ const width =
|
||||
|
||||
const PREVIOUSLY_OWNED_KEY = 'previouslyOwned';
|
||||
|
||||
export default function CollectiblesItems({
|
||||
export default function NftsItems({
|
||||
collections = {},
|
||||
previouslyOwnedCollection = {},
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const collectionsKeys = Object.keys(collections);
|
||||
const collectiblesDropdownState = useSelector(getCollectiblesDropdownState);
|
||||
const nftsDropdownState = useSelector(getNftsDropdownState);
|
||||
const previousCollectionKeys = usePrevious(collectionsKeys);
|
||||
const selectedAddress = useSelector(getSelectedAddress);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
@ -57,29 +57,29 @@ export default function CollectiblesItems({
|
||||
chainId !== undefined &&
|
||||
selectedAddress !== undefined &&
|
||||
!isEqual(previousCollectionKeys, collectionsKeys) &&
|
||||
(collectiblesDropdownState?.[selectedAddress]?.[chainId] === undefined ||
|
||||
Object.keys(collectiblesDropdownState?.[selectedAddress]?.[chainId])
|
||||
.length === 0)
|
||||
(nftsDropdownState?.[selectedAddress]?.[chainId] === undefined ||
|
||||
Object.keys(nftsDropdownState?.[selectedAddress]?.[chainId]).length ===
|
||||
0)
|
||||
) {
|
||||
const initState = {};
|
||||
collectionsKeys.forEach((key) => {
|
||||
initState[key] = true;
|
||||
});
|
||||
|
||||
const newCollectibleDropdownState = {
|
||||
...collectiblesDropdownState,
|
||||
const newNftDropdownState = {
|
||||
...nftsDropdownState,
|
||||
[selectedAddress]: {
|
||||
...collectiblesDropdownState?.[selectedAddress],
|
||||
...nftsDropdownState?.[selectedAddress],
|
||||
[chainId]: initState,
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(updateCollectibleDropDownState(newCollectibleDropdownState));
|
||||
dispatch(updateNftDropDownState(newNftDropdownState));
|
||||
}
|
||||
}, [
|
||||
collectionsKeys,
|
||||
previousCollectionKeys,
|
||||
collectiblesDropdownState,
|
||||
nftsDropdownState,
|
||||
selectedAddress,
|
||||
chainId,
|
||||
dispatch,
|
||||
@ -94,51 +94,44 @@ export default function CollectiblesItems({
|
||||
<img
|
||||
alt={collectionName}
|
||||
src={getAssetImageURL(collectionImage, ipfsGateway)}
|
||||
className="collectibles-items__collection-image"
|
||||
className="nfts-items__collection-image"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="collectibles-items__collection-image-alt">
|
||||
<div className="nfts-items__collection-image-alt">
|
||||
{collectionName?.[0]?.toUpperCase() ?? null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const updateCollectibleDropDownStateKey = (key, isExpanded) => {
|
||||
const currentAccountCollectibleDropdownState =
|
||||
collectiblesDropdownState[selectedAddress][chainId];
|
||||
const updateNftDropDownStateKey = (key, isExpanded) => {
|
||||
const currentAccountNftDropdownState =
|
||||
nftsDropdownState[selectedAddress][chainId];
|
||||
|
||||
const newCurrentAccountState = {
|
||||
...currentAccountCollectibleDropdownState,
|
||||
...currentAccountNftDropdownState,
|
||||
[key]: !isExpanded,
|
||||
};
|
||||
|
||||
collectiblesDropdownState[selectedAddress][chainId] =
|
||||
newCurrentAccountState;
|
||||
nftsDropdownState[selectedAddress][chainId] = newCurrentAccountState;
|
||||
|
||||
dispatch(updateCollectibleDropDownState(collectiblesDropdownState));
|
||||
dispatch(updateNftDropDownState(nftsDropdownState));
|
||||
};
|
||||
|
||||
const renderCollection = ({
|
||||
collectibles,
|
||||
collectionName,
|
||||
collectionImage,
|
||||
key,
|
||||
}) => {
|
||||
if (!collectibles.length) {
|
||||
const renderCollection = ({ nfts, collectionName, collectionImage, key }) => {
|
||||
if (!nfts.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isExpanded =
|
||||
collectiblesDropdownState[selectedAddress]?.[chainId]?.[key];
|
||||
const isExpanded = nftsDropdownState[selectedAddress]?.[chainId]?.[key];
|
||||
return (
|
||||
<div className="collectibles-items__collection" key={`collection-${key}`}>
|
||||
<div className="nfts-items__collection" key={`collection-${key}`}>
|
||||
<button
|
||||
className="collectibles-items__collection-wrapper"
|
||||
className="nfts-items__collection-wrapper"
|
||||
data-testid="collection-expander-button"
|
||||
onClick={() => {
|
||||
updateCollectibleDropDownStateKey(key, isExpanded);
|
||||
updateNftDropDownStateKey(key, isExpanded);
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -146,11 +139,11 @@ export default function CollectiblesItems({
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={AlignItems.center}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
className="collectibles-items__collection-accordion-title"
|
||||
className="nfts-items__collection-accordion-title"
|
||||
>
|
||||
<Box
|
||||
alignItems={AlignItems.center}
|
||||
className="collectibles-items__collection-header"
|
||||
className="nfts-items__collection-header"
|
||||
>
|
||||
{renderCollectionImage(collectionImage, collectionName)}
|
||||
<Typography
|
||||
@ -158,9 +151,7 @@ export default function CollectiblesItems({
|
||||
variant={TypographyVariant.H5}
|
||||
margin={2}
|
||||
>
|
||||
{`${collectionName ?? t('unknownCollection')} (${
|
||||
collectibles.length
|
||||
})`}
|
||||
{`${collectionName ?? t('unknownCollection')} (${nfts.length})`}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box alignItems={AlignItems.flexEnd}>
|
||||
@ -176,43 +167,42 @@ export default function CollectiblesItems({
|
||||
|
||||
{isExpanded ? (
|
||||
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}>
|
||||
{collectibles.map((collectible, i) => {
|
||||
const { image, address, tokenId, backgroundColor, name } =
|
||||
collectible;
|
||||
const collectibleImage = getAssetImageURL(image, ipfsGateway);
|
||||
const collectibleImageAlt = getCollectibleImageAlt(collectible);
|
||||
{nfts.map((nft, i) => {
|
||||
const { image, address, tokenId, backgroundColor, name } = nft;
|
||||
const nftImage = getAssetImageURL(image, ipfsGateway);
|
||||
const nftImageAlt = getNftImageAlt(nft);
|
||||
const handleImageClick = () =>
|
||||
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`);
|
||||
|
||||
return (
|
||||
<Box
|
||||
data-testid="collectible-wrapper"
|
||||
data-testid="nft-wrapper"
|
||||
width={width}
|
||||
key={`collectible-${i}`}
|
||||
className="collectibles-items__item-wrapper"
|
||||
key={`nft-${i}`}
|
||||
className="nfts-items__item-wrapper"
|
||||
>
|
||||
<Card
|
||||
padding={0}
|
||||
justifyContent={JustifyContent.center}
|
||||
className="collectibles-items__item-wrapper__card"
|
||||
className="nfts-items__item-wrapper__card"
|
||||
>
|
||||
{collectibleImage ? (
|
||||
{nftImage ? (
|
||||
<button
|
||||
className="collectibles-items__item"
|
||||
className="nfts-items__item"
|
||||
style={{
|
||||
backgroundColor,
|
||||
}}
|
||||
onClick={handleImageClick}
|
||||
>
|
||||
<img
|
||||
className="collectibles-items__item-image"
|
||||
data-testid="collectible-image"
|
||||
src={collectibleImage}
|
||||
alt={collectibleImageAlt}
|
||||
className="nfts-items__item-image"
|
||||
data-testid="nft-image"
|
||||
src={nftImage}
|
||||
alt={nftImageAlt}
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<CollectibleDefaultImage
|
||||
<NftDefaultImage
|
||||
name={name}
|
||||
tokenId={tokenId}
|
||||
handleImageClick={handleImageClick}
|
||||
@ -229,7 +219,7 @@ export default function CollectiblesItems({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="collectibles-items">
|
||||
<div className="nfts-items">
|
||||
<Box
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
@ -239,11 +229,10 @@ export default function CollectiblesItems({
|
||||
>
|
||||
<>
|
||||
{collectionsKeys.map((key) => {
|
||||
const { collectibles, collectionName, collectionImage } =
|
||||
collections[key];
|
||||
const { nfts, collectionName, collectionImage } = collections[key];
|
||||
|
||||
return renderCollection({
|
||||
collectibles,
|
||||
nfts,
|
||||
collectionName,
|
||||
collectionImage,
|
||||
key,
|
||||
@ -251,9 +240,9 @@ export default function CollectiblesItems({
|
||||
});
|
||||
})}
|
||||
{renderCollection({
|
||||
collectibles: previouslyOwnedCollection.collectibles,
|
||||
nfts: previouslyOwnedCollection.nfts,
|
||||
collectionName: previouslyOwnedCollection.collectionName,
|
||||
collectionImage: previouslyOwnedCollection.collectibles[0]?.image,
|
||||
collectionImage: previouslyOwnedCollection.nfts[0]?.image,
|
||||
isPreviouslyOwnedCollection: true,
|
||||
key: PREVIOUSLY_OWNED_KEY,
|
||||
})}
|
||||
@ -263,9 +252,9 @@ export default function CollectiblesItems({
|
||||
);
|
||||
}
|
||||
|
||||
CollectiblesItems.propTypes = {
|
||||
NftsItems.propTypes = {
|
||||
previouslyOwnedCollection: PropTypes.shape({
|
||||
collectibles: PropTypes.arrayOf(
|
||||
nfts: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
address: PropTypes.string.isRequired,
|
||||
tokenId: PropTypes.string.isRequired,
|
||||
@ -286,7 +275,7 @@ CollectiblesItems.propTypes = {
|
||||
collectionImage: PropTypes.string,
|
||||
}),
|
||||
collections: PropTypes.shape({
|
||||
collectibles: PropTypes.arrayOf(
|
||||
nfts: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
address: PropTypes.string.isRequired,
|
||||
tokenId: PropTypes.string.isRequired,
|
||||
|
@ -4,8 +4,8 @@ import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import { updateCollectibleDropDownState } from '../../../store/actions';
|
||||
import CollectiblesItems from '.';
|
||||
import { updateNftDropDownState } from '../../../store/actions';
|
||||
import NftsItems from '.';
|
||||
|
||||
const mockHistoryPush = jest.fn();
|
||||
|
||||
@ -19,30 +19,30 @@ jest.mock('react-router-dom', () => ({
|
||||
|
||||
jest.mock('../../../store/actions.ts', () => ({
|
||||
...jest.requireActual('../../../store/actions.ts'),
|
||||
updateCollectibleDropDownState: jest.fn().mockReturnValue(jest.fn()),
|
||||
updateNftDropDownState: jest.fn().mockReturnValue(jest.fn()),
|
||||
}));
|
||||
|
||||
describe('Collectibles Item Component', () => {
|
||||
const collectibles =
|
||||
describe('NFTs Item Component', () => {
|
||||
const nfts =
|
||||
mockState.metamask.allNftContracts[mockState.metamask.selectedAddress][5];
|
||||
const props = {
|
||||
collections: {
|
||||
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
|
||||
collectibles,
|
||||
nfts,
|
||||
collectionImage: '',
|
||||
collectionName: 'Collectible Collection',
|
||||
collectionName: 'NFT Collection',
|
||||
},
|
||||
},
|
||||
previouslyOwnedCollection: {
|
||||
collectibles: [],
|
||||
nfts: [],
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = configureMockStore([thunk])(mockState);
|
||||
|
||||
it('should expand collectible collection showing individual collectibles', async () => {
|
||||
it('should expand NFT collection showing individual NFTs', async () => {
|
||||
const { queryByTestId, queryAllByTestId, rerender } = renderWithProvider(
|
||||
<CollectiblesItems {...props} />,
|
||||
<NftsItems {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
@ -50,11 +50,11 @@ describe('Collectibles Item Component', () => {
|
||||
'collection-expander-button',
|
||||
);
|
||||
|
||||
expect(queryAllByTestId('collectible-wrapper')).toHaveLength(0);
|
||||
expect(queryAllByTestId('nft-wrapper')).toHaveLength(0);
|
||||
|
||||
fireEvent.click(collectionExpanderButton);
|
||||
|
||||
expect(updateCollectibleDropDownState).toHaveBeenCalledWith({
|
||||
expect(updateNftDropDownState).toHaveBeenCalledWith({
|
||||
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
|
||||
'0x5': {
|
||||
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': true,
|
||||
@ -63,24 +63,24 @@ describe('Collectibles Item Component', () => {
|
||||
},
|
||||
});
|
||||
|
||||
rerender(<CollectiblesItems {...props} />, mockStore);
|
||||
rerender(<NftsItems {...props} />, mockStore);
|
||||
|
||||
expect(queryAllByTestId('collectible-wrapper')).toHaveLength(8);
|
||||
expect(queryAllByTestId('nft-wrapper')).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('should collectible click image', () => {
|
||||
it('should NFT click image', () => {
|
||||
const { queryAllByTestId } = renderWithProvider(
|
||||
<CollectiblesItems {...props} />,
|
||||
<NftsItems {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const collectibleImages = queryAllByTestId('collectible-image');
|
||||
const nftImages = queryAllByTestId('nft-image');
|
||||
|
||||
fireEvent.click(collectibleImages[0]);
|
||||
fireEvent.click(nftImages[0]);
|
||||
|
||||
const firstCollectible = collectibles[0];
|
||||
const collectibleRoute = `/asset/${firstCollectible.address}/${firstCollectible.tokenId}`;
|
||||
const firstNft = nfts[0];
|
||||
const nftRoute = `/asset/${firstNft.address}/${firstNft.tokenId}`;
|
||||
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(collectibleRoute);
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(nftRoute);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
.collectibles-tab {
|
||||
.nfts-tab {
|
||||
&__link {
|
||||
a {
|
||||
padding: 4px;
|
||||
|
@ -5,8 +5,8 @@ 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 '../nfts-detection-notice';
|
||||
import CollectiblesItems from '../nfts-items';
|
||||
import NftsDetectionNotice from '../nfts-detection-notice';
|
||||
import NftsItems from '../nfts-items';
|
||||
import {
|
||||
TypographyVariant,
|
||||
TEXT_ALIGN,
|
||||
@ -23,18 +23,18 @@ import {
|
||||
checkAndUpdateAllNftsOwnershipStatus,
|
||||
detectNfts,
|
||||
} from '../../../store/actions';
|
||||
import { useCollectiblesCollections } from '../../../hooks/useNftsCollections';
|
||||
import { useNftsCollections } from '../../../hooks/useNftsCollections';
|
||||
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
|
||||
|
||||
export default function CollectiblesTab({ onAddNFT }) {
|
||||
export default function NftsTab({ onAddNFT }) {
|
||||
const useNftDetection = useSelector(getUseNftDetection);
|
||||
const isMainnet = useSelector(getIsMainnet);
|
||||
const history = useHistory();
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { collectiblesLoading, collections, previouslyOwnedCollection } =
|
||||
useCollectiblesCollections();
|
||||
const { nftsLoading, collections, previouslyOwnedCollection } =
|
||||
useNftsCollections();
|
||||
|
||||
const onEnableAutoDetect = () => {
|
||||
history.push(EXPERIMENTAL_ROUTE);
|
||||
@ -47,23 +47,21 @@ export default function CollectiblesTab({ onAddNFT }) {
|
||||
checkAndUpdateAllNftsOwnershipStatus();
|
||||
};
|
||||
|
||||
if (collectiblesLoading) {
|
||||
return <div className="collectibles-tab__loading">{t('loadingNFTs')}</div>;
|
||||
if (nftsLoading) {
|
||||
return <div className="nfts-tab__loading">{t('loadingNFTs')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="collectibles-tab">
|
||||
<Box className="nfts-tab">
|
||||
{Object.keys(collections).length > 0 ||
|
||||
previouslyOwnedCollection.collectibles.length > 0 ? (
|
||||
<CollectiblesItems
|
||||
previouslyOwnedCollection.nfts.length > 0 ? (
|
||||
<NftsItems
|
||||
collections={collections}
|
||||
previouslyOwnedCollection={previouslyOwnedCollection}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{isMainnet && !useNftDetection ? (
|
||||
<CollectiblesDetectionNotice />
|
||||
) : null}
|
||||
{isMainnet && !useNftDetection ? <NftsDetectionNotice /> : null}
|
||||
<Box padding={12}>
|
||||
<Box justifyContent={JustifyContent.center}>
|
||||
<img src="./images/no-nfts.svg" />
|
||||
@ -73,7 +71,7 @@ export default function CollectiblesTab({ onAddNFT }) {
|
||||
marginBottom={12}
|
||||
justifyContent={JustifyContent.center}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="collectibles-tab__link"
|
||||
className="nfts-tab__link"
|
||||
>
|
||||
<Typography
|
||||
color={TextColor.textMuted}
|
||||
@ -114,7 +112,7 @@ export default function CollectiblesTab({ onAddNFT }) {
|
||||
{!isMainnet && Object.keys(collections).length < 1 ? null : (
|
||||
<>
|
||||
<Box
|
||||
className="collectibles-tab__link"
|
||||
className="nfts-tab__link"
|
||||
justifyContent={JustifyContent.flexEnd}
|
||||
>
|
||||
{isMainnet && !useNftDetection ? (
|
||||
@ -138,7 +136,7 @@ export default function CollectiblesTab({ onAddNFT }) {
|
||||
)}
|
||||
<Box
|
||||
justifyContent={JustifyContent.flexStart}
|
||||
className="collectibles-tab__link"
|
||||
className="nfts-tab__link"
|
||||
>
|
||||
<Button type="link" onClick={onAddNFT}>
|
||||
{t('importNFTs')}
|
||||
@ -150,6 +148,6 @@ export default function CollectiblesTab({ onAddNFT }) {
|
||||
);
|
||||
}
|
||||
|
||||
CollectiblesTab.propTypes = {
|
||||
NftsTab.propTypes = {
|
||||
onAddNFT: PropTypes.func.isRequired,
|
||||
};
|
||||
|
@ -6,9 +6,9 @@ import { renderWithProvider } from '../../../../test/jest/rendering';
|
||||
import { EXPERIMENTAL_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { setBackgroundConnection } from '../../../../test/jest';
|
||||
import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
|
||||
import CollectiblesTab from '.';
|
||||
import NftsTab from '.';
|
||||
|
||||
const COLLECTIBLES = [
|
||||
const NFTS = [
|
||||
{
|
||||
address: '0x495f947276749Ce646f68AC8c248420045cb7b5e',
|
||||
tokenId:
|
||||
@ -123,7 +123,7 @@ const COLLECTIBLES = [
|
||||
},
|
||||
];
|
||||
|
||||
const COLLECTIBLES_CONTRACTS = [
|
||||
const NFTS_CONTRACTS = [
|
||||
{
|
||||
address: '0x495f947276749Ce646f68AC8c248420045cb7b5e',
|
||||
name: 'PUNKS',
|
||||
@ -137,7 +137,7 @@ const COLLECTIBLES_CONTRACTS = [
|
||||
},
|
||||
];
|
||||
|
||||
const collectiblesDropdownState = {
|
||||
const nftsDropdownState = {
|
||||
'0x495f947276749ce646f68ac8c248420045cb7b5e': true,
|
||||
'0xdc7382eb0bc9c352a4cba23c909bda01e0206414': true,
|
||||
};
|
||||
@ -146,8 +146,8 @@ const ACCOUNT_1 = '0x123';
|
||||
const ACCOUNT_2 = '0x456';
|
||||
|
||||
const render = ({
|
||||
collectibleContracts = [],
|
||||
collectibles = [],
|
||||
nftContracts = [],
|
||||
nfts = [],
|
||||
selectedAddress,
|
||||
chainId = '0x1',
|
||||
useNftDetection,
|
||||
@ -158,34 +158,34 @@ const render = ({
|
||||
metamask: {
|
||||
allNfts: {
|
||||
[ACCOUNT_1]: {
|
||||
[chainIdAsDecimal]: collectibles,
|
||||
[chainIdAsDecimal]: nfts,
|
||||
},
|
||||
},
|
||||
allNftContracts: {
|
||||
[ACCOUNT_1]: {
|
||||
[chainIdAsDecimal]: collectibleContracts,
|
||||
[chainIdAsDecimal]: nftContracts,
|
||||
},
|
||||
},
|
||||
provider: { chainId },
|
||||
selectedAddress,
|
||||
useNftDetection,
|
||||
collectiblesDropdownState,
|
||||
nftsDropdownState,
|
||||
},
|
||||
});
|
||||
return renderWithProvider(<CollectiblesTab onAddNFT={onAddNFT} />, store);
|
||||
return renderWithProvider(<NftsTab onAddNFT={onAddNFT} />, store);
|
||||
};
|
||||
|
||||
describe('Collectible Items', () => {
|
||||
const detectCollectiblesStub = jest.fn();
|
||||
describe('NFT Items', () => {
|
||||
const detectNftsStub = jest.fn();
|
||||
const getStateStub = jest.fn();
|
||||
const checkAndUpdateAllCollectiblesOwnershipStatusStub = jest.fn();
|
||||
const updateCollectibleDropDownStateStub = jest.fn();
|
||||
const checkAndUpdateAllNftsOwnershipStatusStub = jest.fn();
|
||||
const updateNftDropDownStateStub = jest.fn();
|
||||
setBackgroundConnection({
|
||||
detectNfts: detectCollectiblesStub,
|
||||
detectNfts: detectNftsStub,
|
||||
getState: getStateStub,
|
||||
checkAndUpdateAllNftsOwnershipStatus:
|
||||
checkAndUpdateAllCollectiblesOwnershipStatusStub,
|
||||
updateCollectibleDropDownState: updateCollectibleDropDownStateStub,
|
||||
checkAndUpdateAllNftsOwnershipStatusStub,
|
||||
updateNftDropDownState: updateNftDropDownStateStub,
|
||||
});
|
||||
const historyPushMock = jest.fn();
|
||||
|
||||
@ -198,25 +198,25 @@ describe('Collectible Items', () => {
|
||||
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', () => {
|
||||
describe('NFTs Detection Notice', () => {
|
||||
it('should render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has no nfts', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_2,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
});
|
||||
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', () => {
|
||||
it('should not render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has NFTs', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_1,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
});
|
||||
expect(screen.queryByText('New! NFT detection')).not.toBeInTheDocument();
|
||||
});
|
||||
it('should take user to the experimental settings tab in settings when user clicks "Turn on NFT detection in Settings"', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_2,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
});
|
||||
fireEvent.click(screen.queryByText('Turn on NFT detection in Settings'));
|
||||
expect(historyPushMock).toHaveBeenCalledTimes(1);
|
||||
@ -224,92 +224,84 @@ describe('Collectible Items', () => {
|
||||
`${EXPERIMENTAL_ROUTE}#autodetect-nfts`,
|
||||
);
|
||||
});
|
||||
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', () => {
|
||||
it('should not render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has no NFTs but use NFT autodetection preference is set to true', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_1,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
useNftDetection: 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', () => {
|
||||
it('should not render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has no NFTs but user has dismissed the notice before', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_1,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
});
|
||||
expect(screen.queryByText('New! NFT detection')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Collections', () => {
|
||||
it('should render the name of the collections and number of collectibles in each collection if current account/chainId combination has collectibles', () => {
|
||||
it('should render the name of the collections and number of NFTs in each collection if current account/chainId combination has NFTs', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_1,
|
||||
collectibles: COLLECTIBLES,
|
||||
collectibleContracts: COLLECTIBLES_CONTRACTS,
|
||||
nfts: NFTS,
|
||||
nftContracts: NFTS_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', () => {
|
||||
it('should not render collections if current account/chainId combination has NFTs', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_2,
|
||||
collectibles: COLLECTIBLES,
|
||||
collectibleContracts: COLLECTIBLES_CONTRACTS,
|
||||
nfts: NFTS,
|
||||
nftContracts: NFTS_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 on mainnet and collectible auto-detection preference is set to true, which, when clicked calls methods DetectCollectibles and checkAndUpdateCollectiblesOwnershipStatus', () => {
|
||||
describe('NFTs options', () => {
|
||||
it('should render a link "Refresh list" when some NFTs are present on mainnet and NFT auto-detection preference is set to true, which, when clicked calls methods DetectNFTs and checkAndUpdateNftsOwnershipStatus', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_1,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
useNftDetection: true,
|
||||
});
|
||||
expect(detectCollectiblesStub).not.toHaveBeenCalled();
|
||||
expect(
|
||||
checkAndUpdateAllCollectiblesOwnershipStatusStub,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(detectNftsStub).not.toHaveBeenCalled();
|
||||
expect(checkAndUpdateAllNftsOwnershipStatusStub).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.queryByText('Refresh list'));
|
||||
expect(detectCollectiblesStub).toHaveBeenCalled();
|
||||
expect(
|
||||
checkAndUpdateAllCollectiblesOwnershipStatusStub,
|
||||
).toHaveBeenCalled();
|
||||
expect(detectNftsStub).toHaveBeenCalled();
|
||||
expect(checkAndUpdateAllNftsOwnershipStatusStub).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render a link "Refresh list" when some collectibles are present on a non-mainnet chain, which, when clicked calls a method checkAndUpdateCollectiblesOwnershipStatus', () => {
|
||||
it('should render a link "Refresh list" when some NFTs are present on a non-mainnet chain, which, when clicked calls a method checkAndUpdateNftsOwnershipStatus', () => {
|
||||
render({
|
||||
chainId: '0x5',
|
||||
selectedAddress: ACCOUNT_1,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
useNftDetection: true,
|
||||
});
|
||||
expect(
|
||||
checkAndUpdateAllCollectiblesOwnershipStatusStub,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(checkAndUpdateAllNftsOwnershipStatusStub).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.queryByText('Refresh list'));
|
||||
expect(
|
||||
checkAndUpdateAllCollectiblesOwnershipStatusStub,
|
||||
).toHaveBeenCalled();
|
||||
expect(checkAndUpdateAllNftsOwnershipStatusStub).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', () => {
|
||||
it('should render a link "Enable autodetect" when some NFTs are present and NFT auto-detection preference is set to false, which, when clicked sends user to the experimental tab of settings', () => {
|
||||
render({
|
||||
selectedAddress: ACCOUNT_1,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
});
|
||||
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', () => {
|
||||
it('should render a link "Import NFTs" when some NFTs are present, which, when clicked calls the passed in onAddNFT method', () => {
|
||||
const onAddNFTStub = jest.fn();
|
||||
render({
|
||||
selectedAddress: ACCOUNT_1,
|
||||
collectibles: COLLECTIBLES,
|
||||
nfts: NFTS,
|
||||
onAddNFT: onAddNFTStub,
|
||||
});
|
||||
expect(onAddNFTStub).toHaveBeenCalledTimes(0);
|
||||
|
@ -5,10 +5,10 @@ import Box from '../box';
|
||||
import { Color, TEXT_ALIGN } from '../../../helpers/constants/design-system';
|
||||
import Identicon from '../identicon';
|
||||
import { getTokenList } from '../../../selectors';
|
||||
import { useCollectiblesCollections } from '../../../hooks/useNftsCollections';
|
||||
import { useNftsCollections } from '../../../hooks/useNftsCollections';
|
||||
|
||||
export default function NftCollectionImage({ assetName, tokenAddress }) {
|
||||
const { collections } = useCollectiblesCollections();
|
||||
const { collections } = useNftsCollections();
|
||||
const tokenList = useSelector(getTokenList);
|
||||
const nftTokenListImage = tokenList[tokenAddress.toLowerCase()]?.iconUrl;
|
||||
|
||||
|
@ -350,13 +350,13 @@ export default function reduceApp(
|
||||
newTokensImported: action.payload,
|
||||
};
|
||||
|
||||
case actionConstants.SET_NEW_COLLECTIBLE_ADDED_MESSAGE:
|
||||
case actionConstants.SET_NEW_NFT_ADDED_MESSAGE:
|
||||
return {
|
||||
...appState,
|
||||
newNftAddedMessage: action.payload,
|
||||
};
|
||||
|
||||
case actionConstants.SET_REMOVE_COLLECTIBLE_MESSAGE:
|
||||
case actionConstants.SET_REMOVE_NFT_MESSAGE:
|
||||
return {
|
||||
...appState,
|
||||
removeNftMessage: action.payload,
|
||||
|
@ -227,11 +227,11 @@ export const getPendingTokens = (state) => state.metamask.pendingTokens;
|
||||
|
||||
export const getTokens = (state) => state.metamask.tokens;
|
||||
|
||||
export function getCollectiblesDropdownState(state) {
|
||||
return state.metamask.collectiblesDropdownState;
|
||||
export function getNftsDropdownState(state) {
|
||||
return state.metamask.nftsDropdownState;
|
||||
}
|
||||
|
||||
export const getCollectibles = (state) => {
|
||||
export const getNfts = (state) => {
|
||||
const {
|
||||
metamask: {
|
||||
allNfts,
|
||||
@ -245,7 +245,7 @@ export const getCollectibles = (state) => {
|
||||
return allNfts?.[selectedAddress]?.[chainIdAsDecimal] ?? [];
|
||||
};
|
||||
|
||||
export const getCollectibleContracts = (state) => {
|
||||
export const getNftContracts = (state) => {
|
||||
const {
|
||||
metamask: {
|
||||
allNftContracts,
|
||||
|
@ -54,7 +54,7 @@ describe('Send Slice Helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a txParams for a collectible transfer', () => {
|
||||
it('should generate a txParams for an NFT transfer', () => {
|
||||
const txParams = generateTransactionParams(
|
||||
getInitialSendStateWithExistingTxState({
|
||||
fromAccount: {
|
||||
|
@ -2120,7 +2120,7 @@ export function updateSendAsset(
|
||||
} catch (err) {
|
||||
if (err.message.includes('Unable to verify ownership.')) {
|
||||
// this would indicate that either our attempts to verify ownership failed because of network issues,
|
||||
// or, somehow a token has been added to collectibles state with an incorrect chainId.
|
||||
// or, somehow a token has been added to NFTs state with an incorrect chainId.
|
||||
} else {
|
||||
// Any other error is unexpected and should be surfaced.
|
||||
dispatch(displayWarning(err.message));
|
||||
@ -2132,7 +2132,7 @@ export function updateSendAsset(
|
||||
asset.balance = '0x1';
|
||||
} else {
|
||||
throw new Error(
|
||||
'Send slice initialized as collectible send with a collectible not currently owned by the select account',
|
||||
'Send slice initialized as NFT send with an NFT not currently owned by the select account',
|
||||
);
|
||||
}
|
||||
await dispatch(
|
||||
|
@ -2497,12 +2497,12 @@ describe('Send Slice', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should set up the appropriate state for editing a collectible asset transaction', async () => {
|
||||
it('should set up the appropriate state for editing an NFT asset transaction', async () => {
|
||||
getTokenStandardAndDetailsStub.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
standard: 'ERC721',
|
||||
balance: '0x1',
|
||||
address: '0xCollectibleAddress',
|
||||
address: '0xNftAddress',
|
||||
}),
|
||||
);
|
||||
const editTransactionState = {
|
||||
@ -2539,7 +2539,7 @@ describe('Send Slice', () => {
|
||||
tokenId: BigNumber.from(15000).toString(),
|
||||
}),
|
||||
from: '0xAddress',
|
||||
to: '0xCollectibleAddress',
|
||||
to: '0xNftAddress',
|
||||
gas: GAS_LIMITS.BASE_TOKEN_ESTIMATE,
|
||||
gasPrice: '0x3b9aca00', // 1000000000
|
||||
value: '0x0',
|
||||
@ -2621,7 +2621,7 @@ describe('Send Slice', () => {
|
||||
expect(actionResult[4]).toStrictEqual({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload:
|
||||
'sendFlow - user set asset to NFT with tokenId 15000 and address 0xCollectibleAddress',
|
||||
'sendFlow - user set asset to NFT with tokenId 15000 and address 0xNftAddress',
|
||||
});
|
||||
expect(actionResult[5]).toStrictEqual({
|
||||
type: 'send/updateAsset',
|
||||
@ -2629,7 +2629,7 @@ describe('Send Slice', () => {
|
||||
asset: {
|
||||
balance: '0x1',
|
||||
details: {
|
||||
address: '0xCollectibleAddress',
|
||||
address: '0xNftAddress',
|
||||
balance: '0x1',
|
||||
standard: TokenStandard.ERC721,
|
||||
tokenId: '15000',
|
||||
|
@ -49,7 +49,7 @@ const SMART_TRANSACTION_STATUS_ROUTE = '/swaps/smart-transaction-status';
|
||||
const AWAITING_SWAP_ROUTE = '/swaps/awaiting-swap';
|
||||
const SWAPS_ERROR_ROUTE = '/swaps/swaps-error';
|
||||
const SWAPS_MAINTENANCE_ROUTE = '/swaps/maintenance';
|
||||
const ADD_COLLECTIBLE_ROUTE = '/add-collectible';
|
||||
const ADD_NFT_ROUTE = '/add-nft';
|
||||
|
||||
const ONBOARDING_ROUTE = '/onboarding';
|
||||
const ONBOARDING_REVIEW_SRP_ROUTE = '/onboarding/review-recovery-phrase';
|
||||
@ -223,7 +223,7 @@ export {
|
||||
SWAPS_ERROR_ROUTE,
|
||||
SWAPS_MAINTENANCE_ROUTE,
|
||||
SMART_TRANSACTION_STATUS_ROUTE,
|
||||
ADD_COLLECTIBLE_ROUTE,
|
||||
ADD_NFT_ROUTE,
|
||||
ONBOARDING_ROUTE,
|
||||
ONBOARDING_HELP_US_IMPROVE_ROUTE,
|
||||
ONBOARDING_CREATE_PASSWORD_ROUTE,
|
||||
|
@ -1,3 +1,3 @@
|
||||
export const getCollectibleImageAlt = ({ name, tokenId, description }) => {
|
||||
export const getNftImageAlt = ({ name, tokenId, description }) => {
|
||||
return description ?? `${name} ${tokenId}`;
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { getCollectibleImageAlt } from './nfts';
|
||||
import { getNftImageAlt } from './nfts';
|
||||
|
||||
describe('Collectibles Utils', () => {
|
||||
describe('getCollectibleImageAlt', () => {
|
||||
describe('NFTs Utils', () => {
|
||||
describe('getNftImageAlt', () => {
|
||||
it('returns the description attribute when it is available', () => {
|
||||
expect(
|
||||
getCollectibleImageAlt({
|
||||
getNftImageAlt({
|
||||
name: 'Cool NFT',
|
||||
tokenId: '555',
|
||||
description: 'This is a really cool NFT',
|
||||
@ -14,14 +14,14 @@ describe('Collectibles Utils', () => {
|
||||
|
||||
it('returns the formatted name and tokenId attributes when a description is not present', () => {
|
||||
expect(
|
||||
getCollectibleImageAlt({
|
||||
getNftImageAlt({
|
||||
name: 'Cool NFT',
|
||||
tokenId: '555',
|
||||
description: null,
|
||||
}),
|
||||
).toBe('Cool NFT 555');
|
||||
expect(
|
||||
getCollectibleImageAlt({
|
||||
getNftImageAlt({
|
||||
name: 'Cool NFT',
|
||||
tokenId: '555',
|
||||
}),
|
||||
|
@ -111,9 +111,9 @@ const t = (key) => {
|
||||
return 'Enable OpenSea API';
|
||||
case 'enableOpenSeaAPIDescription':
|
||||
return "Use OpenSea's API to fetch NFT data. NFT auto-detection relies on OpenSea's API, and will not be available when this is turned off.";
|
||||
case 'useCollectibleDetection':
|
||||
case 'useNftDetection':
|
||||
return 'Autodetect NFTs';
|
||||
case 'useCollectibleDetectionDescription':
|
||||
case 'useNftDetectionDescription':
|
||||
return 'Displaying NFTs media & data may expose your IP address to centralized servers. Third-party APIs (like OpenSea) are used to detect NFTs in your wallet. This exposes your account address with those services. Leave this disabled if you don’t want the app to pull data from those those services.';
|
||||
case 'about':
|
||||
return 'About';
|
||||
|
@ -217,7 +217,7 @@ export async function getAssetDetails(
|
||||
tokenAddress,
|
||||
currentUserAddress,
|
||||
transactionData,
|
||||
existingCollectibles,
|
||||
existingNfts,
|
||||
) {
|
||||
const tokenData = parseStandardTokenTransactionData(transactionData);
|
||||
if (!tokenData) {
|
||||
@ -234,18 +234,18 @@ export async function getAssetDetails(
|
||||
|
||||
let tokenDetails;
|
||||
|
||||
// if a tokenId is present check if there is a collectible in state matching the address/tokenId
|
||||
// if a tokenId is present check if there is an NFT in state matching the address/tokenId
|
||||
// and avoid unnecessary network requests to query token details we already have
|
||||
if (existingCollectibles?.length && tokenId) {
|
||||
const existingCollectible = existingCollectibles.find(
|
||||
if (existingNfts?.length && tokenId) {
|
||||
const existingNft = existingNfts.find(
|
||||
({ address, tokenId: _tokenId }) =>
|
||||
isEqualCaseInsensitive(tokenAddress, address) && _tokenId === tokenId,
|
||||
);
|
||||
|
||||
if (existingCollectible) {
|
||||
if (existingNft) {
|
||||
return {
|
||||
toAddress,
|
||||
...existingCollectible,
|
||||
...existingNft,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -277,7 +277,7 @@ export async function getAssetDetails(
|
||||
tokenId = undefined;
|
||||
}
|
||||
|
||||
// else if not a collectible already in state or standard === ERC20 return tokenDetails and tokenId
|
||||
// else if not an NFT already in state or standard === ERC20 return tokenDetails and tokenId
|
||||
return {
|
||||
tokenAmount,
|
||||
toAddress,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { getCollectibles, getTokens } from '../ducks/metamask/metamask';
|
||||
import { getNfts, getTokens } from '../ducks/metamask/metamask';
|
||||
import { getAssetDetails } from '../helpers/utils/token-util';
|
||||
import { hideLoadingIndication, showLoadingIndication } from '../store/actions';
|
||||
import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
|
||||
@ -11,7 +11,7 @@ import { useTokenTracker } from './useTokenTracker';
|
||||
export function useAssetDetails(tokenAddress, userAddress, transactionData) {
|
||||
const dispatch = useDispatch();
|
||||
// state selectors
|
||||
const collectibles = useSelector(getCollectibles);
|
||||
const nfts = useSelector(getNfts);
|
||||
const tokens = useSelector(getTokens, isEqual);
|
||||
const currentToken = tokens.find((token) =>
|
||||
isEqualCaseInsensitive(token.address, tokenAddress),
|
||||
@ -36,7 +36,7 @@ export function useAssetDetails(tokenAddress, userAddress, transactionData) {
|
||||
tokenAddress,
|
||||
userAddress,
|
||||
transactionData,
|
||||
collectibles,
|
||||
nfts,
|
||||
);
|
||||
setCurrentAsset(assetDetails);
|
||||
dispatch(hideLoadingIndication());
|
||||
@ -57,7 +57,7 @@ export function useAssetDetails(tokenAddress, userAddress, transactionData) {
|
||||
tokenAddress,
|
||||
userAddress,
|
||||
transactionData,
|
||||
collectibles,
|
||||
nfts,
|
||||
tokensWithBalances,
|
||||
prevTokenBalance,
|
||||
]);
|
||||
|
@ -1,79 +1,74 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
import {
|
||||
getCollectibles,
|
||||
getCollectibleContracts,
|
||||
} from '../ducks/metamask/metamask';
|
||||
import { getNfts, getNftContracts } from '../ducks/metamask/metamask';
|
||||
import { getCurrentChainId, getSelectedAddress } from '../selectors';
|
||||
import { usePrevious } from './usePrevious';
|
||||
|
||||
export function useCollectiblesCollections() {
|
||||
export function useNftsCollections() {
|
||||
const [collections, setCollections] = useState({});
|
||||
const [previouslyOwnedCollection, setPreviouslyOwnedCollection] = useState({
|
||||
collectionName: 'Previously Owned',
|
||||
collectibles: [],
|
||||
nfts: [],
|
||||
});
|
||||
const collectibles = useSelector(getCollectibles);
|
||||
const [collectiblesLoading, setCollectiblesLoading] = useState(
|
||||
() => collectibles?.length >= 0,
|
||||
);
|
||||
const nfts = useSelector(getNfts);
|
||||
const [nftsLoading, setNftsLoading] = useState(() => nfts?.length >= 0);
|
||||
const selectedAddress = useSelector(getSelectedAddress);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const collectibleContracts = useSelector(getCollectibleContracts);
|
||||
const prevCollectibles = usePrevious(collectibles);
|
||||
const nftContracts = useSelector(getNftContracts);
|
||||
const prevNfts = usePrevious(nfts);
|
||||
const prevChainId = usePrevious(chainId);
|
||||
const prevSelectedAddress = usePrevious(selectedAddress);
|
||||
useEffect(() => {
|
||||
const getCollections = () => {
|
||||
setCollectiblesLoading(true);
|
||||
setNftsLoading(true);
|
||||
if (selectedAddress === undefined || chainId === undefined) {
|
||||
return;
|
||||
}
|
||||
const newCollections = {};
|
||||
const newPreviouslyOwnedCollections = {
|
||||
collectionName: 'Previously Owned',
|
||||
collectibles: [],
|
||||
nfts: [],
|
||||
};
|
||||
|
||||
collectibles.forEach((collectible) => {
|
||||
if (collectible?.isCurrentlyOwned === false) {
|
||||
newPreviouslyOwnedCollections.collectibles.push(collectible);
|
||||
} else if (newCollections[collectible.address]) {
|
||||
newCollections[collectible.address].collectibles.push(collectible);
|
||||
nfts.forEach((nft) => {
|
||||
if (nft?.isCurrentlyOwned === false) {
|
||||
newPreviouslyOwnedCollections.nfts.push(nft);
|
||||
} else if (newCollections[nft.address]) {
|
||||
newCollections[nft.address].nfts.push(nft);
|
||||
} else {
|
||||
const collectionContract = collectibleContracts.find(
|
||||
({ address }) => address === collectible.address,
|
||||
const collectionContract = nftContracts.find(
|
||||
({ address }) => address === nft.address,
|
||||
);
|
||||
newCollections[collectible.address] = {
|
||||
collectionName: collectionContract?.name || collectible.name,
|
||||
collectionImage: collectionContract?.logo || collectible.image,
|
||||
collectibles: [collectible],
|
||||
newCollections[nft.address] = {
|
||||
collectionName: collectionContract?.name || nft.name,
|
||||
collectionImage: collectionContract?.logo || nft.image,
|
||||
nfts: [nft],
|
||||
};
|
||||
}
|
||||
});
|
||||
setCollections(newCollections);
|
||||
setPreviouslyOwnedCollection(newPreviouslyOwnedCollections);
|
||||
setCollectiblesLoading(false);
|
||||
setNftsLoading(false);
|
||||
};
|
||||
|
||||
if (
|
||||
!isEqual(prevCollectibles, collectibles) ||
|
||||
!isEqual(prevNfts, nfts) ||
|
||||
!isEqual(prevSelectedAddress, selectedAddress) ||
|
||||
!isEqual(prevChainId, chainId)
|
||||
) {
|
||||
getCollections();
|
||||
}
|
||||
}, [
|
||||
collectibles,
|
||||
prevCollectibles,
|
||||
collectibleContracts,
|
||||
setCollectiblesLoading,
|
||||
nfts,
|
||||
prevNfts,
|
||||
nftContracts,
|
||||
setNftsLoading,
|
||||
chainId,
|
||||
prevChainId,
|
||||
selectedAddress,
|
||||
prevSelectedAddress,
|
||||
]);
|
||||
|
||||
return { collectiblesLoading, collections, previouslyOwnedCollection };
|
||||
return { nftsLoading, collections, previouslyOwnedCollection };
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
PENDING_STATUS_HASH,
|
||||
TOKEN_CATEGORY_HASH,
|
||||
} from '../helpers/constants/transactions';
|
||||
import { getCollectibles, getTokens } from '../ducks/metamask/metamask';
|
||||
import { getNfts, getTokens } from '../ducks/metamask/metamask';
|
||||
import {
|
||||
TransactionType,
|
||||
TransactionGroupCategory,
|
||||
@ -92,7 +92,7 @@ export function useTransactionDisplayData(transactionGroup) {
|
||||
const dispatch = useDispatch();
|
||||
const currentAsset = useCurrentAsset();
|
||||
const knownTokens = useSelector(getTokens);
|
||||
const knownCollectibles = useSelector(getCollectibles);
|
||||
const knownNfts = useSelector(getNfts);
|
||||
const t = useI18nContext();
|
||||
|
||||
const { initialTransaction, primaryTransaction } = transactionGroup;
|
||||
@ -145,9 +145,9 @@ export function useTransactionDisplayData(transactionGroup) {
|
||||
const transactionDataTokenId =
|
||||
getTokenIdParam(tokenData) ?? getTokenValueParam(tokenData);
|
||||
|
||||
const collectible =
|
||||
const nft =
|
||||
isTokenCategory &&
|
||||
knownCollectibles.find(
|
||||
knownNfts.find(
|
||||
({ address, tokenId }) =>
|
||||
isEqualCaseInsensitive(address, recipientAddress) &&
|
||||
tokenId === transactionDataTokenId,
|
||||
@ -258,7 +258,7 @@ export function useTransactionDisplayData(transactionGroup) {
|
||||
) {
|
||||
category = TransactionGroupCategory.send;
|
||||
title = t('sendSpecifiedTokens', [
|
||||
token?.symbol || collectible?.name || t('token'),
|
||||
token?.symbol || nft?.name || t('token'),
|
||||
]);
|
||||
recipientAddress = getTokenAddressParam(tokenData);
|
||||
subtitle = t('toAddress', [shortenAddress(recipientAddress)]);
|
||||
|
@ -18,8 +18,8 @@ import {
|
||||
addNftVerifyOwnership,
|
||||
getTokenStandardAndDetails,
|
||||
ignoreTokens,
|
||||
setNewCollectibleAddedMessage,
|
||||
updateCollectibleDropDownState,
|
||||
setNewNftAddedMessage,
|
||||
updateNftDropDownState,
|
||||
} from '../../store/actions';
|
||||
import FormField from '../../components/ui/form-field';
|
||||
import {
|
||||
@ -28,69 +28,69 @@ import {
|
||||
getSelectedAddress,
|
||||
getUseNftDetection,
|
||||
} from '../../selectors';
|
||||
import { getCollectiblesDropdownState } from '../../ducks/metamask/metamask';
|
||||
import CollectiblesDetectionNotice from '../../components/app/nfts-detection-notice';
|
||||
import { getNftsDropdownState } from '../../ducks/metamask/metamask';
|
||||
import NftsDetectionNotice from '../../components/app/nfts-detection-notice';
|
||||
import { MetaMetricsContext } from '../../contexts/metametrics';
|
||||
import { AssetType } from '../../../shared/constants/transaction';
|
||||
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
|
||||
|
||||
export default function AddCollectible() {
|
||||
export default function AddNft() {
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const useNftDetection = useSelector(getUseNftDetection);
|
||||
const isMainnet = useSelector(getIsMainnet);
|
||||
const collectiblesDropdownState = useSelector(getCollectiblesDropdownState);
|
||||
const nftsDropdownState = useSelector(getNftsDropdownState);
|
||||
const selectedAddress = useSelector(getSelectedAddress);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const addressEnteredOnImportTokensPage =
|
||||
history?.location?.state?.addressEnteredOnImportTokensPage;
|
||||
const contractAddressToConvertFromTokenToCollectible =
|
||||
const contractAddressToConvertFromTokenToNft =
|
||||
history?.location?.state?.tokenAddress;
|
||||
|
||||
const [collectibleAddress, setCollectibleAddress] = useState(
|
||||
const [nftAddress, setNftAddress] = useState(
|
||||
addressEnteredOnImportTokensPage ??
|
||||
contractAddressToConvertFromTokenToCollectible ??
|
||||
contractAddressToConvertFromTokenToNft ??
|
||||
'',
|
||||
);
|
||||
const [tokenId, setTokenId] = useState('');
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
const [collectibleAddFailed, setCollectibleAddFailed] = useState(false);
|
||||
const [nftAddFailed, setNftAddFailed] = useState(false);
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
|
||||
const handleAddCollectible = async () => {
|
||||
const handleAddNft = async () => {
|
||||
try {
|
||||
await dispatch(addNftVerifyOwnership(collectibleAddress, tokenId));
|
||||
const newCollectibleDropdownState = {
|
||||
...collectiblesDropdownState,
|
||||
await dispatch(addNftVerifyOwnership(nftAddress, tokenId));
|
||||
const newNftDropdownState = {
|
||||
...nftsDropdownState,
|
||||
[selectedAddress]: {
|
||||
...collectiblesDropdownState?.[selectedAddress],
|
||||
...nftsDropdownState?.[selectedAddress],
|
||||
[chainId]: {
|
||||
...collectiblesDropdownState?.[selectedAddress]?.[chainId],
|
||||
[collectibleAddress]: true,
|
||||
...nftsDropdownState?.[selectedAddress]?.[chainId],
|
||||
[nftAddress]: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(updateCollectibleDropDownState(newCollectibleDropdownState));
|
||||
dispatch(updateNftDropDownState(newNftDropdownState));
|
||||
} catch (error) {
|
||||
const { message } = error;
|
||||
dispatch(setNewCollectibleAddedMessage(message));
|
||||
setCollectibleAddFailed(true);
|
||||
dispatch(setNewNftAddedMessage(message));
|
||||
setNftAddFailed(true);
|
||||
return;
|
||||
}
|
||||
if (contractAddressToConvertFromTokenToCollectible) {
|
||||
if (contractAddressToConvertFromTokenToNft) {
|
||||
await dispatch(
|
||||
ignoreTokens({
|
||||
tokensToIgnore: contractAddressToConvertFromTokenToCollectible,
|
||||
tokensToIgnore: contractAddressToConvertFromTokenToNft,
|
||||
dontShowLoadingIndicator: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
dispatch(setNewCollectibleAddedMessage('success'));
|
||||
dispatch(setNewNftAddedMessage('success'));
|
||||
|
||||
const tokenDetails = await getTokenStandardAndDetails(
|
||||
collectibleAddress,
|
||||
nftAddress,
|
||||
null,
|
||||
tokenId.toString(),
|
||||
);
|
||||
@ -99,7 +99,7 @@ export default function AddCollectible() {
|
||||
event: EVENT_NAMES.TOKEN_ADDED,
|
||||
category: 'Wallet',
|
||||
sensitiveProperties: {
|
||||
token_contract_address: collectibleAddress,
|
||||
token_contract_address: nftAddress,
|
||||
token_symbol: tokenDetails?.symbol,
|
||||
tokenId: tokenId.toString(),
|
||||
asset_type: AssetType.NFT,
|
||||
@ -113,13 +113,11 @@ export default function AddCollectible() {
|
||||
|
||||
const validateAndSetAddress = (val) => {
|
||||
setDisabled(!isValidHexAddress(val) || !tokenId);
|
||||
setCollectibleAddress(val);
|
||||
setNftAddress(val);
|
||||
};
|
||||
|
||||
const validateAndSetTokenId = (val) => {
|
||||
setDisabled(
|
||||
!isValidHexAddress(collectibleAddress) || !val || isNaN(Number(val)),
|
||||
);
|
||||
setDisabled(!isValidHexAddress(nftAddress) || !val || isNaN(Number(val)));
|
||||
setTokenId(val);
|
||||
};
|
||||
|
||||
@ -127,7 +125,7 @@ export default function AddCollectible() {
|
||||
<PageContainer
|
||||
title={t('importNFT')}
|
||||
onSubmit={() => {
|
||||
handleAddCollectible();
|
||||
handleAddNft();
|
||||
}}
|
||||
submitText={t('add')}
|
||||
onCancel={() => {
|
||||
@ -139,10 +137,8 @@ export default function AddCollectible() {
|
||||
disabled={disabled}
|
||||
contentComponent={
|
||||
<Box>
|
||||
{isMainnet && !useNftDetection ? (
|
||||
<CollectiblesDetectionNotice />
|
||||
) : null}
|
||||
{collectibleAddFailed && (
|
||||
{isMainnet && !useNftDetection ? <NftsDetectionNotice /> : null}
|
||||
{nftAddFailed && (
|
||||
<Box marginLeft={4} marginRight={4}>
|
||||
<ActionableMessage
|
||||
type="danger"
|
||||
@ -158,9 +154,9 @@ export default function AddCollectible() {
|
||||
{t('nftAddFailedMessage')}
|
||||
</Typography>
|
||||
<button
|
||||
className="fas fa-times add-collectible__close"
|
||||
className="fas fa-times add-nft__close"
|
||||
title={t('close')}
|
||||
onClick={() => setCollectibleAddFailed(false)}
|
||||
onClick={() => setNftAddFailed(false)}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
@ -172,10 +168,10 @@ export default function AddCollectible() {
|
||||
dataTestId="address"
|
||||
titleText={t('address')}
|
||||
placeholder="0x..."
|
||||
value={collectibleAddress}
|
||||
value={nftAddress}
|
||||
onChange={(val) => {
|
||||
validateAndSetAddress(val);
|
||||
setCollectibleAddFailed(false);
|
||||
setNftAddFailed(false);
|
||||
}}
|
||||
tooltipText={t('importNFTAddressToolTip')}
|
||||
autoFocus
|
||||
@ -187,7 +183,7 @@ export default function AddCollectible() {
|
||||
value={tokenId}
|
||||
onChange={(val) => {
|
||||
validateAndSetTokenId(val);
|
||||
setCollectibleAddFailed(false);
|
||||
setNftAddFailed(false);
|
||||
}}
|
||||
tooltipText={t('importNFTTokenIdToolTip')}
|
||||
/>
|
||||
|
@ -9,10 +9,10 @@ import { DEFAULT_ROUTE } from '../../helpers/constants/routes';
|
||||
import {
|
||||
addNftVerifyOwnership,
|
||||
ignoreTokens,
|
||||
setNewCollectibleAddedMessage,
|
||||
updateCollectibleDropDownState,
|
||||
setNewNftAddedMessage,
|
||||
updateNftDropDownState,
|
||||
} from '../../store/actions';
|
||||
import AddCollectible from '.';
|
||||
import AddNft from '.';
|
||||
|
||||
const VALID_ADDRESS = '0x312BE6a98441F9F6e3F6246B13CA19701e0AC3B9';
|
||||
const INVALID_ADDRESS = 'aoinsafasdfa';
|
||||
@ -39,15 +39,15 @@ jest.mock('../../store/actions.ts', () => ({
|
||||
.mockReturnValue(jest.fn().mockResolvedValue()),
|
||||
getTokenStandardAndDetails: jest.fn().mockResolvedValue(),
|
||||
ignoreTokens: jest.fn().mockReturnValue(jest.fn().mockResolvedValue()),
|
||||
setNewCollectibleAddedMessage: jest
|
||||
setNewNftAddedMessage: jest
|
||||
.fn()
|
||||
.mockReturnValue(jest.fn().mockResolvedValue()),
|
||||
updateCollectibleDropDownState: jest
|
||||
updateNftDropDownState: jest
|
||||
.fn()
|
||||
.mockReturnValue(jest.fn().mockResolvedValue()),
|
||||
}));
|
||||
|
||||
describe('AddCollectible', () => {
|
||||
describe('AddNft', () => {
|
||||
const store = configureMockStore([thunk])(mockState);
|
||||
|
||||
beforeEach(() => {
|
||||
@ -55,10 +55,7 @@ describe('AddCollectible', () => {
|
||||
});
|
||||
|
||||
it('should enable the "Add" button when valid entries are input into both Address and TokenId fields', () => {
|
||||
const { getByTestId, getByText } = renderWithProvider(
|
||||
<AddCollectible />,
|
||||
store,
|
||||
);
|
||||
const { getByTestId, getByText } = renderWithProvider(<AddNft />, store);
|
||||
expect(getByText('Add')).not.toBeEnabled();
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
target: { value: VALID_ADDRESS },
|
||||
@ -70,10 +67,7 @@ describe('AddCollectible', () => {
|
||||
});
|
||||
|
||||
it('should not enable the "Add" button when an invalid entry is input into one or both Address and TokenId fields', () => {
|
||||
const { getByTestId, getByText } = renderWithProvider(
|
||||
<AddCollectible />,
|
||||
store,
|
||||
);
|
||||
const { getByTestId, getByText } = renderWithProvider(<AddNft />, store);
|
||||
expect(getByText('Add')).not.toBeEnabled();
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
target: { value: INVALID_ADDRESS },
|
||||
@ -92,11 +86,8 @@ describe('AddCollectible', () => {
|
||||
expect(getByText('Add')).not.toBeEnabled();
|
||||
});
|
||||
|
||||
it('should call addNftVerifyOwnership, updateCollectibleDropDownState, setNewCollectibleAddedMessage, and ignoreTokens action with correct values (tokenId should not be in scientific notation)', async () => {
|
||||
const { getByTestId, getByText } = renderWithProvider(
|
||||
<AddCollectible />,
|
||||
store,
|
||||
);
|
||||
it('should call addNftVerifyOwnership, updateNftDropDownState, setNewNftAddedMessage, and ignoreTokens action with correct values (tokenId should not be in scientific notation)', async () => {
|
||||
const { getByTestId, getByText } = renderWithProvider(<AddNft />, store);
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
target: { value: VALID_ADDRESS },
|
||||
});
|
||||
@ -113,7 +104,7 @@ describe('AddCollectible', () => {
|
||||
'9007199254740992',
|
||||
);
|
||||
|
||||
expect(updateCollectibleDropDownState).toHaveBeenCalledWith({
|
||||
expect(updateNftDropDownState).toHaveBeenCalledWith({
|
||||
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
|
||||
'0x5': {
|
||||
'0x312BE6a98441F9F6e3F6246B13CA19701e0AC3B9': true,
|
||||
@ -122,7 +113,7 @@ describe('AddCollectible', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(setNewCollectibleAddedMessage).toHaveBeenCalledWith('success');
|
||||
expect(setNewNftAddedMessage).toHaveBeenCalledWith('success');
|
||||
|
||||
expect(ignoreTokens).toHaveBeenCalledWith({
|
||||
dontShowLoadingIndicator: true,
|
||||
@ -137,7 +128,7 @@ describe('AddCollectible', () => {
|
||||
);
|
||||
|
||||
const { getByTestId, getByText, queryByTitle } = renderWithProvider(
|
||||
<AddCollectible />,
|
||||
<AddNft />,
|
||||
store,
|
||||
);
|
||||
fireEvent.change(getByTestId('address'), {
|
||||
@ -151,16 +142,16 @@ describe('AddCollectible', () => {
|
||||
fireEvent.click(getByText('Add'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setNewCollectibleAddedMessage).toHaveBeenCalledWith('error');
|
||||
expect(setNewNftAddedMessage).toHaveBeenCalledWith('error');
|
||||
});
|
||||
|
||||
const addCollectibleClose = queryByTitle('Close');
|
||||
const addNftClose = queryByTitle('Close');
|
||||
|
||||
fireEvent.click(addCollectibleClose);
|
||||
fireEvent.click(addNftClose);
|
||||
});
|
||||
|
||||
it('should route to default route when cancel button is clicked', () => {
|
||||
const { queryByTestId } = renderWithProvider(<AddCollectible />, store);
|
||||
const { queryByTestId } = renderWithProvider(<AddNft />, store);
|
||||
|
||||
const cancelButton = queryByTestId('page-container-footer-cancel');
|
||||
fireEvent.click(cancelButton);
|
||||
@ -169,7 +160,7 @@ describe('AddCollectible', () => {
|
||||
});
|
||||
|
||||
it('should route to default route when close button is clicked', () => {
|
||||
const { queryByLabelText } = renderWithProvider(<AddCollectible />, store);
|
||||
const { queryByLabelText } = renderWithProvider(<AddNft />, store);
|
||||
|
||||
const closeButton = queryByLabelText('close');
|
||||
fireEvent.click(closeButton);
|
||||
|
@ -1,4 +1,4 @@
|
||||
.add-collectible {
|
||||
.add-nft {
|
||||
&__close {
|
||||
color: var(--color-icon-default);
|
||||
background: none;
|
||||
|
@ -2,8 +2,8 @@ import React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Redirect, useParams } from 'react-router-dom';
|
||||
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
||||
import CollectibleDetails from '../../components/app/nft-details/nft-details';
|
||||
import { getCollectibles, getTokens } from '../../ducks/metamask/metamask';
|
||||
import NftDetails from '../../components/app/nft-details/nft-details';
|
||||
import { getNfts, getTokens } from '../../ducks/metamask/metamask';
|
||||
import { DEFAULT_ROUTE } from '../../helpers/constants/routes';
|
||||
|
||||
import NativeAsset from './components/native-asset';
|
||||
@ -12,14 +12,14 @@ import TokenAsset from './components/token-asset';
|
||||
const Asset = () => {
|
||||
const nativeCurrency = useSelector((state) => state.metamask.nativeCurrency);
|
||||
const tokens = useSelector(getTokens);
|
||||
const collectibles = useSelector(getCollectibles);
|
||||
const nfts = useSelector(getNfts);
|
||||
const { asset, id } = useParams();
|
||||
|
||||
const token = tokens.find(({ address }) =>
|
||||
isEqualCaseInsensitive(address, asset),
|
||||
);
|
||||
|
||||
const collectible = collectibles.find(
|
||||
const nft = nfts.find(
|
||||
({ address, tokenId }) =>
|
||||
isEqualCaseInsensitive(address, asset) && id === tokenId.toString(),
|
||||
);
|
||||
@ -30,8 +30,8 @@ const Asset = () => {
|
||||
}, []);
|
||||
|
||||
let content;
|
||||
if (collectible) {
|
||||
content = <CollectibleDetails collectible={collectible} />;
|
||||
if (nft) {
|
||||
content = <NftDetails nft={nft} />;
|
||||
} else if (token) {
|
||||
content = <TokenAsset token={token} />;
|
||||
} else if (asset === nativeCurrency) {
|
||||
|
@ -184,7 +184,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
customTxParamsData,
|
||||
);
|
||||
|
||||
const isCollectibleTransfer = Boolean(
|
||||
const isNftTransfer = Boolean(
|
||||
allNftContracts?.[selectedAddress]?.[chainId]?.find((contract) => {
|
||||
return isEqualCaseInsensitive(contract.address, fullTxData.txParams.to);
|
||||
}),
|
||||
@ -235,7 +235,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
useNonceField: getUseNonceField(state),
|
||||
customNonceValue,
|
||||
insufficientBalance,
|
||||
hideSubtitle: !getShouldShowFiat(state) && !isCollectibleTransfer,
|
||||
hideSubtitle: !getShouldShowFiat(state) && !isNftTransfer,
|
||||
hideFiatConversion: !getShouldShowFiat(state),
|
||||
type,
|
||||
nextNonce,
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
CONTEXT_PROPS,
|
||||
} from '../../../shared/constants/metametrics';
|
||||
import AssetList from '../../components/app/asset-list';
|
||||
import CollectiblesTab from '../../components/app/nfts-tab';
|
||||
import NftsTab from '../../components/app/nfts-tab';
|
||||
import HomeNotification from '../../components/app/home-notification';
|
||||
import MultipleNotifications from '../../components/app/multiple-notifications';
|
||||
import TransactionList from '../../components/app/transaction-list';
|
||||
@ -47,7 +47,7 @@ import {
|
||||
BUILD_QUOTE_ROUTE,
|
||||
VIEW_QUOTE_ROUTE,
|
||||
CONFIRMATION_V_NEXT_ROUTE,
|
||||
ADD_COLLECTIBLE_ROUTE,
|
||||
ADD_NFT_ROUTE,
|
||||
ONBOARDING_SECURE_YOUR_WALLET_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
import ZENDESK_URLS from '../../helpers/constants/zendesk-url';
|
||||
@ -140,9 +140,9 @@ export default class Home extends PureComponent {
|
||||
// eslint-disable-next-line react/no-unused-prop-types
|
||||
isSigningQRHardwareTransaction: PropTypes.bool.isRequired,
|
||||
newNftAddedMessage: PropTypes.string,
|
||||
setNewCollectibleAddedMessage: PropTypes.func.isRequired,
|
||||
setNewNftAddedMessage: PropTypes.func.isRequired,
|
||||
removeNftMessage: PropTypes.string,
|
||||
setRemoveCollectibleMessage: PropTypes.func.isRequired,
|
||||
setRemoveNftMessage: PropTypes.func.isRequired,
|
||||
closeNotificationPopup: PropTypes.func.isRequired,
|
||||
newTokensImported: PropTypes.string,
|
||||
setNewTokensImported: PropTypes.func.isRequired,
|
||||
@ -276,9 +276,9 @@ export default class Home extends PureComponent {
|
||||
newNetworkAdded,
|
||||
setNewNetworkAdded,
|
||||
newNftAddedMessage,
|
||||
setNewCollectibleAddedMessage,
|
||||
setNewNftAddedMessage,
|
||||
removeNftMessage,
|
||||
setRemoveCollectibleMessage,
|
||||
setRemoveNftMessage,
|
||||
newTokensImported,
|
||||
setNewTokensImported,
|
||||
newCustomNetworkAdded,
|
||||
@ -287,8 +287,8 @@ export default class Home extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
const onAutoHide = () => {
|
||||
setNewCollectibleAddedMessage('');
|
||||
setRemoveCollectibleMessage('');
|
||||
setNewNftAddedMessage('');
|
||||
setRemoveNftMessage('');
|
||||
};
|
||||
|
||||
const autoHideDelay = 5 * SECOND;
|
||||
@ -763,9 +763,9 @@ export default class Home extends PureComponent {
|
||||
name={this.context.t('nfts')}
|
||||
tabKey="nfts"
|
||||
>
|
||||
<CollectiblesTab
|
||||
<NftsTab
|
||||
onAddNFT={() => {
|
||||
history.push(ADD_COLLECTIBLE_ROUTE);
|
||||
history.push(ADD_NFT_ROUTE);
|
||||
}}
|
||||
/>
|
||||
</Tab>
|
||||
|
@ -21,11 +21,11 @@ import {
|
||||
getNewNetworkAdded,
|
||||
hasUnsignedQRHardwareTransaction,
|
||||
hasUnsignedQRHardwareMessage,
|
||||
getNewCollectibleAddedMessage,
|
||||
getNewNftAddedMessage,
|
||||
getNewTokensImported,
|
||||
getShowPortfolioTooltip,
|
||||
getShouldShowSeedPhraseReminder,
|
||||
getRemoveCollectibleMessage,
|
||||
getRemoveNftMessage,
|
||||
} from '../../selectors';
|
||||
|
||||
import {
|
||||
@ -39,8 +39,8 @@ import {
|
||||
setRecoveryPhraseReminderLastShown,
|
||||
setOutdatedBrowserWarningLastShown,
|
||||
setNewNetworkAdded,
|
||||
setNewCollectibleAddedMessage,
|
||||
setRemoveCollectibleMessage,
|
||||
setNewNftAddedMessage,
|
||||
setRemoveNftMessage,
|
||||
setNewTokensImported,
|
||||
setRpcTarget,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
@ -151,8 +151,8 @@ const mapStateToProps = (state) => {
|
||||
seedPhraseBackedUp,
|
||||
newNetworkAdded: getNewNetworkAdded(state),
|
||||
isSigningQRHardwareTransaction,
|
||||
newNftAddedMessage: getNewCollectibleAddedMessage(state),
|
||||
removeNftMessage: getRemoveCollectibleMessage(state),
|
||||
newNftAddedMessage: getNewNftAddedMessage(state),
|
||||
removeNftMessage: getRemoveNftMessage(state),
|
||||
newTokensImported: getNewTokensImported(state),
|
||||
newCustomNetworkAdded: appState.newCustomNetworkAdded,
|
||||
onboardedInThisUISession: appState.onboardedInThisUISession,
|
||||
@ -184,11 +184,11 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
console.log({ newNetwork });
|
||||
dispatch(setNewNetworkAdded(newNetwork));
|
||||
},
|
||||
setNewCollectibleAddedMessage: (message) => {
|
||||
dispatch(setNewCollectibleAddedMessage(message));
|
||||
setNewNftAddedMessage: (message) => {
|
||||
dispatch(setNewNftAddedMessage(message));
|
||||
},
|
||||
setRemoveCollectibleMessage: (message) => {
|
||||
dispatch(setRemoveCollectibleMessage(message));
|
||||
setRemoveNftMessage: (message) => {
|
||||
dispatch(setRemoveNftMessage(message));
|
||||
},
|
||||
setNewTokensImported: (newTokens) => {
|
||||
dispatch(setNewTokensImported(newTokens));
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from '../../helpers/utils/util';
|
||||
import { tokenInfoGetter } from '../../helpers/utils/token-util';
|
||||
import {
|
||||
ADD_COLLECTIBLE_ROUTE,
|
||||
ADD_NFT_ROUTE,
|
||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||
SECURITY_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
@ -133,7 +133,7 @@ class ImportToken extends Component {
|
||||
customAddressError: null,
|
||||
customSymbolError: null,
|
||||
customDecimalsError: null,
|
||||
collectibleAddressError: null,
|
||||
nftAddressError: null,
|
||||
forceEditSymbol: false,
|
||||
symbolAutoFilled: false,
|
||||
decimalAutoFilled: false,
|
||||
@ -198,7 +198,7 @@ class ImportToken extends Component {
|
||||
customAddressError,
|
||||
customSymbolError,
|
||||
customDecimalsError,
|
||||
collectibleAddressError,
|
||||
nftAddressError,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
@ -206,7 +206,7 @@ class ImportToken extends Component {
|
||||
customAddressError ||
|
||||
customSymbolError ||
|
||||
customDecimalsError ||
|
||||
collectibleAddressError
|
||||
nftAddressError
|
||||
);
|
||||
}
|
||||
|
||||
@ -265,7 +265,7 @@ class ImportToken extends Component {
|
||||
this.setState({
|
||||
customAddress,
|
||||
customAddressError: null,
|
||||
collectibleAddressError: null,
|
||||
nftAddressError: null,
|
||||
tokenSelectorError: null,
|
||||
symbolAutoFilled: false,
|
||||
decimalAutoFilled: false,
|
||||
@ -312,18 +312,18 @@ class ImportToken extends Component {
|
||||
case process.env.NFTS_V1 &&
|
||||
(standard === 'ERC1155' || standard === 'ERC721'):
|
||||
this.setState({
|
||||
collectibleAddressError: this.context.t('nftAddressError', [
|
||||
nftAddressError: this.context.t('nftAddressError', [
|
||||
<a
|
||||
className="import-token__collectible-address-error-link"
|
||||
className="import-token__nft-address-error-link"
|
||||
onClick={() =>
|
||||
this.props.history.push({
|
||||
pathname: ADD_COLLECTIBLE_ROUTE,
|
||||
pathname: ADD_NFT_ROUTE,
|
||||
state: {
|
||||
addressEnteredOnImportTokensPage: this.state.customAddress,
|
||||
},
|
||||
})
|
||||
}
|
||||
key="collectibleAddressError"
|
||||
key="nftAddressError"
|
||||
>
|
||||
{this.context.t('importNFTPage')}
|
||||
</a>,
|
||||
@ -406,7 +406,7 @@ class ImportToken extends Component {
|
||||
symbolAutoFilled,
|
||||
decimalAutoFilled,
|
||||
mainnetTokenWarning,
|
||||
collectibleAddressError,
|
||||
nftAddressError,
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
@ -493,9 +493,7 @@ class ImportToken extends Component {
|
||||
type="text"
|
||||
value={customAddress}
|
||||
onChange={(e) => this.handleCustomAddressChange(e.target.value)}
|
||||
error={
|
||||
customAddressError || mainnetTokenWarning || collectibleAddressError
|
||||
}
|
||||
error={customAddressError || mainnetTokenWarning || nftAddressError}
|
||||
fullWidth
|
||||
autoFocus
|
||||
margin="normal"
|
||||
|
@ -68,7 +68,7 @@
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&__collectible-address-error-link {
|
||||
&__nft-address-error-link {
|
||||
color: var(--color-primary-default);
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -17,7 +17,7 @@ import RestoreVaultPage from '../keychains/restore-vault';
|
||||
import RevealSeedConfirmation from '../keychains/reveal-seed';
|
||||
import MobileSyncPage from '../mobile-sync';
|
||||
import ImportTokenPage from '../import-token';
|
||||
import AddCollectiblePage from '../add-nft';
|
||||
import AddNftPage from '../add-nft';
|
||||
import ConfirmImportTokenPage from '../confirm-import-token';
|
||||
import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token';
|
||||
import CreateAccountPage from '../create-account';
|
||||
@ -57,7 +57,7 @@ import {
|
||||
CONFIRMATION_V_NEXT_ROUTE,
|
||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||
ONBOARDING_ROUTE,
|
||||
ADD_COLLECTIBLE_ROUTE,
|
||||
ADD_NFT_ROUTE,
|
||||
ONBOARDING_UNLOCK_ROUTE,
|
||||
TOKEN_DETAILS,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
@ -217,11 +217,7 @@ export default class Routes extends Component {
|
||||
exact
|
||||
/>
|
||||
{process.env.NFTS_V1 ? (
|
||||
<Authenticated
|
||||
path={ADD_COLLECTIBLE_ROUTE}
|
||||
component={AddCollectiblePage}
|
||||
exact
|
||||
/>
|
||||
<Authenticated path={ADD_NFT_ROUTE} component={AddNftPage} exact />
|
||||
) : null}
|
||||
<Authenticated
|
||||
path={CONFIRM_IMPORT_TOKEN_ROUTE}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SendAmountRow Component render Collectible Asset Type should match snapshot for token collectible type 1`] = `<div />`;
|
||||
exports[`SendAmountRow Component render NFT Asset Type should match snapshot for token NFT type 1`] = `<div />`;
|
||||
|
||||
exports[`SendAmountRow Component render Native Asset Type should match snapshot for native asset type 1`] = `
|
||||
<div>
|
||||
|
@ -62,9 +62,9 @@ describe('SendAmountRow Component', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Collectible Asset Type', () => {
|
||||
it('should match snapshot for token collectible type', () => {
|
||||
const collectibleState = {
|
||||
describe('NFT Asset Type', () => {
|
||||
it('should match snapshot for token NFT type', () => {
|
||||
const nftState = {
|
||||
...mockSendState,
|
||||
send: {
|
||||
currentTransactionUUID: '1-tx',
|
||||
@ -81,7 +81,7 @@ describe('SendAmountRow Component', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = configureMockStore([thunk])(collectibleState);
|
||||
const mockStore = configureMockStore([thunk])(nftState);
|
||||
|
||||
const { container } = renderWithProvider(<SendAmountRow />, mockStore);
|
||||
|
||||
|
@ -29,7 +29,7 @@ export default class SendAssetRow extends Component {
|
||||
updateSendAsset: PropTypes.func.isRequired,
|
||||
nativeCurrency: PropTypes.string,
|
||||
nativeCurrencyImage: PropTypes.string,
|
||||
collectibles: PropTypes.arrayOf(
|
||||
nfts: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
address: PropTypes.string.isRequired,
|
||||
tokenId: PropTypes.string.isRequired,
|
||||
@ -62,17 +62,15 @@ export default class SendAssetRow extends Component {
|
||||
state = {
|
||||
isShowingDropdown: false,
|
||||
sendableTokens: [],
|
||||
sendableCollectibles: [],
|
||||
sendableNfts: [],
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
const sendableTokens = this.props.tokens.filter((token) => !token.isERC721);
|
||||
const sendableCollectibles = this.props.collectibles.filter(
|
||||
(collectible) =>
|
||||
collectible.isCurrentlyOwned &&
|
||||
collectible.standard === TokenStandard.ERC721,
|
||||
const sendableNfts = this.props.nfts.filter(
|
||||
(nft) => nft.isCurrentlyOwned && nft.standard === TokenStandard.ERC721,
|
||||
);
|
||||
this.setState({ sendableTokens, sendableCollectibles });
|
||||
this.setState({ sendableTokens, sendableNfts });
|
||||
}
|
||||
|
||||
openDropdown = () => this.setState({ isShowingDropdown: true });
|
||||
@ -127,8 +125,7 @@ export default class SendAssetRow extends Component {
|
||||
>
|
||||
{this.renderSendAsset()}
|
||||
</div>
|
||||
{[...this.state.sendableTokens, ...this.state.sendableCollectibles]
|
||||
.length > 0
|
||||
{[...this.state.sendableTokens, ...this.state.sendableNfts].length > 0
|
||||
? this.renderAssetDropdown()
|
||||
: null}
|
||||
</div>
|
||||
@ -140,7 +137,7 @@ export default class SendAssetRow extends Component {
|
||||
const {
|
||||
sendAsset: { details, type },
|
||||
tokens,
|
||||
collectibles,
|
||||
nfts,
|
||||
} = this.props;
|
||||
|
||||
if (type === AssetType.token) {
|
||||
@ -151,13 +148,13 @@ export default class SendAssetRow extends Component {
|
||||
return this.renderToken(token);
|
||||
}
|
||||
} else if (type === AssetType.NFT) {
|
||||
const collectible = collectibles.find(
|
||||
const nft = nfts.find(
|
||||
({ address, tokenId }) =>
|
||||
isEqualCaseInsensitive(address, details.address) &&
|
||||
tokenId === details.tokenId,
|
||||
);
|
||||
if (collectible) {
|
||||
return this.renderCollectible(collectible);
|
||||
if (nft) {
|
||||
return this.renderNft(nft);
|
||||
}
|
||||
}
|
||||
return this.renderNativeCurrency();
|
||||
@ -177,9 +174,7 @@ export default class SendAssetRow extends Component {
|
||||
clickHandler={(token) => this.selectToken(AssetType.token, token)}
|
||||
/>
|
||||
|
||||
{this.state.sendableCollectibles.map((collectible) =>
|
||||
this.renderCollectible(collectible, true),
|
||||
)}
|
||||
{this.state.sendableNfts.map((nft) => this.renderNft(nft, true))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -191,13 +186,13 @@ export default class SendAssetRow extends Component {
|
||||
const { accounts, selectedAddress, nativeCurrency, nativeCurrencyImage } =
|
||||
this.props;
|
||||
|
||||
const { sendableTokens, sendableCollectibles } = this.state;
|
||||
const { sendableTokens, sendableNfts } = this.state;
|
||||
|
||||
const balanceValue = accounts[selectedAddress]
|
||||
? accounts[selectedAddress].balance
|
||||
: '';
|
||||
|
||||
const sendableAssets = [...sendableTokens, ...sendableCollectibles];
|
||||
const sendableAssets = [...sendableTokens, ...sendableNfts];
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
@ -264,24 +259,24 @@ export default class SendAssetRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderCollectible(collectible, insideDropdown = false) {
|
||||
const { address, name, image, tokenId } = collectible;
|
||||
renderNft(nft, insideDropdown = false) {
|
||||
const { address, name, image, tokenId } = nft;
|
||||
const { t } = this.context;
|
||||
const collectibleCollection = this.props.collections.find(
|
||||
const nftCollection = this.props.collections.find(
|
||||
(collection) => collection.address === address,
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={address}
|
||||
className="send-v2__asset-dropdown__asset"
|
||||
onClick={() => this.selectToken(AssetType.NFT, collectible)}
|
||||
onClick={() => this.selectToken(AssetType.NFT, nft)}
|
||||
>
|
||||
<div className="send-v2__asset-dropdown__asset-icon">
|
||||
<Identicon address={address} diameter={36} image={image} />
|
||||
</div>
|
||||
<div className="send-v2__asset-dropdown__asset-data">
|
||||
<div className="send-v2__asset-dropdown__symbol">
|
||||
{collectibleCollection.name || name}
|
||||
{nftCollection.name || name}
|
||||
</div>
|
||||
<div className="send-v2__asset-dropdown__name">
|
||||
<span className="send-v2__asset-dropdown__name__label">
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getCollectibleContracts,
|
||||
getCollectibles,
|
||||
getNftContracts,
|
||||
getNfts,
|
||||
getNativeCurrency,
|
||||
} from '../../../../ducks/metamask/metamask';
|
||||
import {
|
||||
@ -15,8 +15,8 @@ function mapStateToProps(state) {
|
||||
return {
|
||||
tokens: state.metamask.tokens,
|
||||
selectedAddress: state.metamask.selectedAddress,
|
||||
collectibles: getCollectibles(state),
|
||||
collections: getCollectibleContracts(state),
|
||||
nfts: getNfts(state),
|
||||
collections: getNftContracts(state),
|
||||
sendAsset: getSendAsset(state),
|
||||
accounts: getMetaMaskAccounts(state),
|
||||
nativeCurrency: getNativeCurrency(state),
|
||||
|
@ -22,7 +22,7 @@ const MIN_GAS_TOTAL = new Numeric(MIN_GAS_LIMIT_HEX, 16)
|
||||
.toPrefixedHexString();
|
||||
|
||||
const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb';
|
||||
const COLLECTIBLE_TRANSFER_FROM_FUNCTION_SIGNATURE = '0x23b872dd';
|
||||
const NFT_TRANSFER_FROM_FUNCTION_SIGNATURE = '0x23b872dd';
|
||||
|
||||
const INSUFFICIENT_FUNDS_ERROR = 'insufficientFunds';
|
||||
const INSUFFICIENT_FUNDS_FOR_GAS_ERROR = 'insufficientFundsForGas';
|
||||
@ -72,6 +72,6 @@ export {
|
||||
REQUIRED_ERROR,
|
||||
CONFUSING_ENS_ERROR,
|
||||
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
|
||||
COLLECTIBLE_TRANSFER_FROM_FUNCTION_SIGNATURE,
|
||||
NFT_TRANSFER_FROM_FUNCTION_SIGNATURE,
|
||||
RECIPIENT_TYPES,
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ import { TokenStandard } from '../../../shared/constants/transaction';
|
||||
import { Numeric } from '../../../shared/modules/Numeric';
|
||||
import {
|
||||
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
|
||||
COLLECTIBLE_TRANSFER_FROM_FUNCTION_SIGNATURE,
|
||||
NFT_TRANSFER_FROM_FUNCTION_SIGNATURE,
|
||||
} from './send.constants';
|
||||
|
||||
export {
|
||||
@ -100,7 +100,7 @@ function generateERC721TransferData({
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
COLLECTIBLE_TRANSFER_FROM_FUNCTION_SIGNATURE +
|
||||
NFT_TRANSFER_FROM_FUNCTION_SIGNATURE +
|
||||
Array.prototype.map
|
||||
.call(
|
||||
abi.rawEncode(
|
||||
|
@ -1152,11 +1152,11 @@ export function doesAddressRequireLedgerHidConnection(state, address) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getNewCollectibleAddedMessage(state) {
|
||||
export function getNewNftAddedMessage(state) {
|
||||
return state.appState.newNftAddedMessage;
|
||||
}
|
||||
|
||||
export function getRemoveCollectibleMessage(state) {
|
||||
export function getRemoveNftMessage(state) {
|
||||
return state.appState.removeNftMessage;
|
||||
}
|
||||
|
||||
|
@ -78,9 +78,8 @@ export const SET_FIRST_TIME_FLOW_TYPE = 'SET_FIRST_TIME_FLOW_TYPE';
|
||||
|
||||
export const SET_SELECTED_SETTINGS_RPC_URL = 'SET_SELECTED_SETTINGS_RPC_URL';
|
||||
export const SET_NEW_NETWORK_ADDED = 'SET_NEW_NETWORK_ADDED';
|
||||
export const SET_NEW_COLLECTIBLE_ADDED_MESSAGE =
|
||||
'SET_NEW_COLLECTIBLE_ADDED_MESSAGE';
|
||||
export const SET_REMOVE_COLLECTIBLE_MESSAGE = 'SET_REMOVE_COLLECTIBLE_MESSAGE';
|
||||
export const SET_NEW_NFT_ADDED_MESSAGE = 'SET_NEW_NFT_ADDED_MESSAGE';
|
||||
export const SET_REMOVE_NFT_MESSAGE = 'SET_REMOVE_NFT_MESSAGE';
|
||||
export const SET_NEW_CUSTOM_NETWORK_ADDED = 'SET_NEW_CUSTOM_NETWORK_ADDED';
|
||||
|
||||
export const LOADING_METHOD_DATA_STARTED = 'LOADING_METHOD_DATA_STARTED';
|
||||
|
@ -2103,10 +2103,10 @@ export function addNft(
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch) => {
|
||||
if (!address) {
|
||||
throw new Error('MetaMask - Cannot add collectible without address');
|
||||
throw new Error('MetaMask - Cannot add NFT without address');
|
||||
}
|
||||
if (!tokenID) {
|
||||
throw new Error('MetaMask - Cannot add collectible without tokenID');
|
||||
throw new Error('MetaMask - Cannot add NFT without tokenID');
|
||||
}
|
||||
if (!dontShowLoadingIndicator) {
|
||||
dispatch(showLoadingIndication());
|
||||
@ -2130,10 +2130,10 @@ export function addNftVerifyOwnership(
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch) => {
|
||||
if (!address) {
|
||||
throw new Error('MetaMask - Cannot add collectible without address');
|
||||
throw new Error('MetaMask - Cannot add NFT without address');
|
||||
}
|
||||
if (!tokenID) {
|
||||
throw new Error('MetaMask - Cannot add collectible without tokenID');
|
||||
throw new Error('MetaMask - Cannot add NFT without tokenID');
|
||||
}
|
||||
if (!dontShowLoadingIndicator) {
|
||||
dispatch(showLoadingIndication());
|
||||
@ -2168,10 +2168,10 @@ export function removeAndIgnoreNft(
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch) => {
|
||||
if (!address) {
|
||||
throw new Error('MetaMask - Cannot ignore collectible without address');
|
||||
throw new Error('MetaMask - Cannot ignore NFT without address');
|
||||
}
|
||||
if (!tokenID) {
|
||||
throw new Error('MetaMask - Cannot ignore collectible without tokenID');
|
||||
throw new Error('MetaMask - Cannot ignore NFT without tokenID');
|
||||
}
|
||||
if (!dontShowLoadingIndicator) {
|
||||
dispatch(showLoadingIndication());
|
||||
@ -2195,10 +2195,10 @@ export function removeNft(
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch) => {
|
||||
if (!address) {
|
||||
throw new Error('MetaMask - Cannot remove collectible without address');
|
||||
throw new Error('MetaMask - Cannot remove NFT without address');
|
||||
}
|
||||
if (!tokenID) {
|
||||
throw new Error('MetaMask - Cannot remove collectible without tokenID');
|
||||
throw new Error('MetaMask - Cannot remove NFT without tokenID');
|
||||
}
|
||||
if (!dontShowLoadingIndicator) {
|
||||
dispatch(showLoadingIndication());
|
||||
@ -2221,13 +2221,13 @@ export async function checkAndUpdateAllNftsOwnershipStatus() {
|
||||
|
||||
export async function isNftOwner(
|
||||
ownerAddress: string,
|
||||
collectibleAddress: string,
|
||||
collectibleId: string,
|
||||
nftAddress: string,
|
||||
nftId: string,
|
||||
): Promise<boolean> {
|
||||
return await submitRequestToBackground('isNftOwner', [
|
||||
ownerAddress,
|
||||
collectibleAddress,
|
||||
collectibleId,
|
||||
nftAddress,
|
||||
nftId,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -2672,19 +2672,19 @@ export function hideAlert(): Action {
|
||||
/**
|
||||
* TODO: this should be moved somewhere else when it makese sense to do so
|
||||
*/
|
||||
interface CollectibleDropDownState {
|
||||
interface NftDropDownState {
|
||||
[address: string]: {
|
||||
[chainId: string]: {
|
||||
[collectibleAddress: string]: boolean;
|
||||
[nftAddress: string]: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function updateCollectibleDropDownState(
|
||||
value: CollectibleDropDownState,
|
||||
export function updateNftDropDownState(
|
||||
value: NftDropDownState,
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch) => {
|
||||
await submitRequestToBackground('updateCollectibleDropDownState', [value]);
|
||||
await submitRequestToBackground('updateNftDropDownState', [value]);
|
||||
await forceUpdateMetamaskState(dispatch);
|
||||
};
|
||||
}
|
||||
@ -3775,20 +3775,20 @@ export function setNewNetworkAdded(
|
||||
};
|
||||
}
|
||||
|
||||
export function setNewCollectibleAddedMessage(
|
||||
export function setNewNftAddedMessage(
|
||||
newNftAddedMessage: string,
|
||||
): PayloadAction<string> {
|
||||
return {
|
||||
type: actionConstants.SET_NEW_COLLECTIBLE_ADDED_MESSAGE,
|
||||
type: actionConstants.SET_NEW_NFT_ADDED_MESSAGE,
|
||||
payload: newNftAddedMessage,
|
||||
};
|
||||
}
|
||||
|
||||
export function setRemoveCollectibleMessage(
|
||||
export function setRemoveNftMessage(
|
||||
removeNftMessage: string,
|
||||
): PayloadAction<string> {
|
||||
return {
|
||||
type: actionConstants.SET_REMOVE_COLLECTIBLE_MESSAGE,
|
||||
type: actionConstants.SET_REMOVE_NFT_MESSAGE,
|
||||
payload: removeNftMessage,
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user