Fixes #3425: Better support for batch transactions (#5437)

feature/default_network_editable
PaddyMc 6 years ago committed by Whymarrh Whitby
parent d943345151
commit 7ce2cf4572
  1. 6
      app/_locales/en/messages.json
  2. 13
      app/images/double-arrow.svg
  3. 10
      app/images/single-arrow.svg
  4. 1
      app/scripts/background.js
  5. 323
      development/states/navigate-txs.json
  6. 1
      test/e2e/beta/from-import-beta-ui.spec.js
  7. 146
      test/e2e/beta/metamask-beta-ui.spec.js
  8. 87
      test/integration/lib/navigate-txs.js
  9. 2
      ui/app/components/confirm-page-container/confirm-page-container-header/index.scss
  10. 69
      ui/app/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js
  11. 1
      ui/app/components/confirm-page-container/confirm-page-container-navigation/index.js
  12. 54
      ui/app/components/confirm-page-container/confirm-page-container-navigation/index.scss
  13. 35
      ui/app/components/confirm-page-container/confirm-page-container.component.js
  14. 2
      ui/app/components/confirm-page-container/index.js
  15. 2
      ui/app/components/confirm-page-container/index.scss
  16. 40
      ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
  17. 5
      ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
  18. 10
      ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
  19. 7
      ui/app/ducks/confirm-transaction.duck.js

@ -785,6 +785,9 @@
"noWebcamFound": {
"message": "Your computer's webcam was not found. Please try again."
},
"ofTextNofM": {
"message": "of"
},
"oldUI": {
"message": "Old UI"
},
@ -932,6 +935,9 @@
"restoreAccountWithSeed": {
"message": "Restore your Account with Seed Phrase"
},
"requestsAwaitingAcknowledgement": {
"message": "requests waiting to be acknowledged"
},
"required": {
"message": "Required"
},

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="8px" viewBox="0 0 12 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>first/last</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="first/last" fill="#5F5C5D">
<polygon id="Path-12-Copy" points="12 0 12 8 6 4"></polygon>
<polygon id="Path-12-Copy-2" points="6 0 6 8 0 4"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 659 B

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="6px" height="8px" viewBox="0 0 6 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>previous/next</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="previous/next" fill="#5F5C5D" points="6 0 6 8 0 4"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 541 B

@ -440,6 +440,7 @@ function triggerUi () {
const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) {
notificationManager.showPopup()
notificationIsOpen = true
}
})
}

@ -0,0 +1,323 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"isAccountMenuOpen": false,
"isMascara": false,
"isPopup": false,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x8cf82b5aa41ff2282427be151dd328568684007a": {
"address": "0x8cf82b5aa41ff2282427be151dd328568684007a",
"name": "Account 3"
},
"0xbe1a00e10ec68b154adb84e8119167146a71c9a2": {
"address": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2",
"name": "Account 2"
},
"0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": {
"address": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"name": "Account 1"
}
},
"unapprovedTxs": {
"2389644572638771": {
"estimatedGas": "0x8544",
"gasLimitSpecified": true,
"gasPriceSpecified": true,
"history": [],
"id": 2389644572638771,
"loadingDefaults": false,
"metamaskNetworkId": "4",
"origin": "MetaMask",
"status": "unapproved",
"time": 1538844175144,
"txParams": {
"data": "0xa9059cbb000000000000000000000000be1a00e10ec68b154adb84e8119167146a71c9a20000000000000000000000000000000000000000000000000000000000000000",
"from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"gas": "0x8544",
"gasPrice": "0x3b9aca00",
"to": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a",
"value": "0x0"
},
"type": "standard"
},
"2389644572638772": {
"estimatedGas": "0x5208",
"gasLimitSpecified": true,
"gasPriceSpecified": true,
"history": [],
"id": 2389644572638772,
"loadingDefaults": false,
"metamaskNetworkId": "4",
"origin": "MetaMask",
"status": "unapproved",
"time": 1538844178492,
"txParams": {
"from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"gas": "0x5208",
"gasPrice": "0x3b9aca00",
"to": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2",
"value": "0x0"
},
"type": "standard"
},
"2389644572638773": {
"estimatedGas": {
"length": 1,
"negative": 0,
"red": null,
"words": [
34061,
null
]
},
"gasLimitSpecified": false,
"gasPriceSpecified": true,
"history": [],
"id": 2389644572638773,
"loadingDefaults": false,
"metamaskNetworkId": "4",
"origin": "localhost",
"status": "unapproved",
"time": 1538844204724,
"txParams": {
"data": "0xdfb29935000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000155468697320697320746865206970667320686173680000000000000000000000",
"from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"gas": "0xc793",
"gasPrice": "0x3b9aca00",
"to": "0xb7ec370c889b3b48ec537e0b2c887faedceb254a",
"value": "0x0"
},
"type": "standard"
},
"2389644572638774": {
"estimatedGas": "0x38f53",
"gasLimitSpecified": true,
"gasPriceSpecified": false,
"history": [],
"id": 2389644572638774,
"loadingDefaults": false,
"metamaskNetworkId": "4",
"origin": "remix.ethereum.org",
"status": "unapproved",
"time": 1538844223352,
"txParams": {
"data": "0x608060405234801561001057600080fd5b506102a7806100206000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663d13319c48114610050578063dfb29935146100da575b600080fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f578181015183820152602001610087565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f10610196576101008083540402835291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675600a165627a7a72305820cf4282c534b8f2faad947d592afa109b907e4e6b2f52335b361b69c24fedb9580029",
"from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"gas": "0x38f53",
"gasPrice": "0x3b9aca00",
"value": "0x0"
},
"type": "standard"
}
},
"noActiveNotices": true,
"frequentRpcList": [],
"addressBook": [],
"selectedTokenAddress": null,
"contractExchangeRates": {},
"tokenExchangeRates": {},
"tokens": [
{
"address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a",
"decimals": 9,
"symbol": "DGD"
}
],
"pendingTokens": {},
"send": {
"gasLimit": null,
"gasPrice": null,
"gasTotal": null,
"tokenBalance": null,
"from": "",
"to": "",
"amount": "0x0",
"memo": "",
"errors": {},
"editingTransactionId": null,
"forceGasMin": null
},
"coinOptions": {},
"useBlockie": false,
"featureFlags": {
"betaUI": true,
"skipAnnounceBetaUI": true
},
"isRevealingSeedWords": false,
"welcomeScreenSeen": false,
"currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
},
"provider": {
"type": "rinkeby"
},
"network": "4",
"accounts": {
"0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": {
"address": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"balance": "0x36aabfb2a0190c00"
},
"0xbe1a00e10ec68b154adb84e8119167146a71c9a2": {
"address": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2",
"balance": "0x7b3ef08c294a000"
},
"0x8cf82b5aa41ff2282427be151dd328568684007a": {
"address": "0x8cf82b5aa41ff2282427be151dd328568684007a",
"balance": "0x0"
}
},
"currentBlockGasLimit": "0x731e25",
"selectedAddressTxList": [],
"computedBalances": {},
"unapprovedMsgs": {},
"unapprovedMsgCount": 0,
"unapprovedPersonalMsgs": {},
"unapprovedPersonalMsgCount": 0,
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree",
"Trezor Hardware",
"Ledger Hardware"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"0xbe1a00e10ec68b154adb84e8119167146a71c9a2",
"0x8cf82b5aa41ff2282427be151dd328568684007a"
]
}
],
"currentAccountTab": "history",
"accountTokens": {
"0x8cf82b5aa41ff2282427be151dd328568684007a": {},
"0xbe1a00e10ec68b154adb84e8119167146a71c9a2": {},
"0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": {
"rinkeby": [
{
"address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a",
"decimals": 9,
"symbol": "DGD"
},
{
"address": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359",
"decimals": 18,
"symbol": "DAI"
}
]
}
},
"assetImages": {
"0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359": null,
"0xe0b7927c4af23765cb51314a0e0521a9645f0e2a": null
},
"suggestedTokens": {},
"lostIdentities": {},
"seedWords": null,
"forgottenPassword": false,
"selectedAddress": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"recentBlocks": [],
"currentCurrency": "usd",
"conversionRate": 225.23,
"conversionDate": 1538859376,
"shapeShiftTxList": [],
"infuraNetworkStatus": {
"kovan": "ok",
"mainnet": "ok",
"rinkeby": "ok",
"ropsten": "ok"
},
"lostAccounts": []
},
"appState": {
"shouldClose": false,
"menuOpen": false,
"modal": {
"open": false,
"modalState": {
"name": null,
"props": {}
},
"previousModalState": {
"name": null
}
},
"sidebar": {
"isOpen": false,
"transitionName": "",
"type": ""
},
"alertOpen": false,
"alertMessage": null,
"qrCodeData": null,
"networkDropdownOpen": false,
"currentView": {
"name": "confTx",
"context": 0
},
"accountDetail": {
"subview": "transactions"
},
"transForward": false,
"isLoading": false,
"warning": null,
"buyView": {},
"isMouseUser": true,
"gasIsLoading": false,
"networkNonce": "0x92",
"defaultHdPaths": {
"trezor": "m/44'/60'/0'/0",
"ledger": "m/44'/60'/0'/0/0"
}
},
"localeMessages": {},
"send": {
"fromDropdownOpen": false,
"toDropdownOpen": false,
"errors": {}
},
"confirmTransaction": {
"txData": {
"estimatedGas": "0x38f53",
"gasLimitSpecified": true,
"gasPriceSpecified": false,
"history": [],
"id": 2389644572638774,
"loadingDefaults": false,
"metamaskNetworkId": "4",
"origin": "remix.ethereum.org",
"status": "unapproved",
"time": 1538844223352,
"txParams": {
"data": "0x608060405234801561001057600080fd5b506102a7806100206000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663d13319c48114610050578063dfb29935146100da575b600080fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f578181015183820152602001610087565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f10610196576101008083540402835291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675600a165627a7a72305820cf4282c534b8f2faad947d592afa109b907e4e6b2f52335b361b69c24fedb9580029",
"from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
"gas": "0x38f53",
"gasPrice": "0x3b9aca00",
"value": "0x0"
},
"type": "standard"
},
"tokenData": {},
"methodData": {},
"tokenProps": {
"tokenDecimals": "",
"tokenSymbol": ""
},
"fiatTransactionAmount": "0",
"fiatTransactionFee": "0.05",
"fiatTransactionTotal": "0.05",
"ethTransactionAmount": "0",
"ethTransactionFee": "0.000233",
"ethTransactionTotal": "0.000233",
"hexGasTotal": "0xd42f28057e00",
"nonce": "",
"toSmartContract": false,
"fetchingData": false
}
}

@ -159,6 +159,7 @@ describe('Using MetaMask with an existing account', function () {
it('clicks through the ToS', async () => {
// terms of use
await delay(largeDelayMs)
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))

@ -484,6 +484,142 @@ describe('MetaMask', function () {
})
})
describe('Navigate transactions', () => {
it('adds multiple transactions', async () => {
await delay(regularDelayMs)
await waitUntilXWindowHandles(driver, 2)
const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const dapp = windowHandles[1]
await driver.switchTo().window(dapp)
await delay(regularDelayMs)
const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000)
await send3eth.click()
await delay(regularDelayMs)
const contractDeployment = await findElement(driver, By.xpath(`//button[contains(text(), 'Deploy Contract')]`), 10000)
await contractDeployment.click()
await delay(regularDelayMs)
await send3eth.click()
await contractDeployment.click()
await delay(regularDelayMs)
await driver.switchTo().window(extension)
await delay(regularDelayMs)
const transactions = await findElements(driver, By.css('.transaction-list-item'))
await transactions[3].click()
await delay(regularDelayMs)
})
it('navigates the transactions', async () => {
let navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
assert.equal(navigateTxButtons.length, 4, 'navigation button present')
await navigateTxButtons[2].click()
let navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
let navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('2'), true, 'changed transaction right')
navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
await navigateTxButtons[2].click()
navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('3'), true, 'changed transaction right')
navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
await navigateTxButtons[2].click()
navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('4'), true, 'changed transaction right')
navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
await navigateTxButtons[0].click()
navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('1'), true, 'navigate to first transaction')
navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
await navigateTxButtons[3].click()
navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
navigationText = await navigationElement.getText()
assert.equal(navigationText.split('4').length, 3, 'navigate to last transaction')
navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
await navigateTxButtons[1].click()
navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('3'), true, 'changed transaction left')
navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
await navigateTxButtons[1].click()
navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('2'), true, 'changed transaction left')
})
it('adds a transaction while confirm screen is in focus', async () => {
let navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
let navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('2'), true, 'second transaction in focus')
const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const dapp = windowHandles[1]
await driver.switchTo().window(dapp)
await delay(regularDelayMs)
const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000)
await send3eth.click()
await delay(regularDelayMs)
await driver.switchTo().window(extension)
await delay(regularDelayMs)
navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('3'), true, 'correct transaction in focus')
})
it('confirms a transaction', async () => {
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
await confirmButton.click()
await delay(regularDelayMs)
const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
const navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('4'), true, 'transaction confirmed')
})
it('rejects a transaction', async () => {
const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject')]`), 10000)
await rejectButton.click()
await delay(regularDelayMs)
const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
const navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('3'), true, 'transaction rejected')
})
it('rejects the rest of the transactions', async () => {
const rejectAllButton = await findElement(driver, By.xpath(`//a[contains(text(), 'Reject 3')]`), 10000)
await rejectAllButton.click()
await delay(regularDelayMs)
const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject All')]`), 10000)
await rejectButton.click()
await delay(largeDelayMs * 2)
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
assert.equal(confirmedTxes.length, 3, '3 transactions present')
})
})
describe('Deploy contract and call contract methods', () => {
let extension
let dapp
@ -531,7 +667,7 @@ describe('MetaMask', function () {
driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 3
return confirmedTxes.length === 4
}, 10000)
const txAction = await findElements(driver, By.css('.transaction-list-item__action'))
@ -588,7 +724,7 @@ describe('MetaMask', function () {
driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 4
return confirmedTxes.length === 5
}, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
@ -620,7 +756,7 @@ describe('MetaMask', function () {
driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 5
return confirmedTxes.length === 6
}, 10000)
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
@ -634,9 +770,9 @@ describe('MetaMask', function () {
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance'))
await delay(regularDelayMs)
if (process.env.SELENIUM_BROWSER !== 'firefox') {
await driver.wait(until.elementTextMatches(balance, /^92.*\s*ETH.*$/), 10000)
await driver.wait(until.elementTextMatches(balance, /^89.*\s*ETH.*$/), 10000)
const tokenAmount = await balance.getText()
assert.ok(/^92.*\s*ETH.*$/.test(tokenAmount))
assert.ok(/^89.*\s*ETH.*$/.test(tokenAmount))
await delay(regularDelayMs)
}
})

@ -0,0 +1,87 @@
const reactTriggerChange = require('react-trigger-change')
const {
timeout,
queryAsync,
} = require('../../lib/util')
QUnit.module('navigate txs')
QUnit.test('successful navigate', (assert) => {
const done = assert.async()
runNavigateTxsFlowTest(assert)
.then(done)
.catch(err => {
assert.notOk(err, `Error was thrown: ${err.stack}`)
done()
})
})
async function runNavigateTxsFlowTest (assert, done) {
const selectState = await queryAsync($, 'select')
selectState.val('navigate txs')
reactTriggerChange(selectState[0])
// Confirm navigation buttons present
let navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
assert.ok(navigateTxButtons[0], 'navigation button present')
assert.ok(navigateTxButtons[1], 'navigation button present')
assert.ok(navigateTxButtons[2], 'navigation button present')
assert.ok(navigateTxButtons[3], 'navigation button present')
// Verify number of transactions present
let trxNum = await queryAsync($, '.confirm-page-container-navigation')
assert.equal(trxNum[0].innerText.includes('1'), true, 'starts on first')
// Verify correct route
let summaryAction = await queryAsync($, '.confirm-page-container-summary__action')
assert.equal(summaryAction[0].innerText, 'CONTRACT DEPLOYMENT', 'correct route')
// Click navigation button
navigateTxButtons[2].click()
await timeout(2000)
// Verify transaction changed to num 2 and routed correctly
trxNum = await queryAsync($, '.confirm-page-container-navigation')
assert.equal(trxNum[0].innerText.includes('2'), true, 'changed transaction right')
summaryAction = await queryAsync($, '.confirm-page-container-summary__action')
// assert.equal(summaryAction[0].innerText, 'CONFIRM', 'correct route')
// Click navigation button
navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
navigateTxButtons[2].click()
// Verify transation changed to num 3 and routed correctly
trxNum = await queryAsync($, '.confirm-page-container-navigation')
assert.equal(trxNum[0].innerText.includes('3'), true, 'changed transaction right')
summaryAction = await queryAsync($, '.confirm-page-container-summary__action')
assert.equal(summaryAction[0].innerText, 'CONFIRM', 'correct route')
// Click navigation button
navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
navigateTxButtons[2].click()
// Verify transation changed to num 4 and routed correctly
trxNum = await queryAsync($, '.confirm-page-container-navigation')
assert.equal(trxNum[0].innerText.split('4').length, 3, '4 transactions present')
summaryAction = await queryAsync($, '.confirm-page-container-summary__action')
assert.equal(summaryAction[0].innerText, 'TRANSFER', 'correct route')
// Verify left arrow is working correctly
navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
navigateTxButtons[1].click()
trxNum = await queryAsync($, '.confirm-page-container-navigation')
assert.equal(trxNum[0].innerText.includes('3'), true, 'changed transaction left')
// Verify navigate to last transaction is working correctly
navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
navigateTxButtons[3].click()
trxNum = await queryAsync($, '.confirm-page-container-navigation')
assert.equal(trxNum[0].innerText.split('4').length, 3, 'navigate to last transaction')
// Verify navigate to first transaction is working correctly
navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
navigateTxButtons[0].click()
trxNum = await queryAsync($, '.confirm-page-container-navigation')
assert.equal(trxNum[0].innerText.includes('1'), true, 'navigate to first transaction')
}

@ -7,7 +7,7 @@
display: flex;
justify-content: space-between;
border-bottom: 1px solid $geyser;
padding: 13px 13px 13px 24px;
padding: 4px 13px 4px 13px;
flex: 0 0 auto;
}

@ -0,0 +1,69 @@
import React from 'react'
import PropTypes from 'prop-types'
const ConfirmPageContainerNavigation = props => {
const { onNextTx, totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = props
return (
<div className="confirm-page-container-navigation"
style={{
display: showNavigation ? 'flex' : 'none',
}}
>
<div className="confirm-page-container-navigation__container"
style={{
visibility: prevTxId ? 'initial' : 'hidden',
}}>
<div
className="confirm-page-container-navigation__arrow"
onClick={() => onNextTx(firstTx)}>
<img src="/images/double-arrow.svg" />
</div>
<div
className="confirm-page-container-navigation__arrow"
onClick={() => onNextTx(prevTxId)}>
<img src="/images/single-arrow.svg" />
</div>
</div>
<div className="confirm-page-container-navigation__textcontainer">
<div className="confirm-page-container-navigation__navtext">
{positionOfCurrentTx} {ofText} {totalTx}
</div>
<div className="confirm-page-container-navigation__longtext">
{requestsWaitingText}
</div>
</div>
<div
className="confirm-page-container-navigation__container"
style={{
visibility: nextTxId ? 'initial' : 'hidden',
}}>
<div
className="confirm-page-container-navigation__arrow"
onClick={() => onNextTx(nextTxId)}>
<img className="confirm-page-container-navigation__imageflip" src="/images/single-arrow.svg" />
</div>
<div
className="confirm-page-container-navigation__arrow"
onClick={() => onNextTx(lastTx)}>
<img className="confirm-page-container-navigation__imageflip" src="/images/double-arrow.svg" />
</div>
</div>
</div>
)
}
ConfirmPageContainerNavigation.propTypes = {
totalTx: PropTypes.number,
positionOfCurrentTx: PropTypes.number,
onNextTx: PropTypes.func,
nextTxId: PropTypes.string,
prevTxId: PropTypes.string,
showNavigation: PropTypes.bool,
firstTx: PropTypes.string,
lastTx: PropTypes.string,
ofText: PropTypes.string,
requestsWaitingText: PropTypes.string,
}
export default ConfirmPageContainerNavigation

@ -0,0 +1 @@
export { default } from './confirm-page-container-navigation.component'

@ -0,0 +1,54 @@
.confirm-page-container-navigation {
display: flex;
justify-content: space-between;
font: inherit;
padding: 4px 10px 4px 10px;
border-bottom: 1px solid $geyser;
flex: 0 0 auto;
&__container {
display: flex;
}
&__arrow {
cursor: pointer;
display: flex;
padding-left: 5px;
padding-right: 5px;
}
&__arrow:hover {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1);
}
&__arrow:active {
-webkit-transform: scale(0.95);
-moz-transform: scale(0.95);
-o-transform: scale(0.95);
transform: scale(0.95);
}
&__textcontainer {
text-align: center;
}
&__navtext {
font-size: 9px;
font-weight: bold;
}
&__longtext {
color: $oslo-gray;
font-size: 8px;
}
&__imageflip {
-webkit-transform: scaleX(-1);
-moz-transform: scaleX(-1);
-o-transform: scaleX(-1);
transform: scaleX(-1);
}
}

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SenderToRecipient from '../sender-to-recipient'
import { PageContainerFooter } from '../page-container'
import { ConfirmPageContainerHeader, ConfirmPageContainerContent } from './'
import { ConfirmPageContainerHeader, ConfirmPageContainerContent, ConfirmPageContainerNavigation } from './'
export default class ConfirmPageContainer extends Component {
static contextTypes = {
@ -43,6 +43,17 @@ export default class ConfirmPageContainer extends Component {
summaryComponent: PropTypes.node,
warning: PropTypes.string,
unapprovedTxCount: PropTypes.number,
// Navigation
totalTx: PropTypes.number,
positionOfCurrentTx: PropTypes.number,
nextTxId: PropTypes.string,
prevTxId: PropTypes.string,
showNavigation: PropTypes.bool,
onNextTx: PropTypes.func,
firstTx: PropTypes.string,
lastTx: PropTypes.string,
ofText: PropTypes.string,
requestsWaitingText: PropTypes.string,
// Footer
onCancelAll: PropTypes.func,
onCancel: PropTypes.func,
@ -79,11 +90,33 @@ export default class ConfirmPageContainer extends Component {
unapprovedTxCount,
assetImage,
warning,
totalTx,
positionOfCurrentTx,
nextTxId,
prevTxId,
showNavigation,
onNextTx,
firstTx,
lastTx,
ofText,
requestsWaitingText,
} = this.props
const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress)
return (
<div className="page-container">
<ConfirmPageContainerNavigation
totalTx={totalTx}
positionOfCurrentTx={positionOfCurrentTx}
nextTxId={nextTxId}
prevTxId={prevTxId}
showNavigation={showNavigation}
onNextTx={(txId) => onNextTx(txId)}
firstTx={firstTx}
lastTx={lastTx}
ofText={ofText}
requestsWaitingText={requestsWaitingText}
/>
<ConfirmPageContainerHeader
showEdit={showEdit}
onEdit={() => onEdit()}

@ -1,6 +1,8 @@
export { default } from './confirm-page-container.component'
export { default as ConfirmPageContainerHeader } from './confirm-page-container-header'
export { default as ConfirmDetailRow } from './confirm-detail-row'
export { default as ConfirmPageContainerNavigation } from './confirm-page-container-navigation'
export {
default as ConfirmPageContainerContent,
ConfirmPageContainerSummary,

@ -3,3 +3,5 @@
@import './confirm-page-container-header/index';
@import './confirm-detail-row/index';
@import './confirm-page-container-navigation/index';

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
import { isBalanceSufficient } from '../../send/send.utils'
import { DEFAULT_ROUTE } from '../../../routes'
import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../../routes'
import {
INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY,
@ -55,6 +55,7 @@ export default class ConfirmTransactionBase extends Component {
transactionStatus: PropTypes.string,
txData: PropTypes.object,
unapprovedTxCount: PropTypes.number,
currentNetworkUnapprovedTxs: PropTypes.object,
// Component props
action: PropTypes.string,
contentComponent: PropTypes.node,
@ -347,6 +348,32 @@ export default class ConfirmTransactionBase extends Component {
/>
)
}
handleNextTx (txId) {
const { history, clearConfirmTransaction } = this.props
if (txId) {
clearConfirmTransaction()
history.push(`${CONFIRM_TRANSACTION_ROUTE}/${txId}`)
}
}
getNavigateTxData () {
const { currentNetworkUnapprovedTxs, txData: { id } = {} } = this.props
const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs).reverse()
const currentPosition = enumUnapprovedTxs.indexOf(id.toString())
return {
totalTx: enumUnapprovedTxs.length,
positionOfCurrentTx: currentPosition + 1,
nextTxId: enumUnapprovedTxs[currentPosition + 1],
prevTxId: enumUnapprovedTxs[currentPosition - 1],
showNavigation: enumUnapprovedTxs.length > 1,
firstTx: enumUnapprovedTxs[0],
lastTx: enumUnapprovedTxs[enumUnapprovedTxs.length - 1],
ofText: this.context.t('ofTextNofM'),
requestsWaitingText: this.context.t('requestsAwaitingAcknowledgement'),
}
}
render () {
const {
@ -376,6 +403,7 @@ export default class ConfirmTransactionBase extends Component {
const { name } = methodData
const { valid, errorKey } = this.getErrorKey()
const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = this.getNavigateTxData()
return (
<ConfirmPageContainer
@ -401,6 +429,16 @@ export default class ConfirmTransactionBase extends Component {
errorMessage={errorMessage || submitError}
errorKey={propsErrorKey || errorKey}
warning={warning}
totalTx={totalTx}
positionOfCurrentTx={positionOfCurrentTx}
nextTxId={nextTxId}
prevTxId={prevTxId}
showNavigation={showNavigation}
onNextTx={(txId) => this.handleNextTx(txId)}
firstTx={firstTx}
lastTx={lastTx}
ofText={ofText}
requestsWaitingText={requestsWaitingText}
disabled={!propsValid || !valid || submitting}
onEdit={() => this.handleEdit()}
onCancelAll={() => this.handleCancelAll()}

@ -73,9 +73,9 @@ const mapStateToProps = (state, props) => {
const currentNetworkUnapprovedTxs = R.filter(
({ metamaskNetworkId }) => metamaskNetworkId === network,
valuesFor(unapprovedTxs),
unapprovedTxs,
)
const unapprovedTxCount = currentNetworkUnapprovedTxs.length
const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length
return {
balance,
@ -104,6 +104,7 @@ const mapStateToProps = (state, props) => {
assetImage,
unapprovedTxs,
unapprovedTxCount,
currentNetworkUnapprovedTxs,
}
}

@ -32,21 +32,15 @@ export default class ConfirmTransactionSwitch extends Component {
txData,
methodData: { name },
fetchingData,
isEtherTransaction,
} = this.props
const { id, txParams: { data } = {} } = txData
if (isConfirmDeployContract(txData)) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`
return <Redirect to={{ pathname }} />
}
if (fetchingData) {
return <Loading />
}
if (isEtherTransaction) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`
if (isConfirmDeployContract(txData)) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`
return <Redirect to={{ pathname }} />
}

@ -370,11 +370,16 @@ export function setTransactionToConfirm (transactionId) {
dispatch(setFetchingData(true))
const methodData = await getMethodData(data)
dispatch(updateMethodData(methodData))
} catch (error) {
dispatch(updateMethodData({}))
dispatch(setFetchingData(false))
}
try {
const toSmartContract = await isSmartContractAddress(to)
dispatch(updateToSmartContract(toSmartContract))
dispatch(setFetchingData(false))
} catch (error) {
dispatch(updateMethodData({}))
dispatch(setFetchingData(false))
}

Loading…
Cancel
Save