From 9c49f338c78aa036d61eebb59e38a402385a6b17 Mon Sep 17 00:00:00 2001 From: Cancu <52618089+Cancuuu@users.noreply.github.com> Date: Mon, 10 Oct 2022 14:33:57 -0300 Subject: [PATCH 1/2] Release/0.6.0 (#563) * Chore/update 0.6.0 (#567) * Fixed transactionId and object proptypes * Auto fix with --fix * Fixed remaining issues * Updated controller to 0.19.8 and bumped plug version to 0.5.4 * Moved whitelist population to requestConnect * Removed whitelist population * Updated branch Co-authored-by: rocky-fleek Co-authored-by: tomiir * add tokens and add nft buttons * add nft form done - form component abstraction not done - add nft not working yet * feat: tests initialization * added get-nft-info method * added few changes * now the buttons looks like the design * removed standards to DIP721v2 only - display token info in step2 * feat: test ids added/first test added and passed * added add-custom-nft function * feat: all tests added and passed * fix: variables changes * feat: extra case added * feat: tests for settings-network added and done * added get nfts after add custom in step2 * Fix: Redundant optimistic update (#587) * Added controller with optimistic update fix. Removed filterCollectionTokens method as it was redundant * Reconciliated yarn lock * Renamed hook to blockNFTFetch and removed unnecessary formatting * Restored lockfile * Reinstalled controller' Co-authored-by: rocky-fleek * feat: send custom tokens on sonic network tests added and passed * fix: hardcode fixes * feat: case with checking that tabs are blocked added * show collections with 0 nfts without chevron - if the icon is null show a default image * change package.json and yarn.lock to upstream content * few console logs removed, text and state names * yarn lock updated to release yarn lock * now can download pem file with your account id * Remove custom token (#589) * Updated controller version * Added removeToken to Keyring * Added remove to AssetItem * Prepopulated protectedAsset * Added translation for remove token modal * Renamed Remove to remove custom token * Feat/account switch refactor (#566) * Added swit account icon * Added AccountItem component * Added edit account field * Added account switch to WalletDetails * Removed replacing icns on set * Made profile edit on click * Added walletId param to icns methods * Lint fixes for profile * Added local ICNS data * Lint fixes * Added shared transition * Conditionaly show switch account button * disable edit name while using icns * removed clgs and moved useEffect * added close curly brace * Update source/views/Extension/Views/AddNFT/hooks/useSteps.jsx Co-authored-by: angarita-dev <44899916+angarita-dev@users.noreply.github.com> * Update source/views/Extension/Views/AddNFT/Steps/Step2.jsx Co-authored-by: angarita-dev <44899916+angarita-dev@users.noreply.github.com> * it work calling sendMessage in AppConnection * fixed length problem * Migrated walletId to uuid * removed console.log * removed unused field on redux * fixed address shortening for ICNS names * Added border radius to custom tokens search * reduced token decimal places * Reduced font weight of text in banner * Added border raduis to second step of addToken * Updated yarn.lock * Added utils for icns address * modified getAssetFee function - removed fixed fee values - now can send max amount of any tokens * added fallback if fee and decimal doenst exist * Serialize host into metadata and pass it into regular connection requests * Controller version bump * Fixed switch account ICNS bug * Added account icon constant * replaced user icon with constant * Fixed switch account & account default icon * Updated dab-js and added fee as a parameter in the send operation * Prevented default on Profile actions (#611) * remove switch account icon (#613) * automation fixes * Installed controller and dabjs with icp standard fixes * Fixed connect account modal (#618) * Fix/Contacts (#616) * WIP * Code cleanup * mend * Fixed hiddenAccounts * Fixed state accounts * Fixed export identity * Bumped controller version * Added missing semicolon * Plug controller bump * Rollback controller to 0.22.3 * Fixed collection info and nft details issues * Fixed popup & connect accounts info (#625) * Abstracted internalRequestBalance and fixed issues with bigint parsing * Removed subaccount validation * Installed controller 0.22.5 with Principal serializatio on pid contacts * feat: new secret added * Removed provider update on useEffect * Removede console.logs * Fix/update provider & toasts (#631) * Updated provider package * Stop showing toast for provider errors * Disconnect only based on principalId * Removed update providerConnection * Made onConfirm method optional * Removed update provider connection from home * Updated contollero to version 0.22.6 * Updated extension icons to rainbow background ones (#595) Co-authored-by: rocky-fleek Co-authored-by: angarita-dev <44899916+angarita-dev@users.noreply.github.com> Co-authored-by: rocky-fleek Co-authored-by: tomiir Co-authored-by: Alina Sytnik Co-authored-by: Alina Sytnik <33018206+alinasytnik@users.noreply.github.com> --- .gitignore | 3 + package.json | 8 +- source/Background/Keyring/index.js | 74 +- source/Background/errors.js | 1 + source/Modules/Controller/connection.js | 15 +- source/Modules/Controller/information.js | 33 +- source/Modules/storageManager.js | 35 +- source/assets/icons/coins.svg | 3 + source/assets/icons/favicon-16.png | Bin 491 -> 974 bytes source/assets/icons/favicon-32.png | Bin 1745 -> 2770 bytes source/assets/icons/imageIcon.svg | 3 + source/assets/icons/switch-account.svg | 5 + source/components/AssetItem/index.jsx | 82 +- source/components/AssetItem/styles.js | 27 + .../components/ConnectAccountItem/index.jsx | 9 +- .../components/ConnectAccountsModal/index.jsx | 17 +- .../ConnectAccountsModal/layout.jsx | 4 +- .../components/ConnectionControls/index.jsx | 13 +- .../components/NetworkCard/index.jsx | 11 +- .../components/NetworkSelector/index.jsx | 4 +- .../components/NetworkSelector/styles.js | 2 +- source/components/ConnectionStatus/styles.js | 2 +- source/components/IDInput/index.jsx | 6 +- .../NFTs/components/NFTCollection/index.jsx | 15 +- source/components/NFTs/index.jsx | 17 +- .../Profile/components/AccountItem/index.jsx | 77 + .../Profile/components/AccountItem/styles.js | 71 + source/components/Profile/components/index.js | 1 + source/components/Profile/index.jsx | 77 +- .../Tokens/components/TokenSelector/index.jsx | 43 + .../Tokens/components/TokenSelector/styles.js | 48 + source/components/Tokens/index.jsx | 30 +- source/components/Tokens/styles.js | 35 + source/components/WalletInfo/index.jsx | 4 +- source/hooks/index.js | 1 - source/hooks/useApps.jsx | 8 +- source/hooks/useContacts.jsx | 22 - source/hooks/useSettingsItems.jsx | 1 + source/locales/en/translation.json | 27 + source/redux/contacts.js | 46 +- source/redux/profile.js | 6 +- source/redux/send.js | 8 +- source/redux/wallet.js | 33 +- source/shared/constants/account.js | 1 + source/shared/constants/addresses.js | 15 +- source/shared/styles/transitions.js | 3 + source/shared/utils/icns.js | 5 + source/shared/utils/short-address.js | 2 + source/views/Extension/Popup.jsx | 2 + .../Extension/Views/AddNFT/Steps/Step1.jsx | 20 + .../Extension/Views/AddNFT/Steps/Step2.jsx | 89 + .../Views/AddNFT/components/CustomNFT.jsx | 171 ++ .../Extension/Views/AddNFT/hooks/useSteps.jsx | 50 + source/views/Extension/Views/AddNFT/index.jsx | 21 + source/views/Extension/Views/AddNFT/styles.js | 105 + .../views/Extension/Views/AddToken/styles.js | 4 + .../views/Extension/Views/Contacts/index.jsx | 7 +- .../Views/ExportIdentity/steps/Step2.jsx | 19 +- source/views/Extension/Views/Home/index.jsx | 50 +- source/views/Extension/Views/Login/index.jsx | 7 +- .../Extension/Views/NFTDetails/index.jsx | 39 +- .../Network/components/NetworkCard/index.jsx | 3 +- .../Network/components/NoNetworks/index.jsx | 2 +- .../views/Extension/Views/Network/index.jsx | 3 +- .../Extension/Views/NetworkCreation/index.jsx | 8 +- .../views/Extension/Views/SendFlow/index.jsx | 9 +- .../SendNFT/components/InputStep/index.jsx | 4 +- .../SendNFT/components/ReviewStep/index.jsx | 16 +- source/views/Extension/Views/SendNFT/utils.js | 27 - .../components/ICNSSelector/index.jsx | 30 +- .../components/ICNSToggle/index.jsx | 38 +- .../Extension/Views/WalletDetails/index.jsx | 281 +- .../Extension/Views/WalletDetails/styles.js | 41 +- source/views/Extension/index.jsx | 12 +- .../Popup/components/AllowAgent/index.jsx | 2 +- .../Popup/components/AppConnection/index.jsx | 9 +- .../views/Popup/components/BurnXTC/index.jsx | 2 +- .../Popup/components/ImportToken/index.jsx | 2 +- .../components/BatchTransactions/index.jsx | 2 +- source/views/Popup/components/Sign/index.jsx | 2 +- .../views/Popup/components/Transfer/index.jsx | 2 +- source/views/Popup/index.jsx | 2 +- tests/README.md | 80 + tests/e2e/import-create-account.test.js | 6 +- tests/e2e/network.test.js | 148 + tests/e2e/send-tokens.test.js | 96 +- ...acts.test.js => settings-contacts.test.js} | 4 +- ...s => settings-export-dfx-identity.test.js} | 0 .../{help.test.js => settings-help.test.js} | 0 tests/e2e/settings-network.test.js | 90 + ...> settings-secret-recovery-phrase.test.js} | 0 tests/setup/jestSetup.js | 36 + yarn.lock | 2503 +++++++++-------- 93 files changed, 3357 insertions(+), 1640 deletions(-) create mode 100644 source/assets/icons/coins.svg create mode 100644 source/assets/icons/imageIcon.svg create mode 100644 source/assets/icons/switch-account.svg create mode 100644 source/components/Profile/components/AccountItem/index.jsx create mode 100644 source/components/Profile/components/AccountItem/styles.js create mode 100644 source/components/Profile/components/index.js create mode 100644 source/components/Tokens/components/TokenSelector/index.jsx create mode 100644 source/components/Tokens/components/TokenSelector/styles.js delete mode 100644 source/hooks/useContacts.jsx create mode 100644 source/shared/constants/account.js create mode 100644 source/shared/styles/transitions.js create mode 100644 source/shared/utils/icns.js create mode 100644 source/views/Extension/Views/AddNFT/Steps/Step1.jsx create mode 100644 source/views/Extension/Views/AddNFT/Steps/Step2.jsx create mode 100644 source/views/Extension/Views/AddNFT/components/CustomNFT.jsx create mode 100644 source/views/Extension/Views/AddNFT/hooks/useSteps.jsx create mode 100644 source/views/Extension/Views/AddNFT/index.jsx create mode 100644 source/views/Extension/Views/AddNFT/styles.js create mode 100644 tests/README.md create mode 100644 tests/e2e/network.test.js rename tests/e2e/{contacts.test.js => settings-contacts.test.js} (96%) rename tests/e2e/{export-dfx-identity.test.js => settings-export-dfx-identity.test.js} (100%) rename tests/e2e/{help.test.js => settings-help.test.js} (100%) create mode 100644 tests/e2e/settings-network.test.js rename tests/e2e/{secret-recovery-phrase.test.js => settings-secret-recovery-phrase.test.js} (100%) diff --git a/.gitignore b/.gitignore index 091a2a97..1b57587b 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,9 @@ typings/ .env .env.test +# PEM files +*.pem + # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/package.json b/package.json index 8fd640f3..e7ef0620 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plug", - "version": "0.5.4.1", + "version": "0.6.0", "description": "Your plug into the Internet Computer", "private": true, "repository": "https://github.com/Psychedelic/plug", @@ -37,9 +37,9 @@ "@material-ui/icons": "^4.11.2", "@metamask/post-message-stream": "^4.0.0", "@psychedelic/browser-rpc": "2.1.0", - "@psychedelic/dab-js": "1.4.5", - "@psychedelic/plug-controller": "0.19.8", - "@psychedelic/plug-inpage-provider": "^2.3.0", + "@psychedelic/dab-js": "1.4.12", + "@psychedelic/plug-controller": "0.22.6", + "@psychedelic/plug-inpage-provider": "^2.3.1", "@reduxjs/toolkit": "^1.6.0", "advanced-css-reset": "^1.2.2", "axios": "^0.21.1", diff --git a/source/Background/Keyring/index.js b/source/Background/Keyring/index.js index 1efc3001..2522959a 100644 --- a/source/Background/Keyring/index.js +++ b/source/Background/Keyring/index.js @@ -87,7 +87,9 @@ export const HANDLER_TYPES = { EDIT_PRINCIPAL: 'edit-principal', GET_PUBLIC_KEY: 'get-public-key', GET_TOKEN_INFO: 'get-token-info', + GET_NFT_INFO: 'get-nft-info', ADD_CUSTOM_TOKEN: 'add-custom-token', + ADD_CUSTOM_NFT: 'add-custom-nft', CREATE_PRINCIPAL: 'create-principal', SET_CURRENT_PRINCIPAL: 'set-current-principal', GET_PEM_FILE: 'get-pem-file', @@ -121,7 +123,9 @@ export const getKeyringErrorMessage = (type) => ({ [HANDLER_TYPES.EDIT_PRINCIPAL]: 'editing your principal.', [HANDLER_TYPES.GET_PUBLIC_KEY]: 'getting your public key.', [HANDLER_TYPES.GET_TOKEN_INFO]: 'fetching token info.', + [HANDLER_TYPES.GET_NFT_INFO]: 'fetching nft info.', [HANDLER_TYPES.ADD_CUSTOM_TOKEN]: 'adding custom token.', + [HANDLER_TYPES.ADD_CUSTOM_NFT]: 'adding custom nft.', [HANDLER_TYPES.CREATE_PRINCIPAL]: 'creating your principal.', [HANDLER_TYPES.SET_CURRENT_PRINCIPAL]: 'setting your principal.', [HANDLER_TYPES.GET_PEM_FILE]: 'getting your PEM file.', @@ -136,6 +140,7 @@ export const getKeyringErrorMessage = (type) => ({ [HANDLER_TYPES.REMOVE_NETWORK]: 'removing the network', [HANDLER_TYPES.SET_CURRENT_NETWORK]: 'setting the current network', [HANDLER_TYPES.GET_CURRENT_NETWORK]: 'getting the current network', + [HANDLER_TYPES.REMOVE_CUSTOM_TOKEN]: 'removing custom token', }[type]); export const sendMessage = (args, callback) => { @@ -179,8 +184,8 @@ export const getKeyringHandler = (type, keyring) => ({ }, [HANDLER_TYPES.CREATE_PRINCIPAL]: async (params) => keyring.createPrincipal(params), [HANDLER_TYPES.SET_CURRENT_PRINCIPAL]: - async (walletNumber) => { - await keyring.setCurrentPrincipal(walletNumber); + async (walletId) => { + await keyring.setCurrentPrincipal(walletId); const state = await keyring.getState(); extension.tabs.query({ active: true }, (tabs) => { extension.tabs.sendMessage(tabs[0].id, { action: 'updateConnection' }); @@ -223,12 +228,13 @@ export const getKeyringHandler = (type, keyring) => ({ return { error: e.message }; } }, - [HANDLER_TYPES.GET_BALANCE]: async (subaccount) => { + [HANDLER_TYPES.GET_BALANCE]: async (walletId) => { try { - const assets = await keyring.getBalances({ subaccount }); + const assets = await keyring.getBalances({ subaccount: walletId }); const parsedAssets = parseAssetsAmount(assets); const icpPrice = await getICPPrice(); - return formatAssets(parsedAssets, icpPrice); + const formattedAssets = formatAssets(parsedAssets, icpPrice); + return formattedAssets.map((asset) => recursiveParseBigint(asset)); } catch (error) { // eslint-disable-next-line console.log('Error when fetching token balances', error); @@ -259,8 +265,8 @@ export const getKeyringHandler = (type, keyring) => ({ } }, [HANDLER_TYPES.EDIT_PRINCIPAL]: - async ({ walletNumber, name, emoji }) => ( - keyring.editPrincipal(walletNumber, { name, emoji }) + async ({ walletId, name, emoji }) => ( + keyring.editPrincipal(walletId, { name, emoji }) ), [HANDLER_TYPES.GET_PUBLIC_KEY]: async () => keyring.getPublicKey(), @@ -279,6 +285,33 @@ export const getKeyringHandler = (type, keyring) => ({ return { error: e.message }; } }, + [HANDLER_TYPES.GET_NFT_INFO]: + async ({ canisterId, standard }) => { + try { + const nftInfo = await keyring.getNFTInfo({ + canisterId, + standard, + }); + return nftInfo; + } catch (e) { + // eslint-disable-next-line + console.log('Error while fetching NFT info', e); + return { error: e.message }; + } + }, + [HANDLER_TYPES.ADD_CUSTOM_NFT]: + async ({ canisterId, standard }) => { + try { + const nfts = await keyring.registerNFT({ + canisterId, standard, + }); + return (nfts || []).map((nft) => recursiveParseBigint(nft)); + } catch (e) { + // eslint-disable-next-line + console.log('Error registering nft', e); + return { error: e.message }; + } + }, [HANDLER_TYPES.ADD_CUSTOM_TOKEN]: async ({ canisterId, standard, logo }) => { try { @@ -294,7 +327,7 @@ export const getKeyringHandler = (type, keyring) => ({ } }, [HANDLER_TYPES.GET_PEM_FILE]: - async (walletNumber) => keyring.getPemFile(walletNumber), + async (walletId) => keyring.getPemFile(walletId), [HANDLER_TYPES.BURN_XTC]: async ({ to, amount }) => { try { @@ -324,19 +357,22 @@ export const getKeyringHandler = (type, keyring) => ({ return { error: e.message }; } }, - [HANDLER_TYPES.GET_ICNS_DATA]: async ({ refresh }) => { - const { wallets, currentWalletId } = await keyring.getState(); - let icnsData = wallets?.[currentWalletId]?.icnsData || { names: [] }; + [HANDLER_TYPES.GET_ICNS_DATA]: async ({ refresh, walletId = keyring.currentWalletId }) => { + const { wallets } = await keyring.getState(); + let icnsData = wallets?.[walletId]?.icnsData || { names: [] }; if (!icnsData?.names?.length || refresh) { - icnsData = await keyring.getICNSData(); + icnsData = await keyring.getICNSData({ subaccount: walletId }); } else { keyring.getICNSData(); } return icnsData; }, - [HANDLER_TYPES.SET_REVERSE_RESOLVED_NAME]: async (name) => { + [HANDLER_TYPES.SET_REVERSE_RESOLVED_NAME]: async ({ + name, + walletId = keyring.currentWalletId, + }) => { try { - const res = await keyring.setReverseResolvedName({ name }); + const res = await keyring.setReverseResolvedName({ name, subaccount: walletId }); return res; } catch (e) { // eslint-disable-next-line @@ -434,6 +470,16 @@ export const getKeyringHandler = (type, keyring) => ({ return { error: e.message }; } }, + [HANDLER_TYPES.REMOVE_CUSTOM_TOKEN]: async (canisterId) => { + try { + const newTokens = await keyring.removeToken(canisterId); + return Object.values(newTokens); + } catch (e) { + // eslint-disable-next-line + console.log('Error removing the network', e); + return { error: e.message }; + } + }, }[type]); export const getContacts = () => new Promise((resolve, reject) => { diff --git a/source/Background/errors.js b/source/Background/errors.js index 3f39ee0e..3977a721 100644 --- a/source/Background/errors.js +++ b/source/Background/errors.js @@ -27,5 +27,6 @@ export default { code: 401, message: 'The transaction that was just attempted failed because it was not a valid batch transaction. Please contact the project’s developers.', }, SIZE_ERROR: { code: 400, message: "There isn't enough space to open the popup" }, + GET_BALANCE_ERROR: { code: 400, message: 'There was an error trying to fetch your balances.' }, ...SILENT_ERRORS, }; diff --git a/source/Modules/Controller/connection.js b/source/Modules/Controller/connection.js index 1d2817f2..d108d393 100644 --- a/source/Modules/Controller/connection.js +++ b/source/Modules/Controller/connection.js @@ -99,8 +99,15 @@ export class ConnectionModule extends ControllerModuleBase { #disconnect() { return { methodName: 'disconnect', - handler: async (opts, url) => { - removeApp(this.keyring?.currentWalletId?.toString(), url, (removed) => { + handler: async (opts, url, principal) => { + const state = await this.keyring.getState(); + + const walletIdFromPrincipal = Object.values(state.wallets).find((wallet) => ( + wallet.principal === principal + ))?.walletId; + const walletIdToRemove = walletIdFromPrincipal ?? this.keyring.currentWalletId; + + removeApp(walletIdToRemove, url, (removed) => { if (!removed) { opts.callback(ERRORS.CONNECTION_ERROR, null); } @@ -124,6 +131,7 @@ export class ConnectionModule extends ControllerModuleBase { const { id: callId } = message.data.data; const { id: portId } = sender; const { url: domainUrl, icons } = metadata; + const newMetadata = { ...metadata, host }; if (isValidWhitelist) { canistersInfo = await fetchCanistersInfo(whitelist); @@ -137,8 +145,6 @@ export class ConnectionModule extends ControllerModuleBase { // If we receive a whitelist, we open the allow agent modal if (isValidWhitelist) { - const newMetadata = { ...metadata, requestConnect: true }; - const fixedHeight = this.keyring?.isUnlocked ? Math.min(422 + 65 * whitelist.length, 550) : SIZES.loginHeight; @@ -170,6 +176,7 @@ export class ConnectionModule extends ControllerModuleBase { argsJson: JSON.stringify({ timeout, transactionId }), type: 'connect', domainUrl, + metadataJson: JSON.stringify(newMetadata), }, callback); } }, diff --git a/source/Modules/Controller/information.js b/source/Modules/Controller/information.js index 7a276348..d9dd8a2a 100644 --- a/source/Modules/Controller/information.js +++ b/source/Modules/Controller/information.js @@ -28,13 +28,13 @@ export class InformationModule extends ControllerModuleBase { ]; } - async #internalRequestBalance(accountId, callback) { + async #internalRequestBalance(subaccount, callback, portConfig) { const getBalance = getKeyringHandler(HANDLER_TYPES.GET_BALANCE, this.keyring); - const icpBalance = await getBalance(accountId); - if (icpBalance.error) { - callback(ERRORS.SERVER_ERROR(icpBalance.error), null); + const balances = await getBalance(subaccount); + if (balances?.error) { + callback(ERRORS.GET_BALANCE_ERROR, null, portConfig); } else { - callback(null, icpBalance); + callback(null, balances, portConfig); } } @@ -44,14 +44,10 @@ export class InformationModule extends ControllerModuleBase { methodName: 'requestBalance', handler: async (opts, metadata, subaccount, transactionId) => { const { callback, message, sender } = opts; - getApps(this.keyring?.currentWalletId.toString(), (apps = {}) => { const app = apps?.[metadata.url] || {}; - if (app?.status === CONNECTION_STATUS.accepted) { - if (subaccount && Number.isNaN(parseInt(subaccount, 10))) { - callback(ERRORS.CLIENT_ERROR('Invalid account id'), null); - } else if (!this.keyring?.isUnlocked) { + if (!this.keyring?.isUnlocked) { this.displayPopUp({ callId: message.data.data.id, portId: sender.id, @@ -78,22 +74,9 @@ export class InformationModule extends ControllerModuleBase { const { subaccount } = args; getApps(this.keyring?.currentWalletId.toString(), async (apps = {}) => { const app = apps?.[url] || {}; - callback(null, true); - + callback(null, true); // Close modal if (app?.status === CONNECTION_STATUS.accepted) { - const getBalance = getKeyringHandler( - HANDLER_TYPES.GET_BALANCE, - this.keyring, - ); - const icpBalance = await getBalance(subaccount); - - if (icpBalance.error) { - callback(ERRORS.SERVER_ERROR(icpBalance.error), null, [ - { portId, callId }, - ]); - } else { - callback(null, icpBalance, [{ portId, callId }]); - } + this.#internalRequestBalance(subaccount, callback, [{ portId, callId }]); } else { callback(ERRORS.CONNECTION_ERROR, null, [{ portId, callId }]); } diff --git a/source/Modules/storageManager.js b/source/Modules/storageManager.js index dd8f8665..f35fb69b 100644 --- a/source/Modules/storageManager.js +++ b/source/Modules/storageManager.js @@ -27,7 +27,7 @@ export const getApps = (currentWalletId, cb) => { const defaultValue = {}; secureGetWrapper(currentWalletId, defaultValue, (state) => ( - cb(state?.[parseInt(currentWalletId, 10)]?.apps || defaultValue) + cb(state?.[currentWalletId]?.apps || defaultValue) )); }; @@ -35,7 +35,7 @@ export const getApp = (currentWalletId, appUrl, cb) => { const defaultValue = {}; secureGetWrapper(currentWalletId, defaultValue, (state) => { - cb(state?.[parseInt(currentWalletId, 10)]?.apps?.[appUrl] || defaultValue); + cb(state?.[currentWalletId]?.apps?.[appUrl] || defaultValue); }); }; @@ -121,13 +121,22 @@ export const getProtectedIds = (cb) => { export const setUseICNS = (useICNS, walletNumber, cb = () => {}) => { const defaultValue = true; - secureSetWrapper({ icns: { [walletNumber]: useICNS } }, defaultValue, cb); + + secureGetWrapper('icns', defaultValue, (state) => { + cb(state?.icns?.[walletNumber] ?? defaultValue); + secureSetWrapper({ + icns: { + ...state?.icns, + [walletNumber]: useICNS, + }, + }, defaultValue, cb); + }); }; export const getUseICNS = (walletNumber, cb) => { const defaultValue = true; secureGetWrapper('icns', defaultValue, (state) => { - cb(state?.icns?.[parseInt(walletNumber, 10)] ?? defaultValue); + cb(state?.icns?.[walletNumber] ?? defaultValue); }); }; @@ -145,12 +154,17 @@ export const getBatchTransactions = (cb) => { export const getWalletsConnectedToUrl = (url, walletIds, cb) => { const wallets = []; - walletIds.forEach((id) => { + if (!walletIds.length) { + cb([]); + return; + } + + walletIds.forEach((id, index) => { getApp(id.toString(), url, (app = {}) => { if (app?.status === CONNECTION_STATUS.accepted) { wallets.push(id); } - if (id === walletIds.length - 1) { + if (index === walletIds.length - 1) { cb(wallets); } }); @@ -193,3 +207,12 @@ export const removePendingTransaction = (transactionId, cb) => { export const resetPendingTransactions = () => { secureSetWrapper({ activeTransactions: { } }, {}, () => {}); }; + +export const updateWalletId = (previousWalletId, newWalletId) => { + getApps(previousWalletId, (apps) => { + setApps(newWalletId, apps); + }); + getUseICNS(previousWalletId, (result) => { + setUseICNS(newWalletId, result); + }); +}; diff --git a/source/assets/icons/coins.svg b/source/assets/icons/coins.svg new file mode 100644 index 00000000..856d2bf4 --- /dev/null +++ b/source/assets/icons/coins.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/assets/icons/favicon-16.png b/source/assets/icons/favicon-16.png index fcf94fc5bd0f90ec4b0cb9d4a0b6275e5784f226..b0756213ae0fd67a4cd838c7e8ccae2ad3d1c1de 100644 GIT binary patch delta 902 zcmV;119|-G1I`DKReu9xNkl+a7XJgNB=3&m9k8i%2A(*r6laa&m5C_~aYH=zxihnDhDYCgSWQqq7oI3%$ zT7(uY(WoSQAs0Nwrw6_ocw6**Hu^Dv4uLtw)hfO6NGFaD@5SWw6+~4H;n%1$?ctpd z2T;0E#GQo_Jf&lv zyn>VkG_(e2k6l5lzlgZ8WFl#N1G>LRqUUU*0fHsxZc7Sr@Dn$Dj`kXnOJ#r8Yb{k$>>Nm=J z`0>OPa;e{OdGG~fihp2tI01NOddM;Wvc~Kv8qdt&Xy4CB-%jEFY!8|piMePLyto-% zA`PqoJ4)@ioNq;X_FEv%MmuTBr@+wBSaurm3x5}}v-=uy6Iq; zq1$`$>|ZsA=r^PS-_&oJ$&(Np_CI!53I*Y6zO>MQCpXU1Q+-v$-4$HCGy@7|G0?UN zFWIj{TQ_lUc#>1XCn7f5X{HCq-_H{TEg}ku%SGH%xQaLL%i_fcmhO%pffu%5`tyBQ z?SC9aQ`~~BV+`ejL#H4%4)5{==_iC#EL|qwwJ|o=jP1=g(a}_^ZwU&#hJXBdBwE1I zvdrF5J#3slDQ=wpX)-~tKSbRl5XdsnxDso>a- cgH!MQ2Z+#wFMzS4*8l(j07*qoM6N<$f~FI+H2?qr delta 416 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 Tdm52n00000NkvXXu0mjfl|3A8 delta 1680 zcmV;B25vXge!s27-Obp71 zMq`*pqeSG9C=a6!k*Guo<0Tjo2SyVOI$jbmQ^7#^2M~m`tJFD=R4my-?;?0z-F^qw0gq=$bWPy2(bawf`1E&YD#fP zQqlF7U9QUmhseOjSf_IVmC4foL}?-rx~$ISDvdT((Mql(uaq%e&8m9*uOO2$vNDOc zz5P&(K)-UjG|?xMCqIF?b6!VL(PYMvqOq|tuH19a6=T-S8H!ws+l}`2_JkzTGBbP~ zUT>w^;c(dGB!9{|oKB}QIR~Hkz=4Bt|7}ah9cMJ*7a; zZZLr*qLf@pU?)0c4WfZ1J|bF0j3^kc)C2Aw_}%`vdVfgnSSP);(@>OxtS>VA1(==K z(O1qthd>0jcNAKpg@z}pfex(B+BXs(xn86FwY@X1#vT&jKL-<3^ zSf8|eCx0TlwuAV)pm{hSk&>ARS*%PZ2W_Dyh?{r7v;7=QHWM7Ljz!*#`?!-`w=+c_ zi|`$5jYb<+leal z1^O^<{&+<9S7sFAS{q4V4pY2%DhTzD+Yr%d5r18htX4zvb|O+*2J!3N2zh->c`UA> zPpd`EIu|6CB1B@FBQgSaU;%T8V%~l~<{s&Rud5Xm-ZrJ*lA5ZNT|OVijUNZv+@x4( zd|@FDA3ds!^D%`)2=KXmNX<@Vs%b(3aV_w4O&ISo0;Z4*vw9e&6&FLg+Jf7gBi==1H5sx0?QuZ52M8Nux&A5>4X0qFIul0! zXrS2%S=1{e0+ii?QVR?!0~Wn80hM+g>+ge3(}}_{t5G-QG(@-f4+5%;%&gK|JAeJ6 zgy&zn2y;5~BwYwZAHv}}H_mw}a~dx~v>PL>63FmokcbvtooaOUcB8iA61+w~rqodE ze0&7Bi$!Dur21BfTTdV!;u7F6X9O_u;cE&3Xj#^X*iwBD9)E2ye53MkR+oV)l?G?6 zA=Kx2kv=^KcTb;yA8PlYs3iwAj(;pPr*R)=_W5Qn)Qt^s2_*D^EkB#^{4?#aj_3iq zfnYbMfPB9L4?VOEh3wd6XOCvts>rsqv|z*f4LJYjdAySSEXL%I!ki;P9I;!Vx_Slr zgZmPx;XaVpndli_kl%x?WlgcR&!@($g(g(~q3ypT$$(cq>zHUBk8d5zm4Bn_OA(N} z$cXfk*-*845)$BB@G>h$#v62dv`Q{|G@w{gucvr z3njguK}U_sjVXl#D6cPv_WWriK{d(E&0ETXMQJiAZizLEJ#0G$dT})Lv3-_Uj1W^G zm9GIV6v8dGp`>LkT;a>frhf&MQKmADww_T%CsfMk;;R;WgZBE`+VulR a{{}cEkJLZRLIeN+00{s|MNUMnLSTYS11<{y diff --git a/source/assets/icons/imageIcon.svg b/source/assets/icons/imageIcon.svg new file mode 100644 index 00000000..6d1716f7 --- /dev/null +++ b/source/assets/icons/imageIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/assets/icons/switch-account.svg b/source/assets/icons/switch-account.svg new file mode 100644 index 00000000..c08325d8 --- /dev/null +++ b/source/assets/icons/switch-account.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/source/components/AssetItem/index.jsx b/source/components/AssetItem/index.jsx index 20f9e257..22ce92a4 100644 --- a/source/components/AssetItem/index.jsx +++ b/source/components/AssetItem/index.jsx @@ -8,17 +8,48 @@ import clsx from 'clsx'; import Skeleton from 'react-loading-skeleton'; import 'react-loading-skeleton/dist/skeleton.css'; +import { TOKENS } from '@shared/constants/currencies'; import RefreshIcon from '@assets/icons/blue-refresh.png'; +import DeleteIcon from '@assets/icons/delete.svg'; import TokenIcon from '../TokenIcon'; +import ActionDialog from '../ActionDialog'; import useStyles from './styles'; const AssetItem = ({ - updateToken, logo, name, amount, value, symbol, loading, failed, assetNameTestId, + updateToken, + logo, + name, + amount, + value, + symbol, + loading, + failed, + assetNameTestId, + protectedAsset, + removeAsset, }) => { const classes = useStyles(); const { t } = useTranslation(); const { currentNetwork, usingMainnet } = useSelector((state) => state.network); + const [ shouldRemove, setShouldRemove] = useState(false); + const [isHovering, setIsHovering] = useState(false); + const [openDelete, setOpenDelete] = useState(false); + + const handleMouseOver = () => { + if (!protectedAsset) return; + setIsHovering(true); + }; + + const handleMouseOut = () => { + if (!protectedAsset) return; + setIsHovering(false); + }; + + const handleModalClose = () => { + setIsHovering(false); + setOpenDelete(false); + }; const handleFetchAssets = async () => { // Avoid calling multiple times @@ -26,9 +57,39 @@ const AssetItem = ({ await updateToken(); }; + + const handleRemoveAssetDisplay = () => { + setShouldRemove(true); + handleModalClose(); + } + const ledgerNotSpecified = !usingMainnet && !currentNetwork?.ledgerCanisterId; + return ( -
+
+ + {t('removeToken.mainText')} + {symbol} + {t('removeToken.mainTextContinue')} +
+
+ {t('removeToken.disclaimer')} + + )} + confirmText={t('removeToken.action')} + buttonVariant="danger" + onClick={handleRemoveAssetDisplay} + onClose={handleModalClose} + />
{failed && !loading @@ -50,7 +111,7 @@ const AssetItem = ({ : ()} - )} + )} + { !failed && !loading && ( +
+ setOpenDelete(true)} + alt="delete-token" + src={DeleteIcon} + /> +
+ )}
- ); }; @@ -98,4 +169,5 @@ AssetItem.propTypes = { loading: PropTypes.bool.isRequired, failed: PropTypes.bool, assetNameTestId: PropTypes.string, + removeAsset: PropTypes.func.isRequired, }; diff --git a/source/components/AssetItem/styles.js b/source/components/AssetItem/styles.js index ed3d6b0d..79b00a9a 100644 --- a/source/components/AssetItem/styles.js +++ b/source/components/AssetItem/styles.js @@ -8,6 +8,13 @@ export default makeStyles((theme) => ({ justifyContent: 'flex-start', padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`, }, + '@keyframes swipeLeft': { + from: { transform: 'translateX(0px)' }, + to: { transform: 'translateX(-100%)' }, + }, + removeAnimation: { + animation: '$swipeLeft 0.6s forwards', + }, image: { height: 41, width: 41, @@ -63,4 +70,24 @@ export default makeStyles((theme) => ({ opacity: 0.9, }, }, + deleteToken: { + opacity: 0, + width: 0, + transition: '0.6s', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, + deleteTokenMoveRight: { + marginLeft: 'auto !important', + }, + deleteTokenActive: { + opacity: 1, + margin: '0 4px 0 21px', + + '& > img': { + cursor: 'pointer', + } + } })); diff --git a/source/components/ConnectAccountsModal/components/ConnectAccountItem/index.jsx b/source/components/ConnectAccountsModal/components/ConnectAccountItem/index.jsx index 0095716e..b7da819c 100644 --- a/source/components/ConnectAccountsModal/components/ConnectAccountItem/index.jsx +++ b/source/components/ConnectAccountsModal/components/ConnectAccountItem/index.jsx @@ -13,9 +13,14 @@ const ConnectAccountItem = ({ connected, wallet, checked, onCheck, name, }) => { const classes = useStyles(); + + const handleCheckboxChange = (event) => { + !connected && onCheck(event, wallet.walletId); + }; + return (
{name || ''} diff --git a/source/components/ConnectAccountsModal/index.jsx b/source/components/ConnectAccountsModal/index.jsx index 19ac88e6..dd4d7664 100644 --- a/source/components/ConnectAccountsModal/index.jsx +++ b/source/components/ConnectAccountsModal/index.jsx @@ -36,8 +36,8 @@ const ConnectAccountsModal = ({ getReverseResolvedNames(); }, [wallets]); - const connectAccountToTab = (wallet) => { - getApps(wallet.walletNumber.toString(), (apps) => { + const connectAccountToTab = (walletId) => { + getApps(walletId.toString(), (apps) => { // If any other account is connected, create an entry for the current wallet const date = new Date().toISOString(); const url = getTabURL(tab); @@ -55,19 +55,21 @@ const ConnectAccountsModal = ({ ], }, }; - setApps(wallet.walletNumber.toString(), newApps); + setApps(walletId.toString(), newApps); }); }; const handleConfirm = () => { - Object.keys(walletsToUpdate).forEach((walletId) => connectAccountToTab(wallets[walletId], tab)); + Object.keys(walletsToUpdate).forEach((walletId) => { + walletsToUpdate[walletId] && connectAccountToTab(walletId, tab); + }); onConfirm?.(); setWalletsToUpdate({}); setSelectAllWallets(false); onClose(); }; - const onCheckWallet = (walletId) => (event) => { + const onCheckWallet = (event, walletId) => { setWalletsToUpdate({ ...walletsToUpdate, [walletId]: event.target.checked, @@ -77,7 +79,7 @@ const ConnectAccountsModal = ({ const handleSelectAll = (event) => { const newWalletsToUpdate = {}; wallets.forEach((wallet) => { - newWalletsToUpdate[wallet.walletNumber] = event.target.checked; + newWalletsToUpdate[wallet.walletId] = event.target.checked; }); setSelectAllWallets(event.target.checked); setWalletsToUpdate(newWalletsToUpdate); @@ -116,12 +118,13 @@ ConnectAccountsModal.propTypes = { tab: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.string)).isRequired, open: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, + onConfirm: PropTypes.func, connectedWallets: PropTypes.arrayOf(PropTypes.number), }; ConnectAccountsModal.defaultProps = { connectedWallets: [], + onConfirm: () => {}, }; export default ConnectAccountsModal; diff --git a/source/components/ConnectAccountsModal/layout.jsx b/source/components/ConnectAccountsModal/layout.jsx index 6090f288..3542d7cb 100644 --- a/source/components/ConnectAccountsModal/layout.jsx +++ b/source/components/ConnectAccountsModal/layout.jsx @@ -46,14 +46,14 @@ const ConnectAccountsModalLayout = ({ onScroll={onScroll} > {wallets.map((wallet) => { - const alreadyConnected = connectedWallets.includes(wallet.walletNumber); + const alreadyConnected = connectedWallets.includes(wallet.walletId); return (