1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +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": {
"message": "ደብቅ"
},
"hideToken": {
"message": "ተለዋጭ ስም ደብቅ"
},
"hideTokenPrompt": {
"message": "ተለዋጭ ስም ይደበቅ?"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -102,7 +102,6 @@ initialize().catch(log.error)
* @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} 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 {Array} tokens - Tokens held by the current user, including their balances.
* @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'
const inDevelopment = process.env.NODE_ENV === 'development'
@ -8,7 +8,7 @@ const METAMETRICS_TRACKING_URL = inDevelopment
: 'http://www.metamask.io/metametrics-prod'
export default function backEndMetaMetricsEvent (metaMaskState, eventData) {
const stateEventData = getMetaMetricState({ metamask: metaMaskState })
const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState })
if (stateEventData.participateInMetaMetrics) {
sendMetaMetricsEvent({

View File

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

View File

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

View File

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

View File

@ -903,7 +903,6 @@ describe('MetaMask', 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 () => {
const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 1
@ -996,13 +995,6 @@ describe('MetaMask', function () {
const txStatuses = await driver.findElements(By.css('.list-item__heading'))
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'))
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.delay(regularDelayMs)
await driver.clickElement(By.css('[data-testid="home__history-tab"]'))
await driver.wait(async () => {
const pendingTxes = await driver.findElements(By.css('.transaction-list__pending-transactions .transaction-list-item'))
return pendingTxes.length === 1
@ -1227,12 +1217,9 @@ describe('MetaMask', function () {
describe('Hide token', 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'))
const byTokenMenuDropdownOption = By.css('.menu__item--clickable')
await driver.clickElement(byTokenMenuDropdownOption)
await driver.clickElement(By.css('[data-testid="token-options__hide"]'))
const confirmHideModal = await driver.findElement(By.css('span .modal'))
@ -1245,6 +1232,7 @@ describe('MetaMask', function () {
describe('Add existing token using search', 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.delay(regularDelayMs)
})

View File

@ -52,15 +52,6 @@ describe('MetaMask Reducers', function () {
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 () {
const state = reduceMetamask({}, {
type: actionConstants.SET_ACCOUNT_LABEL,

View File

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

View File

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

View File

@ -1,5 +1,6 @@
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 AddTokenButton from '../add-token-button'
import TokenList from '../token-list'
@ -9,14 +10,12 @@ import CurrencyDisplay from '../../ui/currency-display'
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
import { useMetricEvent } from '../../../hooks/useMetricEvent'
import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency'
import { getCurrentAccountWithSendEtherInfo, getShouldShowFiat } from '../../../selectors/selectors'
import { setSelectedToken } from '../../../store/actions'
import { getCurrentAccountWithSendEtherInfo, getNativeCurrency, getShouldShowFiat } from '../../../selectors'
const AssetList = () => {
const dispatch = useDispatch()
const AssetList = ({ onClickAsset }) => {
const history = useHistory()
const selectedAccountBalance = useSelector((state) => getCurrentAccountWithSendEtherInfo(state).balance)
const selectedTokenAddress = useSelector((state) => state.metamask.selectedTokenAddress)
const nativeCurrency = useSelector(getNativeCurrency)
const showFiat = useSelector(getShouldShowFiat)
const selectTokenEvent = useMetricEvent({
eventOpts: {
@ -45,8 +44,7 @@ const AssetList = () => {
return (
<>
<AssetListItem
active={!selectedTokenAddress}
onClick={() => dispatch(setSelectedToken())}
onClick={() => onClickAsset(nativeCurrency)}
data-testid="wallet-balance"
>
<CurrencyDisplay
@ -68,7 +66,7 @@ const AssetList = () => {
</AssetListItem>
<TokenList
onTokenClick={(tokenAddress) => {
dispatch(setSelectedToken(tokenAddress))
onClickAsset(tokenAddress)
selectTokenEvent()
}}
/>
@ -82,4 +80,8 @@ const AssetList = () => {
)
}
AssetList.propTypes = {
onClickAsset: PropTypes.func.isRequired,
}
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;
grid-template-columns: 30% minmax(30%, 1fr) 30%;
column-gap: 5px;
margin-bottom: 24px;
padding: 0 8px;
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 { conversionUtil } from '../../../helpers/utils/conversion-util'
import Button from '../../ui/button'
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'
export default class SignatureRequestOriginal extends Component {
static contextTypes = {
@ -28,6 +27,7 @@ export default class SignatureRequestOriginal extends Component {
clearConfirmTransaction: PropTypes.func.isRequired,
conversionRate: PropTypes.number,
history: PropTypes.object.isRequired,
mostRecentOverviewPage: PropTypes.string.isRequired,
requesterAddress: PropTypes.string,
sign: PropTypes.func.isRequired,
txData: PropTypes.object.isRequired,
@ -268,7 +268,7 @@ export default class SignatureRequestOriginal extends Component {
}
renderFooter = () => {
const { cancel, sign } = this.props
const { cancel, clearConfirmTransaction, history, mostRecentOverviewPage, sign } = this.props
return (
<div className="request-signature__footer">
@ -286,8 +286,8 @@ export default class SignatureRequestOriginal extends Component {
name: 'Cancel',
},
})
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
clearConfirmTransaction()
history.push(mostRecentOverviewPage)
}}
>
{ this.context.t('cancel') }
@ -306,8 +306,8 @@ export default class SignatureRequestOriginal extends Component {
name: 'Confirm',
},
})
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
clearConfirmTransaction()
history.push(mostRecentOverviewPage)
}}
>
{ this.context.t('sign') }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,11 +3,12 @@
justify-content: space-between;
align-items: center;
flex: 1;
height: 54px;
height: 209px;
min-width: 0;
padding-top: 10px;
flex-direction: column;
height: initial;
width: 100%;
&__balance {
@ -21,7 +22,8 @@
&__buttons {
display: flex;
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 { useMetricEvent } from '../../../hooks/useMetricEvent'
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 t = useContext(I18nContext)
const sendTokenEvent = useMetricEvent({
@ -43,16 +43,17 @@ const TokenOverview = ({ token }) => {
className="token-overview__button"
onClick={() => {
sendTokenEvent()
dispatch(updateSend({ token }))
dispatch(updateSendToken(token))
history.push(SEND_ROUTE)
}}
>
{ t('send') }
</Button>
)}
className={className}
icon={(
<Identicon
diameter={50}
diameter={32}
address={token.address}
image={assetImages[token.address]}
/>
@ -62,6 +63,7 @@ const TokenOverview = ({ token }) => {
}
TokenOverview.propTypes = {
className: PropTypes.string,
token: PropTypes.shape({
address: PropTypes.string.isRequired,
decimals: PropTypes.number,
@ -69,4 +71,8 @@ TokenOverview.propTypes = {
}).isRequired,
}
TokenOverview.defaultProps = {
className: undefined,
}
export default TokenOverview

View File

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

View File

@ -6,11 +6,11 @@ import { captureException } from '@sentry/browser'
import {
getCurrentNetworkId,
getSelectedAsset,
getAccountType,
getNumberOfAccounts,
getNumberOfTokens,
} from '../selectors/selectors'
import { getSendToken } from '../selectors/send'
import {
txDataSelector,
} from '../selectors/confirm-transaction'
@ -31,7 +31,7 @@ export function MetaMetricsProvider ({ children }) {
const txData = useSelector(txDataSelector) || {}
const network = useSelector(getCurrentNetworkId)
const environmentType = getEnvironmentType()
const activeCurrency = useSelector(getSelectedAsset)
const activeCurrency = useSelector(getSendToken)?.symbol
const accountType = useSelector(getAccountType)
const confirmTransactionOrigin = txData.origin
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 gasReducer from './gas/gas.duck'
import { switchToConnected, unconnectedAccount } from './alerts'
import historyReducer from './history/history'
import { ALERT_TYPES } from '../../../app/scripts/controllers/alert'
export default combineReducers({
@ -14,6 +15,7 @@ export default combineReducers({
activeTab: (s) => (s === undefined ? null : s),
metamask: metamaskReducer,
appState: appStateReducer,
history: historyReducer,
send: sendReducer,
confirmTransaction: confirmTransactionReducer,
gas: gasReducer,

View File

@ -11,7 +11,6 @@ export default function reduceMetamask (state = {}, action) {
unapprovedTxs: {},
frequentRpcList: [],
addressBook: [],
selectedTokenAddress: null,
contractExchangeRates: {},
tokens: [],
pendingTokens: {},
@ -87,33 +86,6 @@ export default function reduceMetamask (state = {}, action) {
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:
const account = action.value.account
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:
return {
...metamaskState,

View File

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

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import ethUtil from 'ethereumjs-util'
import { checkExistingAddresses } from './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 TokenList from './token-list'
import TokenSearch from './token-search'
@ -24,6 +24,7 @@ class AddToken extends Component {
clearPendingTokens: PropTypes.func,
tokens: PropTypes.array,
identities: PropTypes.object,
mostRecentOverviewPage: PropTypes.string.isRequired,
}
state = {
@ -307,7 +308,7 @@ class AddToken extends Component {
}
render () {
const { history, clearPendingTokens } = this.props
const { history, clearPendingTokens, mostRecentOverviewPage } = this.props
return (
<PageContainer
@ -317,7 +318,7 @@ class AddToken extends Component {
disabled={this.hasError() || !this.hasSelected()}
onCancel={() => {
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 { setPendingTokens, clearPendingTokens } from '../../store/actions'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
const mapStateToProps = ({ metamask }) => {
const { identities, tokens, pendingTokens } = metamask
const mapStateToProps = (state) => {
const { metamask: { identities, tokens, pendingTokens } } = state
return {
identities,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
tokens,
pendingTokens,
}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'
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 Identicon from '../../components/ui/identicon'
import TokenBalance from '../../components/ui/token-balance'
@ -14,14 +14,15 @@ export default class ConfirmAddToken extends Component {
history: PropTypes.object,
clearPendingTokens: PropTypes.func,
addTokens: PropTypes.func,
mostRecentOverviewPage: PropTypes.string.isRequired,
pendingTokens: PropTypes.object,
}
componentDidMount () {
const { pendingTokens = {}, history } = this.props
const { mostRecentOverviewPage, pendingTokens = {}, history } = this.props
if (Object.keys(pendingTokens).length === 0) {
history.push(DEFAULT_ROUTE)
history.push(mostRecentOverviewPage)
}
}
@ -32,7 +33,7 @@ export default class ConfirmAddToken extends Component {
}
render () {
const { history, addTokens, clearPendingTokens, pendingTokens } = this.props
const { history, addTokens, clearPendingTokens, mostRecentOverviewPage, pendingTokens } = this.props
return (
<div className="page-container">
@ -103,7 +104,12 @@ export default class ConfirmAddToken extends Component {
addTokens(pendingTokens)
.then(() => {
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 { addTokens, clearPendingTokens } from '../../store/actions'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
const mapStateToProps = ({ metamask }) => {
const { pendingTokens } = metamask
const mapStateToProps = (state) => {
const { metamask: { pendingTokens } } = state
return {
mostRecentOverviewPage: getMostRecentOverviewPage(state),
pendingTokens,
}
}

View File

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

View File

@ -14,6 +14,7 @@ import {
} from '../../selectors'
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
import ConfirmDecryptMessage from './confirm-decrypt-message.component'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
function mapStateToProps (state) {
const { confirmTransaction,
@ -35,6 +36,7 @@ function mapStateToProps (state) {
requester: null,
requesterAddress: null,
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 { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { conversionUtil } from '../../helpers/utils/conversion-util'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
export default class ConfirmEncryptionPublicKey extends Component {
static contextTypes = {
@ -30,6 +29,7 @@ export default class ConfirmEncryptionPublicKey extends Component {
requesterAddress: PropTypes.string,
txData: PropTypes.object,
domainMetadata: PropTypes.object,
mostRecentOverviewPage: PropTypes.string.isRequired,
}
state = {
@ -183,7 +183,14 @@ export default class ConfirmEncryptionPublicKey extends Component {
}
renderFooter = () => {
const { txData } = this.props
const {
cancelEncryptionPublicKey,
clearConfirmTransaction,
encryptionPublicKey,
history,
mostRecentOverviewPage,
txData,
} = this.props
return (
<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"
onClick={async (event) => {
this._removeBeforeUnload()
await this.props.cancelEncryptionPublicKey(txData, event)
await cancelEncryptionPublicKey(txData, event)
this.context.metricsEvent({
eventOpts: {
category: 'Messages',
@ -201,8 +208,8 @@ export default class ConfirmEncryptionPublicKey extends Component {
name: 'Cancel',
},
})
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
clearConfirmTransaction()
history.push(mostRecentOverviewPage)
}}
>
{ this.context.t('cancel') }
@ -213,7 +220,7 @@ export default class ConfirmEncryptionPublicKey extends Component {
className="request-encryption-public-key__footer__sign-button"
onClick={async (event) => {
this._removeBeforeUnload()
await this.props.encryptionPublicKey(txData, event)
await encryptionPublicKey(txData, event)
this.context.metricsEvent({
eventOpts: {
category: 'Messages',
@ -221,8 +228,8 @@ export default class ConfirmEncryptionPublicKey extends Component {
name: 'Confirm',
},
})
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
clearConfirmTransaction()
history.push(mostRecentOverviewPage)
}}
>
{ this.context.t('provide') }

View File

@ -15,6 +15,7 @@ import {
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
import ConfirmEncryptionPublicKey from './confirm-encryption-public-key.component'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
function mapStateToProps (state) {
const { confirmTransaction,
@ -36,6 +37,7 @@ function mapStateToProps (state) {
requester: null,
requesterAddress: null,
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 ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container'
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 {
INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY,
@ -96,6 +96,7 @@ export default class ConfirmTransactionBase extends Component {
tryReverseResolveAddress: PropTypes.func.isRequired,
hideSenderToRecipient: PropTypes.bool,
showAccountInHeader: PropTypes.bool,
mostRecentOverviewPage: PropTypes.string.isRequired,
}
state = {
@ -110,6 +111,7 @@ export default class ConfirmTransactionBase extends Component {
showTransactionConfirmedModal,
history,
clearConfirmTransaction,
mostRecentOverviewPage,
nextNonce,
customNonceValue,
toAddress,
@ -136,7 +138,7 @@ export default class ConfirmTransactionBase extends Component {
showTransactionConfirmedModal({
onSubmit: () => {
clearConfirmTransaction()
history.push(DEFAULT_ROUTE)
history.push(mostRecentOverviewPage)
},
})
}
@ -383,6 +385,7 @@ export default class ConfirmTransactionBase extends Component {
cancelAllTransactions,
clearConfirmTransaction,
history,
mostRecentOverviewPage,
showRejectTransactionsConfirmationModal,
unapprovedTxCount,
} = this.props
@ -393,7 +396,7 @@ export default class ConfirmTransactionBase extends Component {
this._removeBeforeUnload()
await cancelAllTransactions()
clearConfirmTransaction()
history.push(DEFAULT_ROUTE)
history.push(mostRecentOverviewPage)
},
})
}
@ -405,6 +408,7 @@ export default class ConfirmTransactionBase extends Component {
txData,
cancelTransaction,
history,
mostRecentOverviewPage,
clearConfirmTransaction,
actionKey,
txData: { origin },
@ -432,7 +436,7 @@ export default class ConfirmTransactionBase extends Component {
cancelTransaction(txData)
.then(() => {
clearConfirmTransaction()
history.push(DEFAULT_ROUTE)
history.push(mostRecentOverviewPage)
})
}
}
@ -447,6 +451,7 @@ export default class ConfirmTransactionBase extends Component {
history,
onSubmit,
actionKey,
mostRecentOverviewPage,
metaMetricsSendCount = 0,
setMetaMetricsSendCount,
methodData = {},
@ -493,7 +498,7 @@ export default class ConfirmTransactionBase extends Component {
this.setState({
submitting: false,
}, () => {
history.push(DEFAULT_ROUTE)
history.push(mostRecentOverviewPage)
updateCustomNonce('')
})
})

View File

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

View File

@ -10,7 +10,7 @@ import R from 'ramda'
import SignatureRequest from '../../components/app/signature-request'
import SignatureRequestOriginal from '../../components/app/signature-request-original'
import Loading from '../../components/ui/loading-screen'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
function mapStateToProps (state) {
const { metamask, appState } = state
@ -25,6 +25,7 @@ function mapStateToProps (state) {
return {
identities: state.metamask.identities,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
unapprovedTxs: state.metamask.unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
@ -45,6 +46,7 @@ function mapStateToProps (state) {
class ConfirmTxScreen extends Component {
static propTypes = {
mostRecentOverviewPage: PropTypes.string.isRequired,
unapprovedMsgCount: PropTypes.number,
unapprovedPersonalMsgCount: PropTypes.number,
unapprovedTypedMessagesCount: PropTypes.number,
@ -167,13 +169,15 @@ class ConfirmTxScreen extends Component {
componentDidMount () {
const {
unapprovedTxs = {},
history,
mostRecentOverviewPage,
network,
send,
} = this.props
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
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,
history,
match: { params: { id: transactionId } = {} },
mostRecentOverviewPage,
} = this.props
let prevTx
@ -203,14 +208,14 @@ class ConfirmTxScreen extends Component {
if (prevTx && prevTx.status === 'dropped') {
this.props.dispatch(actions.showModal({
name: 'TRANSACTION_CONFIRMED',
onSubmit: () => history.push(DEFAULT_ROUTE),
onSubmit: () => history.push(mostRecentOverviewPage),
}))
return
}
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 {
DEFAULT_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
CONFIRM_DEPLOY_CONTRACT_PATH,
CONFIRM_SEND_ETHER_PATH,
@ -39,6 +38,7 @@ export default class ConfirmTransaction extends Component {
setTransactionToConfirm: PropTypes.func,
clearConfirmTransaction: PropTypes.func,
fetchBasicGasAndTimeEstimates: PropTypes.func,
mostRecentOverviewPage: PropTypes.string.isRequired,
transaction: PropTypes.object,
getContractMethodData: PropTypes.func,
transactionId: PropTypes.string,
@ -52,6 +52,7 @@ export default class ConfirmTransaction extends Component {
totalUnapprovedCount = 0,
send = {},
history,
mostRecentOverviewPage,
transaction: { txParams: { data, to } = {} } = {},
fetchBasicGasAndTimeEstimates,
getContractMethodData,
@ -62,7 +63,7 @@ export default class ConfirmTransaction extends Component {
} = this.props
if (!totalUnapprovedCount && !send.to) {
history.replace(DEFAULT_ROUTE)
history.replace(mostRecentOverviewPage)
return
}
@ -86,6 +87,7 @@ export default class ConfirmTransaction extends Component {
paramsTransactionId,
transactionId,
history,
mostRecentOverviewPage,
totalUnapprovedCount,
} = this.props
@ -95,10 +97,10 @@ export default class ConfirmTransaction extends Component {
setTransactionToConfirm(paramsTransactionId)
return
} else if (prevProps.transactionId && !transactionId && !totalUnapprovedCount) {
history.replace(DEFAULT_ROUTE)
history.replace(mostRecentOverviewPage)
return
} else if (prevProps.transactionId && transactionId && prevProps.transactionId !== transactionId) {
history.replace(DEFAULT_ROUTE)
history.replace(mostRecentOverviewPage)
return
}
}

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