From 706dc02cb4814d8f80d52c7ae4406cc7cb46b784 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Tue, 26 May 2020 15:49:11 -0500 Subject: [PATCH] Implement new transaction list design (#8564) Co-authored-by: Whymarrh Whitby Co-authored-by: Mark Stacey --- app/_locales/en/messages.json | 20 +- development/states/tx-list-items.json | 19 +- test/data/transaction-data.json | 499 ++++++++++++++++++ test/e2e/address-book.spec.js | 4 +- test/e2e/from-import-ui.spec.js | 2 +- test/e2e/metamask-responsive-ui.spec.js | 2 +- test/e2e/metamask-ui.spec.js | 57 +- test/e2e/send-edit.spec.js | 2 +- test/integration/lib/tx-list-items.js | 20 +- .../transaction-list-item-details/index.scss | 15 +- ...action-list-item-details.component.test.js | 31 +- ...transaction-list-item-details.component.js | 183 ++++--- ...transaction-list-item-details.container.js | 4 +- .../app/transaction-list-item/index.js | 2 +- .../app/transaction-list-item/index.scss | 161 ++---- .../transaction-list-item.component.js | 448 +++++++--------- .../transaction-list-item.container.js | 113 ---- .../components/app/transaction-list/index.js | 2 +- .../app/transaction-list/index.scss | 5 +- .../transaction-list.component.js | 161 ++---- .../transaction-list.container.js | 45 -- .../components/ui/button/button.component.js | 5 +- ui/app/components/ui/button/buttons.scss | 87 +++ ui/app/components/ui/list-item/index.scss | 2 +- .../ui/list-item/list-item.component.js | 2 +- ui/app/helpers/constants/transactions.js | 27 + ui/app/helpers/utils/util.js | 6 + .../hooks/tests/useCancelTransaction.test.js | 103 ++++ ui/app/hooks/tests/useCurrencyDisplay.test.js | 15 +- .../hooks/tests/useRetryTransaction.test.js | 66 +++ ui/app/hooks/tests/useTokenData.test.js | 76 +++ .../tests/useTransactionDisplayData.test.js | 146 +++++ ui/app/hooks/useCancelTransaction.js | 50 ++ ui/app/hooks/useCurrencyDisplay.js | 11 +- ui/app/hooks/useMethodData.js | 30 ++ ui/app/hooks/useRetryTransaction.js | 62 +++ ui/app/hooks/useShouldShowSpeedUp.js | 46 ++ ui/app/hooks/useTokenData.js | 23 + ui/app/hooks/useTokenDisplayValue.js | 39 +- ui/app/hooks/useTransactionDisplayData.js | 153 ++++++ ui/app/hooks/useUserPreferencedCurrency.js | 4 +- ui/app/selectors/transactions.js | 21 +- 42 files changed, 1909 insertions(+), 860 deletions(-) create mode 100644 test/data/transaction-data.json delete mode 100644 ui/app/components/app/transaction-list-item/transaction-list-item.container.js delete mode 100644 ui/app/components/app/transaction-list/transaction-list.container.js create mode 100644 ui/app/hooks/tests/useCancelTransaction.test.js create mode 100644 ui/app/hooks/tests/useRetryTransaction.test.js create mode 100644 ui/app/hooks/tests/useTokenData.test.js create mode 100644 ui/app/hooks/tests/useTransactionDisplayData.test.js create mode 100644 ui/app/hooks/useCancelTransaction.js create mode 100644 ui/app/hooks/useMethodData.js create mode 100644 ui/app/hooks/useRetryTransaction.js create mode 100644 ui/app/hooks/useShouldShowSpeedUp.js create mode 100644 ui/app/hooks/useTokenData.js create mode 100644 ui/app/hooks/useTransactionDisplayData.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2973b24ae..ec2e1221b 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -226,7 +226,7 @@ "description": "The name of the application" }, "approve": { - "message": "Approve" + "message": "Approve spend limit" }, "approved": { "message": "Approved" @@ -663,6 +663,10 @@ "from": { "message": "From" }, + "fromAddress": { + "message": "From: $1", + "description": "$1 is the address to include in the From label. It is typically shortened first using shortenAddress" + }, "functionApprove": { "message": "Function: Approve" }, @@ -1117,12 +1121,18 @@ "queue": { "message": "Queue" }, + "queued": { + "message": "Queued" + }, "readdToken": { "message": "You can add this token back in the future by going to “Add token” in your accounts options menu." }, "recents": { "message": "Recents" }, + "receive": { + "message": "Receive" + }, "recipientAddress": { "message": "Recipient Address" }, @@ -1301,6 +1311,10 @@ "sentTokens": { "message": "sent tokens" }, + "sendSpecifiedTokens": { + "message": "Send $1", + "description": "Symbol of the specified token" + }, "separateEachWord": { "message": "Separate each word with a single space" }, @@ -1507,6 +1521,10 @@ "to": { "message": "To" }, + "toAddress": { + "message": "To: $1", + "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" + }, "toWithColon": { "message": "To:" }, diff --git a/development/states/tx-list-items.json b/development/states/tx-list-items.json index 62704fe2a..969535180 100644 --- a/development/states/tx-list-items.json +++ b/development/states/tx-list-items.json @@ -67,7 +67,17 @@ "name": "Address Book Account 1" } ], - "tokens": [], + "tokens": [{ + "name": "FakeTokenOne", + "address": "0x66f30b996a7d345cd00badcfe75e81e25dc5e1ec", + "symbol": "FTO", + "decimals": 2 + }, { + "name": "FakeTokenTwo", + "address": "0x66f30b996a7d345cd00badcfe75e81e25dc5e1eb", + "symbol": "FTT", + "decimals": 2 + }], "transactions": {}, "incomingTransactions": {}, "currentNetworkTxList": [ @@ -208,6 +218,7 @@ "rawTx": "0xf8610384773594008094f45d68f31b3c9ac84ff0d07b86c59b753a60b1e3808029a052e5246c9a404f756a246b8cec545099741aeb4e6e0add935a5b7a366fa88f95a0538eaa2421e50377c534244dcdcd15ace00bf9c0adbd9eb162baae2b9e89a36f", "status": "failed", "time": 1522378334455, + "transactionCategory": "sentEther", "txParams": { "chainId": "0x3", "from": "0xd85a4b6a394794842887b8284293d69163007bbb", @@ -224,6 +235,7 @@ "status": "approved", "metamaskNetworkId": "1", "loadingDefaults": false, + "transactionCategory": "sentEther", "txParams": { "from": "0xd85a4b6a394794842887b8284293d69163007bbb", "to": "0xf45d68f31b3c9ac84ff0d07b86c59b753a60b1e3", @@ -451,6 +463,7 @@ "status": "confirmed", "submittedTime": 1522346282571, "time": 1522348270251, + "transactionCategory": "transfer", "txParams": { "chainId": "0x3", "data": "0xa9059cbb000000000000000000000000e7884118ee52ec3f4eef715cb022279d7d4181a9000000000000000000000000000000000000000000000000000000000000000b", @@ -641,6 +654,7 @@ "status": "confirmed", "submittedTime": 1522346282571, "time": 1522346270251, + "transactionCategory": "transfer", "txParams": { "chainId": "0x3", "data": "0xa9059cbb000000000000000000000000e7884118ee52ec3f4eef715cb022279d7d4181a9000000000000000000000000000000000000000000000000000000000000000b", @@ -658,6 +672,7 @@ "status": "submitted", "metamaskNetworkId": "1", "loadingDefaults": false, + "transactionCategory": "sentEther", "txParams": { "from": "0xd85a4b6a394794842887b8284293d69163007bbb", "to": "0xf45d68f31b3c9ac84ff0d07b86c59b753a60b1e3", @@ -819,6 +834,7 @@ "status": "unapproved", "metamaskNetworkId": "1", "loadingDefaults": false, + "transactionCategory": "sentEther", "txParams": { "from": "0xd85a4b6a394794842887b8284293d69163007bbb", "to": "0xf45d68f31b3c9ac84ff0d07b86c59b753a60b1e3", @@ -858,6 +874,7 @@ "status": "unapproved", "metamaskNetworkId": "1", "loadingDefaults": false, + "transactionCategory": "sentEther", "txParams": { "from": "0x5b1cbd5636d484bf1cb6927a9425db9e7dc73ce4", "to": "0xf45d68f31b3c9ac84ff0d07b86c59b753a60b1e3", diff --git a/test/data/transaction-data.json b/test/data/transaction-data.json new file mode 100644 index 000000000..dfbdb735a --- /dev/null +++ b/test/data/transaction-data.json @@ -0,0 +1,499 @@ +[ + { + "nonce": "0xc", + "initialTransaction": { + "id": 4243712234858512, + "time": 1589314601567, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97", + "nonce": "0xc", + "value": "0xde0b6b3a7640000", + "gas": "0x5208", + "gasPrice": "0x2540be400" + }, + "type": "standard", + "origin": "metamask", + "transactionCategory": "sentEther", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 12, + "highestSuggested": 12, + "nextNetworkNonce": 12 + }, + "local": { + "name": "local", + "nonce": 12, + "details": { + "startPoint": 12, + "highest": 12 + } + }, + "network": { + "name": "network", + "nonce": 12, + "details": { + "blockNumber": "0x62d5dc", + "baseCount": 12 + } + } + }, + "r": "0xe0b79a8e33b15460ea79b05a5fb16bc067a796592eeb4edc5007c88615c12595", + "s": "0x1c834a25f1df07af5122996a40e99e554a40dc971a25041bc6e31638846c4f58", + "v": "0x2c", + "rawTx": "0xf86c0c8502540be40082520894ffe5bc4e8f1f969934d773fa67da095d2e491a97880de0b6b3a7640000802ca0e0b79a8e33b15460ea79b05a5fb16bc067a796592eeb4edc5007c88615c12595a01c834a25f1df07af5122996a40e99e554a40dc971a25041bc6e31638846c4f58", + "hash": "0x06bb79b856f5eb67025e4c4ffff44bca26ae135d1c3e6bd9a4193f422dcecca2", + "submittedTime": 1589314602908, + "txReceipt": { + "blockHash": "0xb9d2d71153b66146fde74b14b1c1ffc0588eb4a02ff464e32a4db9ae4bbfad8a", + "blockNumber": "62d5de", + "contractAddress": null, + "cumulativeGasUsed": "5208", + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "gasUsed": "5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97", + "transactionHash": "0x06bb79b856f5eb67025e4c4ffff44bca26ae135d1c3e6bd9a4193f422dcecca2", + "transactionIndex": "0" + } + }, + "primaryTransaction": { + "id": 4243712234858512, + "time": 1589314601567, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97", + "nonce": "0xc", + "value": "0xde0b6b3a7640000", + "gas": "0x5208", + "gasPrice": "0x2540be400" + }, + "type": "standard", + "origin": "metamask", + "transactionCategory": "sentEther", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 12, + "highestSuggested": 12, + "nextNetworkNonce": 12 + }, + "local": { + "name": "local", + "nonce": 12, + "details": { + "startPoint": 12, + "highest": 12 + } + }, + "network": { + "name": "network", + "nonce": 12, + "details": { + "blockNumber": "0x62d5dc", + "baseCount": 12 + } + } + }, + "r": "0xe0b79a8e33b15460ea79b05a5fb16bc067a796592eeb4edc5007c88615c12595", + "s": "0x1c834a25f1df07af5122996a40e99e554a40dc971a25041bc6e31638846c4f58", + "v": "0x2c", + "rawTx": "0xf86c0c8502540be40082520894ffe5bc4e8f1f969934d773fa67da095d2e491a97880de0b6b3a7640000802ca0e0b79a8e33b15460ea79b05a5fb16bc067a796592eeb4edc5007c88615c12595a01c834a25f1df07af5122996a40e99e554a40dc971a25041bc6e31638846c4f58", + "hash": "0x06bb79b856f5eb67025e4c4ffff44bca26ae135d1c3e6bd9a4193f422dcecca2", + "submittedTime": 1589314602908, + "txReceipt": { + "blockHash": "0xb9d2d71153b66146fde74b14b1c1ffc0588eb4a02ff464e32a4db9ae4bbfad8a", + "blockNumber": "62d5de", + "contractAddress": null, + "cumulativeGasUsed": "5208", + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "gasUsed": "5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97", + "transactionHash": "0x06bb79b856f5eb67025e4c4ffff44bca26ae135d1c3e6bd9a4193f422dcecca2", + "transactionIndex": "0" + } + }, + "hasRetried": false, + "hasCancelled": false + }, + { + "nonce": "0xb", + "initialTransaction": { + "id": 4243712234858507, + "time": 1589314355872, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "to": "0x0ccc8aeeaf5ce790f3b448325981a143fdef8848", + "nonce": "0xb", + "value": "0x1bc16d674ec80000", + "gas": "0x5208", + "gasPrice": "0x2540be400" + }, + "type": "standard", + "origin": "metamask", + "transactionCategory": "sentEther", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 0, + "highestSuggested": 10, + "nextNetworkNonce": 10 + }, + "local": { + "name": "local", + "nonce": 11, + "details": { + "startPoint": 10, + "highest": 11 + } + }, + "network": { + "name": "network", + "nonce": 10, + "details": { + "blockNumber": "0x62d5cc", + "baseCount": 10 + } + } + }, + "r": "0xe6828baea0a93a52779ffa5ea55e927781fb7d4be58107a29c75d314d433d055", + "s": "0x10613f984c57b8928d8ed9fce16ddda5746767e5f68f4c8fc29542e86a61f458", + "v": "0x2b", + "rawTx": "0xf86c0b8502540be400825208940ccc8aeeaf5ce790f3b448325981a143fdef8848881bc16d674ec80000802ba0e6828baea0a93a52779ffa5ea55e927781fb7d4be58107a29c75d314d433d055a010613f984c57b8928d8ed9fce16ddda5746767e5f68f4c8fc29542e86a61f458", + "hash": "0x2ccb9e2c0c64399ebc5c4ac70bebc5b537248458dee6cbce32df4b50c9e73bbd", + "submittedTime": 1589314356907, + "txReceipt": { + "blockHash": "0xfa3c8b63aaba2ef64ab328af72811dd5110a7641bd435cc6fbdfd9ea0d334542", + "blockNumber": "62d5ce", + "contractAddress": null, + "cumulativeGasUsed": "5208", + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "gasUsed": "5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0x0ccc8aeeaf5ce790f3b448325981a143fdef8848", + "transactionHash": "0x2ccb9e2c0c64399ebc5c4ac70bebc5b537248458dee6cbce32df4b50c9e73bbd", + "transactionIndex": "0" + } + }, + "primaryTransaction": { + "id": 4243712234858507, + "time": 1589314355872, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "to": "0x0ccc8aeeaf5ce790f3b448325981a143fdef8848", + "nonce": "0xb", + "value": "0x1bc16d674ec80000", + "gas": "0x5208", + "gasPrice": "0x2540be400" + }, + "type": "standard", + "origin": "metamask", + "transactionCategory": "sentEther", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 0, + "highestSuggested": 10, + "nextNetworkNonce": 10 + }, + "local": { + "name": "local", + "nonce": 11, + "details": { + "startPoint": 10, + "highest": 11 + } + }, + "network": { + "name": "network", + "nonce": 10, + "details": { + "blockNumber": "0x62d5cc", + "baseCount": 10 + } + } + }, + "r": "0xe6828baea0a93a52779ffa5ea55e927781fb7d4be58107a29c75d314d433d055", + "s": "0x10613f984c57b8928d8ed9fce16ddda5746767e5f68f4c8fc29542e86a61f458", + "v": "0x2b", + "rawTx": "0xf86c0b8502540be400825208940ccc8aeeaf5ce790f3b448325981a143fdef8848881bc16d674ec80000802ba0e6828baea0a93a52779ffa5ea55e927781fb7d4be58107a29c75d314d433d055a010613f984c57b8928d8ed9fce16ddda5746767e5f68f4c8fc29542e86a61f458", + "hash": "0x2ccb9e2c0c64399ebc5c4ac70bebc5b537248458dee6cbce32df4b50c9e73bbd", + "submittedTime": 1589314356907, + "txReceipt": { + "blockHash": "0xfa3c8b63aaba2ef64ab328af72811dd5110a7641bd435cc6fbdfd9ea0d334542", + "blockNumber": "62d5ce", + "contractAddress": null, + "cumulativeGasUsed": "5208", + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "gasUsed": "5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0x0ccc8aeeaf5ce790f3b448325981a143fdef8848", + "transactionHash": "0x2ccb9e2c0c64399ebc5c4ac70bebc5b537248458dee6cbce32df4b50c9e73bbd", + "transactionIndex": "0" + } + }, + "hasRetried": false, + "hasCancelled": false + }, + { + "nonce": "0xa", + "initialTransaction": { + "id": 4243712234858506, + "time": 1589314345433, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97", + "nonce": "0xa", + "value": "0x1bc16d674ec80000", + "gas": "0x5208", + "gasPrice": "0x306dc4200" + }, + "type": "standard", + "origin": "metamask", + "transactionCategory": "sentEther", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 0, + "highestSuggested": 10, + "nextNetworkNonce": 10 + }, + "local": { + "name": "local", + "nonce": 10, + "details": { + "startPoint": 10, + "highest": 10 + } + }, + "network": { + "name": "network", + "nonce": 10, + "details": { + "blockNumber": "0x62d5cb", + "baseCount": 10 + } + } + }, + "r": "0x94b120a1df80be3dfad057b7ccac866b6b7583b63d61e5b021811c8b7ffc9a3b", + "s": "0x1778de08e29a4c8dfc3aa3e2c2338e98494ebd2c380c901d0dfba95126dde65f", + "v": "0x2c", + "rawTx": "0xf86c0a850306dc420082520894ffe5bc4e8f1f969934d773fa67da095d2e491a97881bc16d674ec80000802ca094b120a1df80be3dfad057b7ccac866b6b7583b63d61e5b021811c8b7ffc9a3ba01778de08e29a4c8dfc3aa3e2c2338e98494ebd2c380c901d0dfba95126dde65f", + "hash": "0x52604fd8d329894a747d8cf521cbbc4adb35eb9a91e3a3ba3ee32d8729c16536", + "submittedTime": 1589314348235, + "firstRetryBlockNumber": "0x62d5cc", + "txReceipt": { + "blockHash": "0x3d61a8d8a0e79e0e7a3a9206bf62f9a8e47791c527cd85cb4fcf800609234115", + "blockNumber": "62d5cd", + "contractAddress": null, + "cumulativeGasUsed": "a810", + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "gasUsed": "5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97", + "transactionHash": "0x52604fd8d329894a747d8cf521cbbc4adb35eb9a91e3a3ba3ee32d8729c16536", + "transactionIndex": "1" + } + }, + "primaryTransaction": { + "id": 4243712234858506, + "time": 1589314345433, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97", + "nonce": "0xa", + "value": "0x1bc16d674ec80000", + "gas": "0x5208", + "gasPrice": "0x306dc4200" + }, + "type": "standard", + "origin": "metamask", + "transactionCategory": "sentEther", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 0, + "highestSuggested": 10, + "nextNetworkNonce": 10 + }, + "local": { + "name": "local", + "nonce": 10, + "details": { + "startPoint": 10, + "highest": 10 + } + }, + "network": { + "name": "network", + "nonce": 10, + "details": { + "blockNumber": "0x62d5cb", + "baseCount": 10 + } + } + }, + "r": "0x94b120a1df80be3dfad057b7ccac866b6b7583b63d61e5b021811c8b7ffc9a3b", + "s": "0x1778de08e29a4c8dfc3aa3e2c2338e98494ebd2c380c901d0dfba95126dde65f", + "v": "0x2c", + "rawTx": "0xf86c0a850306dc420082520894ffe5bc4e8f1f969934d773fa67da095d2e491a97881bc16d674ec80000802ca094b120a1df80be3dfad057b7ccac866b6b7583b63d61e5b021811c8b7ffc9a3ba01778de08e29a4c8dfc3aa3e2c2338e98494ebd2c380c901d0dfba95126dde65f", + "hash": "0x52604fd8d329894a747d8cf521cbbc4adb35eb9a91e3a3ba3ee32d8729c16536", + "submittedTime": 1589314348235, + "firstRetryBlockNumber": "0x62d5cc", + "txReceipt": { + "blockHash": "0x3d61a8d8a0e79e0e7a3a9206bf62f9a8e47791c527cd85cb4fcf800609234115", + "blockNumber": "62d5cd", + "contractAddress": null, + "cumulativeGasUsed": "a810", + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "gasUsed": "5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97", + "transactionHash": "0x52604fd8d329894a747d8cf521cbbc4adb35eb9a91e3a3ba3ee32d8729c16536", + "transactionIndex": "1" + } + }, + "hasRetried": false, + "hasCancelled": false + }, + { + "initialTransaction": { + "blockNumber": "6477257", + "id": 4243712234858505, + "metamaskNetworkId": "4", + "status": "confirmed", + "time": 1589314295000, + "txParams": { + "from": "0x31b98d14007bdee637298086988a0bbd31184523", + "gas": "0x5208", + "gasPrice": "0x3b9aca00", + "nonce": "0x56540", + "to": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "value": "0x1043561a882930000" + }, + "hash": "0x5ca26d1cdcabef1ac2ad5b2b38604c9ced65d143efc7525f848c46f28e0e4116", + "transactionCategory": "incoming" + }, + "primaryTransaction": { + "blockNumber": "6477257", + "id": 4243712234858505, + "metamaskNetworkId": "4", + "status": "confirmed", + "time": 1589314295000, + "txParams": { + "from": "0x31b98d14007bdee637298086988a0bbd31184523", + "gas": "0x5208", + "gasPrice": "0x3b9aca00", + "nonce": "0x56540", + "to": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "value": "0x1043561a882930000" + }, + "hash": "0x5ca26d1cdcabef1ac2ad5b2b38604c9ced65d143efc7525f848c46f28e0e4116", + "transactionCategory": "incoming" + }, + "hasRetried": false, + "hasCancelled": false + }, + { + "initialTransaction": { + "blockNumber": "6454493", + "id": 4243712234858475, + "metamaskNetworkId": "4", + "status": "confirmed", + "time": 1588972833000, + "txParams": { + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "gas": "0x5208", + "gasPrice": "0x24e160300", + "nonce": "0x8", + "to": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "value": "0x0" + }, + "hash": "0xa42b2b433e5bd2616b52e30792aedb6a3c374a752a95d43d99e2a8b143937889", + "transactionCategory": "incoming" + }, + "primaryTransaction": { + "blockNumber": "6454493", + "id": 4243712234858475, + "metamaskNetworkId": "4", + "status": "confirmed", + "time": 1588972833000, + "txParams": { + "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "gas": "0x5208", + "gasPrice": "0x24e160300", + "nonce": "0x8", + "to": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "value": "0x0" + }, + "hash": "0xa42b2b433e5bd2616b52e30792aedb6a3c374a752a95d43d99e2a8b143937889", + "transactionCategory": "incoming" + }, + "hasRetried": false, + "hasCancelled": false + }, + { + "initialTransaction": { + "blockNumber": "6195526", + "id": 4243712234858466, + "metamaskNetworkId": "4", + "status": "confirmed", + "time": 1585087013000, + "txParams": { + "from": "0xee014609ef9e09776ac5fe00bdbfef57bcdefebb", + "gas": "0x5208", + "gasPrice": "0x77359400", + "nonce": "0x3", + "to": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "value": "0xde0b6b3a7640000" + }, + "hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a", + "transactionCategory": "incoming" + }, + "primaryTransaction": { + "blockNumber": "6195526", + "id": 4243712234858466, + "metamaskNetworkId": "4", + "status": "confirmed", + "time": 1585087013000, + "txParams": { + "from": "0xee014609ef9e09776ac5fe00bdbfef57bcdefebb", + "gas": "0x5208", + "gasPrice": "0x77359400", + "nonce": "0x3", + "to": "0x9eca64466f257793eaa52fcfff5066894b76a149", + "value": "0xde0b6b3a7640000" + }, + "hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a", + "transactionCategory": "incoming" + }, + "hasRetried": false, + "hasCancelled": false + } +] diff --git a/test/e2e/address-book.spec.js b/test/e2e/address-book.spec.js index 5ccd563d9..35a86a58d 100644 --- a/test/e2e/address-book.spec.js +++ b/test/e2e/address-book.spec.js @@ -200,7 +200,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) }) }) @@ -237,7 +237,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 2 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues, /-2\s*ETH/), 10000) }) }) diff --git a/test/e2e/from-import-ui.spec.js b/test/e2e/from-import-ui.spec.js index de9c588a5..f187cdf04 100644 --- a/test/e2e/from-import-ui.spec.js +++ b/test/e2e/from-import-ui.spec.js @@ -219,7 +219,7 @@ describe('Using MetaMask with an existing account', function () { return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) assert.equal(txValues.length, 1) assert.ok(/-1\s*ETH/.test(await txValues[0].getText())) }) diff --git a/test/e2e/metamask-responsive-ui.spec.js b/test/e2e/metamask-responsive-ui.spec.js index 497023d20..b3fd15a6d 100644 --- a/test/e2e/metamask-responsive-ui.spec.js +++ b/test/e2e/metamask-responsive-ui.spec.js @@ -214,7 +214,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) }) }) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 6b38f059c..bf1e9c6c2 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -270,7 +270,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) }) }) @@ -309,7 +309,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 2 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) }) }) @@ -357,7 +357,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 3 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) }) }) @@ -462,18 +462,19 @@ describe('MetaMask', function () { return confirmedTxes.length === 4 }, 10000) - const txValue = await driver.findClickableElement(By.css('.transaction-list-item__amount--primary')) + const txValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValue, /-3\s*ETH/), 10000) }) it('the transaction has the expected gas price', async function () { - const txValue = await driver.findClickableElement(By.css('.transaction-list-item__amount--primary')) + const txValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) await txValue.click() + const popoverCloseButton = await driver.findClickableElement(By.css('.popover-header__button')) const txGasPrices = await driver.findElements(By.css('.transaction-breakdown__value')) const txGasPriceLabels = await driver.findElements(By.css('.transaction-breakdown-row__title')) await driver.wait(until.elementTextMatches(txGasPrices[3], /^10$/), 10000) assert(txGasPriceLabels[2]) - await txValue.click() + await popoverCloseButton.click() }) }) @@ -624,7 +625,7 @@ describe('MetaMask', function () { await driver.switchToWindow(extension) await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//div[contains(text(), 'Contract Deployment')]`)) + await driver.clickElement(By.xpath(`//h2[contains(text(), 'Contract Deployment')]`)) await driver.delay(largeDelayMs) }) @@ -654,7 +655,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 6 }, 10000) - const txAction = await driver.findElements(By.css('.transaction-list-item__action')) + const txAction = await driver.findElements(By.css('.list-item__heading')) await driver.wait(until.elementTextMatches(txAction[0], /Contract\sDeployment/), 10000) await driver.delay(regularDelayMs) }) @@ -676,7 +677,7 @@ describe('MetaMask', function () { await driver.delay(largeDelayMs * 2) await driver.findElements(By.css('.transaction-list-item')) - const txListValue = await driver.findClickableElement(By.css('.transaction-list-item__amount--primary')) + const txListValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txListValue, /-4\s*ETH/), 10000) await txListValue.click() await driver.delay(regularDelayMs) @@ -718,7 +719,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 7 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues[0], /-4\s*ETH/), 10000) }) @@ -743,7 +744,7 @@ describe('MetaMask', function () { return confirmedTxes.length === 8 }, 10000) - const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues, /-0\s*ETH/), 10000) await driver.closeAllWindowHandlesExcept([extension, dapp]) @@ -904,12 +905,12 @@ describe('MetaMask', function () { return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) assert.equal(txValues.length, 1) await driver.wait(until.elementTextMatches(txValues[0], /-1\s*TST/), 10000) - const txStatuses = await driver.findElements(By.css('.transaction-list-item__action')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/i), 10000) + const txStatuses = await driver.findElements(By.css('.list-item__heading')) + await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/i), 10000) }) }) @@ -930,7 +931,7 @@ describe('MetaMask', function () { await driver.delay(largeDelayMs) await driver.findElements(By.css('.transaction-list__pending-transactions')) - const txListValue = await driver.findClickableElement(By.css('.transaction-list-item__amount--primary')) + const txListValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/), 10000) await txListValue.click() await driver.delay(regularDelayMs) @@ -986,10 +987,10 @@ describe('MetaMask', function () { return confirmedTxes.length === 2 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/)) - const txStatuses = await driver.findElements(By.css('.transaction-list-item__action')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/), 10000) + 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="wallet-balance"]')) @@ -1023,7 +1024,7 @@ describe('MetaMask', function () { return pendingTxes.length === 1 }, 10000) - const [txListValue] = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const [txListValue] = await driver.findElements(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/)) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) @@ -1109,9 +1110,9 @@ describe('MetaMask', function () { return confirmedTxes.length === 3 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues[0], /-5\s*TST/)) - const txStatuses = await driver.findElements(By.css('.transaction-list-item__action')) + const txStatuses = await driver.findElements(By.css('.list-item__heading')) await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/)) }) }) @@ -1136,7 +1137,7 @@ describe('MetaMask', function () { return pendingTxes.length === 1 }, 10000) - const [txListValue] = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const [txListValue] = await driver.findElements(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/)) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) @@ -1154,10 +1155,10 @@ describe('MetaMask', function () { return confirmedTxes.length === 4 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/)) - const txStatuses = await driver.findElements(By.css('.transaction-list-item__action')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Sent Tokens/)) + const txStatuses = await driver.findElements(By.css('.list-item__heading')) + await driver.wait(until.elementTextMatches(txStatuses[0], /Send TST/)) }) }) @@ -1182,7 +1183,7 @@ describe('MetaMask', function () { return pendingTxes.length === 1 }, 10000) - const [txListValue] = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const [txListValue] = await driver.findElements(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/)) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) @@ -1209,9 +1210,9 @@ describe('MetaMask', function () { return confirmedTxes.length === 5 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/)) - const txStatuses = await driver.findElements(By.css('.transaction-list-item__action')) + const txStatuses = await driver.findElements(By.css('.list-item__heading')) await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/)) }) }) diff --git a/test/e2e/send-edit.spec.js b/test/e2e/send-edit.spec.js index c2ba99f95..d335d3a9a 100644 --- a/test/e2e/send-edit.spec.js +++ b/test/e2e/send-edit.spec.js @@ -202,7 +202,7 @@ describe('Using MetaMask with an existing account', function () { return confirmedTxes.length === 1 }, 10000) - const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary')) + const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) assert.equal(txValues.length, 1) assert.ok(/-2.2\s*ETH/.test(await txValues[0].getText())) }) diff --git a/test/integration/lib/tx-list-items.js b/test/integration/lib/tx-list-items.js index 88e96c41f..0319276b9 100644 --- a/test/integration/lib/tx-list-items.js +++ b/test/integration/lib/tx-list-items.js @@ -46,18 +46,22 @@ async function runTxListItemsTest (assert) { assert.equal(txListItems.length, 6, 'all tx list items are rendered') const unapprovedMsg = txListItems[0] - const unapprovedMsgDescription = await findAsync($(unapprovedMsg), '.transaction-list-item__action') - assert.equal(unapprovedMsgDescription[0].textContent, 'Signature Request', 'unapprovedMsg has correct description') + const unapprovedMsgDescription = await findAsync($(unapprovedMsg), '.transaction-list-item__status--unapproved') + assert.equal(unapprovedMsgDescription[0].textContent, 'Unapproved', 'unapprovedMsg has correct description') const approvedTx = txListItems[2] - const approvedTxRenderedStatus = await findAsync($(approvedTx), '.transaction-list-item__status') - assert.equal(approvedTxRenderedStatus[0].textContent, 'pending', 'approvedTx has correct label') + const approvedTxRenderedStatus = await findAsync($(approvedTx), '.transaction-list-item__status--queued') + assert.equal(approvedTxRenderedStatus[0].textContent, 'Queued', 'approvedTx has correct label') const confirmedTokenTx1 = txListItems[4] - const confirmedTokenTx1Address = await findAsync($(confirmedTokenTx1), '.transaction-list-item__status') - assert.equal(confirmedTokenTx1Address[0].textContent, 'Confirmed', 'confirmedTokenTx has correct status') + const confirmedTokenTx1Token = await findAsync($(confirmedTokenTx1), '.list-item__heading') + const confirmedTokenTx1Address = await findAsync($(confirmedTokenTx1), '.list-item__subheading') + assert.equal(confirmedTokenTx1Token[0].textContent, 'Send FTO ', 'Confirm token symbol is correct') + assert.equal(confirmedTokenTx1Address[0].textContent, 'Mar 29, 2018 · To: 0xe788...81a9', 'confirmedTokenTx has correct status') const confirmedTokenTx2 = txListItems[5] - const confirmedTokenTx2Address = await findAsync($(confirmedTokenTx2), '.transaction-list-item__status') - assert.equal(confirmedTokenTx2Address[0].textContent, 'Confirmed', 'confirmedTokenTx has correct status') + const confirmedTokenTx2Address = await findAsync($(confirmedTokenTx2), '.list-item__subheading') + const confirmedTokenTx2Token = await findAsync($(confirmedTokenTx2), '.list-item__heading') + assert.equal(confirmedTokenTx2Token[0].textContent, 'Send FTT ', 'Confirm token symbol is correct') + assert.equal(confirmedTokenTx2Address[0].textContent, 'Mar 29, 2018 · To: 0xe788...81a9', 'confirmedTokenTx has correct status') } diff --git a/ui/app/components/app/transaction-list-item-details/index.scss b/ui/app/components/app/transaction-list-item-details/index.scss index 2e3a06f84..dc35506ae 100644 --- a/ui/app/components/app/transaction-list-item-details/index.scss +++ b/ui/app/components/app/transaction-list-item-details/index.scss @@ -30,29 +30,20 @@ &__cards-container { display: flex; - flex-direction: row; - - @media screen and (max-width: $break-small) { - flex-direction: column; - } + flex-direction: column; } &__transaction-breakdown { flex: 1; margin-right: 8px; min-width: 0; + margin: 0 0 8px 0; - @media screen and (max-width: $break-small) { - margin: 0 0 8px 0; - } } &__transaction-activity-log { flex: 2; min-width: 0; - - @media screen and (min-width: $break-large) { - padding-left: 12px; - } + padding-left: 12px; } } diff --git a/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js index 62b2a4ba1..2834eb800 100644 --- a/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js +++ b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js @@ -31,6 +31,7 @@ describe('TransactionListItemDetails Component', function () { const wrapper = shallow( {}} @@ -40,12 +41,12 @@ describe('TransactionListItemDetails Component', function () { />, { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } } ) - - assert.ok(wrapper.hasClass('transaction-list-item-details')) - assert.equal(wrapper.find(Button).length, 2) - assert.equal(wrapper.find(SenderToRecipient).length, 1) - assert.equal(wrapper.find(TransactionBreakdown).length, 1) - assert.equal(wrapper.find(TransactionActivityLog).length, 1) + const child = wrapper.childAt(0) + assert.ok(child.hasClass('transaction-list-item-details')) + assert.equal(child.find(Button).length, 2) + assert.equal(child.find(SenderToRecipient).length, 1) + assert.equal(child.find(TransactionBreakdown).length, 1) + assert.equal(child.find(TransactionActivityLog).length, 1) }) it('should render a retry button', function () { @@ -85,8 +86,10 @@ describe('TransactionListItemDetails Component', function () { { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } } ) - assert.ok(wrapper.hasClass('transaction-list-item-details')) - assert.equal(wrapper.find(Button).length, 3) + const child = wrapper.childAt(0) + + assert.ok(child.hasClass('transaction-list-item-details')) + assert.equal(child.find(Button).length, 3) }) it('should disable the Copy Tx ID and View In Etherscan buttons when tx hash is missing', function () { @@ -122,8 +125,10 @@ describe('TransactionListItemDetails Component', function () { { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } } ) - assert.ok(wrapper.hasClass('transaction-list-item-details')) - const buttons = wrapper.find(Button) + const child = wrapper.childAt(0) + + assert.ok(child.hasClass('transaction-list-item-details')) + const buttons = child.find(Button) assert.strictEqual(buttons.at(0).prop('disabled'), true) assert.strictEqual(buttons.at(1).prop('disabled'), true) }) @@ -162,8 +167,10 @@ describe('TransactionListItemDetails Component', function () { { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } } ) - assert.ok(wrapper.hasClass('transaction-list-item-details')) - const buttons = wrapper.find(Button) + const child = wrapper.childAt(0) + + assert.ok(child.hasClass('transaction-list-item-details')) + const buttons = child.find(Button) assert.strictEqual(buttons.at(0).prop('disabled'), false) assert.strictEqual(buttons.at(1).prop('disabled'), false) }) diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js index 09c560d1a..c9196cde2 100644 --- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -11,6 +11,7 @@ import TransactionBreakdown from '../transaction-breakdown' import Button from '../../ui/button' import Tooltip from '../../ui/tooltip' import Copy from '../../ui/icon/copy-icon.component' +import Popover from '../../ui/popover' export default class TransactionListItemDetails extends PureComponent { static contextTypes = { @@ -31,6 +32,8 @@ export default class TransactionListItemDetails extends PureComponent { isEarliestNonce: PropTypes.bool, cancelDisabled: PropTypes.bool, transactionGroup: PropTypes.object, + title: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, recipientEns: PropTypes.string, recipientAddress: PropTypes.string.isRequired, rpcPrefs: PropTypes.object, @@ -150,108 +153,112 @@ export default class TransactionListItemDetails extends PureComponent { senderAddress, isEarliestNonce, senderNickname, + title, + onClose, recipientNickname, } = this.props const { primaryTransaction: transaction } = transactionGroup const { hash } = transaction return ( -
-
-
{ t('details') }
-
- { - showSpeedUp && ( - - ) - } - { this.renderCancel() } - - - - - - - { - showRetry && ( - + +
+
+
{ t('details') }
+
+ { + showSpeedUp && ( - - ) - } + ) + } + { this.renderCancel() } + + + + + + + { + showRetry && ( + + + + ) + } +
+
+
+
+ { + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Activity Log', + name: 'Copied "To" Address', + }, + }) + }} + onSenderClick={() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Activity Log', + name: 'Copied "From" Address', + }, + }) + }} + /> +
+
+ + +
-
-
- { - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Activity Log', - name: 'Copied "To" Address', - }, - }) - }} - onSenderClick={() => { - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Activity Log', - name: 'Copied "From" Address', - }, - }) - }} - /> -
-
- - -
-
-
+ ) } } diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js index 935b80b48..226d1b46a 100644 --- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js +++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux' import TransactionListItemDetails from './transaction-list-item-details.component' import { checksumAddress } from '../../../helpers/utils/util' import { tryReverseResolveAddress } from '../../../store/actions' -import { getAddressBook } from '../../../selectors' +import { getAddressBook, getRpcPrefsForCurrentProvider } from '../../../selectors' const mapStateToProps = (state, ownProps) => { const { metamask } = state @@ -20,8 +20,10 @@ const mapStateToProps = (state, ownProps) => { }) return (entry && entry.name) || '' } + const rpcPrefs = getRpcPrefsForCurrentProvider(state) return { + rpcPrefs, recipientEns, senderNickname: getNickName(senderAddress), recipientNickname: getNickName(recipientAddress), diff --git a/ui/app/components/app/transaction-list-item/index.js b/ui/app/components/app/transaction-list-item/index.js index 697cc55e9..1fdbb29ac 100644 --- a/ui/app/components/app/transaction-list-item/index.js +++ b/ui/app/components/app/transaction-list-item/index.js @@ -1 +1 @@ -export { default } from './transaction-list-item.container' +export { default } from './transaction-list-item.component' diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss index 9804ecd97..b2d344f36 100644 --- a/ui/app/components/app/transaction-list-item/index.scss +++ b/ui/app/components/app/transaction-list-item/index.scss @@ -1,149 +1,50 @@ .transaction-list-item { - box-sizing: border-box; - min-height: 74px; - border-bottom: 1px solid $Grey-100; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - background: $white; + cursor: pointer; - &__grid { - cursor: pointer; - width: 100%; - padding: 16px 20px; - display: grid; - grid-template-columns: 45px 1fr 1fr 1fr 1fr; - grid-template-areas: - "identicon action status estimated-time primary-amount" - "identicon nonce status estimated-time secondary-amount"; - grid-template-rows: 24px; - - @media screen and (max-width: $break-small) { - padding: .5rem 1rem; - grid-template-columns: 45px 5fr 3fr; - grid-template-areas: - "nonce nonce nonce nonce" - "identicon action estimated-time primary-amount" - "identicon status estimated-time secondary-amount"; - grid-template-rows: auto 24px; - } - - &:hover { - background: rgba($alto, .2); - } + &:hover { + background-color: $Grey-000; } - &__identicon { - grid-area: identicon; - grid-row: 1 / span 2; - align-self: center; - - @media screen and (max-width: $break-small) { - grid-row: 2 / span 2; - } + &__primary-currency { + color: $Black-100; } - &__action { - text-transform: capitalize; - padding: 0 0 4px 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - grid-area: action; - color: $Grey-800; - line-height: 20px; + &__secondary-currency { + color: $Grey-500; + } + + &--pending { + color: $Grey-500; + } + + &--pending &__primary-currency { + color: $Grey-500; } &__status { - grid-area: status; - grid-row: 1 / span 2; - align-self: center; - - @media screen and (max-width: $break-small) { - grid-row: 3; + &--unapproved { + color: $flamingo; } - } - - &__estimated-time { - grid-area: estimated-time; - grid-row: 1 / span 2; - align-self: center; - - @media screen and (max-width: $break-small) { - grid-row: 3; - grid-column: 4; - font-size: small; + &--failed { + color: $valencia; } - } - - &__nonce { - font-size: .75rem; - color: #5e6064; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - grid-area: nonce; - align-self: start; - - @media screen and (max-width: $break-small) { - padding-bottom: 8px; - line-height: 12px; + &--cancelled { + color: $valencia; } - } - - &__amount { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - max-width: 100%; - - &--primary { - text-align: end; - grid-area: primary-amount; - align-self: end; - justify-self: end; - line-height: 20px; - - @media screen and (max-width: $break-small) { - padding-bottom: 4px; - height: 100%; - color: $Grey-800; - } - } - - &--secondary { - text-align: end; - font-size: .75rem; - grid-area: secondary-amount; - align-self: start; - justify-self: end; + &--queued { color: $Grey-500; } } - &__retry { - background: #d1edff; - border-radius: 12px; - font-size: .75rem; - padding: 4px 12px; - cursor: pointer; - margin-top: 8px; - - @media screen and (max-width: $break-small) { - font-size: .5rem; - } - } - - &__expander { - max-height: 0px; - width: 100%; - overflow: hidden; - - &--show { - max-height: 1000px; - transition: max-height 700ms ease-out; + &__pending-actions { + padding-top: 12px; + display: flex; + .button { + font-size: 0.625rem; + padding: 8px; + width: 75px; + white-space: nowrap; + line-height: 1rem; } } } diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js index 4ca071beb..7d0507f3f 100644 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js @@ -1,278 +1,200 @@ -import React, { PureComponent } from 'react' +import React, { useMemo, useState, useCallback } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' -import Identicon from '../../ui/identicon' -import TransactionStatus from '../transaction-status' -import TransactionAction from '../transaction-action' -import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display' -import TokenCurrencyDisplay from '../../ui/token-currency-display' -import TransactionListItemDetails from '../transaction-list-item-details' -import TransactionTimeRemaining from '../transaction-time-remaining' +import ListItem from '../../ui/list-item' +import { useTransactionDisplayData } from '../../../hooks/useTransactionDisplayData' +import Approve from '../../ui/icon/approve-icon.component' +import Interaction from '../../ui/icon/interaction-icon.component' +import Receive from '../../ui/icon/receive-icon.component' +import Preloader from '../../ui/icon/preloader' +import Send from '../../ui/icon/send-icon.component' +import { useI18nContext } from '../../../hooks/useI18nContext' +import { useCancelTransaction } from '../../../hooks/useCancelTransaction' +import { useRetryTransaction } from '../../../hooks/useRetryTransaction' +import Button from '../../ui/button' +import Tooltip from '../../ui/tooltip' +import TransactionListItemDetails from '../transaction-list-item-details/transaction-list-item-details.component' +import { useHistory } from 'react-router-dom' import { CONFIRM_TRANSACTION_ROUTE } from '../../../helpers/constants/routes' -import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../../helpers/constants/transactions' -import { PRIMARY, SECONDARY } from '../../../helpers/constants/common' -import { getStatusKey } from '../../../helpers/utils/transactions.util' -import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums' -import { getEnvironmentType } from '../../../../../app/scripts/lib/util' +import Identicon from '../../ui/identicon/identicon.component' +import { + TRANSACTION_CATEGORY_APPROVAL, + TRANSACTION_CATEGORY_SIGNATURE_REQUEST, + TRANSACTION_CATEGORY_INTERACTION, + TRANSACTION_CATEGORY_SEND, + TRANSACTION_CATEGORY_RECEIVE, + UNAPPROVED_STATUS, + FAILED_STATUS, + CANCELLED_STATUS, +} from '../../../helpers/constants/transactions' +import { useShouldShowSpeedUp } from '../../../hooks/useShouldShowSpeedUp' -export default class TransactionListItem extends PureComponent { - static propTypes = { - assetImages: PropTypes.object, - history: PropTypes.object, - methodData: PropTypes.object, - nonceAndDate: PropTypes.string, - primaryTransaction: PropTypes.object, - retryTransaction: PropTypes.func, - setSelectedToken: PropTypes.func, - showCancelModal: PropTypes.func, - showCancel: PropTypes.bool, - hasEnoughCancelGas: PropTypes.bool, - showSpeedUp: PropTypes.bool, - isEarliestNonce: PropTypes.bool, - showFiat: PropTypes.bool, - token: PropTypes.object, - tokenData: PropTypes.object, - transaction: PropTypes.object, - transactionGroup: PropTypes.object, - value: PropTypes.string, - fetchBasicGasAndTimeEstimates: PropTypes.func, - fetchGasEstimates: PropTypes.func, - rpcPrefs: PropTypes.object, - data: PropTypes.string, - getContractMethodData: PropTypes.func, - isDeposit: PropTypes.bool, - transactionTimeFeatureActive: PropTypes.bool, - firstPendingTransactionId: PropTypes.number, + +export default function TransactionListItem ({ transactionGroup, isEarliestNonce = false }) { + const t = useI18nContext() + const history = useHistory() + const { hasCancelled } = transactionGroup + const [showDetails, setShowDetails] = useState(false) + + const { initialTransaction: { id } } = transactionGroup + + const [cancelEnabled, cancelTransaction] = useCancelTransaction(transactionGroup) + const retryTransaction = useRetryTransaction(transactionGroup) + const shouldShowSpeedUp = useShouldShowSpeedUp(transactionGroup, isEarliestNonce) + + const { + title, + subtitle, + date, + category, + primaryCurrency, + recipientAddress, + secondaryCurrency, + status, + isPending, + senderAddress, + } = useTransactionDisplayData(transactionGroup) + + const isApprove = category === TRANSACTION_CATEGORY_APPROVAL + const isSignatureReq = category === TRANSACTION_CATEGORY_SIGNATURE_REQUEST + const isInteraction = category === TRANSACTION_CATEGORY_INTERACTION + const isSend = category === TRANSACTION_CATEGORY_SEND + const isReceive = category === TRANSACTION_CATEGORY_RECEIVE + const isUnapproved = status === UNAPPROVED_STATUS + const isFailed = status === FAILED_STATUS + const isCancelled = status === CANCELLED_STATUS + + const color = isFailed ? '#D73A49' : '#2F80ED' + + let Icon + if (isApprove) { + Icon = Approve + } else if (isSend) { + Icon = Send + } else if (isReceive) { + Icon = Receive + } else if (isInteraction) { + Icon = Interaction } - static defaultProps = { - showFiat: true, + let subtitleStatus = {date} · + if (isUnapproved) { + subtitleStatus = ( + {t('unapproved')} · + ) + } else if (isFailed) { + subtitleStatus = ( + {t('failed')} · + ) + } else if (isCancelled) { + subtitleStatus = ( + {t('cancelled')} · + ) + } else if (isPending && !isEarliestNonce) { + subtitleStatus = ( + {t('queued')} · + ) } - static contextTypes = { - metricsEvent: PropTypes.func, - } + const className = classnames('transaction-list-item', { 'transaction-list-item--pending': isPending }) - state = { - showTransactionDetails: false, - } - - componentDidMount () { - if (this.props.data) { - this.props.getContractMethodData(this.props.data) - } - - } - - handleClick = () => { - const { - transaction, - history, - } = this.props - const { id, status } = transaction - const { showTransactionDetails } = this.state - - if (status === UNAPPROVED_STATUS) { + const toggleShowDetails = useCallback(() => { + if (isUnapproved) { history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`) return } + setShowDetails((prev) => !prev) + }, [isUnapproved, id]) - if (!showTransactionDetails) { - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Home', - name: 'Expand Transaction', - }, - }) - } - - this.setState({ showTransactionDetails: !showTransactionDetails }) - } - - handleCancel = (id) => { - const { - primaryTransaction: { txParams: { gasPrice } } = {}, - transaction: { id: initialTransactionId }, - showCancelModal, - } = this.props - - const cancelId = id || initialTransactionId - showCancelModal(cancelId, gasPrice) - } - - /** - * @name handleRetry - * @description Resubmits a transaction. Retrying a transaction within a list of transactions with - * the same nonce requires keeping the original value while increasing the gas price of the latest - * transaction. - * @param {number} id - Transaction id - */ - handleRetry = (id) => { - const { - primaryTransaction: { txParams: { gasPrice } } = {}, - transaction: { txParams: { to } = {}, id: initialTransactionId }, - methodData: { name } = {}, - setSelectedToken, - retryTransaction, - fetchBasicGasAndTimeEstimates, - fetchGasEstimates, - } = this.props - - if (name === TOKEN_METHOD_TRANSFER) { - setSelectedToken(to) - } - - const retryId = id || initialTransactionId - - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Activity Log', - name: 'Clicked "Speed Up"', - }, - }) - - return fetchBasicGasAndTimeEstimates() - .then((basicEstimates) => fetchGasEstimates(basicEstimates.blockTime)) - .then(retryTransaction(retryId, gasPrice)) - } - - renderPrimaryCurrency () { - const { token, primaryTransaction: { txParams: { data } = {} } = {}, value, isDeposit } = this.props - - return token - ? ( - - ) : ( - - ) - } - - renderSecondaryCurrency () { - const { token, value, showFiat } = this.props - - return token || !showFiat - ? null - : ( - - ) - } - - render () { - const { - assetImages, - transaction, - methodData, - nonceAndDate, - primaryTransaction, - showCancel, - hasEnoughCancelGas, - showSpeedUp, - tokenData, - transactionGroup, - rpcPrefs, - isEarliestNonce, - firstPendingTransactionId, - transactionTimeFeatureActive, - } = this.props - const { txParams = {} } = transaction - const { showTransactionDetails } = this.state - const fromAddress = txParams.from - const toAddress = tokenData - ? (tokenData.params && tokenData.params[0] && tokenData.params[0].value) || txParams.to - : txParams.to - - const isFullScreen = getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN - const showEstimatedTime = transactionTimeFeatureActive && - (transaction.id === firstPendingTransactionId) && - isFullScreen - - return ( -
-
- - -
- { nonceAndDate } -
- - { showEstimatedTime - ? ( - - ) - : null - } - { this.renderPrimaryCurrency() } - { this.renderSecondaryCurrency() } -
-
- { - showTransactionDetails && ( -
- -
- ) - } -
-
+ const cancelButton = useMemo(() => { + const cancelButton = ( + ) - } + if (hasCancelled || !isPending || isUnapproved) { + return null + } + + return !cancelEnabled ? ( + +
+ {cancelButton} +
+
+ ) : cancelButton + + }, [cancelEnabled, cancelTransaction, hasCancelled]) + + const speedUpButton = useMemo(() => { + if (!shouldShowSpeedUp || !isPending || isUnapproved) { + return null + } + return ( + + ) + }, [shouldShowSpeedUp, isPending, retryTransaction]) + + return ( + <> + + )} + icon={isSignatureReq ? : } + subtitle={subtitle} + subtitleStatus={subtitleStatus} + rightContent={!isSignatureReq && ( + <> +

{primaryCurrency}

+

{secondaryCurrency}

+ + )} + > +
+ {speedUpButton} + {cancelButton} +
+
+ {showDetails && ( + + )} + + ) +} + +TransactionListItem.propTypes = { + transactionGroup: PropTypes.object.isRequired, + isEarliestNonce: PropTypes.bool, } diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js deleted file mode 100644 index 62607d4c4..000000000 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js +++ /dev/null @@ -1,113 +0,0 @@ -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import { compose } from 'redux' -import TransactionListItem from './transaction-list-item.component' -import { setSelectedToken, showModal, showSidebar, getContractMethodData } from '../../../store/actions' -import { hexToDecimal } from '../../../helpers/utils/conversions.util' -import { getTokenData } from '../../../helpers/utils/transactions.util' -import { getHexGasTotal, increaseLastGasPrice } from '../../../helpers/utils/confirm-tx.util' -import { formatDate } from '../../../helpers/utils/util' -import { - fetchGasEstimates, - fetchBasicGasAndTimeEstimates, - setCustomGasPriceForRetry, - setCustomGasLimit, -} from '../../../ducks/gas/gas.duck' -import { - getIsMainnet, - getPreferences, - getSelectedAddress, - conversionRateSelector, - getKnownMethodData, - getFeatureFlags, -} from '../../../selectors' -import { isBalanceSufficient } from '../../../pages/send/send.utils' - -const mapStateToProps = (state, ownProps) => { - const { metamask: { accounts, provider, frequentRpcListDetail } } = state - const { showFiatInTestnets } = getPreferences(state) - const isMainnet = getIsMainnet(state) - const { transactionGroup: { primaryTransaction } = {} } = ownProps - const { txParams: { gas: gasLimit, gasPrice, data } = {}, transactionCategory } = primaryTransaction - const selectedAddress = getSelectedAddress(state) - const selectedAccountBalance = accounts[selectedAddress].balance - const isDeposit = transactionCategory === 'incoming' - const selectRpcInfo = frequentRpcListDetail.find((rpcInfo) => rpcInfo.rpcUrl === provider.rpcTarget) - const { rpcPrefs } = selectRpcInfo || {} - - const hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({ - amount: '0x0', - gasTotal: getHexGasTotal({ - gasPrice: increaseLastGasPrice(gasPrice), - gasLimit, - }), - balance: selectedAccountBalance, - conversionRate: conversionRateSelector(state), - }) - - const transactionTimeFeatureActive = getFeatureFlags(state).transactionTime - - return { - methodData: getKnownMethodData(state, data) || {}, - showFiat: (isMainnet || !!showFiatInTestnets), - selectedAccountBalance, - hasEnoughCancelGas, - rpcPrefs, - isDeposit, - transactionTimeFeatureActive, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), - fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)), - setSelectedToken: (tokenAddress) => dispatch(setSelectedToken(tokenAddress)), - getContractMethodData: (methodData) => dispatch(getContractMethodData(methodData)), - retryTransaction: (transaction, gasPrice) => { - dispatch(setCustomGasPriceForRetry(gasPrice || transaction.txParams.gasPrice)) - dispatch(setCustomGasLimit(transaction.txParams.gas)) - dispatch(showSidebar({ - transitionName: 'sidebar-left', - type: 'customize-gas', - props: { transaction }, - })) - }, - showCancelModal: (transactionId, originalGasPrice) => { - return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice })) - }, - } -} - -const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { transactionGroup: { primaryTransaction, initialTransaction } = {} } = ownProps - const { isDeposit } = stateProps - const { retryTransaction, ...restDispatchProps } = dispatchProps - const { txParams: { nonce, data } = {}, time = 0 } = initialTransaction - const { txParams: { value } = {} } = primaryTransaction - - const tokenData = data && getTokenData(data) - const nonceAndDate = nonce && !isDeposit ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time) - - return { - ...stateProps, - ...restDispatchProps, - ...ownProps, - value, - nonceAndDate, - tokenData, - transaction: initialTransaction, - primaryTransaction, - retryTransaction: (transactionId, gasPrice) => { - const { transactionGroup: { transactions = [] } } = ownProps - const transaction = transactions.find((tx) => tx.id === transactionId) || {} - const increasedGasPrice = increaseLastGasPrice(gasPrice) - retryTransaction(transaction, increasedGasPrice) - }, - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps, mergeProps), -)(TransactionListItem) diff --git a/ui/app/components/app/transaction-list/index.js b/ui/app/components/app/transaction-list/index.js index 688994367..1ec6c5124 100644 --- a/ui/app/components/app/transaction-list/index.js +++ b/ui/app/components/app/transaction-list/index.js @@ -1 +1 @@ -export { default } from './transaction-list.container' +export { default } from './transaction-list.component' diff --git a/ui/app/components/app/transaction-list/index.scss b/ui/app/components/app/transaction-list/index.scss index 42eddd31e..80c0ff500 100644 --- a/ui/app/components/app/transaction-list/index.scss +++ b/ui/app/components/app/transaction-list/index.scss @@ -2,7 +2,6 @@ display: flex; flex-direction: column; flex: 1; - margin-top: 8px; &__completed-transactions { display: flex; @@ -44,4 +43,8 @@ justify-content: center; color: $silver; } + &__view-more { + margin: 16px auto; + max-width: 200px; + } } diff --git a/ui/app/components/app/transaction-list/transaction-list.component.js b/ui/app/components/app/transaction-list/transaction-list.component.js index 6a1aa3f7f..e1215c876 100644 --- a/ui/app/components/app/transaction-list/transaction-list.component.js +++ b/ui/app/components/app/transaction-list/transaction-list.component.js @@ -1,79 +1,56 @@ -import React, { PureComponent } from 'react' +import React, { useMemo, useEffect, useRef, useState, useCallback } from 'react' import PropTypes from 'prop-types' +import { useSelector, useDispatch } from 'react-redux' +import { + nonceSortedCompletedTransactionsSelector, + nonceSortedPendingTransactionsSelector, +} from '../../../selectors/transactions' +import { + getFeatureFlags, +} from '../../../selectors/selectors' +import * as actions from '../../../ducks/gas/gas.duck' +import { useI18nContext } from '../../../hooks/useI18nContext' import TransactionListItem from '../transaction-list-item' +import Button from '../../ui/button' -export default class TransactionList extends PureComponent { - static contextTypes = { - t: PropTypes.func, - } +const PAGE_INCREMENT = 10 - static defaultProps = { - pendingTransactions: [], - completedTransactions: [], - } +export default function TransactionList ({ isWideViewport = false } = {}) { + const [limit, setLimit] = useState(PAGE_INCREMENT) + const t = useI18nContext() - static propTypes = { - isWideViewport: PropTypes.bool.isRequired, - pendingTransactions: PropTypes.array, - completedTransactions: PropTypes.array, - selectedToken: PropTypes.object, - assetImages: PropTypes.object, - fetchBasicGasAndTimeEstimates: PropTypes.func, - fetchGasEstimates: PropTypes.func, - transactionTimeFeatureActive: PropTypes.bool, - firstPendingTransactionId: PropTypes.number, - } + const dispatch = useDispatch() + const pendingTransactions = useSelector(nonceSortedPendingTransactionsSelector) + const completedTransactions = useSelector(nonceSortedCompletedTransactionsSelector) + const { transactionTime: transactionTimeFeatureActive } = useSelector(getFeatureFlags) - componentDidMount () { - const { - pendingTransactions, - fetchBasicGasAndTimeEstimates, - fetchGasEstimates, - transactionTimeFeatureActive, - } = this.props + const { fetchGasEstimates, fetchBasicGasAndTimeEstimates } = useMemo(() => ({ + fetchGasEstimates: (blockTime) => dispatch(actions.fetchGasEstimates(blockTime)), + fetchBasicGasAndTimeEstimates: () => dispatch(actions.fetchBasicGasAndTimeEstimates()), + }), [dispatch]) - if (transactionTimeFeatureActive && pendingTransactions.length) { + // keep track of previous values from state. + // loaded is used here to determine if our effect has ran at least once. + const prevState = useRef({ loaded: false, pendingTransactions, transactionTimeFeatureActive }) + + useEffect(() => { + const { loaded } = prevState.current + const pendingTransactionAdded = pendingTransactions.length > 0 && prevState.current.pendingTransactions.length === 0 + const transactionTimeFeatureWasActivated = !prevState.current.transactionTimeFeatureActive && transactionTimeFeatureActive + if (transactionTimeFeatureActive && pendingTransactions.length > 0 && (loaded === false || transactionTimeFeatureWasActivated || pendingTransactionAdded)) { fetchBasicGasAndTimeEstimates() .then(({ blockTime }) => fetchGasEstimates(blockTime)) } - } + prevState.current = { loaded: true, pendingTransactions, transactionTimeFeatureActive } + }, [fetchGasEstimates, fetchBasicGasAndTimeEstimates, transactionTimeFeatureActive, pendingTransactions ]) - componentDidUpdate (prevProps) { - const { pendingTransactions: prevPendingTransactions = [] } = prevProps - const { - pendingTransactions = [], - fetchBasicGasAndTimeEstimates, - fetchGasEstimates, - transactionTimeFeatureActive, - } = this.props + const viewMore = useCallback(() => setLimit((prev) => prev + PAGE_INCREMENT), []) - const transactionTimeFeatureWasActivated = !prevProps.transactionTimeFeatureActive && transactionTimeFeatureActive - const pendingTransactionAdded = pendingTransactions.length > 0 && prevPendingTransactions.length === 0 - if (transactionTimeFeatureActive && pendingTransactions.length > 0 && (transactionTimeFeatureWasActivated || pendingTransactionAdded)) { - fetchBasicGasAndTimeEstimates() - .then(({ blockTime }) => fetchGasEstimates(blockTime)) - } - } + const pendingLength = pendingTransactions.length - shouldShowSpeedUp = (transactionGroup, isEarliestNonce) => { - const { transactions = [], hasRetried } = transactionGroup - const [earliestTransaction = {}] = transactions - const { submittedTime } = earliestTransaction - return Date.now() - submittedTime > 5000 && isEarliestNonce && !hasRetried - } - - shouldShowCancel (transactionGroup) { - const { hasCancelled } = transactionGroup - return !hasCancelled - } - - renderTransactions () { - const { t } = this.context - const { isWideViewport, pendingTransactions = [], completedTransactions = [] } = this.props - const pendingLength = pendingTransactions.length - - return ( + return ( +
{ pendingLength > 0 && ( @@ -83,7 +60,7 @@ export default class TransactionList extends PureComponent {
{ pendingTransactions.map((transactionGroup, index) => ( - this.renderTransaction(transactionGroup, index, true) + )) }
@@ -101,48 +78,26 @@ export default class TransactionList extends PureComponent { } { completedTransactions.length > 0 - ? completedTransactions.map((transactionGroup, index) => ( - this.renderTransaction(transactionGroup, index) + ? completedTransactions.slice(0, limit).map((transactionGroup, index) => ( + )) - : this.renderEmpty() + : ( +
+
+ { t('noTransactions') } +
+
+ ) } + {(completedTransactions.length - limit + PAGE_INCREMENT) > 0 && ( + + )}
- ) - } - - renderTransaction (transactionGroup, index, isPendingTx = false) { - const { selectedToken, assetImages, firstPendingTransactionId } = this.props - - return ( - - ) - } - - renderEmpty () { - return ( -
-
- { this.context.t('noTransactions') } -
-
- ) - } - - render () { - return ( -
- { this.renderTransactions() } -
- ) - } + + ) +} + +TransactionList.propTypes = { + isWideViewport: PropTypes.bool.isRequired, } diff --git a/ui/app/components/app/transaction-list/transaction-list.container.js b/ui/app/components/app/transaction-list/transaction-list.container.js deleted file mode 100644 index 92dde3aaf..000000000 --- a/ui/app/components/app/transaction-list/transaction-list.container.js +++ /dev/null @@ -1,45 +0,0 @@ -import { connect } from 'react-redux' -import PropTypes from 'prop-types' -import TransactionList from './transaction-list.component' -import { - getAssetImages, - getFeatureFlags, - getSelectedAddress, - selectedTokenSelector, - nonceSortedCompletedTransactionsSelector, - nonceSortedPendingTransactionsSelector, -} from '../../../selectors' -import { fetchBasicGasAndTimeEstimates, fetchGasEstimates } from '../../../ducks/gas/gas.duck' - -const mapStateToProps = (state) => { - const pendingTransactions = nonceSortedPendingTransactionsSelector(state) - const firstPendingTransactionId = pendingTransactions[0] && pendingTransactions[0].primaryTransaction.id - return { - completedTransactions: nonceSortedCompletedTransactionsSelector(state), - pendingTransactions, - firstPendingTransactionId, - selectedToken: selectedTokenSelector(state), - selectedAddress: getSelectedAddress(state), - assetImages: getAssetImages(state), - transactionTimeFeatureActive: getFeatureFlags(state).transactionTime, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)), - fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), - } -} - -const TransactionListContainer = connect(mapStateToProps, mapDispatchToProps)(TransactionList) - -TransactionListContainer.propTypes = { - isWideViewport: PropTypes.bool, -} - -TransactionListContainer.defaultProps = { - isWideViewport: false, -} - -export default TransactionListContainer diff --git a/ui/app/components/ui/button/button.component.js b/ui/app/components/ui/button/button.component.js index 621d3cecc..cc4bb45e2 100644 --- a/ui/app/components/ui/button/button.component.js +++ b/ui/app/components/ui/button/button.component.js @@ -8,6 +8,7 @@ const CLASSNAME_SECONDARY = 'btn-secondary' const CLASSNAME_CONFIRM = 'btn-primary' const CLASSNAME_RAISED = 'btn-raised' const CLASSNAME_LARGE = 'btn--large' +const CLASSNAME_ROUNDED = 'btn--rounded' const CLASSNAME_FIRST_TIME = 'btn--first-time' const typeHash = { @@ -24,13 +25,14 @@ const typeHash = { 'first-time': CLASSNAME_FIRST_TIME, } -const Button = ({ type, submit, large, children, className, ...buttonProps }) => ( +const Button = ({ type, submit, large, children, rounded, className, ...buttonProps }) => (