1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Implement asset page (#8696)

A new page has been created for viewing assets. This replaces the old
`selectedToken` state, which previously would augment the home page
to show token-specific information.

The new asset page shows the standard token overview as seen previously
on the home page, plus a history filtered to show just transactions
relevant to that token.

The actions that were available in the old token list menu have been
moved to a "Token Options" menu that mirrors the "Account Options"
menu.

The `selectedTokenAddress` state has been removed, as it is no longer
being used for anything.

`getMetaMetricState` has been renamed to `getBackgroundMetaMetricState`
because its sole purpose is extracting data from the background state
to send metrics from the background. It's not really a selector, but
it was convenient for it to use the same selectors the UI uses to
extract background data, so I left it there for now.

A new Redux store has been added to track state related to browser history.
The most recent "overview" page (i.e. the home page or the asset page) is
currently being tracked, so that actions taken from the asset page can return
the user back to the asset page when the action has finished.
This commit is contained in:
Mark Stacey 2020-06-01 14:54:32 -03:00 committed by GitHub
parent ec2e5c848b
commit df85ab6e10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 583 additions and 555 deletions

View File

@ -519,9 +519,6 @@
"hide": { "hide": {
"message": "ደብቅ" "message": "ደብቅ"
}, },
"hideToken": {
"message": "ተለዋጭ ስም ደብቅ"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "ተለዋጭ ስም ይደበቅ?" "message": "ተለዋጭ ስም ይደበቅ?"
}, },

View File

@ -515,9 +515,6 @@
"hide": { "hide": {
"message": "إخفاء" "message": "إخفاء"
}, },
"hideToken": {
"message": "إخفاء الرمز"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "أتريد إخفاء العملة الرمزية؟" "message": "أتريد إخفاء العملة الرمزية؟"
}, },

View File

@ -515,9 +515,6 @@
"hide": { "hide": {
"message": "Скриване" "message": "Скриване"
}, },
"hideToken": {
"message": "Скриване на жетон"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Скриване на жетон?" "message": "Скриване на жетон?"
}, },

View File

@ -519,9 +519,6 @@
"hide": { "hide": {
"message": "লুকান" "message": "লুকান"
}, },
"hideToken": {
"message": "টোকেন লুকান"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "টোকেন লুকাবেন?" "message": "টোকেন লুকাবেন?"
}, },

View File

@ -506,9 +506,6 @@
"hide": { "hide": {
"message": "Amaga" "message": "Amaga"
}, },
"hideToken": {
"message": "Amagar Fitxa"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Amagar fitxa?" "message": "Amagar fitxa?"
}, },

View File

@ -197,9 +197,6 @@
"hide": { "hide": {
"message": "Skrýt" "message": "Skrýt"
}, },
"hideToken": {
"message": "Skrýt token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Skrýt token?" "message": "Skrýt token?"
}, },

View File

@ -512,9 +512,6 @@
"hide": { "hide": {
"message": "Skjul" "message": "Skjul"
}, },
"hideToken": {
"message": "Skjul token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Skjul Token?" "message": "Skjul Token?"
}, },

View File

@ -504,9 +504,6 @@
"hide": { "hide": {
"message": "Ausblenden" "message": "Ausblenden"
}, },
"hideToken": {
"message": "Token ausblenden"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Token ausblenden?" "message": "Token ausblenden?"
}, },

View File

@ -516,9 +516,6 @@
"hide": { "hide": {
"message": "Απόκρυψη" "message": "Απόκρυψη"
}, },
"hideToken": {
"message": "Απόκρυψη Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Απόκρυψη του Token;" "message": "Απόκρυψη του Token;"
}, },

View File

@ -759,8 +759,9 @@
"hide": { "hide": {
"message": "Hide" "message": "Hide"
}, },
"hideToken": { "hideTokenSymbol": {
"message": "Hide Token" "message": "Hide $1",
"description": "$1 is the symbol for a token (e.g. 'DAI')"
}, },
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Hide Token?" "message": "Hide Token?"
@ -1544,6 +1545,9 @@
"tokenContractAddress": { "tokenContractAddress": {
"message": "Token Contract Address" "message": "Token Contract Address"
}, },
"tokenOptions": {
"message": "Token options"
},
"tokenSymbol": { "tokenSymbol": {
"message": "Token Symbol" "message": "Token Symbol"
}, },

View File

@ -419,9 +419,6 @@
"hide": { "hide": {
"message": "Ocultar" "message": "Ocultar"
}, },
"hideToken": {
"message": "Ocultar token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "¿Ocultar token?" "message": "¿Ocultar token?"
}, },

View File

@ -510,9 +510,6 @@
"hide": { "hide": {
"message": "Ocultar" "message": "Ocultar"
}, },
"hideToken": {
"message": "Ocultar token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "¿Ocultar token?" "message": "¿Ocultar token?"
}, },

View File

@ -515,9 +515,6 @@
"hide": { "hide": {
"message": "Peida" "message": "Peida"
}, },
"hideToken": {
"message": "Peida luba"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Peida luba?" "message": "Peida luba?"
}, },

View File

@ -519,9 +519,6 @@
"hide": { "hide": {
"message": "عدم نمایش" "message": "عدم نمایش"
}, },
"hideToken": {
"message": "مخفی سازی رمزیاب"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "آیا رمزیاب مخفی شود؟" "message": "آیا رمزیاب مخفی شود؟"
}, },

View File

@ -516,9 +516,6 @@
"hide": { "hide": {
"message": "Piilota" "message": "Piilota"
}, },
"hideToken": {
"message": "Piilota tietue"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Piilotetaanko tietue?" "message": "Piilotetaanko tietue?"
}, },

View File

@ -479,9 +479,6 @@
"hide": { "hide": {
"message": "Itago" "message": "Itago"
}, },
"hideToken": {
"message": "Itago ang Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Itago ang Token?" "message": "Itago ang Token?"
}, },

View File

@ -504,9 +504,6 @@
"hide": { "hide": {
"message": "Cacher" "message": "Cacher"
}, },
"hideToken": {
"message": "Masquer le jeton"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Masquer le jeton?" "message": "Masquer le jeton?"
}, },

View File

@ -519,9 +519,6 @@
"hide": { "hide": {
"message": "הסתר" "message": "הסתר"
}, },
"hideToken": {
"message": "הסתר טוקן"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "להסתיר טוקן?" "message": "להסתיר טוקן?"
}, },

View File

@ -519,9 +519,6 @@
"hide": { "hide": {
"message": "छुपाएं" "message": "छुपाएं"
}, },
"hideToken": {
"message": "टोकन छिपाएँ?"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "टोकन छिपाएँ?" "message": "टोकन छिपाएँ?"
}, },

View File

@ -173,9 +173,6 @@
"hide": { "hide": {
"message": "छुपाएं" "message": "छुपाएं"
}, },
"hideToken": {
"message": "टोकन छिपाएं"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "टोकन छिपाएंn?" "message": "टोकन छिपाएंn?"
}, },

View File

@ -515,9 +515,6 @@
"hide": { "hide": {
"message": "Sakrij preglednik" "message": "Sakrij preglednik"
}, },
"hideToken": {
"message": "Sakrij token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Sakriti token?" "message": "Sakriti token?"
}, },

View File

@ -287,9 +287,6 @@
"hide": { "hide": {
"message": "Kache" "message": "Kache"
}, },
"hideToken": {
"message": "Kache Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Kache Token?" "message": "Kache Token?"
}, },

View File

@ -515,9 +515,6 @@
"hide": { "hide": {
"message": "Elrejtés" "message": "Elrejtés"
}, },
"hideToken": {
"message": "Token elrejtése"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Elrejted a tokent?" "message": "Elrejted a tokent?"
}, },

View File

@ -506,9 +506,6 @@
"hide": { "hide": {
"message": "Sembunyikan" "message": "Sembunyikan"
}, },
"hideToken": {
"message": "Sembunyikan Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Sembunyikan Token?" "message": "Sembunyikan Token?"
}, },

View File

@ -633,9 +633,6 @@
"hide": { "hide": {
"message": "Nascondi" "message": "Nascondi"
}, },
"hideToken": {
"message": "Nascondi Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Nascondi Token?" "message": "Nascondi Token?"
}, },

View File

@ -242,9 +242,6 @@
"hide": { "hide": {
"message": "隠す" "message": "隠す"
}, },
"hideToken": {
"message": "トークンを隠す"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "トークンを隠しますか?" "message": "トークンを隠しますか?"
}, },

View File

@ -519,9 +519,6 @@
"hide": { "hide": {
"message": "ಮರೆಮಾಡಿ" "message": "ಮರೆಮಾಡಿ"
}, },
"hideToken": {
"message": "ಟೋಕನ್ ಮರೆಮಾಡಿ"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "ಟೋಕನ್ ಮರೆಮಾಡುವುದೇ?" "message": "ಟೋಕನ್ ಮರೆಮಾಡುವುದೇ?"
}, },

View File

@ -513,9 +513,6 @@
"hide": { "hide": {
"message": "숨기기" "message": "숨기기"
}, },
"hideToken": {
"message": "토큰 숨기기"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "토큰 숨기기?" "message": "토큰 숨기기?"
}, },

View File

@ -519,9 +519,6 @@
"hide": { "hide": {
"message": "Slėpti" "message": "Slėpti"
}, },
"hideToken": {
"message": "Slėpti prieigos raktą"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Slėpti prieigos raktą?" "message": "Slėpti prieigos raktą?"
}, },

View File

@ -515,9 +515,6 @@
"hide": { "hide": {
"message": "Slēpt" "message": "Slēpt"
}, },
"hideToken": {
"message": "Paslēpt marķieri"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Paslēpt žetonu?" "message": "Paslēpt žetonu?"
}, },

View File

@ -503,9 +503,6 @@
"hide": { "hide": {
"message": "Sembunyikan" "message": "Sembunyikan"
}, },
"hideToken": {
"message": "Sembunyikan Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Sembunyikan Token?" "message": "Sembunyikan Token?"
}, },

View File

@ -167,9 +167,6 @@
"hide": { "hide": {
"message": "Verbergen" "message": "Verbergen"
}, },
"hideToken": {
"message": "Token verbergen"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Token verbergen?" "message": "Token verbergen?"
}, },

View File

@ -509,9 +509,6 @@
"hide": { "hide": {
"message": "Skjul" "message": "Skjul"
}, },
"hideToken": {
"message": "Skjul tokenet"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Skjul sjetonger?" "message": "Skjul sjetonger?"
}, },

View File

@ -137,9 +137,6 @@
"hide": { "hide": {
"message": "Itago" "message": "Itago"
}, },
"hideToken": {
"message": "Itago ang Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Itago ang Token?" "message": "Itago ang Token?"
}, },

View File

@ -516,9 +516,6 @@
"hide": { "hide": {
"message": "Schowaj" "message": "Schowaj"
}, },
"hideToken": {
"message": "Schowaj token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Schować token?" "message": "Schować token?"
}, },

View File

@ -173,9 +173,6 @@
"hide": { "hide": {
"message": "Ocultar" "message": "Ocultar"
}, },
"hideToken": {
"message": "Ocultar Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Ocultar Token?" "message": "Ocultar Token?"
}, },

View File

@ -513,9 +513,6 @@
"hide": { "hide": {
"message": "Ocultar" "message": "Ocultar"
}, },
"hideToken": {
"message": "Ocultar Token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Esconder token?" "message": "Esconder token?"
}, },

View File

@ -509,9 +509,6 @@
"hide": { "hide": {
"message": "Ascunde" "message": "Ascunde"
}, },
"hideToken": {
"message": "Ascunde tokenul"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Ascunde simbol?" "message": "Ascunde simbol?"
}, },

View File

@ -203,9 +203,6 @@
"hide": { "hide": {
"message": "Скрыть" "message": "Скрыть"
}, },
"hideToken": {
"message": "Скрыть токен"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Скрыть токен?" "message": "Скрыть токен?"
}, },

View File

@ -507,9 +507,6 @@
"hide": { "hide": {
"message": "Skrýt" "message": "Skrýt"
}, },
"hideToken": {
"message": "Skrýt token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Skrýt token?" "message": "Skrýt token?"
}, },

View File

@ -510,9 +510,6 @@
"hide": { "hide": {
"message": "Skrij" "message": "Skrij"
}, },
"hideToken": {
"message": "Skrij žeton"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Skrijem žeton?" "message": "Skrijem žeton?"
}, },

View File

@ -516,9 +516,6 @@
"hide": { "hide": {
"message": "Сакриј" "message": "Сакриј"
}, },
"hideToken": {
"message": "Sakrij token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Da li želite da sakrijete token?" "message": "Da li želite da sakrijete token?"
}, },

View File

@ -509,9 +509,6 @@
"hide": { "hide": {
"message": "Dölj" "message": "Dölj"
}, },
"hideToken": {
"message": "Göm token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Göm token?" "message": "Göm token?"
}, },

View File

@ -506,9 +506,6 @@
"hide": { "hide": {
"message": "Ficha" "message": "Ficha"
}, },
"hideToken": {
"message": "Ficha Kianzio"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Ungependa Kianzio?" "message": "Ungependa Kianzio?"
}, },

View File

@ -194,9 +194,6 @@
"hide": { "hide": {
"message": "மறை" "message": "மறை"
}, },
"hideToken": {
"message": "டோக்கனை மறை"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "டோக்கனை மறை?" "message": "டோக்கனை மறை?"
}, },

View File

@ -248,9 +248,6 @@
"hide": { "hide": {
"message": "ซ่อน" "message": "ซ่อน"
}, },
"hideToken": {
"message": "ซ่อนโทเค็น"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "ซ่อนโทเค็นหรือไม่?" "message": "ซ่อนโทเค็นหรือไม่?"
}, },

View File

@ -200,9 +200,6 @@
"hide": { "hide": {
"message": "Gizle" "message": "Gizle"
}, },
"hideToken": {
"message": "Jetonu gizle"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Jetonu gizle?" "message": "Jetonu gizle?"
}, },

View File

@ -519,9 +519,6 @@
"hide": { "hide": {
"message": "Сховати" "message": "Сховати"
}, },
"hideToken": {
"message": "Приховати токен"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Приховати токен?" "message": "Приховати токен?"
}, },

View File

@ -149,9 +149,6 @@
"hide": { "hide": {
"message": "Ẩn" "message": "Ẩn"
}, },
"hideToken": {
"message": "Ẩn mã token"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Ẩn mã token?" "message": "Ẩn mã token?"
}, },

View File

@ -513,9 +513,6 @@
"hide": { "hide": {
"message": "隐藏" "message": "隐藏"
}, },
"hideToken": {
"message": "隐藏代币"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "隐藏代币?" "message": "隐藏代币?"
}, },

View File

@ -516,9 +516,6 @@
"hide": { "hide": {
"message": "隱藏" "message": "隱藏"
}, },
"hideToken": {
"message": "隱藏代幣"
},
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "隱藏代幣?" "message": "隱藏代幣?"
}, },

View File

@ -102,7 +102,6 @@ initialize().catch(log.error)
* @property {Object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions. * @property {Object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
* @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones. * @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones.
* @property {Array} addressBook - A list of previously sent to addresses. * @property {Array} addressBook - A list of previously sent to addresses.
* @property {address} selectedTokenAddress - Used to indicate if a token is globally selected. Should be deprecated in favor of UI-centric token selection.
* @property {Object} contractExchangeRates - Info about current token prices. * @property {Object} contractExchangeRates - Info about current token prices.
* @property {Array} tokens - Tokens held by the current user, including their balances. * @property {Array} tokens - Tokens held by the current user, including their balances.
* @property {Object} send - TODO: Document * @property {Object} send - TODO: Document

View File

@ -1,4 +1,4 @@
import { getMetaMetricState } from '../../../ui/app/selectors' import { getBackgroundMetaMetricState } from '../../../ui/app/selectors'
import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util' import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util'
const inDevelopment = process.env.NODE_ENV === 'development' const inDevelopment = process.env.NODE_ENV === 'development'
@ -8,7 +8,7 @@ const METAMETRICS_TRACKING_URL = inDevelopment
: 'http://www.metamask.io/metametrics-prod' : 'http://www.metamask.io/metametrics-prod'
export default function backEndMetaMetricsEvent (metaMaskState, eventData) { export default function backEndMetaMetricsEvent (metaMaskState, eventData) {
const stateEventData = getMetaMetricState({ metamask: metaMaskState }) const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState })
if (stateEventData.participateInMetaMetrics) { if (stateEventData.participateInMetaMetrics) {
sendMetaMetricsEvent({ sendMetaMetricsEvent({

View File

@ -527,5 +527,8 @@
}, },
"unconnectedAccount": { "unconnectedAccount": {
"state": "CLOSED" "state": "CLOSED"
},
"history": {
"mostRecentOverviewPage": "/"
} }
} }

View File

@ -478,5 +478,8 @@
}, },
"unconnectedAccount": { "unconnectedAccount": {
"state": "CLOSED" "state": "CLOSED"
},
"history": {
"mostRecentOverviewPage": "/"
} }
} }

View File

@ -113,7 +113,6 @@
} }
} }
}, },
"selectedTokenAddress": "0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d",
"unapprovedMsgs": {}, "unapprovedMsgs": {},
"unapprovedMsgCount": 0, "unapprovedMsgCount": 0,
"unapprovedPersonalMsgs": {}, "unapprovedPersonalMsgs": {},

View File

@ -903,7 +903,6 @@ describe('MetaMask', function () {
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {
await driver.clickElement(By.css(`[data-testid="home__history-tab"]`))
await driver.wait(async () => { await driver.wait(async () => {
const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item')) const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 1 return confirmedTxes.length === 1
@ -996,13 +995,6 @@ describe('MetaMask', function () {
const txStatuses = await driver.findElements(By.css('.list-item__heading')) const txStatuses = await driver.findElements(By.css('.list-item__heading'))
await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/), 10000) await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/), 10000)
await driver.clickElement(By.css('[data-testid="home__asset-tab"]'))
await driver.clickElement(By.css('[data-testid="wallet-balance"]'))
await driver.clickElement(By.css('.token-cell'))
await driver.delay(1000)
const tokenBalanceAmount = await driver.findElements(By.css('.token-overview__primary-balance')) const tokenBalanceAmount = await driver.findElements(By.css('.token-overview__primary-balance'))
await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /7.500\s*TST/), 10000) await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /7.500\s*TST/), 10000)
}) })
@ -1025,8 +1017,6 @@ describe('MetaMask', function () {
await driver.switchToWindow(extension) await driver.switchToWindow(extension)
await driver.delay(regularDelayMs) await driver.delay(regularDelayMs)
await driver.clickElement(By.css('[data-testid="home__history-tab"]'))
await driver.wait(async () => { await driver.wait(async () => {
const pendingTxes = await driver.findElements(By.css('.transaction-list__pending-transactions .transaction-list-item')) const pendingTxes = await driver.findElements(By.css('.transaction-list__pending-transactions .transaction-list-item'))
return pendingTxes.length === 1 return pendingTxes.length === 1
@ -1227,12 +1217,9 @@ describe('MetaMask', function () {
describe('Hide token', function () { describe('Hide token', function () {
it('hides the token when clicked', async function () { it('hides the token when clicked', async function () {
await driver.clickElement(By.css('[data-testid="home__asset-tab"]')) await driver.clickElement(By.css('[data-testid="token-options__button"]'))
await driver.clickElement(By.css('.token-cell__ellipsis')) await driver.clickElement(By.css('[data-testid="token-options__hide"]'))
const byTokenMenuDropdownOption = By.css('.menu__item--clickable')
await driver.clickElement(byTokenMenuDropdownOption)
const confirmHideModal = await driver.findElement(By.css('span .modal')) const confirmHideModal = await driver.findElement(By.css('span .modal'))
@ -1245,6 +1232,7 @@ describe('MetaMask', function () {
describe('Add existing token using search', function () { describe('Add existing token using search', function () {
it('clicks on the Add Token button', async function () { it('clicks on the Add Token button', async function () {
await driver.clickElement(By.css('[data-testid="asset__back"]'))
await driver.clickElement(By.xpath(`//button[contains(text(), 'Add Token')]`)) await driver.clickElement(By.xpath(`//button[contains(text(), 'Add Token')]`))
await driver.delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })

View File

@ -52,15 +52,6 @@ describe('MetaMask Reducers', function () {
assert.equal(state.selectedAddress, 'test address') assert.equal(state.selectedAddress, 'test address')
}) })
it('sets select ', function () {
const state = reduceMetamask({}, {
type: actionConstants.SET_SELECTED_TOKEN,
value: 'test token',
})
assert.equal(state.selectedTokenAddress, 'test token')
})
it('sets account label', function () { it('sets account label', function () {
const state = reduceMetamask({}, { const state = reduceMetamask({}, {
type: actionConstants.SET_ACCOUNT_LABEL, type: actionConstants.SET_ACCOUNT_LABEL,

View File

@ -4,12 +4,10 @@ import classnames from 'classnames'
import Identicon from '../../ui/identicon' import Identicon from '../../ui/identicon'
const AssetListItem = ({ const AssetListItem = ({
active,
children, children,
className, className,
'data-testid': dataTestId, 'data-testid': dataTestId,
iconClassName, iconClassName,
menu,
onClick, onClick,
tokenAddress, tokenAddress,
tokenImage, tokenImage,
@ -17,9 +15,7 @@ const AssetListItem = ({
}) => { }) => {
return ( return (
<div <div
className={classnames('asset-list-item__container', className, { className={classnames('asset-list-item__container', className)}
'asset-list-item__container--active': active,
})}
data-testid={dataTestId} data-testid={dataTestId}
onClick={onClick} onClick={onClick}
> >
@ -35,18 +31,16 @@ const AssetListItem = ({
{ children } { children }
</div> </div>
{ warning } { warning }
{ menu } <i className="fas fa-chevron-right asset-list-item__chevron-right" />
</div> </div>
) )
} }
AssetListItem.propTypes = { AssetListItem.propTypes = {
active: PropTypes.bool,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
className: PropTypes.string, className: PropTypes.string,
'data-testid': PropTypes.string, 'data-testid': PropTypes.string,
iconClassName: PropTypes.string, iconClassName: PropTypes.string,
menu: PropTypes.node,
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
tokenAddress: PropTypes.string, tokenAddress: PropTypes.string,
tokenImage: PropTypes.string, tokenImage: PropTypes.string,
@ -54,10 +48,8 @@ AssetListItem.propTypes = {
} }
AssetListItem.defaultProps = { AssetListItem.defaultProps = {
active: undefined,
className: undefined, className: undefined,
'data-testid': undefined, 'data-testid': undefined,
menu: undefined,
iconClassName: undefined, iconClassName: undefined,
tokenAddress: undefined, tokenAddress: undefined,
tokenImage: undefined, tokenImage: undefined,

View File

@ -3,9 +3,12 @@
display: flex; display: flex;
padding: 24px 16px; padding: 24px 16px;
align-items: center; align-items: center;
border-top: 1px solid $mercury;
border-bottom: 1px solid $mercury;
cursor: pointer;
&--active { &:hover {
background: #D9D7DA; background-color: $Grey-000;
} }
} }
@ -16,4 +19,8 @@
flex: 1; flex: 1;
min-width: 0; min-width: 0;
} }
&__chevron-right {
color: $Grey-500;
}
} }

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import { useDispatch, useSelector } from 'react-redux' import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import AddTokenButton from '../add-token-button' import AddTokenButton from '../add-token-button'
import TokenList from '../token-list' import TokenList from '../token-list'
@ -9,14 +10,12 @@ import CurrencyDisplay from '../../ui/currency-display'
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common' import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
import { useMetricEvent } from '../../../hooks/useMetricEvent' import { useMetricEvent } from '../../../hooks/useMetricEvent'
import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency' import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency'
import { getCurrentAccountWithSendEtherInfo, getShouldShowFiat } from '../../../selectors/selectors' import { getCurrentAccountWithSendEtherInfo, getNativeCurrency, getShouldShowFiat } from '../../../selectors'
import { setSelectedToken } from '../../../store/actions'
const AssetList = () => { const AssetList = ({ onClickAsset }) => {
const dispatch = useDispatch()
const history = useHistory() const history = useHistory()
const selectedAccountBalance = useSelector((state) => getCurrentAccountWithSendEtherInfo(state).balance) const selectedAccountBalance = useSelector((state) => getCurrentAccountWithSendEtherInfo(state).balance)
const selectedTokenAddress = useSelector((state) => state.metamask.selectedTokenAddress) const nativeCurrency = useSelector(getNativeCurrency)
const showFiat = useSelector(getShouldShowFiat) const showFiat = useSelector(getShouldShowFiat)
const selectTokenEvent = useMetricEvent({ const selectTokenEvent = useMetricEvent({
eventOpts: { eventOpts: {
@ -45,8 +44,7 @@ const AssetList = () => {
return ( return (
<> <>
<AssetListItem <AssetListItem
active={!selectedTokenAddress} onClick={() => onClickAsset(nativeCurrency)}
onClick={() => dispatch(setSelectedToken())}
data-testid="wallet-balance" data-testid="wallet-balance"
> >
<CurrencyDisplay <CurrencyDisplay
@ -68,7 +66,7 @@ const AssetList = () => {
</AssetListItem> </AssetListItem>
<TokenList <TokenList
onTokenClick={(tokenAddress) => { onTokenClick={(tokenAddress) => {
dispatch(setSelectedToken(tokenAddress)) onClickAsset(tokenAddress)
selectTokenEvent() selectTokenEvent()
}} }}
/> />
@ -82,4 +80,8 @@ const AssetList = () => {
) )
} }
AssetList.propTypes = {
onClickAsset: PropTypes.func.isRequired,
}
export default AssetList export default AssetList

View File

@ -1,67 +0,0 @@
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as actions from '../../../store/actions'
import { createAccountLink as genAccountLink } from '@metamask/etherscan-link'
import { Menu, Item, CloseArea } from './components/menu'
class TokenMenuDropdown extends Component {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
onClose: PropTypes.func.isRequired,
showHideTokenConfirmationModal: PropTypes.func.isRequired,
token: PropTypes.object.isRequired,
network: PropTypes.string.isRequired,
}
onClose = (e) => {
e.stopPropagation()
this.props.onClose()
}
render () {
const { showHideTokenConfirmationModal } = this.props
return (
<Menu className="token-menu-dropdown" isShowing>
<CloseArea onClick={this.onClose} />
<Item
onClick={(e) => {
e.stopPropagation()
showHideTokenConfirmationModal(this.props.token)
this.props.onClose()
}}
text={this.context.t('hideToken')}
/>
<Item
onClick={(e) => {
e.stopPropagation()
const url = genAccountLink(this.props.token.address, this.props.network)
global.platform.openTab({ url })
this.props.onClose()
}}
text={this.context.t('viewOnEtherscan')}
/>
</Menu>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TokenMenuDropdown)
function mapStateToProps (state) {
return {
network: state.metamask.network,
}
}
function mapDispatchToProps (dispatch) {
return {
showHideTokenConfirmationModal: (token) => {
dispatch(actions.showModal({ name: 'HIDE_TOKEN_CONFIRMATION', token }))
},
}
}

View File

@ -2,8 +2,6 @@
display: grid; display: grid;
grid-template-columns: 30% minmax(30%, 1fr) 30%; grid-template-columns: 30% minmax(30%, 1fr) 30%;
column-gap: 5px; column-gap: 5px;
margin-bottom: 24px;
padding: 0 8px; padding: 0 8px;
border-bottom: 1px solid $Grey-100; border-bottom: 1px solid $Grey-100;

View File

@ -10,7 +10,6 @@ import Identicon from '../../ui/identicon'
import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component' import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component'
import { conversionUtil } from '../../../helpers/utils/conversion-util' import { conversionUtil } from '../../../helpers/utils/conversion-util'
import Button from '../../ui/button' import Button from '../../ui/button'
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'
export default class SignatureRequestOriginal extends Component { export default class SignatureRequestOriginal extends Component {
static contextTypes = { static contextTypes = {
@ -28,6 +27,7 @@ export default class SignatureRequestOriginal extends Component {
clearConfirmTransaction: PropTypes.func.isRequired, clearConfirmTransaction: PropTypes.func.isRequired,
conversionRate: PropTypes.number, conversionRate: PropTypes.number,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
mostRecentOverviewPage: PropTypes.string.isRequired,
requesterAddress: PropTypes.string, requesterAddress: PropTypes.string,
sign: PropTypes.func.isRequired, sign: PropTypes.func.isRequired,
txData: PropTypes.object.isRequired, txData: PropTypes.object.isRequired,
@ -268,7 +268,7 @@ export default class SignatureRequestOriginal extends Component {
} }
renderFooter = () => { renderFooter = () => {
const { cancel, sign } = this.props const { cancel, clearConfirmTransaction, history, mostRecentOverviewPage, sign } = this.props
return ( return (
<div className="request-signature__footer"> <div className="request-signature__footer">
@ -286,8 +286,8 @@ export default class SignatureRequestOriginal extends Component {
name: 'Cancel', name: 'Cancel',
}, },
}) })
this.props.clearConfirmTransaction() clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}} }}
> >
{ this.context.t('cancel') } { this.context.t('cancel') }
@ -306,8 +306,8 @@ export default class SignatureRequestOriginal extends Component {
name: 'Confirm', name: 'Confirm',
}, },
}) })
this.props.clearConfirmTransaction() clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}} }}
> >
{ this.context.t('sign') } { this.context.t('sign') }

View File

@ -10,12 +10,14 @@ import {
import { getAccountByAddress } from '../../../helpers/utils/util' import { getAccountByAddress } from '../../../helpers/utils/util'
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck' import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck'
import SignatureRequestOriginal from './signature-request-original.component' import SignatureRequestOriginal from './signature-request-original.component'
import { getMostRecentOverviewPage } from '../../../ducks/history/history'
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
requester: null, requester: null,
requesterAddress: null, requesterAddress: null,
conversionRate: conversionRateSelector(state), conversionRate: conversionRateSelector(state),
mostRecentOverviewPage: getMostRecentOverviewPage(state),
// not passed to component // not passed to component
allAccounts: accountsWithSendEtherInfoSelector(state), allAccounts: accountsWithSendEtherInfoSelector(state),
} }

View File

@ -2,7 +2,6 @@ import classnames from 'classnames'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React, { Component } from 'react' import React, { Component } from 'react'
import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util' import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util'
import TokenMenuDropdown from '../dropdowns/token-menu-dropdown.js'
import Tooltip from '../../ui/tooltip-v2' import Tooltip from '../../ui/tooltip-v2'
import { I18nContext } from '../../../contexts/i18n' import { I18nContext } from '../../../contexts/i18n'
import AssetListItem from '../asset-list-item' import AssetListItem from '../asset-list-item'
@ -15,7 +14,6 @@ export default class TokenCell extends Component {
outdatedBalance: PropTypes.bool, outdatedBalance: PropTypes.bool,
symbol: PropTypes.string, symbol: PropTypes.string,
string: PropTypes.string, string: PropTypes.string,
selectedTokenAddress: PropTypes.string,
contractExchangeRates: PropTypes.object, contractExchangeRates: PropTypes.object,
conversionRate: PropTypes.number, conversionRate: PropTypes.number,
currentCurrency: PropTypes.string, currentCurrency: PropTypes.string,
@ -28,18 +26,12 @@ export default class TokenCell extends Component {
outdatedBalance: false, outdatedBalance: false,
} }
state = {
tokenMenuOpen: false,
}
render () { render () {
const t = this.context const t = this.context
const { tokenMenuOpen } = this.state
const { const {
address, address,
symbol, symbol,
string, string,
selectedTokenAddress,
contractExchangeRates, contractExchangeRates,
conversionRate, conversionRate,
onClick, onClick,
@ -71,26 +63,6 @@ export default class TokenCell extends Component {
const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol
const menu = (
<>
<div>
<i
className="fa fa-ellipsis-h fa-lg token-cell__ellipsis cursor-pointer"
onClick={(e) => {
e.stopPropagation()
this.setState({ tokenMenuOpen: true })
}}
/>
</div>
{tokenMenuOpen && (
<TokenMenuDropdown
onClose={() => this.setState({ tokenMenuOpen: false })}
token={{ symbol, address }}
/>
)}
</>
)
const warning = outdatedBalance const warning = outdatedBalance
? ( ? (
<Tooltip <Tooltip
@ -117,10 +89,8 @@ export default class TokenCell extends Component {
return ( return (
<AssetListItem <AssetListItem
active={selectedTokenAddress === address}
className={classnames('token-cell', { 'token-cell--outdated': outdatedBalance })} className={classnames('token-cell', { 'token-cell--outdated': outdatedBalance })}
iconClassName="token-cell__icon" iconClassName="token-cell__icon"
menu={menu}
onClick={onClick.bind(null, address)} onClick={onClick.bind(null, address)}
tokenAddress={address} tokenAddress={address}
tokenImage={image} tokenImage={image}

View File

@ -7,7 +7,6 @@ function mapStateToProps (state) {
contractExchangeRates: state.metamask.contractExchangeRates, contractExchangeRates: state.metamask.contractExchangeRates,
conversionRate: state.metamask.conversionRate, conversionRate: state.metamask.conversionRate,
currentCurrency: state.metamask.currentCurrency, currentCurrency: state.metamask.currentCurrency,
selectedTokenAddress: state.metamask.selectedTokenAddress,
userAddress: getSelectedAddress(state), userAddress: getSelectedAddress(state),
} }
} }

View File

@ -34,10 +34,6 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
opacity: 0.5 opacity: 0.5
} }
&__ellipsis {
line-height: 38px;
}
&__balance-wrapper { &__balance-wrapper {
flex: 1; flex: 1;
flex-flow: row wrap; flex-flow: row wrap;

View File

@ -15,7 +15,6 @@ describe('Token Cell', function () {
const state = { const state = {
metamask: { metamask: {
currentCurrency: 'usd', currentCurrency: 'usd',
selectedTokenAddress: '0xToken',
selectedAddress: '0xAddress', selectedAddress: '0xAddress',
contractExchangeRates: { contractExchangeRates: {
'0xAnotherToken': 0.015, '0xAnotherToken': 0.015,

View File

@ -1,4 +1,5 @@
import React, { useContext } from 'react' import React, { useContext } from 'react'
import PropTypes from 'prop-types'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import classnames from 'classnames' import classnames from 'classnames'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
@ -15,7 +16,7 @@ import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
import { showModal } from '../../../store/actions' import { showModal } from '../../../store/actions'
import { isBalanceCached, getSelectedAccount, getShouldShowFiat } from '../../../selectors/selectors' import { isBalanceCached, getSelectedAccount, getShouldShowFiat } from '../../../selectors/selectors'
const EthOverview = () => { const EthOverview = ({ className }) => {
const dispatch = useDispatch() const dispatch = useDispatch()
const t = useContext(I18nContext) const t = useContext(I18nContext)
const sendEvent = useMetricEvent({ const sendEvent = useMetricEvent({
@ -99,13 +100,18 @@ const EthOverview = () => {
</Button> </Button>
</> </>
)} )}
icon={<Identicon diameter={50} />} className={className}
icon={<Identicon diameter={32} />}
/> />
) )
} }
EthOverview.propTypes = { EthOverview.propTypes = {
className: PropTypes.string,
}
EthOverview.defaultProps = {
className: undefined,
} }
export default EthOverview export default EthOverview

View File

@ -3,11 +3,12 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex: 1; flex: 1;
height: 54px; height: 209px;
min-width: 0; min-width: 0;
padding-top: 10px;
flex-direction: column; flex-direction: column;
height: initial;
width: 100%; width: 100%;
&__balance { &__balance {
@ -21,7 +22,8 @@
&__buttons { &__buttons {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-bottom: 16px; height: 44px;
margin-bottom: 24px;
} }
} }

View File

@ -11,9 +11,9 @@ import WalletOverview from './wallet-overview'
import { SEND_ROUTE } from '../../../helpers/constants/routes' import { SEND_ROUTE } from '../../../helpers/constants/routes'
import { useMetricEvent } from '../../../hooks/useMetricEvent' import { useMetricEvent } from '../../../hooks/useMetricEvent'
import { getAssetImages } from '../../../selectors/selectors' import { getAssetImages } from '../../../selectors/selectors'
import { updateSend } from '../../../store/actions' import { updateSendToken } from '../../../store/actions'
const TokenOverview = ({ token }) => { const TokenOverview = ({ className, token }) => {
const dispatch = useDispatch() const dispatch = useDispatch()
const t = useContext(I18nContext) const t = useContext(I18nContext)
const sendTokenEvent = useMetricEvent({ const sendTokenEvent = useMetricEvent({
@ -43,16 +43,17 @@ const TokenOverview = ({ token }) => {
className="token-overview__button" className="token-overview__button"
onClick={() => { onClick={() => {
sendTokenEvent() sendTokenEvent()
dispatch(updateSend({ token })) dispatch(updateSendToken(token))
history.push(SEND_ROUTE) history.push(SEND_ROUTE)
}} }}
> >
{ t('send') } { t('send') }
</Button> </Button>
)} )}
className={className}
icon={( icon={(
<Identicon <Identicon
diameter={50} diameter={32}
address={token.address} address={token.address}
image={assetImages[token.address]} image={assetImages[token.address]}
/> />
@ -62,6 +63,7 @@ const TokenOverview = ({ token }) => {
} }
TokenOverview.propTypes = { TokenOverview.propTypes = {
className: PropTypes.string,
token: PropTypes.shape({ token: PropTypes.shape({
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
decimals: PropTypes.number, decimals: PropTypes.number,
@ -69,4 +71,8 @@ TokenOverview.propTypes = {
}).isRequired, }).isRequired,
} }
TokenOverview.defaultProps = {
className: undefined,
}
export default TokenOverview export default TokenOverview

View File

@ -1,9 +1,10 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames'
const WalletOverview = ({ balance, buttons, icon }) => { const WalletOverview = ({ balance, buttons, className, icon }) => {
return ( return (
<div className="wallet-overview"> <div className={classnames('wallet-overview', className)}>
<div className="wallet-overview__balance"> <div className="wallet-overview__balance">
{ icon } { icon }
{ balance } { balance }
@ -18,7 +19,12 @@ const WalletOverview = ({ balance, buttons, icon }) => {
WalletOverview.propTypes = { WalletOverview.propTypes = {
balance: PropTypes.element.isRequired, balance: PropTypes.element.isRequired,
buttons: PropTypes.element.isRequired, buttons: PropTypes.element.isRequired,
className: PropTypes.string,
icon: PropTypes.element.isRequired, icon: PropTypes.element.isRequired,
} }
WalletOverview.defaultProps = {
className: undefined,
}
export default WalletOverview export default WalletOverview

View File

@ -6,11 +6,11 @@ import { captureException } from '@sentry/browser'
import { import {
getCurrentNetworkId, getCurrentNetworkId,
getSelectedAsset,
getAccountType, getAccountType,
getNumberOfAccounts, getNumberOfAccounts,
getNumberOfTokens, getNumberOfTokens,
} from '../selectors/selectors' } from '../selectors/selectors'
import { getSendToken } from '../selectors/send'
import { import {
txDataSelector, txDataSelector,
} from '../selectors/confirm-transaction' } from '../selectors/confirm-transaction'
@ -31,7 +31,7 @@ export function MetaMetricsProvider ({ children }) {
const txData = useSelector(txDataSelector) || {} const txData = useSelector(txDataSelector) || {}
const network = useSelector(getCurrentNetworkId) const network = useSelector(getCurrentNetworkId)
const environmentType = getEnvironmentType() const environmentType = getEnvironmentType()
const activeCurrency = useSelector(getSelectedAsset) const activeCurrency = useSelector(getSendToken)?.symbol
const accountType = useSelector(getAccountType) const accountType = useSelector(getAccountType)
const confirmTransactionOrigin = txData.origin const confirmTransactionOrigin = txData.origin
const metaMetricsId = useSelector((state) => state.metamask.metaMetricsId) const metaMetricsId = useSelector((state) => state.metamask.metaMetricsId)

View File

@ -0,0 +1,38 @@
import { createSlice } from '@reduxjs/toolkit'
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../helpers/constants/routes'
// Constants
const initialState = {
mostRecentOverviewPage: DEFAULT_ROUTE,
}
const name = 'history'
// Slice (reducer plus auto-generated actions and action creators)
const slice = createSlice({
name,
initialState,
reducers: {
pageChanged: (state, action) => {
const path = action.payload
if (path === DEFAULT_ROUTE || path.startsWith(ASSET_ROUTE)) {
state.mostRecentOverviewPage = path
}
},
},
})
const { actions, reducer } = slice
export default reducer
// Selectors
export const getMostRecentOverviewPage = (state) => state[name].mostRecentOverviewPage
// Actions / action-creators
export const { pageChanged } = actions

View File

@ -6,6 +6,7 @@ import appStateReducer from './app/app'
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck' import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck'
import gasReducer from './gas/gas.duck' import gasReducer from './gas/gas.duck'
import { switchToConnected, unconnectedAccount } from './alerts' import { switchToConnected, unconnectedAccount } from './alerts'
import historyReducer from './history/history'
import { ALERT_TYPES } from '../../../app/scripts/controllers/alert' import { ALERT_TYPES } from '../../../app/scripts/controllers/alert'
export default combineReducers({ export default combineReducers({
@ -14,6 +15,7 @@ export default combineReducers({
activeTab: (s) => (s === undefined ? null : s), activeTab: (s) => (s === undefined ? null : s),
metamask: metamaskReducer, metamask: metamaskReducer,
appState: appStateReducer, appState: appStateReducer,
history: historyReducer,
send: sendReducer, send: sendReducer,
confirmTransaction: confirmTransactionReducer, confirmTransaction: confirmTransactionReducer,
gas: gasReducer, gas: gasReducer,

View File

@ -11,7 +11,6 @@ export default function reduceMetamask (state = {}, action) {
unapprovedTxs: {}, unapprovedTxs: {},
frequentRpcList: [], frequentRpcList: [],
addressBook: [], addressBook: [],
selectedTokenAddress: null,
contractExchangeRates: {}, contractExchangeRates: {},
tokens: [], tokens: [],
pendingTokens: {}, pendingTokens: {},
@ -87,33 +86,6 @@ export default function reduceMetamask (state = {}, action) {
selectedAddress: action.value, selectedAddress: action.value,
} }
case actionConstants.SET_SELECTED_TOKEN: {
const newState = {
...metamaskState,
selectedTokenAddress: action.value,
}
const newSend = { ...metamaskState.send }
if (metamaskState.send.editingTransactionId && !action.value) {
delete newSend.token
const unapprovedTx = newState.unapprovedTxs[newSend.editingTransactionId] || {}
const txParams = unapprovedTx.txParams || {}
newState.unapprovedTxs = {
...newState.unapprovedTxs,
[newSend.editingTransactionId]: {
...unapprovedTx,
txParams: { ...txParams, data: '' },
},
}
newSend.tokenBalance = null
newSend.balance = '0'
newSend.from = unapprovedTx.from || ''
}
newState.send = newSend
return newState
}
case actionConstants.SET_ACCOUNT_LABEL: case actionConstants.SET_ACCOUNT_LABEL:
const account = action.value.account const account = action.value.account
const name = action.value.label const name = action.value.label
@ -227,6 +199,35 @@ export default function reduceMetamask (state = {}, action) {
}, },
}) })
case actionConstants.UPDATE_SEND_TOKEN:
const newSend = {
...metamaskState.send,
token: action.value,
}
// erase token-related state when switching back to native currency
if (newSend.editingTransactionId && !newSend.token) {
const unapprovedTx = newSend?.unapprovedTxs?.[newSend.editingTransactionId] || {}
const txParams = unapprovedTx.txParams || {}
Object.assign(newSend, {
tokenBalance: null,
balance: '0',
from: unapprovedTx.from || '',
unapprovedTxs: {
...newSend.unapprovedTxs,
[newSend.editingTransactionId]: {
...unapprovedTx,
txParams: {
...txParams,
data: '',
},
},
},
})
}
return Object.assign(metamaskState, {
send: newSend,
})
case actionConstants.UPDATE_SEND_ENS_RESOLUTION: case actionConstants.UPDATE_SEND_ENS_RESOLUTION:
return { return {
...metamaskState, ...metamaskState,

View File

@ -1,6 +1,7 @@
const DEFAULT_ROUTE = '/' const DEFAULT_ROUTE = '/'
const UNLOCK_ROUTE = '/unlock' const UNLOCK_ROUTE = '/unlock'
const LOCK_ROUTE = '/lock' const LOCK_ROUTE = '/lock'
const ASSET_ROUTE = '/asset'
const SETTINGS_ROUTE = '/settings' const SETTINGS_ROUTE = '/settings'
const GENERAL_ROUTE = '/settings/general' const GENERAL_ROUTE = '/settings/general'
const CONNECTIONS_ROUTE = '/settings/connections' const CONNECTIONS_ROUTE = '/settings/connections'
@ -57,6 +58,7 @@ const ENCRYPTION_PUBLIC_KEY_REQUEST_PATH = '/encryption-public-key-request'
export { export {
DEFAULT_ROUTE, DEFAULT_ROUTE,
ALERTS_ROUTE, ALERTS_ROUTE,
ASSET_ROUTE,
UNLOCK_ROUTE, UNLOCK_ROUTE,
LOCK_ROUTE, LOCK_ROUTE,
SETTINGS_ROUTE, SETTINGS_ROUTE,

View File

@ -256,10 +256,6 @@ export function exportAsFile (filename, data, type = 'text/csv') {
} }
} }
export function getTokenAddressFromTokenObject (token) {
return Object.values(token)[0].address.toLowerCase()
}
/** /**
* Safely checksumms a potentially-null address * Safely checksumms a potentially-null address
* *

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import { checkExistingAddresses } from './util' import { checkExistingAddresses } from './util'
import { tokenInfoGetter } from '../../helpers/utils/token-util' import { tokenInfoGetter } from '../../helpers/utils/token-util'
import { DEFAULT_ROUTE, CONFIRM_ADD_TOKEN_ROUTE } from '../../helpers/constants/routes' import { CONFIRM_ADD_TOKEN_ROUTE } from '../../helpers/constants/routes'
import TextField from '../../components/ui/text-field' import TextField from '../../components/ui/text-field'
import TokenList from './token-list' import TokenList from './token-list'
import TokenSearch from './token-search' import TokenSearch from './token-search'
@ -24,6 +24,7 @@ class AddToken extends Component {
clearPendingTokens: PropTypes.func, clearPendingTokens: PropTypes.func,
tokens: PropTypes.array, tokens: PropTypes.array,
identities: PropTypes.object, identities: PropTypes.object,
mostRecentOverviewPage: PropTypes.string.isRequired,
} }
state = { state = {
@ -307,7 +308,7 @@ class AddToken extends Component {
} }
render () { render () {
const { history, clearPendingTokens } = this.props const { history, clearPendingTokens, mostRecentOverviewPage } = this.props
return ( return (
<PageContainer <PageContainer
@ -317,7 +318,7 @@ class AddToken extends Component {
disabled={this.hasError() || !this.hasSelected()} disabled={this.hasError() || !this.hasSelected()}
onCancel={() => { onCancel={() => {
clearPendingTokens() clearPendingTokens()
history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}} }}
/> />
) )

View File

@ -2,11 +2,13 @@ import { connect } from 'react-redux'
import AddToken from './add-token.component' import AddToken from './add-token.component'
import { setPendingTokens, clearPendingTokens } from '../../store/actions' import { setPendingTokens, clearPendingTokens } from '../../store/actions'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
const mapStateToProps = ({ metamask }) => { const mapStateToProps = (state) => {
const { identities, tokens, pendingTokens } = metamask const { metamask: { identities, tokens, pendingTokens } } = state
return { return {
identities, identities,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
tokens, tokens,
pendingTokens, pendingTokens,
} }

View File

@ -25,6 +25,7 @@ describe('Add Token', function () {
clearPendingTokens: sinon.spy(), clearPendingTokens: sinon.spy(),
tokens: [], tokens: [],
identities: {}, identities: {},
mostRecentOverviewPage: '/',
} }
describe('Add Token', function () { describe('Add Token', function () {

View File

@ -0,0 +1,65 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Redirect, useHistory, useParams } from 'react-router-dom'
import { createAccountLink } from '@metamask/etherscan-link'
import TransactionList from '../../components/app/transaction-list'
import { EthOverview, TokenOverview } from '../../components/app/wallet-overview'
import { getCurrentNetworkId, getSelectedIdentity } from '../../selectors/selectors'
import { getTokens } from '../../ducks/metamask/metamask'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
import { showModal } from '../../store/actions'
import AssetNavigation from './components/asset-navigation'
import TokenOptions from './components/token-options'
const Asset = () => {
const dispatch = useDispatch()
const network = useSelector(getCurrentNetworkId)
const selectedAccountName = useSelector((state) => getSelectedIdentity(state).name)
const nativeCurrency = useSelector((state) => state.metamask.nativeCurrency)
const tokens = useSelector(getTokens)
const history = useHistory()
const { asset } = useParams()
const token = tokens.find((token) => token.address === asset)
let assetName
let optionsButton
if (token) {
assetName = token.symbol
optionsButton = (
<TokenOptions
onRemove={() => dispatch(showModal({ name: 'HIDE_TOKEN_CONFIRMATION', token }))}
onViewEtherscan={() => {
const url = createAccountLink(token.address, network)
global.platform.openTab({ url })
}}
tokenSymbol={token.symbol}
/>
)
} else if (asset === nativeCurrency) {
assetName = nativeCurrency
} else {
return <Redirect to={{ pathname: DEFAULT_ROUTE }} />
}
const overview = token
? <TokenOverview className="asset__overview" token={token} />
: <EthOverview className="asset__overview" />
return (
<div className="main-container asset__container">
<AssetNavigation
accountName={selectedAccountName}
assetName={assetName}
onBack={() => history.push(DEFAULT_ROUTE)}
optionsButton={optionsButton}
/>
{ overview }
<TransactionList tokenAddress={token?.address} />
</div>
)
}
export default Asset

View File

@ -0,0 +1,45 @@
.asset {
&__container {
background-color: white;
}
&__overview {
box-shadow: 0px 3px 4px rgba(135, 134, 134, 0.16);
}
}
.asset-navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
height: 54px;
}
.asset-breadcrumb {
font-size: 14px;
color: $Black-100;
&__chevron {
padding: 0 10px 0 2px;
font-size: 16px;
background-color: inherit;
}
&__asset {
font-weight: bold;
}
}
.token-options {
&__button {
font-size: 20px;
color: $Black-100;
background-color: inherit;
padding: 2px 8px;
}
&__icon {
font-size: 16px;
}
}

View File

@ -0,0 +1,25 @@
import React from 'react'
import PropTypes from 'prop-types'
const AssetBreadcrumb = ({ accountName, assetName, onBack }) => {
return (
<div className="asset-breadcrumb">
<button className="fas fa-chevron-left asset-breadcrumb__chevron" data-testid="asset__back" onClick={onBack} />
<span>
{accountName}
</span>
&nbsp;/&nbsp;
<span className="asset-breadcrumb__asset">
{ assetName }
</span>
</div>
)
}
AssetBreadcrumb.propTypes = {
accountName: PropTypes.string.isRequired,
assetName: PropTypes.string.isRequired,
onBack: PropTypes.func.isRequired,
}
export default AssetBreadcrumb

View File

@ -0,0 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import AssetBreadcrumb from './asset-breadcrumb'
const AssetNavigation = ({ accountName, assetName, onBack, optionsButton }) => {
return (
<div className="asset-navigation">
<AssetBreadcrumb accountName={accountName} assetName={assetName} onBack={onBack} />
{ optionsButton }
</div>
)
}
AssetNavigation.propTypes = {
accountName: PropTypes.string.isRequired,
assetName: PropTypes.string.isRequired,
onBack: PropTypes.func.isRequired,
optionsButton: PropTypes.element,
}
AssetNavigation.defaultProps = {
optionsButton: undefined,
}
export default AssetNavigation

View File

@ -0,0 +1,59 @@
import React, { useContext, useState } from 'react'
import PropTypes from 'prop-types'
import { I18nContext } from '../../../contexts/i18n'
import { Menu, MenuItem } from '../../../components/ui/menu'
const TokenOptions = ({ onRemove, onViewEtherscan, tokenSymbol }) => {
const t = useContext(I18nContext)
const [tokenOptionsButtonElement, setTokenOptionsButtonElement] = useState(null)
const [tokenOptionsOpen, setTokenOptionsOpen] = useState(false)
return (
<>
<button
className="fas fa-ellipsis-v token-options__button"
data-testid="token-options__button"
onClick={() => setTokenOptionsOpen(true)}
ref={setTokenOptionsButtonElement}
title={t('tokenOptions')}
/>
{
tokenOptionsOpen
? (
<Menu anchorElement={tokenOptionsButtonElement} onHide={() => setTokenOptionsOpen(false)} >
<MenuItem
iconClassName="fas fa-external-link-alt token-options__icon"
data-testid="token-options__etherscan"
onClick={() => {
setTokenOptionsOpen(false)
onViewEtherscan()
}}
>
{ t('viewOnEtherscan') }
</MenuItem>
<MenuItem
iconClassName="fas fa-trash-alt token-options__icon"
data-testid="token-options__hide"
onClick={() => {
setTokenOptionsOpen(false)
onRemove()
}}
>
{ t('hideTokenSymbol', [tokenSymbol]) }
</MenuItem>
</Menu>
)
: null
}
</>
)
}
TokenOptions.propTypes = {
onRemove: PropTypes.func.isRequired,
onViewEtherscan: PropTypes.func.isRequired,
tokenSymbol: PropTypes.string.isRequired,
}
export default TokenOptions

View File

@ -0,0 +1 @@
export { default } from './asset'

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
import Button from '../../components/ui/button' import Button from '../../components/ui/button'
import Identicon from '../../components/ui/identicon' import Identicon from '../../components/ui/identicon'
import TokenBalance from '../../components/ui/token-balance' import TokenBalance from '../../components/ui/token-balance'
@ -13,16 +12,17 @@ export default class ConfirmAddSuggestedToken extends Component {
static propTypes = { static propTypes = {
history: PropTypes.object, history: PropTypes.object,
addToken: PropTypes.func, addToken: PropTypes.func,
mostRecentOverviewPage: PropTypes.string.isRequired,
pendingTokens: PropTypes.object, pendingTokens: PropTypes.object,
removeSuggestedTokens: PropTypes.func, removeSuggestedTokens: PropTypes.func,
tokens: PropTypes.array, tokens: PropTypes.array,
} }
componentDidMount () { componentDidMount () {
const { pendingTokens = {}, history } = this.props const { mostRecentOverviewPage, pendingTokens = {}, history } = this.props
if (Object.keys(pendingTokens).length === 0) { if (Object.keys(pendingTokens).length === 0) {
history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
} }
} }
@ -33,7 +33,7 @@ export default class ConfirmAddSuggestedToken extends Component {
} }
render () { render () {
const { addToken, pendingTokens, tokens, removeSuggestedTokens, history } = this.props const { addToken, pendingTokens, tokens, removeSuggestedTokens, history, mostRecentOverviewPage } = this.props
const pendingTokenKey = Object.keys(pendingTokens)[0] const pendingTokenKey = Object.keys(pendingTokens)[0]
const pendingToken = pendingTokens[pendingTokenKey] const pendingToken = pendingTokens[pendingTokenKey]
const hasTokenDuplicates = this.checkTokenDuplicates(pendingTokens, tokens) const hasTokenDuplicates = this.checkTokenDuplicates(pendingTokens, tokens)
@ -113,7 +113,7 @@ export default class ConfirmAddSuggestedToken extends Component {
className="page-container__footer-button" className="page-container__footer-button"
onClick={() => { onClick={() => {
removeSuggestedTokens() removeSuggestedTokens()
.then(() => history.push(DEFAULT_ROUTE)) .then(() => history.push(mostRecentOverviewPage))
}} }}
> >
{ this.context.t('cancel') } { this.context.t('cancel') }
@ -125,7 +125,7 @@ export default class ConfirmAddSuggestedToken extends Component {
onClick={() => { onClick={() => {
addToken(pendingToken) addToken(pendingToken)
.then(() => removeSuggestedTokens()) .then(() => removeSuggestedTokens())
.then(() => history.push(DEFAULT_ROUTE)) .then(() => history.push(mostRecentOverviewPage))
}} }}
> >
{ this.context.t('addToken') } { this.context.t('addToken') }

View File

@ -3,12 +3,14 @@ import { compose } from 'redux'
import ConfirmAddSuggestedToken from './confirm-add-suggested-token.component' import ConfirmAddSuggestedToken from './confirm-add-suggested-token.component'
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import { addToken, removeSuggestedTokens } from '../../store/actions' import { addToken, removeSuggestedTokens } from '../../store/actions'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
const mapStateToProps = ({ metamask }) => { const mapStateToProps = (state) => {
const { pendingTokens, suggestedTokens, tokens } = metamask const { metamask: { pendingTokens, suggestedTokens, tokens } } = state
const params = { ...pendingTokens, ...suggestedTokens } const params = { ...pendingTokens, ...suggestedTokens }
return { return {
mostRecentOverviewPage: getMostRecentOverviewPage(state),
pendingTokens: params, pendingTokens: params,
tokens, tokens,
} }

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { DEFAULT_ROUTE, ADD_TOKEN_ROUTE } from '../../helpers/constants/routes' import { ASSET_ROUTE, ADD_TOKEN_ROUTE } from '../../helpers/constants/routes'
import Button from '../../components/ui/button' import Button from '../../components/ui/button'
import Identicon from '../../components/ui/identicon' import Identicon from '../../components/ui/identicon'
import TokenBalance from '../../components/ui/token-balance' import TokenBalance from '../../components/ui/token-balance'
@ -14,14 +14,15 @@ export default class ConfirmAddToken extends Component {
history: PropTypes.object, history: PropTypes.object,
clearPendingTokens: PropTypes.func, clearPendingTokens: PropTypes.func,
addTokens: PropTypes.func, addTokens: PropTypes.func,
mostRecentOverviewPage: PropTypes.string.isRequired,
pendingTokens: PropTypes.object, pendingTokens: PropTypes.object,
} }
componentDidMount () { componentDidMount () {
const { pendingTokens = {}, history } = this.props const { mostRecentOverviewPage, pendingTokens = {}, history } = this.props
if (Object.keys(pendingTokens).length === 0) { if (Object.keys(pendingTokens).length === 0) {
history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
} }
} }
@ -32,7 +33,7 @@ export default class ConfirmAddToken extends Component {
} }
render () { render () {
const { history, addTokens, clearPendingTokens, pendingTokens } = this.props const { history, addTokens, clearPendingTokens, mostRecentOverviewPage, pendingTokens } = this.props
return ( return (
<div className="page-container"> <div className="page-container">
@ -103,7 +104,12 @@ export default class ConfirmAddToken extends Component {
addTokens(pendingTokens) addTokens(pendingTokens)
.then(() => { .then(() => {
clearPendingTokens() clearPendingTokens()
history.push(DEFAULT_ROUTE) const firstTokenAddress = Object.values(pendingTokens)?.[0].address?.toLowerCase()
if (firstTokenAddress) {
history.push(`${ASSET_ROUTE}/${firstTokenAddress}`)
} else {
history.push(mostRecentOverviewPage)
}
}) })
}} }}
> >

View File

@ -2,10 +2,12 @@ import { connect } from 'react-redux'
import ConfirmAddToken from './confirm-add-token.component' import ConfirmAddToken from './confirm-add-token.component'
import { addTokens, clearPendingTokens } from '../../store/actions' import { addTokens, clearPendingTokens } from '../../store/actions'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
const mapStateToProps = ({ metamask }) => { const mapStateToProps = (state) => {
const { pendingTokens } = metamask const { metamask: { pendingTokens } } = state
return { return {
mostRecentOverviewPage: getMostRecentOverviewPage(state),
pendingTokens, pendingTokens,
} }
} }

View File

@ -11,7 +11,6 @@ import Tooltip from '../../components/ui/tooltip-v2'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums' import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { conversionUtil } from '../../helpers/utils/conversion-util' import { conversionUtil } from '../../helpers/utils/conversion-util'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
export default class ConfirmDecryptMessage extends Component { export default class ConfirmDecryptMessage extends Component {
static contextTypes = { static contextTypes = {
@ -31,6 +30,7 @@ export default class ConfirmDecryptMessage extends Component {
decryptMessageInline: PropTypes.func.isRequired, decryptMessageInline: PropTypes.func.isRequired,
conversionRate: PropTypes.number, conversionRate: PropTypes.number,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
mostRecentOverviewPage: PropTypes.string.isRequired,
requesterAddress: PropTypes.string, requesterAddress: PropTypes.string,
txData: PropTypes.object, txData: PropTypes.object,
domainMetadata: PropTypes.object, domainMetadata: PropTypes.object,
@ -278,7 +278,14 @@ export default class ConfirmDecryptMessage extends Component {
} }
renderFooter = () => { renderFooter = () => {
const { txData } = this.props const {
cancelDecryptMessage,
clearConfirmTransaction,
decryptMessage,
history,
mostRecentOverviewPage,
txData,
} = this.props
return ( return (
<div className="request-decrypt-message__footer"> <div className="request-decrypt-message__footer">
@ -288,7 +295,7 @@ export default class ConfirmDecryptMessage extends Component {
className="request-decrypt-message__footer__cancel-button" className="request-decrypt-message__footer__cancel-button"
onClick={async (event) => { onClick={async (event) => {
this._removeBeforeUnload() this._removeBeforeUnload()
await this.props.cancelDecryptMessage(txData, event) await cancelDecryptMessage(txData, event)
this.context.metricsEvent({ this.context.metricsEvent({
eventOpts: { eventOpts: {
category: 'Messages', category: 'Messages',
@ -296,8 +303,8 @@ export default class ConfirmDecryptMessage extends Component {
name: 'Cancel', name: 'Cancel',
}, },
}) })
this.props.clearConfirmTransaction() clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}} }}
> >
{ this.context.t('cancel') } { this.context.t('cancel') }
@ -308,7 +315,7 @@ export default class ConfirmDecryptMessage extends Component {
className="request-decrypt-message__footer__sign-button" className="request-decrypt-message__footer__sign-button"
onClick={async (event) => { onClick={async (event) => {
this._removeBeforeUnload() this._removeBeforeUnload()
await this.props.decryptMessage(txData, event) await decryptMessage(txData, event)
this.context.metricsEvent({ this.context.metricsEvent({
eventOpts: { eventOpts: {
category: 'Messages', category: 'Messages',
@ -316,8 +323,8 @@ export default class ConfirmDecryptMessage extends Component {
name: 'Confirm', name: 'Confirm',
}, },
}) })
this.props.clearConfirmTransaction() clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}} }}
> >
{ this.context.t('decrypt') } { this.context.t('decrypt') }

View File

@ -14,6 +14,7 @@ import {
} from '../../selectors' } from '../../selectors'
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck' import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
import ConfirmDecryptMessage from './confirm-decrypt-message.component' import ConfirmDecryptMessage from './confirm-decrypt-message.component'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
function mapStateToProps (state) { function mapStateToProps (state) {
const { confirmTransaction, const { confirmTransaction,
@ -35,6 +36,7 @@ function mapStateToProps (state) {
requester: null, requester: null,
requesterAddress: null, requesterAddress: null,
conversionRate: conversionRateSelector(state), conversionRate: conversionRateSelector(state),
mostRecentOverviewPage: getMostRecentOverviewPage(state),
} }
} }

View File

@ -8,7 +8,6 @@ import Identicon from '../../components/ui/identicon'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums' import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { conversionUtil } from '../../helpers/utils/conversion-util' import { conversionUtil } from '../../helpers/utils/conversion-util'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
export default class ConfirmEncryptionPublicKey extends Component { export default class ConfirmEncryptionPublicKey extends Component {
static contextTypes = { static contextTypes = {
@ -30,6 +29,7 @@ export default class ConfirmEncryptionPublicKey extends Component {
requesterAddress: PropTypes.string, requesterAddress: PropTypes.string,
txData: PropTypes.object, txData: PropTypes.object,
domainMetadata: PropTypes.object, domainMetadata: PropTypes.object,
mostRecentOverviewPage: PropTypes.string.isRequired,
} }
state = { state = {
@ -183,7 +183,14 @@ export default class ConfirmEncryptionPublicKey extends Component {
} }
renderFooter = () => { renderFooter = () => {
const { txData } = this.props const {
cancelEncryptionPublicKey,
clearConfirmTransaction,
encryptionPublicKey,
history,
mostRecentOverviewPage,
txData,
} = this.props
return ( return (
<div className="request-encryption-public-key__footer"> <div className="request-encryption-public-key__footer">
@ -193,7 +200,7 @@ export default class ConfirmEncryptionPublicKey extends Component {
className="request-encryption-public-key__footer__cancel-button" className="request-encryption-public-key__footer__cancel-button"
onClick={async (event) => { onClick={async (event) => {
this._removeBeforeUnload() this._removeBeforeUnload()
await this.props.cancelEncryptionPublicKey(txData, event) await cancelEncryptionPublicKey(txData, event)
this.context.metricsEvent({ this.context.metricsEvent({
eventOpts: { eventOpts: {
category: 'Messages', category: 'Messages',
@ -201,8 +208,8 @@ export default class ConfirmEncryptionPublicKey extends Component {
name: 'Cancel', name: 'Cancel',
}, },
}) })
this.props.clearConfirmTransaction() clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}} }}
> >
{ this.context.t('cancel') } { this.context.t('cancel') }
@ -213,7 +220,7 @@ export default class ConfirmEncryptionPublicKey extends Component {
className="request-encryption-public-key__footer__sign-button" className="request-encryption-public-key__footer__sign-button"
onClick={async (event) => { onClick={async (event) => {
this._removeBeforeUnload() this._removeBeforeUnload()
await this.props.encryptionPublicKey(txData, event) await encryptionPublicKey(txData, event)
this.context.metricsEvent({ this.context.metricsEvent({
eventOpts: { eventOpts: {
category: 'Messages', category: 'Messages',
@ -221,8 +228,8 @@ export default class ConfirmEncryptionPublicKey extends Component {
name: 'Confirm', name: 'Confirm',
}, },
}) })
this.props.clearConfirmTransaction() clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}} }}
> >
{ this.context.t('provide') } { this.context.t('provide') }

View File

@ -15,6 +15,7 @@ import {
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck' import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
import ConfirmEncryptionPublicKey from './confirm-encryption-public-key.component' import ConfirmEncryptionPublicKey from './confirm-encryption-public-key.component'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
function mapStateToProps (state) { function mapStateToProps (state) {
const { confirmTransaction, const { confirmTransaction,
@ -36,6 +37,7 @@ function mapStateToProps (state) {
requester: null, requester: null,
requesterAddress: null, requesterAddress: null,
conversionRate: conversionRateSelector(state), conversionRate: conversionRateSelector(state),
mostRecentOverviewPage: getMostRecentOverviewPage(state),
} }
} }

View File

@ -5,7 +5,7 @@ import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums
import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container' import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container'
import { isBalanceSufficient } from '../send/send.utils' import { isBalanceSufficient } from '../send/send.utils'
import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes' import { CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes'
import { import {
INSUFFICIENT_FUNDS_ERROR_KEY, INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY, TRANSACTION_ERROR_KEY,
@ -96,6 +96,7 @@ export default class ConfirmTransactionBase extends Component {
tryReverseResolveAddress: PropTypes.func.isRequired, tryReverseResolveAddress: PropTypes.func.isRequired,
hideSenderToRecipient: PropTypes.bool, hideSenderToRecipient: PropTypes.bool,
showAccountInHeader: PropTypes.bool, showAccountInHeader: PropTypes.bool,
mostRecentOverviewPage: PropTypes.string.isRequired,
} }
state = { state = {
@ -110,6 +111,7 @@ export default class ConfirmTransactionBase extends Component {
showTransactionConfirmedModal, showTransactionConfirmedModal,
history, history,
clearConfirmTransaction, clearConfirmTransaction,
mostRecentOverviewPage,
nextNonce, nextNonce,
customNonceValue, customNonceValue,
toAddress, toAddress,
@ -136,7 +138,7 @@ export default class ConfirmTransactionBase extends Component {
showTransactionConfirmedModal({ showTransactionConfirmedModal({
onSubmit: () => { onSubmit: () => {
clearConfirmTransaction() clearConfirmTransaction()
history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}, },
}) })
} }
@ -383,6 +385,7 @@ export default class ConfirmTransactionBase extends Component {
cancelAllTransactions, cancelAllTransactions,
clearConfirmTransaction, clearConfirmTransaction,
history, history,
mostRecentOverviewPage,
showRejectTransactionsConfirmationModal, showRejectTransactionsConfirmationModal,
unapprovedTxCount, unapprovedTxCount,
} = this.props } = this.props
@ -393,7 +396,7 @@ export default class ConfirmTransactionBase extends Component {
this._removeBeforeUnload() this._removeBeforeUnload()
await cancelAllTransactions() await cancelAllTransactions()
clearConfirmTransaction() clearConfirmTransaction()
history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}, },
}) })
} }
@ -405,6 +408,7 @@ export default class ConfirmTransactionBase extends Component {
txData, txData,
cancelTransaction, cancelTransaction,
history, history,
mostRecentOverviewPage,
clearConfirmTransaction, clearConfirmTransaction,
actionKey, actionKey,
txData: { origin }, txData: { origin },
@ -432,7 +436,7 @@ export default class ConfirmTransactionBase extends Component {
cancelTransaction(txData) cancelTransaction(txData)
.then(() => { .then(() => {
clearConfirmTransaction() clearConfirmTransaction()
history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
}) })
} }
} }
@ -447,6 +451,7 @@ export default class ConfirmTransactionBase extends Component {
history, history,
onSubmit, onSubmit,
actionKey, actionKey,
mostRecentOverviewPage,
metaMetricsSendCount = 0, metaMetricsSendCount = 0,
setMetaMetricsSendCount, setMetaMetricsSendCount,
methodData = {}, methodData = {},
@ -493,7 +498,7 @@ export default class ConfirmTransactionBase extends Component {
this.setState({ this.setState({
submitting: false, submitting: false,
}, () => { }, () => {
history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
updateCustomNonce('') updateCustomNonce('')
}) })
}) })

View File

@ -37,6 +37,7 @@ import {
getPreferences, getPreferences,
transactionFeeSelector, transactionFeeSelector,
} from '../../selectors' } from '../../selectors'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => { const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
return { return {
@ -178,6 +179,7 @@ const mapStateToProps = (state, ownProps) => {
metaMetricsSendCount, metaMetricsSendCount,
transactionCategory, transactionCategory,
nextNonce, nextNonce,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
} }
} }

View File

@ -10,7 +10,7 @@ import R from 'ramda'
import SignatureRequest from '../../components/app/signature-request' import SignatureRequest from '../../components/app/signature-request'
import SignatureRequestOriginal from '../../components/app/signature-request-original' import SignatureRequestOriginal from '../../components/app/signature-request-original'
import Loading from '../../components/ui/loading-screen' import Loading from '../../components/ui/loading-screen'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes' import { getMostRecentOverviewPage } from '../../ducks/history/history'
function mapStateToProps (state) { function mapStateToProps (state) {
const { metamask, appState } = state const { metamask, appState } = state
@ -25,6 +25,7 @@ function mapStateToProps (state) {
return { return {
identities: state.metamask.identities, identities: state.metamask.identities,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
unapprovedTxs: state.metamask.unapprovedTxs, unapprovedTxs: state.metamask.unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs, unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs, unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
@ -45,6 +46,7 @@ function mapStateToProps (state) {
class ConfirmTxScreen extends Component { class ConfirmTxScreen extends Component {
static propTypes = { static propTypes = {
mostRecentOverviewPage: PropTypes.string.isRequired,
unapprovedMsgCount: PropTypes.number, unapprovedMsgCount: PropTypes.number,
unapprovedPersonalMsgCount: PropTypes.number, unapprovedPersonalMsgCount: PropTypes.number,
unapprovedTypedMessagesCount: PropTypes.number, unapprovedTypedMessagesCount: PropTypes.number,
@ -167,13 +169,15 @@ class ConfirmTxScreen extends Component {
componentDidMount () { componentDidMount () {
const { const {
unapprovedTxs = {}, unapprovedTxs = {},
history,
mostRecentOverviewPage,
network, network,
send, send,
} = this.props } = this.props
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) { if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) {
this.props.history.push(DEFAULT_ROUTE) history.push(mostRecentOverviewPage)
} }
} }
@ -185,6 +189,7 @@ class ConfirmTxScreen extends Component {
send, send,
history, history,
match: { params: { id: transactionId } = {} }, match: { params: { id: transactionId } = {} },
mostRecentOverviewPage,
} = this.props } = this.props
let prevTx let prevTx
@ -203,14 +208,14 @@ class ConfirmTxScreen extends Component {
if (prevTx && prevTx.status === 'dropped') { if (prevTx && prevTx.status === 'dropped') {
this.props.dispatch(actions.showModal({ this.props.dispatch(actions.showModal({
name: 'TRANSACTION_CONFIRMED', name: 'TRANSACTION_CONFIRMED',
onSubmit: () => history.push(DEFAULT_ROUTE), onSubmit: () => history.push(mostRecentOverviewPage),
})) }))
return return
} }
if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) { if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) {
this.props.history.push(DEFAULT_ROUTE) this.props.history.push(mostRecentOverviewPage)
} }
} }

View File

@ -14,7 +14,6 @@ import ConfirmDecryptMessage from '../confirm-decrypt-message'
import ConfirmEncryptionPublicKey from '../confirm-encryption-public-key' import ConfirmEncryptionPublicKey from '../confirm-encryption-public-key'
import { import {
DEFAULT_ROUTE,
CONFIRM_TRANSACTION_ROUTE, CONFIRM_TRANSACTION_ROUTE,
CONFIRM_DEPLOY_CONTRACT_PATH, CONFIRM_DEPLOY_CONTRACT_PATH,
CONFIRM_SEND_ETHER_PATH, CONFIRM_SEND_ETHER_PATH,
@ -39,6 +38,7 @@ export default class ConfirmTransaction extends Component {
setTransactionToConfirm: PropTypes.func, setTransactionToConfirm: PropTypes.func,
clearConfirmTransaction: PropTypes.func, clearConfirmTransaction: PropTypes.func,
fetchBasicGasAndTimeEstimates: PropTypes.func, fetchBasicGasAndTimeEstimates: PropTypes.func,
mostRecentOverviewPage: PropTypes.string.isRequired,
transaction: PropTypes.object, transaction: PropTypes.object,
getContractMethodData: PropTypes.func, getContractMethodData: PropTypes.func,
transactionId: PropTypes.string, transactionId: PropTypes.string,
@ -52,6 +52,7 @@ export default class ConfirmTransaction extends Component {
totalUnapprovedCount = 0, totalUnapprovedCount = 0,
send = {}, send = {},
history, history,
mostRecentOverviewPage,
transaction: { txParams: { data, to } = {} } = {}, transaction: { txParams: { data, to } = {} } = {},
fetchBasicGasAndTimeEstimates, fetchBasicGasAndTimeEstimates,
getContractMethodData, getContractMethodData,
@ -62,7 +63,7 @@ export default class ConfirmTransaction extends Component {
} = this.props } = this.props
if (!totalUnapprovedCount && !send.to) { if (!totalUnapprovedCount && !send.to) {
history.replace(DEFAULT_ROUTE) history.replace(mostRecentOverviewPage)
return return
} }
@ -86,6 +87,7 @@ export default class ConfirmTransaction extends Component {
paramsTransactionId, paramsTransactionId,
transactionId, transactionId,
history, history,
mostRecentOverviewPage,
totalUnapprovedCount, totalUnapprovedCount,
} = this.props } = this.props
@ -95,10 +97,10 @@ export default class ConfirmTransaction extends Component {
setTransactionToConfirm(paramsTransactionId) setTransactionToConfirm(paramsTransactionId)
return return
} else if (prevProps.transactionId && !transactionId && !totalUnapprovedCount) { } else if (prevProps.transactionId && !transactionId && !totalUnapprovedCount) {
history.replace(DEFAULT_ROUTE) history.replace(mostRecentOverviewPage)
return return
} else if (prevProps.transactionId && transactionId && prevProps.transactionId !== transactionId) { } else if (prevProps.transactionId && transactionId && prevProps.transactionId !== transactionId) {
history.replace(DEFAULT_ROUTE) history.replace(mostRecentOverviewPage)
return return
} }
} }

Some files were not shown because too many files have changed in this diff Show More