aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
authorpldespaigne <pl.despaigne@gmail.com>2019-05-31 00:22:55 +0800
committerpldespaigne <pl.despaigne@gmail.com>2019-05-31 00:22:55 +0800
commit9a658ee53d1f75ce07c33581ac1189fa8c4fd173 (patch)
treeea92ef1971ffaa72c29bf16904906bc1841654c7 /ui/app
parent9b87aaae1907eb04ca0a4055b5bb2c863e56aa39 (diff)
parent681f3f67b89b64fc837df1103198b641c7e7b2d6 (diff)
downloadtangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.tar
tangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.tar.gz
tangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.tar.bz2
tangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.tar.lz
tangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.tar.xz
tangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.tar.zst
tangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.zip
merge
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/components/app/account-panel.js17
-rw-r--r--ui/app/components/app/add-token-button/index.scss5
-rw-r--r--ui/app/components/app/app-header/index.scss17
-rw-r--r--ui/app/components/app/bn-as-decimal-input.js2
-rw-r--r--ui/app/components/app/coinbase-form.js69
-rw-r--r--ui/app/components/app/customize-gas-modal/index.js8
-rw-r--r--ui/app/components/app/dropdowns/account-details-dropdown.js17
-rw-r--r--ui/app/components/app/dropdowns/network-dropdown.js36
-rw-r--r--ui/app/components/app/dropdowns/tests/network-dropdown.test.js14
-rw-r--r--ui/app/components/app/ens-input.js18
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js2
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js4
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js3
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js2
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js74
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js12
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js2
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/index.scss17
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js6
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js2
-rw-r--r--ui/app/components/app/loading-network-screen/loading-network-screen.component.js4
-rw-r--r--ui/app/components/app/modal/modal.component.js2
-rw-r--r--ui/app/components/app/modal/tests/modal.component.test.js4
-rw-r--r--ui/app/components/app/modals/account-details-modal.js16
-rw-r--r--ui/app/components/app/modals/customize-gas/customize-gas.component.js4
-rw-r--r--ui/app/components/app/modals/deposit-ether-modal.js6
-rw-r--r--ui/app/components/app/modals/edit-account-name-modal.js2
-rw-r--r--ui/app/components/app/modals/export-private-key-modal.js8
-rw-r--r--ui/app/components/app/modals/hide-token-confirmation-modal.js4
-rw-r--r--ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js2
-rw-r--r--ui/app/components/app/modals/notification-modal.js4
-rw-r--r--ui/app/components/app/modals/qr-scanner/qr-scanner.component.js2
-rw-r--r--ui/app/components/app/modals/reject-transactions/reject-transactions.container.js2
-rw-r--r--ui/app/components/app/network-display/index.scss8
-rw-r--r--ui/app/components/app/network-display/network-display.component.js2
-rw-r--r--ui/app/components/app/network.js32
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container.component.js13
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js65
-rw-r--r--ui/app/components/app/shapeshift-form.js2
-rw-r--r--ui/app/components/app/signature-request.js2
-rw-r--r--ui/app/components/app/token-cell.js2
-rw-r--r--ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js15
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.component.js3
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.container.js7
-rw-r--r--ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js6
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js2
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js2
-rw-r--r--ui/app/components/app/wallet-view.js2
-rw-r--r--ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js2
-rw-r--r--ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js2
-rw-r--r--ui/app/components/ui/alert/index.js4
-rw-r--r--ui/app/components/ui/button/button.component.js9
-rw-r--r--ui/app/components/ui/button/button.stories.js47
-rw-r--r--ui/app/components/ui/button/buttons.scss247
-rw-r--r--ui/app/components/ui/currency-display/currency-display.component.js2
-rw-r--r--ui/app/components/ui/currency-display/tests/currency-display.container.test.js2
-rw-r--r--ui/app/components/ui/currency-input/currency-input.component.js4
-rw-r--r--ui/app/components/ui/currency-input/currency-input.container.js3
-rw-r--r--ui/app/components/ui/currency-input/tests/currency-input.container.test.js18
-rw-r--r--ui/app/components/ui/page-container/index.scss8
-rw-r--r--ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js2
-rw-r--r--ui/app/components/ui/text-field/text-field.component.js6
-rw-r--r--ui/app/components/ui/token-input/tests/token-input.container.test.js2
-rw-r--r--ui/app/components/ui/unit-input/index.scss20
-rw-r--r--ui/app/components/ui/unit-input/unit-input.component.js14
-rw-r--r--ui/app/css/itcss/components/buttons.scss230
-rw-r--r--ui/app/css/itcss/components/index.scss2
-rw-r--r--ui/app/css/itcss/components/modal.scss2
-rw-r--r--ui/app/css/itcss/components/network.scss35
-rw-r--r--ui/app/css/itcss/components/send.scss205
-rw-r--r--ui/app/css/itcss/generic/index.scss1
-rw-r--r--ui/app/css/itcss/settings/typography.scss37
-rw-r--r--ui/app/css/itcss/settings/variables.scss18
-rw-r--r--ui/app/ducks/app/app.js12
-rw-r--r--ui/app/ducks/confirm-transaction/confirm-transaction.duck.js2
-rw-r--r--ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js2
-rw-r--r--ui/app/ducks/gas/gas-duck.test.js4
-rw-r--r--ui/app/ducks/metamask/metamask.js19
-rw-r--r--ui/app/helpers/constants/common.js1
-rw-r--r--ui/app/helpers/constants/routes.js2
-rw-r--r--ui/app/helpers/higher-order-components/i18n-provider.js12
-rw-r--r--ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js2
-rw-r--r--ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js2
-rw-r--r--ui/app/helpers/utils/conversion-util.js2
-rw-r--r--ui/app/helpers/utils/conversions.util.js11
-rw-r--r--ui/app/helpers/utils/metametrics.util.js61
-rw-r--r--ui/app/helpers/utils/transactions.util.js16
-rw-r--r--ui/app/helpers/utils/util.js9
-rw-r--r--ui/app/pages/add-token/token-list/token-list-placeholder/index.scss3
-rw-r--r--ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js2
-rw-r--r--ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js2
-rw-r--r--ui/app/pages/confirm-add-token/confirm-add-token.component.js2
-rw-r--r--ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js2
-rw-r--r--ui/app/pages/confirm-send-ether/confirm-send-ether.component.js2
-rw-r--r--ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js42
-rw-r--r--ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js4
-rw-r--r--ui/app/pages/create-account/connect-hardware/account-list.js6
-rw-r--r--ui/app/pages/create-account/connect-hardware/connect-screen.js6
-rw-r--r--ui/app/pages/create-account/connect-hardware/index.js12
-rw-r--r--ui/app/pages/create-account/import-account/json.js2
-rw-r--r--ui/app/pages/create-account/import-account/private-key.js2
-rw-r--r--ui/app/pages/create-account/import-account/seed.js2
-rw-r--r--ui/app/pages/create-account/new-account.js2
-rw-r--r--ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js20
-rw-r--r--ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js2
-rw-r--r--ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js2
-rw-r--r--ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js2
-rw-r--r--ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js4
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js193
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js41
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js126
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss93
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js2
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js76
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js169
-rw-r--r--ui/app/pages/first-time-flow/select-action/select-action.component.js2
-rw-r--r--ui/app/pages/first-time-flow/welcome/welcome.component.js2
-rw-r--r--ui/app/pages/home/home.component.js19
-rw-r--r--ui/app/pages/home/home.container.js2
-rw-r--r--ui/app/pages/keychains/reveal-seed.js2
-rw-r--r--ui/app/pages/mobile-sync/index.js2
-rw-r--r--ui/app/pages/provider-approval/provider-approval.component.js10
-rw-r--r--ui/app/pages/provider-approval/provider-approval.container.js6
-rw-r--r--ui/app/pages/routes/index.js34
-rw-r--r--ui/app/pages/send/README.md (renamed from ui/app/components/app/send/README.md)0
-rw-r--r--ui/app/pages/send/account-list-item/account-list-item-README.md (renamed from ui/app/components/app/send/account-list-item/account-list-item-README.md)0
-rw-r--r--ui/app/pages/send/account-list-item/account-list-item.component.js (renamed from ui/app/components/app/send/account-list-item/account-list-item.component.js)10
-rw-r--r--ui/app/pages/send/account-list-item/account-list-item.container.js (renamed from ui/app/components/app/send/account-list-item/account-list-item.container.js)2
-rw-r--r--ui/app/pages/send/account-list-item/index.js (renamed from ui/app/components/app/send/account-list-item/index.js)0
-rw-r--r--ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js (renamed from ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js)6
-rw-r--r--ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js (renamed from ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js)4
-rw-r--r--ui/app/pages/send/index.js (renamed from ui/app/components/app/send/index.js)0
-rw-r--r--ui/app/pages/send/send-content/index.js (renamed from ui/app/components/app/send/send-content/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/README.md (renamed from ui/app/components/app/send/send-content/send-amount-row/README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js75
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js)9
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js)2
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js)14
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js)6
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/index.js (renamed from ui/app/components/app/send/send-content/send-amount-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js (renamed from ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js)6
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.container.js (renamed from ui/app/components/app/send/send-content/send-amount-row/send-amount-row.container.js)4
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.scss (renamed from ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.selectors.js (renamed from ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-container.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-container.test.js)4
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-asset-row/index.js1
-rw-r--r--ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js152
-rw-r--r--ui/app/pages/send/send-content/send-asset-row/send-asset-row.container.js21
-rw-r--r--ui/app/pages/send/send-content/send-content.component.js (renamed from ui/app/components/app/send/send-content/send-content.component.js)4
-rw-r--r--ui/app/pages/send/send-content/send-dropdown-list/index.js (renamed from ui/app/components/app/send/send-content/send-dropdown-list/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js (renamed from ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js)0
-rw-r--r--ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js (renamed from ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/index.js (renamed from ui/app/components/app/send/send-content/send-from-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/send-from-row.component.js (renamed from ui/app/components/app/send/send-content/send-from-row/send-from-row.component.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/send-from-row.container.js (renamed from ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/send-from-row.selectors.js (renamed from ui/app/components/app/send/send-content/send-from-row/send-from-row.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/tests/send-from-row-component.test.js (renamed from ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/tests/send-from-row-container.test.js (renamed from ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/tests/send-from-row-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/README.md (renamed from ui/app/components/app/send/send-content/send-gas-row/README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js (renamed from ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js)4
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/gas-fee-display/index.js (renamed from ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js (renamed from ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/index.js (renamed from ui/app/components/app/send/send-content/send-gas-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js (renamed from ui/app/components/app/send/send-content/send-gas-row/send-gas-row.component.js)45
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js (renamed from ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js)26
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss (renamed from ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.selectors.js (renamed from ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-component.test.js (renamed from ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js (renamed from ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js)19
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-hex-data-row/index.js (renamed from ui/app/components/app/send/send-content/send-hex-data-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js (renamed from ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js)0
-rw-r--r--ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.container.js (renamed from ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.container.js)2
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/index.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/index.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js)3
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/index.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper-README.md (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js)46
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.scss (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/index.js (renamed from ui/app/components/app/send/send-content/send-to-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row-README.md (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.component.js (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js)2
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.container.js (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js)4
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.selectors.js (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js)9
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-component.test.js (renamed from ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-container.test.js (renamed from ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js)4
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js (renamed from ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js)12
-rw-r--r--ui/app/pages/send/send-content/tests/send-content-component.test.js (renamed from ui/app/components/app/send/send-content/tests/send-content-component.test.js)17
-rw-r--r--ui/app/pages/send/send-footer/README.md (renamed from ui/app/components/app/send/send-footer/README.md)0
-rw-r--r--ui/app/pages/send/send-footer/index.js (renamed from ui/app/components/app/send/send-footer/index.js)0
-rw-r--r--ui/app/pages/send/send-footer/send-footer.component.js (renamed from ui/app/components/app/send/send-footer/send-footer.component.js)9
-rw-r--r--ui/app/pages/send/send-footer/send-footer.container.js (renamed from ui/app/components/app/send/send-footer/send-footer.container.js)14
-rw-r--r--ui/app/pages/send/send-footer/send-footer.scss (renamed from ui/app/components/app/send/send-footer/send-footer.scss)0
-rw-r--r--ui/app/pages/send/send-footer/send-footer.selectors.js (renamed from ui/app/components/app/send/send-footer/send-footer.selectors.js)0
-rw-r--r--ui/app/pages/send/send-footer/send-footer.utils.js (renamed from ui/app/components/app/send/send-footer/send-footer.utils.js)1
-rw-r--r--ui/app/pages/send/send-footer/tests/send-footer-component.test.js (renamed from ui/app/components/app/send/send-footer/tests/send-footer-component.test.js)4
-rw-r--r--ui/app/pages/send/send-footer/tests/send-footer-container.test.js (renamed from ui/app/components/app/send/send-footer/tests/send-footer-container.test.js)7
-rw-r--r--ui/app/pages/send/send-footer/tests/send-footer-selectors.test.js (renamed from ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-footer/tests/send-footer-utils.test.js (renamed from ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js)1
-rw-r--r--ui/app/pages/send/send-header/README.md (renamed from ui/app/components/app/send/send-header/README.md)0
-rw-r--r--ui/app/pages/send/send-header/index.js (renamed from ui/app/components/app/send/send-header/index.js)0
-rw-r--r--ui/app/pages/send/send-header/send-header.component.js (renamed from ui/app/components/app/send/send-header/send-header.component.js)5
-rw-r--r--ui/app/pages/send/send-header/send-header.container.js (renamed from ui/app/components/app/send/send-header/send-header.container.js)2
-rw-r--r--ui/app/pages/send/send-header/send-header.selectors.js (renamed from ui/app/components/app/send/send-header/send-header.selectors.js)0
-rw-r--r--ui/app/pages/send/send-header/tests/send-header-component.test.js (renamed from ui/app/components/app/send/send-header/tests/send-header-component.test.js)6
-rw-r--r--ui/app/pages/send/send-header/tests/send-header-container.test.js (renamed from ui/app/components/app/send/send-header/tests/send-header-container.test.js)2
-rw-r--r--ui/app/pages/send/send-header/tests/send-header-selectors.test.js (renamed from ui/app/components/app/send/send-header/tests/send-header-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send.component.js (renamed from ui/app/components/app/send/send.component.js)38
-rw-r--r--ui/app/pages/send/send.constants.js (renamed from ui/app/components/app/send/send.constants.js)2
-rw-r--r--ui/app/pages/send/send.container.js (renamed from ui/app/components/app/send/send.container.js)13
-rw-r--r--ui/app/pages/send/send.scss (renamed from ui/app/components/app/send/send.scss)0
-rw-r--r--ui/app/pages/send/send.selectors.js (renamed from ui/app/components/app/send/send.selectors.js)20
-rw-r--r--ui/app/pages/send/send.utils.js (renamed from ui/app/components/app/send/send.utils.js)4
-rw-r--r--ui/app/pages/send/tests/send-component.test.js (renamed from ui/app/components/app/send/tests/send-component.test.js)2
-rw-r--r--ui/app/pages/send/tests/send-container.test.js (renamed from ui/app/components/app/send/tests/send-container.test.js)11
-rw-r--r--ui/app/pages/send/tests/send-selectors-test-data.js (renamed from ui/app/components/app/send/tests/send-selectors-test-data.js)0
-rw-r--r--ui/app/pages/send/tests/send-selectors.test.js (renamed from ui/app/components/app/send/tests/send-selectors.test.js)9
-rw-r--r--ui/app/pages/send/tests/send-utils.test.js (renamed from ui/app/components/app/send/tests/send-utils.test.js)10
-rw-r--r--ui/app/pages/send/to-autocomplete.component.js (renamed from ui/app/components/app/send/to-autocomplete.component.js)0
-rw-r--r--ui/app/pages/send/to-autocomplete/index.js (renamed from ui/app/components/app/send/to-autocomplete/index.js)0
-rw-r--r--ui/app/pages/send/to-autocomplete/to-autocomplete.js (renamed from ui/app/components/app/send/to-autocomplete/to-autocomplete.js)27
-rw-r--r--ui/app/pages/settings/advanced-tab/advanced-tab.component.js53
-rw-r--r--ui/app/pages/settings/advanced-tab/advanced-tab.container.js11
-rw-r--r--ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js44
-rw-r--r--ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js46
-rw-r--r--ui/app/pages/settings/index.scss60
-rw-r--r--ui/app/pages/settings/info-tab/index.scss2
-rw-r--r--ui/app/pages/settings/networks-tab/index.js1
-rw-r--r--ui/app/pages/settings/networks-tab/index.scss200
-rw-r--r--ui/app/pages/settings/networks-tab/network-form/index.js1
-rw-r--r--ui/app/pages/settings/networks-tab/network-form/network-form.component.js225
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.component.js214
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.constants.js50
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.container.js77
-rw-r--r--ui/app/pages/settings/security-tab/security-tab.component.js6
-rw-r--r--ui/app/pages/settings/settings-tab/index.scss20
-rw-r--r--ui/app/pages/settings/settings.component.js28
-rw-r--r--ui/app/selectors/custom-gas.js2
-rw-r--r--ui/app/selectors/selectors.js29
-rw-r--r--ui/app/store/actions.js124
261 files changed, 3404 insertions, 1030 deletions
diff --git a/ui/app/components/app/account-panel.js b/ui/app/components/app/account-panel.js
index 79882f34a..e61cb8ad6 100644
--- a/ui/app/components/app/account-panel.js
+++ b/ui/app/components/app/account-panel.js
@@ -69,18 +69,9 @@ AccountPanel.prototype.render = function () {
)
}
-function balanceOrFaucetingIndication (account, isFauceting) {
- // Temporarily deactivating isFauceting indication
- // because it shows fauceting for empty restored accounts.
- if (/* isFauceting*/ false) {
- return {
- key: 'Account is auto-funding.',
- value: 'Please wait.',
- }
- } else {
- return {
- key: 'BALANCE',
- value: formatBalance(account.balance),
- }
+function balanceOrFaucetingIndication (account) {
+ return {
+ key: 'BALANCE',
+ value: formatBalance(account.balance),
}
}
diff --git a/ui/app/components/app/add-token-button/index.scss b/ui/app/components/app/add-token-button/index.scss
index 39f404716..c4350a2d3 100644
--- a/ui/app/components/app/add-token-button/index.scss
+++ b/ui/app/components/app/add-token-button/index.scss
@@ -17,10 +17,7 @@
}
&__button {
- font-size: 0.75rem;
+ @extend %small-link;
margin: 1rem;
- text-transform: uppercase;
- color: $curious-blue;
- cursor: pointer;
}
}
diff --git a/ui/app/components/app/app-header/index.scss b/ui/app/components/app/app-header/index.scss
index 325844af5..d46b16f25 100644
--- a/ui/app/components/app/app-header/index.scss
+++ b/ui/app/components/app/app-header/index.scss
@@ -48,7 +48,6 @@
&__contents {
display: flex;
- justify-content: space-between;
flex-flow: row nowrap;
width: 100%;
@@ -74,17 +73,33 @@
flex-direction: row;
align-items: center;
cursor: pointer;
+ flex: 0 0 auto;
}
&__account-menu-container {
display: flex;
flex-flow: row nowrap;
align-items: center;
+ flex: 1 1 auto;
+ width: 0;
+ flex-flow: row nowrap;
+ justify-content: flex-end;
}
&__network-component-wrapper {
display: flex;
flex-direction: row;
align-items: center;
+ flex: 1 0 auto;
+ width: 0;
+ justify-content: flex-end;
+
+ .network-component.pointer {
+ max-width: 200px;
+ }
+
+ .network-indicator {
+ width: 100%;
+ }
}
}
diff --git a/ui/app/components/app/bn-as-decimal-input.js b/ui/app/components/app/bn-as-decimal-input.js
index 9a033f893..834bab0a4 100644
--- a/ui/app/components/app/bn-as-decimal-input.js
+++ b/ui/app/components/app/bn-as-decimal-input.js
@@ -116,7 +116,7 @@ BnAsDecimalInput.prototype.render = function () {
)
}
-BnAsDecimalInput.prototype.setValid = function (message) {
+BnAsDecimalInput.prototype.setValid = function () {
this.setState({ invalid: null })
}
diff --git a/ui/app/components/app/coinbase-form.js b/ui/app/components/app/coinbase-form.js
deleted file mode 100644
index 24d287604..000000000
--- a/ui/app/components/app/coinbase-form.js
+++ /dev/null
@@ -1,69 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const connect = require('react-redux').connect
-const actions = require('../../store/actions')
-
-CoinbaseForm.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps)(CoinbaseForm)
-
-
-function mapStateToProps (state) {
- return {
- warning: state.appState.warning,
- }
-}
-
-inherits(CoinbaseForm, Component)
-
-function CoinbaseForm () {
- Component.call(this)
-}
-
-CoinbaseForm.prototype.render = function () {
- var props = this.props
-
- return h('.flex-column', {
- style: {
- marginTop: '35px',
- padding: '25px',
- width: '100%',
- },
- }, [
- h('.flex-row', {
- style: {
- justifyContent: 'space-around',
- margin: '33px',
- marginTop: '0px',
- },
- }, [
- h('button.btn-green', {
- onClick: this.toCoinbase.bind(this),
- }, this.context.t('continueToCoinbase')),
-
- h('button.btn-red', {
- onClick: () => props.dispatch(actions.goHome()),
- }, this.context.t('cancel')),
- ]),
- ])
-}
-
-CoinbaseForm.prototype.toCoinbase = function () {
- const props = this.props
- const address = props.buyView.buyAddress
- props.dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
-}
-
-CoinbaseForm.prototype.renderLoading = function () {
- return h('img', {
- style: {
- width: '27px',
- marginRight: '-27px',
- },
- src: 'images/loading.svg',
- })
-}
diff --git a/ui/app/components/app/customize-gas-modal/index.js b/ui/app/components/app/customize-gas-modal/index.js
index dca77bb00..1f9436810 100644
--- a/ui/app/components/app/customize-gas-modal/index.js
+++ b/ui/app/components/app/customize-gas-modal/index.js
@@ -18,11 +18,11 @@ const {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
MIN_GAS_PRICE_GWEI,
-} = require('../send/send.constants')
+} = require('../../../pages/send/send.constants')
const {
isBalanceSufficient,
-} = require('../send/send.utils')
+} = require('../../../pages/send/send.utils')
const {
conversionUtil,
@@ -47,7 +47,7 @@ const {
const {
getGasPrice,
getGasLimit,
-} = require('../send/send.selectors')
+} = require('../../../pages/send/send.selectors')
function mapStateToProps (state) {
const selectedToken = getSelectedToken(state)
@@ -382,7 +382,7 @@ CustomizeGasModal.prototype.render = function () {
onClick: this.props.hideModal,
}, [this.context.t('cancel')]),
h(Button, {
- type: 'primary',
+ type: 'secondary',
className: 'send-v2__customize-gas__save',
onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal),
disabled: error,
diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js
index 3d4598946..cbeccdd81 100644
--- a/ui/app/components/app/dropdowns/account-details-dropdown.js
+++ b/ui/app/components/app/dropdowns/account-details-dropdown.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../../store/actions')
-const { getSelectedIdentity } = require('../../../selectors/selectors')
+const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors')
const genAccountLink = require('../../../../lib/account-link.js')
const { Menu, Item, CloseArea } = require('./components/menu')
@@ -20,6 +20,7 @@ function mapStateToProps (state) {
selectedIdentity: getSelectedIdentity(state),
network: state.metamask.network,
keyrings: state.metamask.keyrings,
+ rpcPrefs: getRpcPrefsForCurrentProvider(state),
}
}
@@ -28,8 +29,8 @@ function mapDispatchToProps (dispatch) {
showAccountDetailModal: () => {
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
},
- viewOnEtherscan: (address, network) => {
- global.platform.openWindow({ url: genAccountLink(address, network) })
+ viewOnEtherscan: (address, network, rpcPrefs) => {
+ global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) })
},
showRemoveAccountConfirmationModal: (identity) => {
return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
@@ -56,7 +57,9 @@ AccountDetailsDropdown.prototype.render = function () {
keyrings,
showAccountDetailModal,
viewOnEtherscan,
- showRemoveAccountConfirmationModal } = this.props
+ showRemoveAccountConfirmationModal,
+ rpcPrefs,
+ } = this.props
const address = selectedIdentity.address
@@ -112,10 +115,12 @@ AccountDetailsDropdown.prototype.render = function () {
name: 'Clicked View on Etherscan',
},
})
- viewOnEtherscan(address, network)
+ viewOnEtherscan(address, network, rpcPrefs)
this.props.onClose()
},
- text: this.context.t('viewOnEtherscan'),
+ text: (rpcPrefs.blockExplorerUrl
+ ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]])
+ : this.context.t('viewOnEtherscan')),
icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }),
}),
isRemovable ? h(Item, {
diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js
index 3d9037a06..378ad3ba6 100644
--- a/ui/app/components/app/dropdowns/network-dropdown.js
+++ b/ui/app/components/app/dropdowns/network-dropdown.js
@@ -10,7 +10,7 @@ const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
const R = require('ramda')
-const { ADVANCED_ROUTE } = require('../../../helpers/constants/routes')
+const { NETWORKS_ROUTE } = require('../../../helpers/constants/routes')
// classes from nodes of the toggle element.
const notToggleElementClassnames = [
@@ -49,6 +49,7 @@ function mapDispatchToProps (dispatch) {
},
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
+ setNetworksTabAddMode: isInAddMode => dispatch(actions.setNetworksTabAddMode(isInAddMode)),
}
}
@@ -72,7 +73,7 @@ module.exports = compose(
// TODO: specify default props and proptypes
NetworkDropdown.prototype.render = function () {
const props = this.props
- const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
+ const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = props
const rpcListDetail = props.frequentRpcListDetail
const isOpen = this.props.networkDropdownOpen
const dropdownMenuItemStyle = {
@@ -207,6 +208,28 @@ NetworkDropdown.prototype.render = function () {
h(
DropdownMenuItem,
{
+ key: 'goerli',
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => this.handleClick('goerli'),
+ style: dropdownMenuItemStyle,
+ },
+ [
+ providerType === 'goerli' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#3099f2', // $dodger-blue
+ isSelected: providerType === 'goerli',
+ }),
+ h('span.network-name-item', {
+ style: {
+ color: providerType === 'goerli' ? '#ffffff' : '#9b9b9b',
+ },
+ }, this.context.t('goerli')),
+ ]
+ ),
+
+ h(
+ DropdownMenuItem,
+ {
key: 'default',
closeMenu: () => this.props.hideNetworkDropdown(),
onClick: () => this.handleClick('localhost'),
@@ -233,7 +256,10 @@ NetworkDropdown.prototype.render = function () {
DropdownMenuItem,
{
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => this.props.history.push(ADVANCED_ROUTE),
+ onClick: () => {
+ setNetworksTabAddMode(true)
+ this.props.history.push(NETWORKS_ROUTE)
+ },
style: dropdownMenuItemStyle,
},
[
@@ -285,6 +311,10 @@ NetworkDropdown.prototype.getNetworkName = function () {
name = this.context.t('kovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby')
+ } else if (providerName === 'localhost') {
+ name = this.context.t('localhost')
+ } else if (providerName === 'goerli') {
+ name = this.context.t('goerli')
} else {
name = provider.nickname || this.context.t('unknownNetwork')
}
diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js
index 91e7899a7..4a81b973f 100644
--- a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js
+++ b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js
@@ -62,7 +62,7 @@ describe('Network Dropdown', () => {
})
it('renders 7 DropDownMenuItems ', () => {
- assert.equal(wrapper.find(DropdownMenuItem).length, 7)
+ assert.equal(wrapper.find(DropdownMenuItem).length, 8)
})
it('checks background color for first NetworkDropdownIcon', () => {
@@ -82,15 +82,19 @@ describe('Network Dropdown', () => {
})
it('checks background color for fifth NetworkDropdownIcon', () => {
- assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('innerBorder'), '1px solid #9b9b9b')
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('backgroundColor'), '#3099f2') // Goerli Blue
+ })
+
+ it('checks background color for sixth NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b')
})
it('checks dropdown for frequestRPCList from state ', () => {
- assert.equal(wrapper.find(DropdownMenuItem).at(5).text(), '✓http://localhost:7545')
+ assert.equal(wrapper.find(DropdownMenuItem).at(6).text(), '✓http://localhost:7545')
})
- it('checks background color for sixth NetworkDropdownIcon', () => {
- assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b')
+ it('checks background color for seventh NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(6).prop('innerBorder'), '1px solid #9b9b9b')
})
})
diff --git a/ui/app/components/app/ens-input.js b/ui/app/components/app/ens-input.js
index 274058a1b..f17f6c3d6 100644
--- a/ui/app/components/app/ens-input.js
+++ b/ui/app/components/app/ens-input.js
@@ -10,7 +10,7 @@ const networkMap = require('ethjs-ens/lib/network-map.json')
const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect
-const ToAutoComplete = require('./send/to-autocomplete').default
+const ToAutoComplete = require('../../pages/send/to-autocomplete').default
const log = require('loglevel')
const { isValidENSAddress } = require('../../helpers/utils/util')
@@ -41,12 +41,15 @@ EnsInput.prototype.onChange = function (recipient) {
ensResolution: null,
ensFailure: null,
toError: null,
+ recipient,
})
}
this.setState({
loadingEns: true,
+ recipient,
})
+
this.checkName(recipient)
}
@@ -56,6 +59,7 @@ EnsInput.prototype.render = function () {
list: 'addresses',
onChange: this.onChange.bind(this),
qrScanner: true,
+ recipient: (this.state || {}).recipient,
})
return h('div', {
style: { width: '100%', position: 'relative' },
@@ -79,19 +83,21 @@ EnsInput.prototype.componentDidMount = function () {
EnsInput.prototype.lookupEnsName = function (recipient) {
const { ensResolution } = this.state
+ recipient = recipient.trim()
log.info(`ENS attempting to resolve name: ${recipient}`)
- this.ens.lookup(recipient.trim())
+ this.ens.lookup(recipient)
.then((address) => {
if (address === ZERO_ADDRESS) throw new Error(this.context.t('noAddressForName'))
if (address !== ensResolution) {
this.setState({
loadingEns: false,
ensResolution: address,
- nickname: recipient.trim(),
+ nickname: recipient,
hoverText: address + '\n' + this.context.t('clickCopy'),
ensFailure: false,
toError: null,
+ recipient,
})
}
})
@@ -101,11 +107,11 @@ EnsInput.prototype.lookupEnsName = function (recipient) {
ensResolution: recipient,
ensFailure: true,
toError: null,
+ recipient: null,
}
if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') {
setStateObj.hoverText = this.context.t('ensNameNotFound')
setStateObj.toError = 'ensNameNotFound'
- setStateObj.ensFailure = false
} else {
log.error(reason)
setStateObj.hoverText = reason.message
@@ -128,7 +134,7 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
}
if (prevState && ensResolution && this.props.onChange &&
ensResolution !== prevState.ensResolution) {
- this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning })
+ this.props.onChange({ toAddress: ensResolution, recipient: state.recipient, nickname, toError: state.toError, toWarning: state.toWarning })
}
}
@@ -144,7 +150,7 @@ EnsInput.prototype.ensIcon = function (recipient) {
}, this.ensIconContents(recipient))
}
-EnsInput.prototype.ensIconContents = function (recipient) {
+EnsInput.prototype.ensIconContents = function () {
const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS }
if (toError) return
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
index 95894140c..d6c259033 100644
--- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
@@ -58,7 +58,7 @@ export default class AdvancedTabContent extends Component {
}
}
- gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
const {
isInError,
errorText,
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
index 90fef1a1b..73bc13481 100644
--- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
@@ -17,8 +17,8 @@ function convertGasLimitForInputs (gasLimitInHexWEI) {
const mapDispatchToProps = dispatch => {
return {
- showGasPriceInfoModal: modalName => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
- showGasLimitInfoModal: modalName => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
+ showGasPriceInfoModal: () => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
+ showGasLimitInfoModal: () => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
}
}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
index ad8628621..eab3434df 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
@@ -67,7 +67,7 @@ export default class AdvancedTabContent extends Component {
}
}
- gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
const {
isInError,
errorText,
@@ -148,7 +148,6 @@ export default class AdvancedTabContent extends Component {
customGasPrice,
updateCustomGasPrice,
customGasLimit,
- updateCustomGasLimit,
insufficientBalance,
customPriceIsSafe,
isSpeedUp,
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
index 8aaccafd5..e18c1067e 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
@@ -122,8 +122,6 @@ export default class GasModalPageContainer extends Component {
}
renderTabs ({
- originalTotalFiat,
- originalTotalEth,
newTotalFiat,
newTotalEth,
sendAmount,
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
index d541056f4..9da9a2ef6 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
@@ -7,6 +7,8 @@ import {
setGasPrice,
createSpeedUpTransaction,
hideSidebar,
+ updateSendAmount,
+ setGasTotal,
} from '../../../../store/actions'
import {
setCustomGasPrice,
@@ -18,6 +20,7 @@ import {
} from '../../../../ducks/gas/gas.duck'
import {
hideGasButtonGroup,
+ updateSendErrors,
} from '../../../../ducks/send/send.duck'
import {
updateGasAndCalculate,
@@ -46,6 +49,9 @@ import {
isCustomPriceSafe,
} from '../../../../selectors/custom-gas'
import {
+ getTokenBalance,
+} from '../../../../pages/send/send.selectors'
+import {
submittedPendingTransactionsSelector,
} from '../../../../selectors/transactions'
import {
@@ -53,6 +59,7 @@ import {
} from '../../../../helpers/utils/confirm-tx.util'
import {
addHexWEIsToDec,
+ subtractHexWEIsToDec,
decEthToConvertedCurrency as ethTotalToConvertedCurrency,
decGWEIToHexWEI,
hexWEIToDecGWEI,
@@ -63,9 +70,11 @@ import {
import {
calcGasTotal,
isBalanceSufficient,
-} from '../../send/send.utils'
+} from '../../../../pages/send/send.utils'
import { addHexPrefix } from 'ethereumjs-util'
import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils'
+import { getMaxModeOn } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors'
+import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils'
const mapStateToProps = (state, ownProps) => {
const { transaction = {} } = ownProps
@@ -75,8 +84,6 @@ const mapStateToProps = (state, ownProps) => {
const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = getTxParams(state, transaction.id)
const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice
const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit
- const gasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
-
const customGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
const gasButtonInfo = getRenderableBasicEstimateData(state, customModalGasLimitInHex)
@@ -90,6 +97,8 @@ const mapStateToProps = (state, ownProps) => {
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
+ const maxModeOn = getMaxModeOn(state)
+
const gasPrices = getEstimatedGasPrices(state)
const estimatedTimes = getEstimatedGasTimes(state)
const balance = getCurrentEthBalance(state)
@@ -98,9 +107,13 @@ const mapStateToProps = (state, ownProps) => {
const isMainnet = getIsMainnet(state)
const showFiat = Boolean(isMainnet || showFiatInTestnets)
- const insufficientBalance = !isBalanceSufficient({
+ const newTotalEth = maxModeOn ? addHexWEIsToRenderableEth(balance, '0x0') : addHexWEIsToRenderableEth(value, customGasTotal)
+
+ const sendAmount = maxModeOn ? subtractHexWEIsFromRenderableEth(balance, customGasTotal) : addHexWEIsToRenderableEth(value, '0x0')
+
+ const insufficientBalance = maxModeOn ? false : !isBalanceSufficient({
amount: value,
- gasTotal,
+ gasTotal: customGasTotal,
balance,
conversionRate,
})
@@ -112,10 +125,12 @@ const mapStateToProps = (state, ownProps) => {
customModalGasLimitInHex,
customGasPrice,
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
+ customGasTotal,
newTotalFiat,
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, gasPrices, estimatedTimes),
blockTime: getBasicGasEstimateBlockTime(state),
customPriceIsSafe: isCustomPriceSafe(state),
+ maxModeOn,
gasPriceButtonGroupProps: {
buttonDataLoading,
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
@@ -129,12 +144,12 @@ const mapStateToProps = (state, ownProps) => {
estimatedTimesMax: estimatedTimes[0],
},
infoRowProps: {
- originalTotalFiat: addHexWEIsToRenderableFiat(value, gasTotal, currentCurrency, conversionRate),
- originalTotalEth: addHexWEIsToRenderableEth(value, gasTotal),
+ originalTotalFiat: addHexWEIsToRenderableFiat(value, customGasTotal, currentCurrency, conversionRate),
+ originalTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
newTotalFiat: showFiat ? newTotalFiat : '',
- newTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
+ newTotalEth,
transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal),
- sendAmount: addHexWEIsToRenderableEth(value, '0x0'),
+ sendAmount,
},
isSpeedUp: transaction.status === 'submitted',
txId: transaction.id,
@@ -142,6 +157,9 @@ const mapStateToProps = (state, ownProps) => {
gasEstimatesLoading,
isMainnet,
isEthereumNetwork: isEthereumNetwork(state),
+ selectedToken: getSelectedToken(state),
+ balance,
+ tokenBalance: getTokenBalance(state),
}
}
@@ -174,11 +192,29 @@ const mapDispatchToProps = dispatch => {
hideSidebar: () => dispatch(hideSidebar()),
fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)),
fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
+ setGasTotal: (total) => dispatch(setGasTotal(total)),
+ setAmountToMax: (maxAmountDataObject) => {
+ dispatch(updateSendErrors({ amount: null }))
+ dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
+ },
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
- const { gasPriceButtonGroupProps, isConfirm, txId, isSpeedUp, insufficientBalance, customGasPrice } = stateProps
+ const {
+ gasPriceButtonGroupProps,
+ isConfirm,
+ txId,
+ isSpeedUp,
+ insufficientBalance,
+ maxModeOn,
+ customGasPrice,
+ customGasTotal,
+ balance,
+ selectedToken,
+ tokenBalance,
+ customGasLimit,
+ } = stateProps
const {
updateCustomGasPrice: dispatchUpdateCustomGasPrice,
hideGasButtonGroup: dispatchHideGasButtonGroup,
@@ -188,6 +224,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
hideSidebar: dispatchHideSidebar,
cancelAndClose: dispatchCancelAndClose,
hideModal: dispatchHideModal,
+ setAmountToMax: dispatchSetAmountToMax,
...otherDispatchProps
} = dispatchProps
@@ -208,6 +245,14 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
dispatchHideGasButtonGroup()
dispatchCancelAndClose()
}
+ if (maxModeOn) {
+ dispatchSetAmountToMax({
+ balance,
+ gasTotal: customGasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
},
gasPriceButtonGroupProps: {
...gasPriceButtonGroupProps,
@@ -219,7 +264,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
dispatchHideSidebar()
}
},
- disableSave: insufficientBalance || (isSpeedUp && customGasPrice === 0),
+ disableSave: insufficientBalance || (isSpeedUp && customGasPrice === 0) || customGasLimit < 21000,
}
}
@@ -258,6 +303,13 @@ function addHexWEIsToRenderableEth (aHexWEI, bHexWEI) {
)(aHexWEI, bHexWEI)
}
+function subtractHexWEIsFromRenderableEth (aHexWEI, bHexWei) {
+ return pipe(
+ subtractHexWEIsToDec,
+ formatETHFee
+ )(aHexWEI, bHexWei)
+}
+
function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conversionRate) {
return pipe(
addHexWEIsToDec,
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
index ab24b9c0e..dbe61d5cf 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
@@ -46,6 +46,10 @@ proxyquire('../gas-modal-page-container.container.js', {
'../../../../ducks/send/send.duck': sendActionSpies,
'../../../../selectors/selectors.js': {
getCurrentEthBalance: (state) => state.metamask.balance || '0x0',
+ getSelectedToken: () => null,
+ },
+ '../../../../pages/send/send.selectors': {
+ getTokenBalance: (state) => state.metamask.send.tokenBalance || '0x0',
},
})
@@ -68,6 +72,7 @@ describe('gas-modal-page-container container', () => {
gasLimit: '16',
gasPrice: '32',
amount: '64',
+ maxModeOn: false,
},
currentCurrency: 'abc',
conversionRate: 50,
@@ -106,6 +111,7 @@ describe('gas-modal-page-container container', () => {
},
}
const baseExpectedResult = {
+ balance: '0x0',
isConfirm: true,
customGasPrice: 4.294967295,
customGasLimit: 2863311530,
@@ -114,6 +120,7 @@ describe('gas-modal-page-container container', () => {
blockTime: 12,
customModalGasLimitInHex: 'aaaaaaaa',
customModalGasPriceInHex: 'ffffffff',
+ customGasTotal: 'aaaaaaa955555556',
customPriceIsSafe: true,
gasChartProps: {
'currentPrice': 4.294967295,
@@ -142,6 +149,9 @@ describe('gas-modal-page-container container', () => {
txId: 34,
isEthereumNetwork: true,
isMainnet: true,
+ maxModeOn: false,
+ selectedToken: null,
+ tokenBalance: '0x0',
}
const baseMockOwnProps = { transaction: { id: 34 } }
const tests = [
@@ -150,7 +160,7 @@ describe('gas-modal-page-container container', () => {
mockState: Object.assign({}, baseMockState, {
metamask: { ...baseMockState.metamask, balance: '0xfffffffffffffffffffff' },
}),
- expectedResult: Object.assign({}, baseExpectedResult, { insufficientBalance: false }),
+ expectedResult: Object.assign({}, baseExpectedResult, { balance: '0xfffffffffffffffffffff', insufficientBalance: false }),
mockOwnProps: baseMockOwnProps,
},
{
diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
index 0456f5262..14952a49a 100644
--- a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
@@ -49,7 +49,7 @@ export default class GasPriceButtonGroup extends Component {
priceInHexWei,
...renderableGasInfo
}, {
- buttonDataLoading,
+ buttonDataLoading: _,
handleGasPriceSelection,
...buttonContentPropsAndFlags
}, index) {
diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/index.scss b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss
index cb2f3ecf1..92b4aba42 100644
--- a/ui/app/components/app/gas-customization/gas-price-button-group/index.scss
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss
@@ -65,6 +65,7 @@
.gas-price-button-group--small {
display: flex;
justify-content: stretch;
+ height: 54px;
@media screen and (max-width: $break-small) {
max-width: 260px;
@@ -80,10 +81,14 @@
&__label {
font-weight: 500;
+ line-height: 16px;
+ padding-bottom: 4px;
}
&__primary-currency {
font-size: 12px;
+ line-height: 12px;
+ padding-bottom: 2px;
@media screen and (max-width: 575px) {
font-size: 10px;
@@ -92,6 +97,8 @@
&__secondary-currency {
font-size: 12px;
+ line-height: 12px;
+ padding-bottom: 2px;
@media screen and (max-width: 575px) {
font-size: 10px;
@@ -99,19 +106,13 @@
}
&__loading-container {
- height: 78px;
+ height: 54px;
}
.button-group__button, .button-group__button--active {
- height: 78px;
background: white;
color: $scorpion;
- padding-top: 9px;
- padding-left: 8.5px;
-
- @media screen and (max-width: $break-small) {
- padding-left: 4px;
- }
+ padding: 0 4px;
div {
display: flex;
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
index f19dafcc1..55512ce09 100644
--- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
+++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
@@ -68,7 +68,7 @@ export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) {
export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) {
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition)
- const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => e > priceToPosition)
+ const closestHigherValueIndex = gasPrices.findIndex((e) => e > priceToPosition)
return {
closestLowerValueIndex,
closestHigherValueIndex,
@@ -133,7 +133,7 @@ export function setTickPosition (axis, n, newPosition, secondNewPosition) {
d3.select('#chart')
.select(`.c3-axis-${axis}`)
.selectAll('.tick')
- .filter((d, i) => i === n)
+ .filter((_, i) => i === n)
.select('text')
.attr(positionToShift, 0)
.select('tspan')
@@ -284,7 +284,7 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate
})
return text + '</table>' + "<div class='tooltip-arrow'></div>"
},
- position: function (data) {
+ position: function () {
if (d3.select('#overlayed-circle').empty()) {
return { top: -100, left: -100 }
}
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
index 7dec7a85f..c960f49a7 100644
--- a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
+++ b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
@@ -6,7 +6,7 @@ import shallow from '../../../../../../lib/shallow-with-context'
import * as d3 from 'd3'
function timeout (time) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
setTimeout(resolve, time)
})
}
diff --git a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js
index 348a997c8..97b16d08f 100644
--- a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js
+++ b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js
@@ -45,6 +45,10 @@ export default class LoadingNetworkScreen extends PureComponent {
name = this.context.t('connectingToKovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby')
+ } else if (providerName === 'localhost') {
+ name = this.context.t('connectingToLocalhost')
+ } else if (providerName === 'goerli') {
+ name = this.context.t('connectingToGoerli')
} else {
name = this.context.t('connectingTo', [providerId])
}
diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js
index 49e131b3c..44b180ac8 100644
--- a/ui/app/components/app/modal/modal.component.js
+++ b/ui/app/components/app/modal/modal.component.js
@@ -20,7 +20,7 @@ export default class Modal extends PureComponent {
}
static defaultProps = {
- submitType: 'primary',
+ submitType: 'secondary',
cancelType: 'default',
}
diff --git a/ui/app/components/app/modal/tests/modal.component.test.js b/ui/app/components/app/modal/tests/modal.component.test.js
index a13d7c06a..5922177a6 100644
--- a/ui/app/components/app/modal/tests/modal.component.test.js
+++ b/ui/app/components/app/modal/tests/modal.component.test.js
@@ -12,7 +12,7 @@ describe('Modal Component', () => {
assert.equal(wrapper.find('.modal-container').length, 1)
const buttons = wrapper.find(Button)
assert.equal(buttons.length, 1)
- assert.equal(buttons.at(0).props().type, 'primary')
+ assert.equal(buttons.at(0).props().type, 'secondary')
})
it('should render a modal with a cancel and a submit button', () => {
@@ -38,7 +38,7 @@ describe('Modal Component', () => {
cancelButton.simulate('click')
assert.equal(handleCancel.callCount, 1)
- assert.equal(submitButton.props().type, 'primary')
+ assert.equal(submitButton.props().type, 'secondary')
assert.equal(submitButton.props().children, 'Submit')
assert.equal(handleSubmit.callCount, 0)
submitButton.simulate('click')
diff --git a/ui/app/components/app/modals/account-details-modal.js b/ui/app/components/app/modals/account-details-modal.js
index 94ed04df9..6cffc918b 100644
--- a/ui/app/components/app/modals/account-details-modal.js
+++ b/ui/app/components/app/modals/account-details-modal.js
@@ -5,7 +5,7 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../../store/actions')
const AccountModalContainer = require('./account-modal-container')
-const { getSelectedIdentity } = require('../../../selectors/selectors')
+const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors')
const genAccountLink = require('../../../../lib/account-link.js')
const QrView = require('../../ui/qr-code')
const EditableLabel = require('../../ui/editable-label')
@@ -17,6 +17,7 @@ function mapStateToProps (state) {
network: state.metamask.network,
selectedIdentity: getSelectedIdentity(state),
keyrings: state.metamask.keyrings,
+ rpcPrefs: getRpcPrefsForCurrentProvider(state),
}
}
@@ -54,6 +55,7 @@ AccountDetailsModal.prototype.render = function () {
showExportPrivateKeyModal,
setAccountLabel,
keyrings,
+ rpcPrefs,
} = this.props
const { name, address } = selectedIdentity
@@ -84,15 +86,19 @@ AccountDetailsModal.prototype.render = function () {
h('div.account-modal-divider'),
h(Button, {
- type: 'primary',
+ type: 'secondary',
className: 'account-modal__button',
- onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
- }, this.context.t('etherscanView')),
+ onClick: () => {
+ global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) })
+ },
+ }, (rpcPrefs.blockExplorerUrl
+ ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]])
+ : this.context.t('viewOnEtherscan'))),
// Holding on redesign for Export Private Key functionality
exportPrivateKeyFeatureEnabled ? h(Button, {
- type: 'primary',
+ type: 'secondary',
className: 'account-modal__button',
onClick: () => showExportPrivateKeyModal(),
}, this.context.t('exportPrivateKey')) : null,
diff --git a/ui/app/components/app/modals/customize-gas/customize-gas.component.js b/ui/app/components/app/modals/customize-gas/customize-gas.component.js
index 5db5c79e7..387da2f79 100644
--- a/ui/app/components/app/modals/customize-gas/customize-gas.component.js
+++ b/ui/app/components/app/modals/customize-gas/customize-gas.component.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import BigNumber from 'bignumber.js'
import GasModalCard from '../../customize-gas-modal/gas-modal-card'
-import { MIN_GAS_PRICE_GWEI } from '../../send/send.constants'
+import { MIN_GAS_PRICE_GWEI } from '../../../../pages/send/send.constants'
import Button from '../../../ui/button'
import {
@@ -128,7 +128,7 @@ export default class CustomizeGas extends Component {
{ t('cancel') }
</Button>
<Button
- type="primary"
+ type="secondary"
className="customize-gas__save"
onClick={() => {
metricsEvent({
diff --git a/ui/app/components/app/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js
index 6f622a17c..f56069d65 100644
--- a/ui/app/components/app/modals/deposit-ether-modal.js
+++ b/ui/app/components/app/modals/deposit-ether-modal.js
@@ -48,7 +48,7 @@ function mapDispatchToProps (dispatch) {
}
inherits(DepositEtherModal, Component)
-function DepositEtherModal (props, context) {
+function DepositEtherModal (_, context) {
Component.call(this)
// need to set after i18n locale has loaded
@@ -119,7 +119,7 @@ DepositEtherModal.prototype.renderRow = function ({
!hideButton && h('div.deposit-ether-modal__buy-row__button', [
h(Button, {
- type: 'primary',
+ type: 'secondary',
className: 'deposit-ether-modal__deposit-button',
large: true,
onClick: onButtonClick,
@@ -133,7 +133,7 @@ DepositEtherModal.prototype.render = function () {
const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props
const { buyingWithShapeshift } = this.state
- const isTestNetwork = ['3', '4', '42'].find(n => n === network)
+ const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network)
const networkName = getNetworkDisplayName(network)
return h('div.page-container.page-container--full-width.page-container--full-height', {}, [
diff --git a/ui/app/components/app/modals/edit-account-name-modal.js b/ui/app/components/app/modals/edit-account-name-modal.js
index 41a9862e9..aa21765c4 100644
--- a/ui/app/components/app/modals/edit-account-name-modal.js
+++ b/ui/app/components/app/modals/edit-account-name-modal.js
@@ -66,7 +66,7 @@ EditAccountNameModal.prototype.render = function () {
value: this.state.inputText,
}, []),
- h('button.btn-clear.edit-account-name-modal-save-button.allcaps', {
+ h('button.button.btn-secondary.edit-account-name-modal-save-button.allcaps', {
onClick: () => {
if (this.state.inputText.length !== 0) {
setAccountLabel(identity.address, this.state.inputText)
diff --git a/ui/app/components/app/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js
index 639887d4c..c3098a16c 100644
--- a/ui/app/components/app/modals/export-private-key-modal.js
+++ b/ui/app/components/app/modals/export-private-key-modal.js
@@ -98,7 +98,7 @@ ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
})
}
-ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
+ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, address, hideModal) {
return h('div.export-private-key-buttons', {}, [
!privateKey && h(Button, {
type: 'default',
@@ -110,14 +110,14 @@ ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password,
(privateKey
? (
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'export-private-key__button',
onClick: () => hideModal(),
}, this.context.t('done'))
) : (
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'export-private-key__button',
onClick: () => this.exportAccountAndGetPrivateKey(this.state.password, address),
@@ -171,7 +171,7 @@ ExportPrivateKeyModal.prototype.render = function () {
h('div.private-key-password-warning', this.context.t('privateKeyWarning')),
- this.renderButtons(privateKey, this.state.password, address, hideModal),
+ this.renderButtons(privateKey, address, hideModal),
])
}
diff --git a/ui/app/components/app/modals/hide-token-confirmation-modal.js b/ui/app/components/app/modals/hide-token-confirmation-modal.js
index 8a9a48fd2..e2b098923 100644
--- a/ui/app/components/app/modals/hide-token-confirmation-modal.js
+++ b/ui/app/components/app/modals/hide-token-confirmation-modal.js
@@ -67,12 +67,12 @@ HideTokenConfirmationModal.prototype.render = function () {
]),
h('div.hide-token-confirmation__buttons', {}, [
- h('button.btn-cancel.hide-token-confirmation__button.allcaps', {
+ h('button.btn-default.hide-token-confirmation__button.btn--large', {
onClick: () => hideModal(),
}, [
this.context.t('cancel'),
]),
- h('button.btn-clear.hide-token-confirmation__button.allcaps', {
+ h('button.btn-secondary.hide-token-confirmation__button.btn--large', {
onClick: () => hideToken(address),
}, [
this.context.t('hide'),
diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
index 83595281f..ea7d71a73 100644
--- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
+++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
@@ -4,7 +4,7 @@ import MetaMetricsOptInModal from './metametrics-opt-in-modal.component'
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
import { setParticipateInMetaMetrics } from '../../../../store/actions'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (_, ownProps) => {
const { unapprovedTxCount } = ownProps
return {
diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js
index 2d73b2cfa..b8503ec1a 100644
--- a/ui/app/components/app/modals/notification-modal.js
+++ b/ui/app/components/app/modals/notification-modal.js
@@ -37,11 +37,11 @@ class NotificationModal extends Component {
showButtons && h('div.notification-modal__buttons', [
- showCancelButton && h('div.btn-cancel.notification-modal__buttons__btn', {
+ showCancelButton && h('div.btn-default.notification-modal__buttons__btn', {
onClick: hideModal,
}, 'Cancel'),
- showConfirmButton && h('div.btn-clear.notification-modal__buttons__btn', {
+ showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', {
onClick: () => {
onConfirm()
hideModal()
diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
index 20915b5f9..a83ba8f8e 100644
--- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
+++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
@@ -71,7 +71,7 @@ export default class QrScanner extends Component {
initCamera () {
this.codeReader = new BrowserQRCodeReader()
this.codeReader.getVideoInputDevices()
- .then(videoInputDevices => {
+ .then(() => {
clearTimeout(this.permissionChecker)
this.checkPermisisions()
this.codeReader.decodeFromInputVideoDevice(undefined, 'video')
diff --git a/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
index d2af05573..aa74fd800 100644
--- a/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
+++ b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
@@ -3,7 +3,7 @@ import { compose } from 'recompose'
import RejectTransactionsModal from './reject-transactions.component'
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (_, ownProps) => {
const { unapprovedTxCount } = ownProps
return {
diff --git a/ui/app/components/app/network-display/index.scss b/ui/app/components/app/network-display/index.scss
index e9f2f2057..d70786d20 100644
--- a/ui/app/components/app/network-display/index.scss
+++ b/ui/app/components/app/network-display/index.scss
@@ -26,6 +26,10 @@
&--rinkeby {
background-color: lighten($tulip-tree, 35%);
}
+
+ &--goerli {
+ background-color: lighten($dodger-blue, 35%);
+ }
}
&__name {
@@ -53,5 +57,9 @@
&--rinkeby {
background-color: $tulip-tree;
}
+
+ &--goerli {
+ background-color: $dodger-blue;
+ }
}
}
diff --git a/ui/app/components/app/network-display/network-display.component.js b/ui/app/components/app/network-display/network-display.component.js
index 1142e8606..9ef5341b0 100644
--- a/ui/app/components/app/network-display/network-display.component.js
+++ b/ui/app/components/app/network-display/network-display.component.js
@@ -6,12 +6,14 @@ import {
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
+ GOERLI_CODE,
} from '../../../../../app/scripts/controllers/network/enums'
const networkToClassHash = {
[MAINNET_CODE]: 'mainnet',
[ROPSTEN_CODE]: 'ropsten',
[RINKEYBY_CODE]: 'rinkeby',
+ [GOERLI_CODE]: 'goerli',
[KOVAN_CODE]: 'kovan',
}
diff --git a/ui/app/components/app/network.js b/ui/app/components/app/network.js
index e18404f42..9ee0a1e17 100644
--- a/ui/app/components/app/network.js
+++ b/ui/app/components/app/network.js
@@ -41,15 +41,15 @@ Network.prototype.render = function () {
} else if (providerName === 'ropsten') {
hoverText = context.t('ropsten')
iconName = 'ropsten-test-network'
- } else if (parseInt(networkNumber) === 3) {
- hoverText = context.t('ropsten')
- iconName = 'ropsten-test-network'
} else if (providerName === 'kovan') {
hoverText = context.t('kovan')
iconName = 'kovan-test-network'
} else if (providerName === 'rinkeby') {
hoverText = context.t('rinkeby')
iconName = 'rinkeby-test-network'
+ } else if (providerName === 'goerli') {
+ hoverText = context.t('goerli')
+ iconName = 'goerli-test-network'
} else {
hoverText = providerId
iconName = 'private-network'
@@ -60,9 +60,10 @@ Network.prototype.render = function () {
className: classnames({
'network-component--disabled': this.props.disabled,
'ethereum-network': providerName === 'mainnet',
- 'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3,
+ 'ropsten-test-network': providerName === 'ropsten',
'kovan-test-network': providerName === 'kovan',
'rinkeby-test-network': providerName === 'rinkeby',
+ 'goerli-test-network': providerName === 'goerli',
}),
title: hoverText,
onClick: (event) => {
@@ -113,33 +114,34 @@ Network.prototype.render = function () {
h('.network-name', context.t('rinkeby')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
+ case 'goerli-test-network':
+ return h('.network-indicator', [
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#3099f2', // $dodger-blue
+ nonSelectBackgroundColor: '#ecb23e',
+ loading: networkNumber === 'loading',
+ }),
+ h('.network-name', context.t('goerli')),
+ h('i.fa.fa-chevron-down.fa-lg.network-caret'),
+ ])
default:
return h('.network-indicator', [
networkNumber === 'loading'
- ? h('span.pointer.network-indicator', {
- style: {
- display: 'flex',
- alignItems: 'center',
- flexDirection: 'row',
- },
+ ? h('span.pointer.network-loading-spinner', {
onClick: (event) => this.props.onClick(event),
}, [
h('img', {
title: context.t('attemptingConnect'),
- style: {
- width: '27px',
- },
src: 'images/loading.svg',
}),
])
: h('i.fa.fa-question-circle.fa-lg', {
style: {
- margin: '10px',
color: 'rgb(125, 128, 130)',
},
}),
- h('.network-name', providerNick || context.t('privateNetwork')),
+ h('.network-name', providerName === 'localhost' ? context.t('localhost') : providerNick || context.t('privateNetwork')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
}
diff --git a/ui/app/components/app/provider-page-container/provider-page-container.component.js b/ui/app/components/app/provider-page-container/provider-page-container.component.js
index 910def2a3..1c655d404 100644
--- a/ui/app/components/app/provider-page-container/provider-page-container.component.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container.component.js
@@ -5,12 +5,11 @@ import { PageContainerFooter } from '../../ui/page-container'
export default class ProviderPageContainer extends PureComponent {
static propTypes = {
- approveProviderRequest: PropTypes.func.isRequired,
+ approveProviderRequestByOrigin: PropTypes.func.isRequired,
+ rejectProviderRequestByOrigin: PropTypes.func.isRequired,
origin: PropTypes.string.isRequired,
- rejectProviderRequest: PropTypes.func.isRequired,
siteImage: PropTypes.string,
siteTitle: PropTypes.string.isRequired,
- tabID: PropTypes.string.isRequired,
};
static contextTypes = {
@@ -29,7 +28,7 @@ export default class ProviderPageContainer extends PureComponent {
}
onCancel = () => {
- const { tabID, rejectProviderRequest } = this.props
+ const { origin, rejectProviderRequestByOrigin } = this.props
this.context.metricsEvent({
eventOpts: {
category: 'Auth',
@@ -37,11 +36,11 @@ export default class ProviderPageContainer extends PureComponent {
name: 'Canceled',
},
})
- rejectProviderRequest(tabID)
+ rejectProviderRequestByOrigin(origin)
}
onSubmit = () => {
- const { approveProviderRequest, tabID } = this.props
+ const { approveProviderRequestByOrigin, origin } = this.props
this.context.metricsEvent({
eventOpts: {
category: 'Auth',
@@ -49,7 +48,7 @@ export default class ProviderPageContainer extends PureComponent {
name: 'Confirmed',
},
})
- approveProviderRequest(tabID)
+ approveProviderRequestByOrigin(origin)
}
render () {
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
deleted file mode 100644
index f17137c1e..000000000
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-
-export default class AmountMaxButton extends Component {
-
- static propTypes = {
- balance: PropTypes.string,
- gasTotal: PropTypes.string,
- maxModeOn: PropTypes.bool,
- selectedToken: PropTypes.object,
- setAmountToMax: PropTypes.func,
- setMaxModeTo: PropTypes.func,
- tokenBalance: PropTypes.string,
- }
-
- static contextTypes = {
- t: PropTypes.func,
- }
-
- setMaxAmount () {
- const {
- balance,
- gasTotal,
- selectedToken,
- setAmountToMax,
- tokenBalance,
- } = this.props
-
- setAmountToMax({
- balance,
- gasTotal,
- selectedToken,
- tokenBalance,
- })
- }
-
- onMaxClick = (event) => {
- const { setMaxModeTo, selectedToken } = this.props
-
- fetch('https://chromeextensionmm.innocraft.cloud/piwik.php?idsite=1&rec=1&e_c=send&e_a=amountMax&e_n=' + (selectedToken ? 'token' : 'eth'), {
- 'headers': {},
- 'method': 'GET',
- })
-
- event.preventDefault()
- setMaxModeTo(true)
- this.setMaxAmount()
- }
-
- render () {
- return this.props.maxModeOn
- ? null
- : (
- <div>
- <span
- className="send-v2__amount-max"
- onClick={this.onMaxClick}
- >
- {this.context.t('max')}
- </span>
- </div>
- )
- }
-
-}
diff --git a/ui/app/components/app/shapeshift-form.js b/ui/app/components/app/shapeshift-form.js
index 11459fd5e..34a6f3acd 100644
--- a/ui/app/components/app/shapeshift-form.js
+++ b/ui/app/components/app/shapeshift-form.js
@@ -245,7 +245,7 @@ ShapeshiftForm.prototype.render = function () {
]),
!depositAddress && h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: `${btnClass} shapeshift-form__shapeshift-buy-btn`,
disabled: !token,
diff --git a/ui/app/components/app/signature-request.js b/ui/app/components/app/signature-request.js
index e47791b67..fa237f1d1 100644
--- a/ui/app/components/app/signature-request.js
+++ b/ui/app/components/app/signature-request.js
@@ -311,7 +311,7 @@ SignatureRequest.prototype.renderFooter = function () {
},
}, this.context.t('cancel')),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'request-signature__footer__sign-button',
onClick: event => {
diff --git a/ui/app/components/app/token-cell.js b/ui/app/components/app/token-cell.js
index cef809e8a..495b9502b 100644
--- a/ui/app/components/app/token-cell.js
+++ b/ui/app/components/app/token-cell.js
@@ -155,7 +155,7 @@ TokenCell.prototype.send = function (address, event) {
}
}
-TokenCell.prototype.view = function (address, userAddress, network, event) {
+TokenCell.prototype.view = function (address, userAddress, network) {
const url = etherscanLinkFor(address, userAddress, network)
if (url) {
navigateTo(url)
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 4a3b04998..72ca784e2 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
@@ -1,13 +1,15 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import copyToClipboard from 'copy-to-clipboard'
+import {
+ getBlockExplorerUrlForTx,
+} from '../../../helpers/utils/transactions.util'
import SenderToRecipient from '../../ui/sender-to-recipient'
import { FLAT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants'
import TransactionActivityLog from '../transaction-activity-log'
import TransactionBreakdown from '../transaction-breakdown'
import Button from '../../ui/button'
import Tooltip from '../../ui/tooltip'
-import prefixForNetwork from '../../../../lib/etherscan-prefix-for-network'
export default class TransactionListItemDetails extends PureComponent {
static contextTypes = {
@@ -22,6 +24,7 @@ export default class TransactionListItemDetails extends PureComponent {
showRetry: PropTypes.bool,
cancelDisabled: PropTypes.bool,
transactionGroup: PropTypes.object,
+ rpcPrefs: PropTypes.object,
}
state = {
@@ -30,12 +33,9 @@ export default class TransactionListItemDetails extends PureComponent {
}
handleEtherscanClick = () => {
- const { transactionGroup: { primaryTransaction } } = this.props
+ const { transactionGroup: { primaryTransaction }, rpcPrefs } = this.props
const { hash, metamaskNetworkId } = primaryTransaction
- const prefix = prefixForNetwork(metamaskNetworkId)
- const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
-
this.context.metricsEvent({
eventOpts: {
category: 'Navigation',
@@ -44,7 +44,7 @@ export default class TransactionListItemDetails extends PureComponent {
},
})
- global.platform.openWindow({ url: etherscanUrl })
+ global.platform.openWindow({ url: getBlockExplorerUrlForTx(metamaskNetworkId, hash, rpcPrefs) })
}
handleCancel = event => {
@@ -125,6 +125,7 @@ export default class TransactionListItemDetails extends PureComponent {
showRetry,
onCancel,
onRetry,
+ rpcPrefs: { blockExplorerUrl } = {},
} = this.props
const { primaryTransaction: transaction } = transactionGroup
const { txParams: { to, from } = {} } = transaction
@@ -158,7 +159,7 @@ export default class TransactionListItemDetails extends PureComponent {
/>
</Button>
</Tooltip>
- <Tooltip title={t('viewOnEtherscan')}>
+ <Tooltip title={blockExplorerUrl ? t('viewOnCustomBlockExplorer', [blockExplorerUrl]) : t('viewOnEtherscan')}>
<Button
type="raised"
onClick={this.handleEtherscanClick}
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 c7d9dd7c7..0d4127b4f 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
@@ -33,6 +33,7 @@ export default class TransactionListItem extends PureComponent {
value: PropTypes.string,
fetchBasicGasAndTimeEstimates: PropTypes.func,
fetchGasEstimates: PropTypes.func,
+ rpcPrefs: PropTypes.object,
}
static defaultProps = {
@@ -161,6 +162,7 @@ export default class TransactionListItem extends PureComponent {
showRetry,
tokenData,
transactionGroup,
+ rpcPrefs,
} = this.props
const { txParams = {} } = transaction
const { showTransactionDetails } = this.state
@@ -216,6 +218,7 @@ export default class TransactionListItem extends PureComponent {
onCancel={this.handleCancel}
showCancel={showCancel}
cancelDisabled={!hasEnoughCancelGas}
+ rpcPrefs={rpcPrefs}
/>
</div>
)
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
index de8a3bbba..5e88a2937 100644
--- 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
@@ -15,15 +15,17 @@ import {
setCustomGasLimit,
} from '../../../ducks/gas/gas.duck'
import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector } from '../../../selectors/selectors'
-import { isBalanceSufficient } from '../send/send.utils'
+import { isBalanceSufficient } from '../../../pages/send/send.utils'
const mapStateToProps = (state, ownProps) => {
- const { metamask: { knownMethodData, accounts } } = state
+ const { metamask: { knownMethodData, accounts, provider, frequentRpcListDetail } } = state
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
const { transactionGroup: { primaryTransaction } = {} } = ownProps
const { txParams: { gas: gasLimit, gasPrice } = {} } = primaryTransaction
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance
+ const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
+ const { rpcPrefs } = selectRpcInfo || {}
const hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({
amount: '0x0',
@@ -40,6 +42,7 @@ const mapStateToProps = (state, ownProps) => {
showFiat: (isMainnet || !!showFiatInTestnets),
selectedAccountBalance,
hasEnoughCancelGas,
+ rpcPrefs,
}
}
diff --git a/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js
index 8559e2233..3f6abbb00 100644
--- a/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js
+++ b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js
@@ -87,7 +87,7 @@ export default class TransactionViewBalance extends PureComponent {
{
!selectedToken && (
<Button
- type="primary"
+ type="secondary"
className="transaction-view-balance__button"
onClick={() => {
metricsEvent({
@@ -105,14 +105,14 @@ export default class TransactionViewBalance extends PureComponent {
)
}
<Button
- type="primary"
+ type="secondary"
className="transaction-view-balance__button"
onClick={() => {
metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Home',
- name: 'Clicked Send',
+ name: selectedToken ? 'Clicked Send: Token' : 'Clicked Send: Eth',
},
})
history.push(SEND_ROUTE)
diff --git a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
index 88d63baae..4ecc0dabb 100644
--- a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
+++ b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../user-preferenced-currency-display.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
index 42d156f92..2a4635955 100644
--- a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
+++ b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
@@ -3,7 +3,7 @@ import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display.
import { preferencesSelector, getIsMainnet } from '../../../selectors/selectors'
import { ETH, PRIMARY, SECONDARY } from '../../../helpers/constants/common'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (state) => {
const {
useNativeCurrencyAsPrimaryCurrency,
showFiatInTestnets,
diff --git a/ui/app/components/app/wallet-view.js b/ui/app/components/app/wallet-view.js
index cec8228b1..b8bae5421 100644
--- a/ui/app/components/app/wallet-view.js
+++ b/ui/app/components/app/wallet-view.js
@@ -190,7 +190,7 @@ WalletView.prototype.render = function () {
identities[selectedAddress].name,
]),
- h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')),
+ h('button.btn-secondary.wallet-view__details-button', this.context.t('details')),
]),
]),
diff --git a/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js b/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js
index 8abe1ab18..d9627e31b 100644
--- a/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js
+++ b/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import AccountListItem from '../../app/send/account-list-item/account-list-item.component'
+import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component'
export default class AccountDropdownMini extends PureComponent {
static propTypes = {
diff --git a/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js b/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js
index bc74ceb3c..9691f38aa 100644
--- a/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js
+++ b/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import AccountDropdownMini from '../account-dropdown-mini.component'
-import AccountListItem from '../../../app/send/account-list-item/account-list-item.component'
+import AccountListItem from '../../../../pages/send/account-list-item/account-list-item.component'
describe('AccountDropdownMini', () => {
it('should render an account with an icon', () => {
diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js
index 5620d847a..b1229f502 100644
--- a/ui/app/components/ui/alert/index.js
+++ b/ui/app/components/ui/alert/index.js
@@ -18,7 +18,7 @@ class Alert extends Component {
if (!this.props.visible && nextProps.visible) {
this.animateIn(nextProps)
} else if (this.props.visible && !nextProps.visible) {
- this.animateOut(nextProps)
+ this.animateOut()
}
}
@@ -30,7 +30,7 @@ class Alert extends Component {
})
}
- animateOut (props) {
+ animateOut () {
this.setState({
msg: null,
className: '.hidden',
diff --git a/ui/app/components/ui/button/button.component.js b/ui/app/components/ui/button/button.component.js
index 5d19219b4..39e81317c 100644
--- a/ui/app/components/ui/button/button.component.js
+++ b/ui/app/components/ui/button/button.component.js
@@ -5,7 +5,7 @@ import classnames from 'classnames'
const CLASSNAME_DEFAULT = 'btn-default'
const CLASSNAME_PRIMARY = 'btn-primary'
const CLASSNAME_SECONDARY = 'btn-secondary'
-const CLASSNAME_CONFIRM = 'btn-confirm'
+const CLASSNAME_CONFIRM = 'btn-primary'
const CLASSNAME_RAISED = 'btn-raised'
const CLASSNAME_LARGE = 'btn--large'
const CLASSNAME_FIRST_TIME = 'btn--first-time'
@@ -14,6 +14,11 @@ const typeHash = {
default: CLASSNAME_DEFAULT,
primary: CLASSNAME_PRIMARY,
secondary: CLASSNAME_SECONDARY,
+ warning: 'btn-warning',
+ danger: 'btn-danger',
+ 'danger-primary': 'btn-danger-primary',
+ link: 'btn-link',
+ // TODO: Legacy button type to be deprecated
confirm: CLASSNAME_CONFIRM,
raised: CLASSNAME_RAISED,
'first-time': CLASSNAME_FIRST_TIME,
@@ -38,7 +43,7 @@ export default class Button extends Component {
<button
className={classnames(
'button',
- typeHash[type],
+ typeHash[type] || CLASSNAME_DEFAULT,
large && CLASSNAME_LARGE,
className
)}
diff --git a/ui/app/components/ui/button/button.stories.js b/ui/app/components/ui/button/button.stories.js
index 667824a47..9df53439d 100644
--- a/ui/app/components/ui/button/button.stories.js
+++ b/ui/app/components/ui/button/button.stories.js
@@ -2,57 +2,70 @@ import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import Button from '.'
-import { text } from '@storybook/addon-knobs/react'
+import { text, boolean } from '@storybook/addon-knobs/react'
+// ', 'secondary', 'default', 'warning', 'danger', 'danger-primary', 'link'], 'primary')}
storiesOf('Button', module)
- .add('primary', () =>
+ .add('Button - Primary', () =>
<Button
onClick={action('clicked')}
type="primary"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
)
- .add('secondary', () =>
+ .add('Button - Secondary', () =>
<Button
onClick={action('clicked')}
type="secondary"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
)
- .add('default', () => (
+ .add('Button - Default', () =>
<Button
onClick={action('clicked')}
type="default"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
- ))
- .add('large primary', () => (
+ )
+ .add('Button - Warning', () =>
<Button
onClick={action('clicked')}
- type="primary"
- large
+ type="warning"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
- ))
- .add('large secondary', () => (
+ )
+ .add('Button - Danger', () =>
<Button
onClick={action('clicked')}
- type="secondary"
- large
+ type="danger"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
- ))
- .add('large default', () => (
+ )
+ .add('Button - Danger Primary', () =>
<Button
onClick={action('clicked')}
- type="default"
- large
+ type="danger-primary"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
- ))
+ )
+ .add('Button - Link', () =>
+ <Button
+ onClick={action('clicked')}
+ type="link"
+ disabled={boolean('disabled', false)}
+ >
+ {text('text', 'Click me')}
+ </Button>
+ )
diff --git a/ui/app/components/ui/button/buttons.scss b/ui/app/components/ui/button/buttons.scss
new file mode 100644
index 000000000..f1366cffe
--- /dev/null
+++ b/ui/app/components/ui/button/buttons.scss
@@ -0,0 +1,247 @@
+/*
+ Buttons
+ */
+
+$hover-secondary: #B0D7F2;
+$hover-default: #B3B3B3;
+$hover-confirm: #0372C3;
+$hover-red: #FEB6BF;
+$hover-red-primary: #C72837;
+$hover-orange: #FFD3B5;
+
+%button {
+ @include h6;
+
+ font-weight: 500;
+ font-family: Roboto, Arial;
+ line-height: 1.25rem;
+ padding: .75rem 1rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+ border-radius: 6px;
+ width: 100%;
+ outline: none;
+ transition: border-color .3s ease, background-color .3s ease;
+
+ &--disabled,
+ &[disabled] {
+ cursor: auto;
+ opacity: .5;
+ pointer-events: none;
+ }
+}
+
+%link {
+ @include h4;
+
+ color: $Blue-500;
+ line-height: 1.25rem;
+ cursor: pointer;
+ background-color: transparent;
+
+ &:hover {
+ color: $Blue-400;
+ }
+
+ &:active {
+ color: $Blue-600;
+ }
+
+ &--disabled,
+ &[disabled] {
+ cursor: auto;
+ opacity: 1;
+ pointer-events: none;
+ color: $hover-secondary;
+ }
+}
+
+%small-link {
+ @extend %link;
+ @include h6;
+}
+
+.button {
+ @extend %button;
+}
+
+.btn-secondary {
+ color: $Blue-500;
+ border: 2px solid $hover-secondary;
+ background-color: $white;
+
+ &:hover {
+ border-color: $Blue-500;
+ }
+
+ &:active {
+ background: $Blue-000;
+ border-color: $Blue-500;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ color: $hover-secondary;
+ }
+}
+
+.btn-warning {
+ color: $Orange-500;
+ border: 2px solid $hover-orange;
+ background-color: $white;
+
+ &:hover {
+ border-color: $Orange-500;
+ }
+
+ &:active {
+ background: $Orange-000;
+ border-color: $Orange-500;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ color: $hover-orange;
+ }
+}
+
+.btn-danger {
+ color: $Red-500;
+ border: 2px solid $hover-red;
+ background-color: $white;
+
+ &:hover {
+ border-color: $Red-500;
+ }
+
+ &:active {
+ background: $Red-000;
+ border-color: $Red-500;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ color: $hover-red;
+ }
+}
+
+.btn-danger-primary {
+ color: $white;
+ border: 2px solid $Red-500;
+ background-color: $Red-500;
+
+ &:hover {
+ border-color: $hover-red-primary;
+ background-color: $hover-red-primary;
+ }
+
+ &:active {
+ background: $Red-600;
+ border-color: $Red-600;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ border-color: $hover-red;
+ background-color: $hover-red;
+ }
+}
+
+.btn-default {
+ color: $Grey-500;
+ border: 2px solid $hover-default;
+
+ &:hover {
+ border-color: $Grey-500;
+ }
+
+ &:active {
+ background: #FBFBFC;
+ border-color: $Grey-500;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ color: $hover-default;
+ }
+}
+
+.btn-primary {
+ color: $white;
+ border: 2px solid $Blue-500;
+ background-color: $Blue-500;
+
+ &:hover {
+ border-color: $hover-confirm;
+ background-color: $hover-confirm;
+ }
+
+ &:active {
+ background: $Blue-600;
+ border-color: $Blue-600;
+ }
+
+ &--disabled,
+ &[disabled] {
+ border-color: $hover-secondary;
+ background-color: $hover-secondary;
+ }
+}
+
+.btn-link {
+ @extend %link;
+}
+
+.btn--large {
+ min-height: 54px;
+}
+
+/**
+ All Buttons styles are deviations from design guide
+ */
+
+.btn-raised {
+ color: $curious-blue;
+ background-color: $white;
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
+ padding: 6px;
+ height: initial;
+ min-height: initial;
+ width: initial;
+ min-width: initial;
+}
+
+.btn--first-time {
+ height: 54px;
+ width: 198px;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
+ color: $white;
+ font-size: 1.25rem;
+ font-weight: 500;
+ transition: 200ms ease-in-out;
+ background-color: rgba(247, 134, 28, .9);
+ border-radius: 0;
+}
+
+button[disabled],
+input[type="submit"][disabled] {
+ cursor: not-allowed;
+ opacity: .5;
+}
+
+button.primary {
+ padding: 8px 12px;
+ background: #f7861c;
+ box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
+ color: $white;
+ font-size: 1.1em;
+ font-family: Roboto;
+ text-transform: uppercase;
+}
diff --git a/ui/app/components/ui/currency-display/currency-display.component.js b/ui/app/components/ui/currency-display/currency-display.component.js
index 04dd89892..c15668da3 100644
--- a/ui/app/components/ui/currency-display/currency-display.component.js
+++ b/ui/app/components/ui/currency-display/currency-display.component.js
@@ -23,7 +23,7 @@ export default class CurrencyDisplay extends PureComponent {
render () {
const { className, displayValue, prefix, prefixComponent, style, suffix, hideTitle } = this.props
const text = `${prefix || ''}${displayValue}`
- const title = `${text} ${suffix}`
+ const title = suffix ? `${text} ${suffix}` : text
return (
<div
diff --git a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
index 9888c366e..182524e59 100644
--- a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
+++ b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../currency-display.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/ui/currency-input/currency-input.component.js b/ui/app/components/ui/currency-input/currency-input.component.js
index b5be0972b..1876c9591 100644
--- a/ui/app/components/ui/currency-input/currency-input.component.js
+++ b/ui/app/components/ui/currency-input/currency-input.component.js
@@ -18,6 +18,7 @@ export default class CurrencyInput extends PureComponent {
static propTypes = {
conversionRate: PropTypes.number,
currentCurrency: PropTypes.string,
+ maxModeOn: PropTypes.bool,
nativeCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
@@ -136,7 +137,7 @@ export default class CurrencyInput extends PureComponent {
}
render () {
- const { fiatSuffix, nativeSuffix, ...restProps } = this.props
+ const { fiatSuffix, nativeSuffix, maxModeOn, ...restProps } = this.props
const { decimalValue } = this.state
return (
@@ -146,6 +147,7 @@ export default class CurrencyInput extends PureComponent {
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
+ maxModeOn={maxModeOn}
actionComponent={(
<div
className="currency-input__swap-component"
diff --git a/ui/app/components/ui/currency-input/currency-input.container.js b/ui/app/components/ui/currency-input/currency-input.container.js
index b5d7dfe6d..46e70bace 100644
--- a/ui/app/components/ui/currency-input/currency-input.container.js
+++ b/ui/app/components/ui/currency-input/currency-input.container.js
@@ -1,18 +1,21 @@
import { connect } from 'react-redux'
import CurrencyInput from './currency-input.component'
import { ETH } from '../../../helpers/constants/common'
+import { getMaxModeOn } from '../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors'
import {getIsMainnet, preferencesSelector} from '../../../selectors/selectors'
const mapStateToProps = state => {
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
+ const maxModeOn = getMaxModeOn(state)
return {
nativeCurrency,
currentCurrency,
conversionRate,
hideFiat: (!isMainnet && !showFiatInTestnets),
+ maxModeOn,
}
}
diff --git a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
index 6109d29b6..f10abe09a 100644
--- a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
+++ b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../currency-input.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
@@ -30,6 +30,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'mainnet',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -37,6 +40,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
// Test # 2
@@ -53,6 +57,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'rinkeby',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -60,6 +67,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: true,
+ maxModeOn: false,
},
},
// Test # 3
@@ -76,6 +84,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'rinkeby',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -83,6 +94,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
// Test # 4
@@ -99,6 +111,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'mainnet',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -106,6 +121,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
]
diff --git a/ui/app/components/ui/page-container/index.scss b/ui/app/components/ui/page-container/index.scss
index b71a3cb9d..003c5a0e2 100644
--- a/ui/app/components/ui/page-container/index.scss
+++ b/ui/app/components/ui/page-container/index.scss
@@ -55,11 +55,6 @@
border-top: 1px solid $geyser;
flex: 0 0 auto;
- .btn-default,
- .btn-confirm {
- font-size: 1rem;
- }
-
header {
display: flex;
flex-flow: row;
@@ -86,9 +81,6 @@
}
&__footer-button {
- height: 55px;
- font-size: 1rem;
- text-transform: uppercase;
margin-right: 16px;
&:last-of-type {
diff --git a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js
index 85b16cefe..4ef203521 100644
--- a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js
+++ b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js
@@ -45,7 +45,7 @@ export default class PageContainerFooter extends Component {
</Button>}
<Button
- type={submitButtonType || 'primary'}
+ type={submitButtonType || 'secondary'}
large
className="page-container__footer-button"
disabled={disabled}
diff --git a/ui/app/components/ui/text-field/text-field.component.js b/ui/app/components/ui/text-field/text-field.component.js
index 2c72d8124..1153a595b 100644
--- a/ui/app/components/ui/text-field/text-field.component.js
+++ b/ui/app/components/ui/text-field/text-field.component.js
@@ -41,11 +41,11 @@ const styles = {
inputFocused: {},
inputRoot: {
'label + &': {
- marginTop: '8px',
+ marginTop: '9px',
},
- border: '1px solid #d2d8dd',
+ border: '2px solid #BBC0C5',
height: '48px',
- borderRadius: '4px',
+ borderRadius: '6px',
padding: '0 16px',
display: 'flex',
alignItems: 'center',
diff --git a/ui/app/components/ui/token-input/tests/token-input.container.test.js b/ui/app/components/ui/token-input/tests/token-input.container.test.js
index 2b1c102c8..6f87e64a5 100644
--- a/ui/app/components/ui/token-input/tests/token-input.container.test.js
+++ b/ui/app/components/ui/token-input/tests/token-input.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../token-input.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/ui/unit-input/index.scss b/ui/app/components/ui/unit-input/index.scss
index e4075d225..58a10c9a1 100644
--- a/ui/app/components/ui/unit-input/index.scss
+++ b/ui/app/components/ui/unit-input/index.scss
@@ -7,7 +7,7 @@
border-radius: 4px;
background-color: #fff;
color: #4d4d4d;
- font-size: 1rem;
+ font-size: 16px;
padding: 8px 10px;
position: relative;
@@ -29,6 +29,8 @@
&__inputs {
flex: 1 0 auto;
+ display: flex;
+ flex-flow: column nowrap;
}
&__input {
@@ -38,18 +40,32 @@
border: none;
outline: 0 !important;
max-width: 22ch;
+ height: 16px;
+ line-height: 18px;
+
+ &__disabled {
+ background-color: rgb(222, 222, 222);
+ }
}
&__input-container {
display: flex;
- align-items: center;
+ align-items: flex-start;
+ padding-bottom: 4px;
}
&__suffix {
margin-left: 3px;
+ font-size: 1rem;
+ line-height: 1rem;
}
&--error {
border-color: $red;
}
+
+ &__disabled {
+ background-color: #F2F3F4;
+ }
+
}
diff --git a/ui/app/components/ui/unit-input/unit-input.component.js b/ui/app/components/ui/unit-input/unit-input.component.js
index 7b414f177..9085a0677 100644
--- a/ui/app/components/ui/unit-input/unit-input.component.js
+++ b/ui/app/components/ui/unit-input/unit-input.component.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { removeLeadingZeroes } from '../../app/send/send.utils'
+import { removeLeadingZeroes } from '../../../pages/send/send.utils'
/**
* Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also
@@ -13,6 +13,7 @@ export default class UnitInput extends PureComponent {
children: PropTypes.node,
actionComponent: PropTypes.node,
error: PropTypes.bool,
+ maxModeOn: PropTypes.bool,
onBlur: PropTypes.func,
onChange: PropTypes.func,
placeholder: PropTypes.string,
@@ -58,7 +59,7 @@ export default class UnitInput extends PureComponent {
this.props.onChange(value)
}
- handleBlur = event => {
+ handleBlur = () => {
const { onBlur } = this.props
typeof onBlur === 'function' && onBlur(this.state.value)
}
@@ -71,25 +72,26 @@ export default class UnitInput extends PureComponent {
}
render () {
- const { error, placeholder, suffix, actionComponent, children } = this.props
+ const { error, placeholder, suffix, actionComponent, children, maxModeOn } = this.props
const { value } = this.state
return (
<div
- className={classnames('unit-input', { 'unit-input--error': error })}
- onClick={this.handleFocus}
+ className={classnames('unit-input', { 'unit-input--error': error }, { 'unit-input__disabled': maxModeOn })}
+ onClick={maxModeOn ? null : this.handleFocus}
>
<div className="unit-input__inputs">
<div className="unit-input__input-container">
<input
type="number"
- className="unit-input__input"
+ className={classnames('unit-input__input', { 'unit-input__disabled': maxModeOn })}
value={value}
placeholder={placeholder}
onChange={this.handleChange}
onBlur={this.handleBlur}
style={{ width: this.getInputWidth(value) }}
ref={ref => { this.unitInput = ref }}
+ disabled={maxModeOn}
/>
{
suffix && (
diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss
deleted file mode 100644
index 3e99d0ac6..000000000
--- a/ui/app/css/itcss/components/buttons.scss
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- Buttons
- */
-
-.button {
- min-height: 44px;
- background: $white;
- display: flex;
- justify-content: center;
- align-items: center;
- box-sizing: border-box;
- border-radius: 4px;
- font-size: 14px;
- font-weight: 400;
- transition: border-color .3s ease;
- padding: 0 16px;
- min-width: 140px;
- width: 100%;
- text-transform: uppercase;
- outline: none;
- font-family: Roboto;
-
- &--disabled,
- &[disabled] {
- cursor: auto;
- opacity: .5;
- pointer-events: none;
- }
-}
-
-.btn-primary {
- color: $curious-blue;
- border: 2px solid $spindle;
-
- &:active {
- background: $zumthor;
- border-color: $curious-blue;
- }
-
- &:hover {
- border-color: $curious-blue;
- }
-}
-
-.btn-secondary {
- color: $monzo;
- border: 2px solid lighten($monzo, 40%);
-
- &:active {
- background: lighten($monzo, 55%);
- border-color: $monzo;
- }
-
- &:hover {
- border-color: $monzo;
- }
-}
-
-.btn-default {
- color: $scorpion;
- border: 2px solid $dusty-gray;
-
- &:active {
- background: $gallery;
- border-color: $dusty-gray;
- }
-
- &:hover {
- border-color: $scorpion;
- }
-}
-
-.btn-confirm {
- color: $white;
- border: 2px solid $curious-blue;
- background-color: $curious-blue;
-}
-
-.btn-raised {
- color: $curious-blue;
- background-color: $white;
- box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
- padding: 6px;
- height: initial;
- min-height: initial;
- width: initial;
- min-width: initial;
-}
-
-.btn--first-time {
- height: 54px;
- width: 198px;
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
- color: $white;
- font-size: 1.25rem;
- font-weight: 500;
- transition: 200ms ease-in-out;
- background-color: rgba(247, 134, 28, .9);
- border-radius: 0;
-}
-
-.btn--large {
- min-height: 54px;
-}
-
-.btn-green {
- background-color: #02c9b1; // TODO: reusable color in colors.css
-}
-
-.btn-clear {
- background: $white;
- text-align: center;
- padding: .8rem 1rem;
- color: $curious-blue;
- border: 2px solid $spindle;
- border-radius: 4px;
- font-size: .85rem;
- font-weight: 400;
- transition: border-color .3s ease;
-
- &:hover {
- border-color: $curious-blue;
- }
-
- &--disabled,
- &[disabled] {
- cursor: auto;
- opacity: .5;
- pointer-events: none;
- }
-}
-
-.btn-cancel {
- background: $white;
- text-align: center;
- padding: .9rem 1rem;
- color: $scorpion;
- border: 2px solid $dusty-gray;
- border-radius: 4px;
- font-size: .85rem;
- font-weight: 400;
- transition: border-color .3s ease;
- width: 100%;
-
- &:hover {
- border-color: $scorpion;
- }
-}
-
-// No longer used in flat design, remove when modal buttons done
-// div.wallet-btn {
-// border: 1px solid rgb(91, 93, 103);
-// border-radius: 2px;
-// height: 30px;
-// width: 75px;
-// font-size: 0.8em;
-// text-align: center;
-// line-height: 25px;
-// }
-
-// .btn-red {
-// background: rgba(254, 35, 17, 1);
-// box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36);
-// }
-
-button[disabled],
-input[type="submit"][disabled] {
- cursor: not-allowed;
- opacity: .5;
- // background: rgba(197, 197, 197, 1);
- // box-shadow: 0 3px 6px rgba(197, 197, 197, .36);
-}
-
-// button.spaced {
-// margin: 2px;
-// }
-
-// button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover {
-// transform: scale(1.1);
-// }
-// button:not([disabled]):active, input[type="submit"]:not([disabled]):active {
-// transform: scale(0.95);
-// }
-
-button.primary {
- padding: 8px 12px;
- background: #f7861c;
- box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
- color: $white;
- font-size: 1.1em;
- font-family: Roboto;
- text-transform: uppercase;
-}
-
-.btn-light {
- padding: 8px 12px;
- // background: #FFFFFF; // $bg-white
- box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
- color: #585d67; // TODO: make reusable light button color
- font-size: 1.1em;
- font-family: Roboto;
- text-transform: uppercase;
- text-align: center;
- line-height: 20px;
- border-radius: 2px;
- border: 1px solid #979797; // #TODO: make reusable light border color
- opacity: .5;
-}
-
-// TODO: cleanup: not used anywhere
-button.btn-thin {
- border: 1px solid;
- border-color: #4d4d4d;
- color: #4d4d4d;
- background: rgb(255, 174, 41);
- border-radius: 4px;
- min-width: 200px;
- margin: 12px 0;
- padding: 6px;
- font-size: 13px;
-}
-
-.btn-tertiary {
- border: 1px solid transparent;
- border-radius: 2px;
- background-color: transparent;
- font-size: 16px;
- line-height: 24px;
- padding: 16px 42px;
-}
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index f2f37bfa3..3d426a33c 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -1,4 +1,4 @@
-@import './buttons.scss';
+@import '../../../components/ui/button/buttons';
@import './footer.scss';
diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss
index 42ef7ae0a..9c0a5cf61 100644
--- a/ui/app/css/itcss/components/modal.scss
+++ b/ui/app/css/itcss/components/modal.scss
@@ -538,6 +538,8 @@
}
&__button {
+ @include paragraph;
+ @extend %button;
width: 141px;
margin: 0 5px;
}
diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss
index c828a2b26..da90b7910 100644
--- a/ui/app/css/itcss/components/network.scss
+++ b/ui/app/css/itcss/components/network.scss
@@ -29,6 +29,10 @@
&.rinkeby-test-network .menu-icon-circle div {
background-color: rgba(235, 179, 63, .7) !important;
}
+
+ &.goerli-test-network .menu-icon-circle div {
+ background-color: rgba(48, 153, 242, .7) !important;
+ }
}
.dropdown-menu-item {
@@ -47,6 +51,13 @@
line-height: 15px;
font-size: 12px;
padding: 0 4px;
+ flex: 0 0 auto;
+ }
+
+ .fa-question-circle {
+ margin: 0 4px 0 6px;
+ font-size: 1rem;
+ flex: 0 0 auto;
}
}
@@ -54,9 +65,12 @@
padding: 0 4px;
font-family: Roboto;
font-size: 12px;
- flex: 1 0 auto;
+ flex: 1 1 auto;
color: $tundora;
font-weight: 500;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
}
.dropdown-menu-item .fa.delete {
@@ -165,5 +179,22 @@
}
.network-caret {
- margin: 0 8px 2px;
+ margin: 0 8px;
+}
+
+.network-loading-spinner {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ position: relative;
+ height: 16px;
+ width: 16px;
+ margin-left: 5px;
+
+ img {
+ height: 26px;
+ position: absolute;
+ top: -5px;
+ left: -6px;
+ }
}
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index 07ab04613..e2f0f9b2f 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -520,6 +520,10 @@
color: $red;
}
+ &__error-amount {
+ margin-top: 5px;
+ }
+
&__warning {
font-size: 12px;
line-height: 12px;
@@ -549,7 +553,7 @@
}
&__form-row {
- margin: 14.5px 18px 0px;
+ margin: 8px 18px 0px;
position: relative;
display: flex;
flex-flow: row;
@@ -557,6 +561,12 @@
justify-content: space-between;
}
+ &__form-field-container {
+ display: flex;
+ flex-direction: column;
+ width: 277px;
+ }
+
&__form-field {
flex: 1 1 auto;
min-width: 0;
@@ -592,8 +602,8 @@
flex: 0 0 auto;
}
- &__from-dropdown {
- height: 73px;
+ &__from-dropdown,
+ &__asset-dropdown {
width: 100%;
border: 1px solid $alto;
border-radius: 4px;
@@ -628,6 +638,112 @@
}
}
+ &__from-dropdown {
+ height: 73px;
+ }
+
+ &__asset-dropdown {
+ height: 54px;
+ border: none;
+
+ &__asset {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 0 8px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: rgba($alto, 0.2);
+ }
+ }
+
+ &__asset-icon {
+ .identicon {
+ border: 1px solid $alto;
+ }
+ }
+
+ &__asset-data {
+ display: flex;
+ flex-flow: column nowrap;
+ margin-left: 8px;
+ }
+
+ &__symbol {
+ font-size: 16px;
+ margin-bottom: 2px;
+ }
+
+ &__name {
+ display: flex;
+ flex-flow: row nowrap;
+ font-size: 12px;
+
+ &__label {
+ margin-right: .25rem;
+ }
+ }
+
+ &__close-area {
+ z-index: 2000;
+ }
+
+ &__list {
+ z-index: 2050;
+ position: absolute;
+ height: 220px;
+ width: 100%;
+ border: 1px solid $geyser;
+ border-radius: 4px;
+ background-color: $white;
+ box-shadow: 0 3px 6px 0 rgba(0 ,0 ,0 ,.11);
+ top: 65px;
+ left: 0;
+ box-sizing: content-box;
+ overflow-y: scroll;
+ margin-top: 0;
+ padding: 4px 0;
+
+ .send-v2__asset-dropdown__asset {
+ padding: 8px;
+ }
+ }
+
+ &__input-wrapper {
+ border: 1px solid $alto;
+ border-radius: 4px;
+ height: 100%;
+
+ &--opened {
+ position: relative;
+ z-index: 2050;
+ }
+
+ .send-v2__asset-dropdown__asset {
+ height: 100%;
+ &:hover {
+ background-color: $white;
+ }
+ }
+ }
+
+ &__input {
+ z-index: 1025;
+ position: relative;
+ height: 54px;
+ width: 100%;
+ border: none;
+ border-radius: 4px;
+ background-color: $white;
+ color: $tundora;
+ padding: 10px;
+ font-family: Roboto;
+ font-size: 16px;
+ line-height: 21px;
+ }
+ }
+
&__to-autocomplete {
position: relative;
@@ -657,7 +773,43 @@
}
}
- &__to-autocomplete, &__memo-text-area, &__hex-data {
+ &__to-autocomplete {
+ display: flex;
+ flex-direction: column;
+ z-index: 1025;
+ position: relative;
+ height: 54px;
+ width: 100%;
+ border: 1px solid $alto;
+ border-radius: 4px;
+ background-color: $white;
+ color: $tundora;
+ padding: 0 10px;
+ font-family: Roboto;
+ line-height: 21px;
+
+ &__input {
+ font-size: 16px;
+ height: 100%;
+ border: none;
+ }
+
+ &__resolved {
+ font-size: 12px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ height: 30px;
+ cursor: pointer;
+
+ + .send-v2__to-autocomplete__qr-code {
+ top: 2px;
+ right: 0;
+ }
+ }
+ }
+
+ &__memo-text-area, &__hex-data {
&__input {
z-index: 1025;
position: relative;
@@ -675,12 +827,47 @@
}
&__amount-max {
- color: $curious-blue;
font-family: Roboto;
font-size: 12px;
- left: 8px;
- border: none;
- cursor: pointer;
+ position: relative;
+ display: inline-block;
+ width: 56px;
+ height: 20px;
+ margin-top: 5px;
+
+ &__button {
+ width: 56px;
+ height: 20px;
+ position: absolute;
+ border: 2px solid #B0D7F2;
+ border-radius: 6px;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #2f9ae0;
+
+ &__disabled {
+ color: #B0D7F2;
+ cursor: auto;
+ }
+ }
+
+ input:checked + &__button {
+ background-color: #037DD6;
+ border: 2px solid #037DD6;
+ color: #fff;
+ }
+ }
+
+ &__amount-max input {
+ opacity: 0;
+ width: 0;
+ height: 0;
}
&__gas-fee-display {
@@ -935,7 +1122,7 @@
font-size: 14px;
color: #2f9ae0;
cursor: pointer;
- margin-top: 16px;
+ margin-top: 5px;
}
.sliders-icon-container {
diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss
index d8e62c97a..8b282aa1e 100644
--- a/ui/app/css/itcss/generic/index.scss
+++ b/ui/app/css/itcss/generic/index.scss
@@ -18,6 +18,7 @@ body {
height: 100%;
margin: 0;
padding: 0;
+ font-size: 16px;
@media screen and (max-width: $break-small) {
overflow-y: overlay;
diff --git a/ui/app/css/itcss/settings/typography.scss b/ui/app/css/itcss/settings/typography.scss
index 18c444c8a..93107a106 100644
--- a/ui/app/css/itcss/settings/typography.scss
+++ b/ui/app/css/itcss/settings/typography.scss
@@ -403,3 +403,40 @@
font-weight: 400;
font-style: normal;
}
+
+@mixin fontScale($weight: 400, $size: 1rem) {
+ font-weight: $weight;
+ font-size: $size;
+}
+
+@mixin h1($weight: 400, $size: 2.5rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h2($weight: 400, $size: 2rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h3($weight: 400, $size: 1.5rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h4($weight: 400, $size: 1.125rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h5($weight: 400, $size: 1rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h6($weight: 400, $size: .875rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h7($weight: 400, $size: .75rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin paragraph($weight: 400, $size: 1rem){
+ @include fontScale($weight, $size);
+}
diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss
index 89bd8b96a..f7003b1f4 100644
--- a/ui/app/css/itcss/settings/variables.scss
+++ b/ui/app/css/itcss/settings/variables.scss
@@ -26,7 +26,7 @@ $dusty-gray: #9b9b9b;
$alto: #dedede;
$alabaster: #fafafa;
$silver-chalice: #aeaeae;
-$curious-blue: #2f9ae0;
+$curious-blue: #037DD6;
$concrete: #f3f3f3;
$tundora: #4d4d4d;
$nile-blue: #1b344d;
@@ -93,3 +93,19 @@ $break-large: 576px;
$primary-font-type: Roboto;
+$Blue-000: #eaf6ff;
+$Blue-400: #1098fc;
+$Blue-500: #037DD6;
+$Blue-600: #0260a4;
+
+$Grey-000: #f2f3f4;
+$Grey-500: #6A737D;
+
+$Red-000: #fcf2f3;
+$Red-500: #D73A49;
+$Red-600: #b92534;
+
+$Orange-000: #fef5ef;
+$Orange-500: #F66A0A;
+
+
diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js
index 295507d70..b181092c1 100644
--- a/ui/app/ducks/app/app.js
+++ b/ui/app/ducks/app/app.js
@@ -77,6 +77,8 @@ function reduceApp (state, action) {
ledger: `m/44'/60'/0'/0/0`,
},
lastSelectedProvider: null,
+ networksTabSelectedRpcUrl: '',
+ networksTabIsInAddMode: false,
}, state.appState)
switch (action.type) {
@@ -751,6 +753,16 @@ function reduceApp (state, action) {
lastSelectedProvider: action.value,
})
+ case actions.SET_SELECTED_SETTINGS_RPC_URL:
+ return extend(appState, {
+ networksTabSelectedRpcUrl: action.value,
+ })
+
+ case actions.SET_NETWORKS_TAB_ADD_MODE:
+ return extend(appState, {
+ networksTabIsInAddMode: action.value,
+ })
+
default:
return appState
}
diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
index 169c9d543..58b0ec8e8 100644
--- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
+++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
@@ -375,7 +375,7 @@ export function setTransactionToConfirm (transactionId) {
dispatch(updateMethodData(methodData))
try {
- const toSmartContract = await isSmartContractAddress(to)
+ const toSmartContract = await isSmartContractAddress(to || '')
dispatch(updateToSmartContract(toSmartContract))
} catch (error) {
log.error(error)
diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
index 483f2f56d..d2e344663 100644
--- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
+++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
@@ -494,7 +494,7 @@ describe('Confirm Transaction Duck', () => {
})
})
- describe('Thunk actions', done => {
+ describe('Thunk actions', () => {
beforeEach(() => {
global.eth = {
getCode: sinon.stub().callsFake(
diff --git a/ui/app/ducks/gas/gas-duck.test.js b/ui/app/ducks/gas/gas-duck.test.js
index c0152c74f..b7e83a81c 100644
--- a/ui/app/ducks/gas/gas-duck.test.js
+++ b/ui/app/ducks/gas/gas-duck.test.js
@@ -461,8 +461,8 @@ describe('Gas Duck', () => {
assert.equal(thirdDispatchCallType, SET_PRICE_AND_TIME_ESTIMATES)
assert(priceAndTimeEstimateResult.length < mockPredictTableResponse.length * 3 - 2)
assert(!priceAndTimeEstimateResult.find(d => d.expectedTime > 100))
- assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime))
- assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice))
+ assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime))
+ assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice))
assert.deepEqual(
mockDistpatch.getCall(3).args,
diff --git a/ui/app/ducks/metamask/metamask.js b/ui/app/ducks/metamask/metamask.js
index 47c767d68..3ca487c1f 100644
--- a/ui/app/ducks/metamask/metamask.js
+++ b/ui/app/ducks/metamask/metamask.js
@@ -154,9 +154,26 @@ function reduceMetamask (state, action) {
return newState
case actions.SET_SELECTED_TOKEN:
- return extend(metamaskState, {
+ newState = extend(metamaskState, {
selectedTokenAddress: action.value,
})
+ const newSend = extend(metamaskState.send)
+
+ if (metamaskState.send.editingTransactionId && !action.value) {
+ delete newSend.token
+ const unapprovedTx = newState.unapprovedTxs[newSend.editingTransactionId] || {}
+ const txParams = unapprovedTx.txParams || {}
+ newState.unapprovedTxs = extend(newState.unapprovedTxs, {
+ [newSend.editingTransactionId]: extend(unapprovedTx, {
+ txParams: extend(txParams, { data: '' }),
+ }),
+ })
+ newSend.tokenBalance = null
+ newSend.balance = '0'
+ }
+
+ newState.send = newSend
+ return newState
case actions.SET_ACCOUNT_LABEL:
const account = action.value.account
diff --git a/ui/app/helpers/constants/common.js b/ui/app/helpers/constants/common.js
index 58fae5e5f..a0d6e65b3 100644
--- a/ui/app/helpers/constants/common.js
+++ b/ui/app/helpers/constants/common.js
@@ -10,4 +10,5 @@ export const NETWORK_TYPES = {
MAINNET: 'mainnet',
RINKEBY: 'rinkeby',
ROPSTEN: 'ropsten',
+ GOERLI: 'goerli',
}
diff --git a/ui/app/helpers/constants/routes.js b/ui/app/helpers/constants/routes.js
index df35112d1..d906fc8e6 100644
--- a/ui/app/helpers/constants/routes.js
+++ b/ui/app/helpers/constants/routes.js
@@ -8,6 +8,7 @@ const ADVANCED_ROUTE = '/settings/advanced'
const SECURITY_ROUTE = '/settings/security'
const COMPANY_ROUTE = '/settings/company'
const ABOUT_US_ROUTE = '/settings/about-us'
+const NETWORKS_ROUTE = '/settings/networks'
const REVEAL_SEED_ROUTE = '/seed'
const MOBILE_SYNC_ROUTE = '/mobile-sync'
const CONFIRM_SEED_ROUTE = '/confirm-seed'
@@ -86,4 +87,5 @@ module.exports = {
COMPANY_ROUTE,
GENERAL_ROUTE,
ABOUT_US_ROUTE,
+ NETWORKS_ROUTE,
}
diff --git a/ui/app/helpers/higher-order-components/i18n-provider.js b/ui/app/helpers/higher-order-components/i18n-provider.js
index 0e34e17e0..5a6650147 100644
--- a/ui/app/helpers/higher-order-components/i18n-provider.js
+++ b/ui/app/helpers/higher-order-components/i18n-provider.js
@@ -15,11 +15,21 @@ class I18nProvider extends Component {
const { localeMessages } = this.props
const { current, en } = localeMessages
return {
+ /**
+ * Returns a localized message for the given key
+ * @param {string} key The message key
+ * @param {string[]} args A list of message substitution replacements
+ * @return {string|undefined|null} The localized message if available
+ */
t (key, ...args) {
+ if (key === undefined || key === null) {
+ return key
+ }
+
return t(current, key, ...args) || t(en, key, ...args) || `[${key}]`
},
tOrDefault: this.tOrDefault,
- tOrKey (key, ...args) {
+ tOrKey: (key, ...args) => {
return this.tOrDefault(key, key, ...args)
},
}
diff --git a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
index 6086e03fb..6281ddcc6 100644
--- a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
+++ b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
@@ -42,7 +42,7 @@ class MetaMetricsProvider extends Component {
currentPath: window.location.href,
}
- props.history.listen(locationObj => {
+ props.history.listen(() => {
this.setState({
previousPath: this.state.currentPath,
currentPath: window.location.href,
diff --git a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
index 654e7062a..81a3512d1 100644
--- a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
+++ b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
@@ -21,7 +21,7 @@ const mockState = {
describe('withModalProps', () => {
it('should return a component wrapped with modal state props', () => {
- const TestComponent = props => (
+ const TestComponent = () => (
<div className="test">Testing</div>
)
const WrappedComponent = withModalProps(TestComponent)
diff --git a/ui/app/helpers/utils/conversion-util.js b/ui/app/helpers/utils/conversion-util.js
index 8cc531773..affddade7 100644
--- a/ui/app/helpers/utils/conversion-util.js
+++ b/ui/app/helpers/utils/conversion-util.js
@@ -42,7 +42,7 @@ const convert = R.invoker(1, 'times')
const round = R.invoker(2, 'round')(R.__, BigNumber.ROUND_HALF_DOWN)
const roundDown = R.invoker(2, 'round')(R.__, BigNumber.ROUND_DOWN)
const invertConversionRate = conversionRate => () => new BigNumber(1.0).div(conversionRate)
-const decToBigNumberViaString = n => R.pipe(String, toBigNumber['dec'])
+const decToBigNumberViaString = () => R.pipe(String, toBigNumber['dec'])
// Setter Maps
const toBigNumber = {
diff --git a/ui/app/helpers/utils/conversions.util.js b/ui/app/helpers/utils/conversions.util.js
index b4ec50626..5e1c21ff7 100644
--- a/ui/app/helpers/utils/conversions.util.js
+++ b/ui/app/helpers/utils/conversions.util.js
@@ -1,6 +1,6 @@
import ethUtil from 'ethereumjs-util'
import { ETH, GWEI, WEI } from '../constants/common'
-import { conversionUtil, addCurrencies } from './conversion-util'
+import { conversionUtil, addCurrencies, subtractCurrencies } from './conversion-util'
export function bnToHex (inputBn) {
return ethUtil.addHexPrefix(inputBn.toString(16))
@@ -92,6 +92,15 @@ export function addHexWEIsToDec (aHexWEI, bHexWEI) {
})
}
+export function subtractHexWEIsToDec (aHexWEI, bHexWEI) {
+ return subtractCurrencies(aHexWEI, bHexWEI, {
+ aBase: 16,
+ bBase: 16,
+ fromDenomination: 'WEI',
+ numberOfDecimals: 6,
+ })
+}
+
export function decEthToConvertedCurrency (ethTotal, convertedCurrency, conversionRate) {
return conversionUtil(ethTotal, {
fromNumericBase: 'dec',
diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js
index 01984bd5e..50270c6a8 100644
--- a/ui/app/helpers/utils/metametrics.util.js
+++ b/ui/app/helpers/utils/metametrics.util.js
@@ -12,6 +12,8 @@ const METAMETRICS_TRACKING_URL = inDevelopment
? 'http://www.metamask.io/metametrics'
: 'http://www.metamask.io/metametrics-prod'
+/** ***************Custom variables*************** **/
+// Custon variable declarations
const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange'
const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange'
const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType'
@@ -23,13 +25,8 @@ const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField'
const METAMETRICS_CUSTOM_ERROR_MESSAGE = 'errorMessage'
const METAMETRICS_CUSTOM_RPC_NETWORK_ID = 'networkId'
const METAMETRICS_CUSTOM_RPC_CHAIN_ID = 'chainId'
-
-const METAMETRICS_CUSTOM_NETWORK = 'network'
-const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType'
-const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency'
-const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType'
-const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens'
-const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts'
+const METAMETRICS_CUSTOM_GAS_CHANGED = 'gasChanged'
+const METAMETRICS_CUSTOM_ASSET_SELECTED = 'assetSelected'
const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_FUNCTION_TYPE]: 1,
@@ -37,13 +34,28 @@ const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN]: 3,
[METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4,
[METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5,
+
[METAMETRICS_CUSTOM_FROM_NETWORK]: 1,
[METAMETRICS_CUSTOM_TO_NETWORK]: 2,
+
[METAMETRICS_CUSTOM_RPC_NETWORK_ID]: 1,
[METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 2,
- [METAMETRICS_CUSTOM_ERROR_FIELD]: 1,
- [METAMETRICS_CUSTOM_ERROR_MESSAGE]: 2,
+
+ [METAMETRICS_CUSTOM_ERROR_FIELD]: 3,
+ [METAMETRICS_CUSTOM_ERROR_MESSAGE]: 4,
+
+ [METAMETRICS_CUSTOM_GAS_CHANGED]: 1,
+ [METAMETRICS_CUSTOM_ASSET_SELECTED]: 2,
}
+/** ********************************************************** **/
+
+const METAMETRICS_CUSTOM_NETWORK = 'network'
+const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType'
+const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency'
+const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType'
+const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens'
+const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts'
+
const customDimensionsNameIdMap = {
[METAMETRICS_CUSTOM_NETWORK]: 5,
@@ -59,6 +71,7 @@ function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) {
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}`
}
+// composes query params of the form &dimension[0-999]=[value]
function composeCustomDimensionParamAddition (customDimensions) {
const customDimensionParamStrings = Object.keys(customDimensions).reduce((acc, name) => {
return [...acc, `dimension${customDimensionsNameIdMap[name]}=${customDimensions[name]}`]
@@ -66,6 +79,8 @@ function composeCustomDimensionParamAddition (customDimensions) {
return `&${customDimensionParamStrings.join('&')}`
}
+// composes query params in form: &cvar={[id]:[[name],[value]]}
+// Example: &cvar={"1":["OS","iphone 5.0"],"2":["Matomo Mobile Version","1.6.2"],"3":["Locale","en::en"],"4":["Num Accounts","2"]}
function composeCustomVarParamAddition (customVariables) {
const customVariableIdValuePairs = Object.keys(customVariables).reduce((acc, name) => {
return {
@@ -82,7 +97,29 @@ function composeParamAddition (paramValue, paramName) {
: `&${paramName}=${paramValue}`
}
-function composeUrl (config, permissionPreferences = {}) {
+/**
+ * @name composeUrl
+ * @param {Object} config - configuration object for composing the metametrics url
+ * @property {object} config.eventOpts Object containing event category, action and name descriptors
+ * @property {object} config.customVariables Object containing custom properties with values relevant to a specific event
+ * @property {object} config.pageOpts Objects containing information about a page/route the event is dispatched from
+ * @property {number} config.network The selected network of the user when the event occurs
+ * @property {string} config.environmentType The "environment" the user is using the app from: 'popup', 'notification' or 'fullscreen'
+ * @property {string} config.activeCurrency The current the user has select as their primary currency at the time of the event
+ * @property {string} config.accountType The account type being used at the time of the event: 'hardware', 'imported' or 'default'
+ * @property {number} config.numberOfTokens The number of tokens that the user has added at the time of the event
+ * @property {number} config.numberOfAccounts The number of accounts the user has added at the time of the event
+ * @property {string} config.previousPath The location path the user was on prior to the path they are on at the time of the event
+ * @property {string} config.currentPath The location path the user is on at the time of the event
+ * @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number
+ * @property {string} config.confirmTransactionOrigin The origin on a transaction
+ * @property {string} config.url The url to track an event at. Overrides `currentPath`
+ * @property {boolean} config.excludeMetaMetricsId Whether or not the tracked event data should be associated with a metametrics id
+ * @property {boolean} config.isNewVisit Whether or not the event should be tracked as a new visit/user sessions
+ * @returns {String} Returns a url to be passed to fetch to make the appropriate request to matomo.
+ * Example: https://chromeextensionmm.innocraft.cloud/piwik.php?idsite=1&rec=1&apiv=1&e_c=Navigation&e_a=Home&e_n=Clicked%20Send:%20Eth&urlref=http%3A%2F%2Fwww.metamask.io%2Fmetametrics%2Fhome.html%23send&dimension5=3&dimension6=fullscreen&dimension7=ETH&dimension8=default&dimension9=0&dimension10=3&url=http%3A%2F%2Fwww.metamask.io%2Fmetametrics%2Fhome.html%23&_id=49c10aff19795e9a&rand=7906028754863992&pv_id=53acad&uid=49c1
+ */
+function composeUrl (config) {
const {
eventOpts = {},
customVariables = '',
@@ -122,10 +159,10 @@ function composeUrl (config, permissionPreferences = {}) {
numberOfTokens: customVariables && customVariables.numberOfTokens || numberOfTokens,
numberOfAccounts: customVariables && customVariables.numberOfAccounts || numberOfAccounts,
}) : ''
- const url = configUrl || `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}`
+ const url = configUrl || currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : ''
const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : ''
const rand = `&rand=${String(Math.random()).slice(2)}`
- const pv_id = `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}`
+ const pv_id = (url || currentPath) && `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}` || ''
const uid = metaMetricsId && !excludeMetaMetricsId
? `&uid=${metaMetricsId.slice(2, 18)}`
: excludeMetaMetricsId
diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js
index cb6c9536c..99ccc3478 100644
--- a/ui/app/helpers/utils/transactions.util.js
+++ b/ui/app/helpers/utils/transactions.util.js
@@ -6,6 +6,8 @@ import {
TRANSACTION_TYPE_CANCEL,
TRANSACTION_STATUS_CONFIRMED,
} from '../../../../app/scripts/controllers/transactions/enums'
+import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
+
import {
TOKEN_METHOD_TRANSFER,
@@ -188,3 +190,17 @@ export function getStatusKey (transaction) {
return transaction.status
}
+
+/**
+ * Returns an external block explorer URL at which a transaction can be viewed.
+ * @param {number} networkId
+ * @param {string} hash
+ * @param {Object} rpcPrefs
+ */
+export function getBlockExplorerUrlForTx (networkId, hash, rpcPrefs = {}) {
+ if (rpcPrefs.blockExplorerUrl) {
+ return `${rpcPrefs.blockExplorerUrl}/tx/${hash}`
+ }
+ const prefix = prefixForNetwork(networkId)
+ return `https://${prefix}etherscan.io/tx/${hash}`
+}
diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js
index c50d7cbe5..94fa9ad42 100644
--- a/ui/app/helpers/utils/util.js
+++ b/ui/app/helpers/utils/util.js
@@ -92,7 +92,7 @@ function miniAddressSummary (address) {
return checked ? checked.slice(0, 4) + '...' + checked.slice(-4) : '...'
}
-function isValidAddress (address, network) {
+function isValidAddress (address) {
var prefixed = ethUtil.addHexPrefix(address)
if (address === '0x0000000000000000000000000000000000000000') return false
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
@@ -268,7 +268,7 @@ function bnMultiplyByFraction (targetBN, numerator, denominator) {
return targetBN.mul(numBN).div(denomBN)
}
-function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimit) {
+function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16)) {
const gasBn = hexToBn(gas)
const gasPriceBn = hexToBn(gasPrice)
const txFeeBn = gasBn.mul(gasPriceBn)
@@ -297,7 +297,7 @@ function exportAsFile (filename, data, type = 'text/csv') {
}
function allNull (obj) {
- return Object.entries(obj).every(([key, value]) => value === null)
+ return Object.entries(obj).every(([_, value]) => value === null)
}
function getTokenAddressFromTokenObject (token) {
@@ -308,11 +308,10 @@ function getTokenAddressFromTokenObject (token) {
* Safely checksumms a potentially-null address
*
* @param {String} [address] - address to checksum
- * @param {String} [network] - network id
* @returns {String} - checksummed address
*
*/
-function checksumAddress (address, network) {
+function checksumAddress (address) {
const checksummed = address ? ethUtil.toChecksumAddress(address) : ''
return checksummed
}
diff --git a/ui/app/pages/add-token/token-list/token-list-placeholder/index.scss b/ui/app/pages/add-token/token-list/token-list-placeholder/index.scss
index cc495dfb0..a363c77c5 100644
--- a/ui/app/pages/add-token/token-list/token-list-placeholder/index.scss
+++ b/ui/app/pages/add-token/token-list/token-list-placeholder/index.scss
@@ -18,6 +18,7 @@
}
&__link {
- color: $curious-blue;
+ @extend %link;
+ margin-top: .5rem;
}
}
diff --git a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
index 7edb8f541..9a118a815 100644
--- a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
+++ b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
@@ -103,7 +103,7 @@ export default class ConfirmAddSuggestedToken extends Component {
{ this.context.t('cancel') }
</Button>
<Button
- type="primary"
+ type="secondary"
large
className="page-container__footer-button"
onClick={() => {
diff --git a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
index a90fe148f..cc73b2ea7 100644
--- a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
+++ b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
@@ -18,7 +18,7 @@ const mapStateToProps = ({ metamask }) => {
const mapDispatchToProps = dispatch => {
return {
- addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, decimals, image)),
+ addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, Number(decimals), image)),
removeSuggestedTokens: () => dispatch(removeSuggestedTokens()),
}
}
diff --git a/ui/app/pages/confirm-add-token/confirm-add-token.component.js b/ui/app/pages/confirm-add-token/confirm-add-token.component.js
index c0ec624ac..f0a19e8d9 100644
--- a/ui/app/pages/confirm-add-token/confirm-add-token.component.js
+++ b/ui/app/pages/confirm-add-token/confirm-add-token.component.js
@@ -96,7 +96,7 @@ export default class ConfirmAddToken extends Component {
{ this.context.t('back') }
</Button>
<Button
- type="primary"
+ type="secondary"
large
className="page-container__footer-button"
onClick={() => {
diff --git a/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js b/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
index 9bc0daab9..c90ccc917 100644
--- a/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
+++ b/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
@@ -56,7 +56,7 @@ export default class ConfirmDeployContract extends Component {
render () {
return (
<ConfirmTransactionBase
- action={this.context.t('contractDeployment')}
+ actionKey={'contractDeployment'}
dataComponent={this.renderData()}
/>
)
diff --git a/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js b/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js
index 8daad675e..68280f624 100644
--- a/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js
+++ b/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js
@@ -30,7 +30,7 @@ export default class ConfirmSendEther extends Component {
return (
<ConfirmTransactionBase
- action={this.context.t('confirm')}
+ actionKey={'confirm'}
hideData={hideData}
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
/>
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 9e749322f..c6a05cf0f 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -4,11 +4,12 @@ import PropTypes from 'prop-types'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container'
-import { isBalanceSufficient } from '../../components/app/send/send.utils'
+import { isBalanceSufficient } from '../send/send.utils'
import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes'
import {
INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY,
+ GAS_LIMIT_TOO_LOW_ERROR_KEY,
} from '../../helpers/constants/error-keys'
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../helpers/constants/transactions'
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'
@@ -18,6 +19,7 @@ import AdvancedGasInputs from '../../components/app/gas-customization/advanced-g
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
t: PropTypes.func,
+ tOrKey: PropTypes.func.isRequired,
metricsEvent: PropTypes.func,
}
@@ -64,7 +66,7 @@ export default class ConfirmTransactionBase extends Component {
updateGasAndCalculate: PropTypes.func,
customGas: PropTypes.object,
// Component props
- action: PropTypes.string,
+ actionKey: PropTypes.string,
contentComponent: PropTypes.node,
dataComponent: PropTypes.node,
detailsComponent: PropTypes.node,
@@ -99,15 +101,18 @@ export default class ConfirmTransactionBase extends Component {
submitError: null,
}
- componentDidUpdate () {
+ componentDidUpdate (prevProps) {
const {
transactionStatus,
showTransactionConfirmedModal,
history,
clearConfirmTransaction,
} = this.props
+ const { transactionStatus: prevTxStatus } = prevProps
+ const statusUpdated = transactionStatus !== prevTxStatus
+ const txDroppedOrConfirmed = transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS
- if (transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS) {
+ if (statusUpdated && txDroppedOrConfirmed) {
showTransactionConfirmedModal({
onSubmit: () => {
clearConfirmTransaction()
@@ -130,6 +135,7 @@ export default class ConfirmTransactionBase extends Component {
value: amount,
} = {},
} = {},
+ customGas,
} = this.props
const insufficientBalance = balance && !isBalanceSufficient({
@@ -146,6 +152,13 @@ export default class ConfirmTransactionBase extends Component {
}
}
+ if (customGas.gasLimit < 21000) {
+ return {
+ valid: false,
+ errorKey: GAS_LIMIT_TOO_LOW_ERROR_KEY,
+ }
+ }
+
if (simulationFails) {
return {
valid: true,
@@ -159,7 +172,7 @@ export default class ConfirmTransactionBase extends Component {
}
handleEditGas () {
- const { onEditGas, showCustomizeGasModal, action, txData: { origin }, methodData = {} } = this.props
+ const { onEditGas, showCustomizeGasModal, actionKey, txData: { origin }, methodData = {} } = this.props
this.context.metricsEvent({
eventOpts: {
@@ -169,7 +182,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
- functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
+ functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@@ -292,7 +305,7 @@ export default class ConfirmTransactionBase extends Component {
}
handleEdit () {
- const { txData, tokenData, tokenProps, onEdit, action, txData: { origin }, methodData = {} } = this.props
+ const { txData, tokenData, tokenProps, onEdit, actionKey, txData: { origin }, methodData = {} } = this.props
this.context.metricsEvent({
eventOpts: {
@@ -302,7 +315,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
- functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
+ functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@@ -331,7 +344,7 @@ export default class ConfirmTransactionBase extends Component {
handleCancel () {
const { metricsEvent } = this.context
- const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, action, txData: { origin }, methodData = {} } = this.props
+ const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, actionKey, txData: { origin }, methodData = {} } = this.props
if (onCancel) {
metricsEvent({
@@ -342,7 +355,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
- functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
+ functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@@ -358,7 +371,7 @@ export default class ConfirmTransactionBase extends Component {
handleSubmit () {
const { metricsEvent } = this.context
- const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, action, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props
+ const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, actionKey, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props
const { submitting } = this.state
if (submitting) {
@@ -377,7 +390,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
- functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
+ functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@@ -517,7 +530,7 @@ export default class ConfirmTransactionBase extends Component {
valid: propsValid = true,
errorMessage,
errorKey: propsErrorKey,
- action,
+ actionKey,
title,
subtitle,
hideSubtitle,
@@ -543,7 +556,8 @@ export default class ConfirmTransactionBase extends Component {
toName={toName}
toAddress={toAddress}
showEdit={onEdit && !isTxReprice}
- action={action || getMethodName(name) || this.context.t('contractInteraction')}
+ // In the event that the key is falsy (and inherently invalid), use a fallback string
+ action={this.context.tOrKey(actionKey) || getMethodName(name) || this.context.t('contractInteraction')}
title={title}
titleComponent={this.renderTitleComponent()}
subtitle={subtitle}
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
index 83543f1a4..2b087f5cc 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -14,9 +14,9 @@ import {
GAS_LIMIT_TOO_LOW_ERROR_KEY,
} from '../../helpers/constants/error-keys'
import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util'
-import { isBalanceSufficient, calcGasTotal } from '../../components/app/send/send.utils'
+import { isBalanceSufficient, calcGasTotal } from '../send/send.utils'
import { conversionGreaterThan } from '../../helpers/utils/conversion-util'
-import { MIN_GAS_LIMIT_DEC } from '../../components/app/send/send.constants'
+import { MIN_GAS_LIMIT_DEC } from '../send/send.constants'
import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util'
import {getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet} from '../../selectors/selectors'
diff --git a/ui/app/pages/create-account/connect-hardware/account-list.js b/ui/app/pages/create-account/connect-hardware/account-list.js
index 617fb8833..247c27a5d 100644
--- a/ui/app/pages/create-account/connect-hardware/account-list.js
+++ b/ui/app/pages/create-account/connect-hardware/account-list.js
@@ -6,10 +6,6 @@ const Select = require('react-select').default
import Button from '../../../components/ui/button'
class AccountList extends Component {
- constructor (props, context) {
- super(props)
- }
-
getHdPaths () {
return [
{
@@ -152,7 +148,7 @@ class AccountList extends Component {
}, [this.context.t('cancel')]),
h(Button, {
- type: 'confirm',
+ type: 'primary',
large: true,
className: 'new-account-connect-form__button unlock',
disabled,
diff --git a/ui/app/pages/create-account/connect-hardware/connect-screen.js b/ui/app/pages/create-account/connect-hardware/connect-screen.js
index 7e9dee970..a3b8ad246 100644
--- a/ui/app/pages/create-account/connect-hardware/connect-screen.js
+++ b/ui/app/pages/create-account/connect-hardware/connect-screen.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
import Button from '../../../components/ui/button'
class ConnectScreen extends Component {
- constructor (props, context) {
+ constructor (props) {
super(props)
this.state = {
selectedDevice: null,
@@ -46,7 +46,7 @@ class ConnectScreen extends Component {
this.renderConnectToTrezorButton(),
]),
h(Button, {
- type: 'confirm',
+ type: 'primary',
large: true,
className: 'hw-connect__connect-btn',
onClick: this.connect,
@@ -103,7 +103,7 @@ class ConnectScreen extends Component {
}
- scrollToTutorial = (e) => {
+ scrollToTutorial = () => {
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
}
diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js
index 1398fa680..80a160205 100644
--- a/ui/app/pages/create-account/connect-hardware/index.js
+++ b/ui/app/pages/create-account/connect-hardware/index.js
@@ -8,11 +8,9 @@ const ConnectScreen = require('./connect-screen')
const AccountList = require('./account-list')
const { DEFAULT_ROUTE } = require('../../../helpers/constants/routes')
const { formatBalance } = require('../../../helpers/utils/util')
-const { getPlatform } = require('../../../../../app/scripts/lib/util')
-const { PLATFORM_FIREFOX } = require('../../../../../app/scripts/lib/enums')
class ConnectHardwareForm extends Component {
- constructor (props, context) {
+ constructor (props) {
super(props)
this.state = {
error: null,
@@ -51,12 +49,6 @@ class ConnectHardwareForm extends Component {
}
connectToHardwareWallet = (device) => {
- // Ledger hardware wallets are not supported on firefox
- if (getPlatform() === PLATFORM_FIREFOX && device === 'ledger') {
- this.setState({ browserSupported: false, error: null})
- return null
- }
-
if (this.state.accounts.length) {
return null
}
@@ -101,7 +93,7 @@ class ConnectHardwareForm extends Component {
const newState = { unlocked: true, device, error: null }
// Default to the first account
if (this.state.selectedAccount === null) {
- accounts.forEach((a, i) => {
+ accounts.forEach((a) => {
if (a.address.toLowerCase() === this.props.address) {
newState.selectedAccount = a.index.toString()
}
diff --git a/ui/app/pages/create-account/import-account/json.js b/ui/app/pages/create-account/import-account/json.js
index 17bef763c..ad430ba58 100644
--- a/ui/app/pages/create-account/import-account/json.js
+++ b/ui/app/pages/create-account/import-account/json.js
@@ -61,7 +61,7 @@ class JsonImportSubview extends Component {
}, [this.context.t('cancel')]),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'new-account-create-form__button',
onClick: () => this.createNewKeychain(),
diff --git a/ui/app/pages/create-account/import-account/private-key.js b/ui/app/pages/create-account/import-account/private-key.js
index 450614e87..0cdf25ce9 100644
--- a/ui/app/pages/create-account/import-account/private-key.js
+++ b/ui/app/pages/create-account/import-account/private-key.js
@@ -75,7 +75,7 @@ PrivateKeyImportView.prototype.render = function () {
}, [this.context.t('cancel')]),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'new-account-create-form__button',
onClick: () => this.createNewKeychain(),
diff --git a/ui/app/pages/create-account/import-account/seed.js b/ui/app/pages/create-account/import-account/seed.js
index d98909baa..73332f926 100644
--- a/ui/app/pages/create-account/import-account/seed.js
+++ b/ui/app/pages/create-account/import-account/seed.js
@@ -11,7 +11,7 @@ SeedImportSubview.contextTypes = {
module.exports = connect(mapStateToProps)(SeedImportSubview)
-function mapStateToProps (state) {
+function mapStateToProps () {
return {}
}
diff --git a/ui/app/pages/create-account/new-account.js b/ui/app/pages/create-account/new-account.js
index 316fbe6f1..d19e6bc38 100644
--- a/ui/app/pages/create-account/new-account.js
+++ b/ui/app/pages/create-account/new-account.js
@@ -47,7 +47,7 @@ class NewAccountCreateForm extends Component {
}, [this.context.t('cancel')]),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'new-account-create-form__button',
onClick: () => {
diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
index 433dad6e2..5092d277e 100644
--- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
@@ -36,6 +36,20 @@ export default class ImportWithSeedPhrase extends PureComponent {
.join(' ')
}
+ componentWillMount () {
+ window.onbeforeunload = () => this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Import Seed Phrase',
+ name: 'Close window on import screen',
+ },
+ customVariables: {
+ errorLabel: 'Seed Phrase Error',
+ errorMessage: this.state.seedPhraseError,
+ },
+ })
+ }
+
handleSeedPhraseChange (seedPhrase) {
let seedPhraseError = ''
@@ -172,6 +186,10 @@ export default class ImportWithSeedPhrase extends PureComponent {
action: 'Import Seed Phrase',
name: 'Go Back from Onboarding Import',
},
+ customVariables: {
+ errorLabel: 'Seed Phrase Error',
+ errorMessage: seedPhraseError,
+ },
})
this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE)
}}
@@ -243,7 +261,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
</span>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked}
onClick={this.handleImport}
diff --git a/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js
index c040cff88..de073af2f 100644
--- a/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js
+++ b/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js
@@ -211,7 +211,7 @@ export default class NewAccount extends PureComponent {
</span>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked}
onClick={this.handleCreate}
diff --git a/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js b/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js
index 3434d117a..590cf0303 100644
--- a/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js
+++ b/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js
@@ -34,7 +34,7 @@ export default class UniqueImageScreen extends PureComponent {
{ t('protectYourKeysMessage2') }
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={() => {
this.context.metricsEvent({
diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js
index c4292331b..83b0e7fc6 100644
--- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js
+++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js
@@ -71,7 +71,7 @@ export default class EndOfFlowScreen extends PureComponent {
</a>.
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={async () => {
await completeOnboarding()
diff --git a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
index 19c668278..6b9d06cf9 100644
--- a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
+++ b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
@@ -119,7 +119,7 @@ export default class MetaMetricsOptIn extends Component {
hideCancel={false}
onSubmit={() => {
setParticipateInMetaMetrics(true)
- .then(([participateStatus, metaMetricsId]) => {
+ .then(([_, metaMetricsId]) => {
const promise = participateInMetaMetrics !== true
? metricsEvent({
eventOpts: {
@@ -149,7 +149,7 @@ export default class MetaMetricsOptIn extends Component {
})
}}
submitText={'I agree'}
- submitButtonType={'confirm'}
+ submitButtonType={'primary'}
disabled={false}
/>
<div className="metametrics-opt-in__bottom-text">
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
index 59b4f73a6..4cfc38fdf 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
@@ -8,7 +8,9 @@ import {
INITIALIZE_SEED_PHRASE_ROUTE,
} from '../../../../helpers/constants/routes'
import { exportAsFile } from '../../../../helpers/utils/util'
-import { selectSeedWord, deselectSeedWord } from './confirm-seed-phrase.state'
+import DraggableSeed from './draggable-seed.component'
+
+const EMPTY_SEEDS = Array(12).fill(null)
export default class ConfirmSeedPhrase extends PureComponent {
static contextTypes = {
@@ -27,10 +29,32 @@ export default class ConfirmSeedPhrase extends PureComponent {
}
state = {
- selectedSeedWords: [],
+ selectedSeedIndices: [],
shuffledSeedWords: [],
- // Hash of shuffledSeedWords index {Number} to selectedSeedWords index {Number}
- selectedSeedWordsHash: {},
+ pendingSeedIndices: [],
+ draggingSeedIndex: -1,
+ hoveringIndex: -1,
+ isDragging: false,
+ }
+
+ shouldComponentUpdate (nextProps, nextState) {
+ const { seedPhrase } = this.props
+ const {
+ selectedSeedIndices,
+ shuffledSeedWords,
+ pendingSeedIndices,
+ draggingSeedIndex,
+ hoveringIndex,
+ isDragging,
+ } = this.state
+
+ return seedPhrase !== nextProps.seedPhrase ||
+ draggingSeedIndex !== nextState.draggingSeedIndex ||
+ isDragging !== nextState.isDragging ||
+ hoveringIndex !== nextState.hoveringIndex ||
+ selectedSeedIndices.join(' ') !== nextState.selectedSeedIndices.join(' ') ||
+ shuffledSeedWords.join(' ') !== nextState.shuffledSeedWords.join(' ') ||
+ pendingSeedIndices.join(' ') !== nextState.pendingSeedIndices.join(' ')
}
componentDidMount () {
@@ -39,6 +63,26 @@ export default class ConfirmSeedPhrase extends PureComponent {
this.setState({ shuffledSeedWords })
}
+ setDraggingSeedIndex = draggingSeedIndex => this.setState({ draggingSeedIndex })
+
+ setHoveringIndex = hoveringIndex => this.setState({ hoveringIndex })
+
+ onDrop = targetIndex => {
+ const {
+ selectedSeedIndices,
+ draggingSeedIndex,
+ } = this.state
+
+ const indices = insert(selectedSeedIndices, draggingSeedIndex, targetIndex, true)
+
+ this.setState({
+ selectedSeedIndices: indices,
+ pendingSeedIndices: indices,
+ draggingSeedIndex: -1,
+ hoveringIndex: -1,
+ })
+ }
+
handleExport = () => {
exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain')
}
@@ -64,24 +108,35 @@ export default class ConfirmSeedPhrase extends PureComponent {
}
}
- handleSelectSeedWord = (word, shuffledIndex) => {
- this.setState(selectSeedWord(word, shuffledIndex))
+ handleSelectSeedWord = (shuffledIndex) => {
+ this.setState({
+ selectedSeedIndices: [...this.state.selectedSeedIndices, shuffledIndex],
+ pendingSeedIndices: [...this.state.pendingSeedIndices, shuffledIndex],
+ })
}
handleDeselectSeedWord = shuffledIndex => {
- this.setState(deselectSeedWord(shuffledIndex))
+ this.setState({
+ selectedSeedIndices: this.state.selectedSeedIndices.filter(i => shuffledIndex !== i),
+ pendingSeedIndices: this.state.pendingSeedIndices.filter(i => shuffledIndex !== i),
+ })
}
isValid () {
const { seedPhrase } = this.props
- const { selectedSeedWords } = this.state
+ const { selectedSeedIndices, shuffledSeedWords } = this.state
+ const selectedSeedWords = selectedSeedIndices.map(i => shuffledSeedWords[i])
return seedPhrase === selectedSeedWords.join(' ')
}
render () {
const { t } = this.context
const { history } = this.props
- const { selectedSeedWords, shuffledSeedWords, selectedSeedWordsHash } = this.state
+ const {
+ selectedSeedIndices,
+ shuffledSeedWords,
+ draggingSeedIndex,
+ } = this.state
return (
<div className="confirm-seed-phrase">
@@ -102,47 +157,45 @@ export default class ConfirmSeedPhrase extends PureComponent {
<div className="first-time-flow__text-block">
{ t('selectEachPhrase') }
</div>
- <div className="confirm-seed-phrase__selected-seed-words">
- {
- selectedSeedWords.map((word, index) => (
- <div
- key={index}
- className="confirm-seed-phrase__seed-word"
- >
- { word }
- </div>
- ))
- }
+ <div
+ className={classnames('confirm-seed-phrase__selected-seed-words', {
+ 'confirm-seed-phrase__selected-seed-words--dragging': draggingSeedIndex > -1,
+ })}
+ >
+ { this.renderPendingSeeds() }
+ { this.renderSelectedSeeds() }
</div>
<div className="confirm-seed-phrase__shuffled-seed-words">
{
shuffledSeedWords.map((word, index) => {
- const isSelected = index in selectedSeedWordsHash
+ const isSelected = selectedSeedIndices.includes(index)
return (
- <div
+ <DraggableSeed
key={index}
- className={classnames(
- 'confirm-seed-phrase__seed-word',
- 'confirm-seed-phrase__seed-word--shuffled',
- { 'confirm-seed-phrase__seed-word--selected': isSelected }
- )}
+ seedIndex={index}
+ index={index}
+ draggingSeedIndex={this.state.draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ className="confirm-seed-phrase__seed-word--shuffled"
+ selected={isSelected}
onClick={() => {
if (!isSelected) {
- this.handleSelectSeedWord(word, index)
+ this.handleSelectSeedWord(index)
} else {
this.handleDeselectSeedWord(index)
}
}}
- >
- { word }
- </div>
+ word={word}
+ />
)
})
}
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={this.handleSubmit}
disabled={!this.isValid()}
@@ -152,4 +205,80 @@ export default class ConfirmSeedPhrase extends PureComponent {
</div>
)
}
+
+ renderSelectedSeeds () {
+ const { shuffledSeedWords, selectedSeedIndices, draggingSeedIndex } = this.state
+ return EMPTY_SEEDS.map((_, index) => {
+ const seedIndex = selectedSeedIndices[index]
+ const word = shuffledSeedWords[seedIndex]
+
+ return (
+ <DraggableSeed
+ key={`selected-${seedIndex}-${index}`}
+ className="confirm-seed-phrase__selected-seed-words__selected-seed"
+ index={index}
+ seedIndex={seedIndex}
+ word={word}
+ draggingSeedIndex={draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ draggable
+ />
+ )
+ })
+ }
+
+ renderPendingSeeds () {
+ const {
+ pendingSeedIndices,
+ shuffledSeedWords,
+ draggingSeedIndex,
+ hoveringIndex,
+ } = this.state
+
+ const indices = insert(pendingSeedIndices, draggingSeedIndex, hoveringIndex)
+
+ return EMPTY_SEEDS.map((_, index) => {
+ const seedIndex = indices[index]
+ const word = shuffledSeedWords[seedIndex]
+
+ return (
+ <DraggableSeed
+ key={`pending-${seedIndex}-${index}`}
+ index={index}
+ className={classnames('confirm-seed-phrase__selected-seed-words__pending-seed', {
+ 'confirm-seed-phrase__seed-word--hidden': draggingSeedIndex === seedIndex && index !== hoveringIndex,
+ })}
+ seedIndex={seedIndex}
+ word={word}
+ draggingSeedIndex={draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ droppable={!!word}
+ />
+ )
+ })
+ }
+}
+
+function insert (list, value, target, removeOld) {
+ let nextList = [...list]
+
+ if (typeof list[target] === 'number') {
+ nextList = [...list.slice(0, target), value, ...list.slice(target)]
+ }
+
+ if (removeOld) {
+ nextList = nextList.filter((seed, i) => {
+ return seed !== value || i === target
+ })
+ }
+
+ if (nextList.length > 12) {
+ nextList.pop()
+ }
+
+ return nextList
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
deleted file mode 100644
index f2476fc5c..000000000
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
+++ /dev/null
@@ -1,41 +0,0 @@
-export function selectSeedWord (word, shuffledIndex) {
- return function update (state) {
- const { selectedSeedWords, selectedSeedWordsHash } = state
- const nextSelectedIndex = selectedSeedWords.length
-
- return {
- selectedSeedWords: [ ...selectedSeedWords, word ],
- selectedSeedWordsHash: { ...selectedSeedWordsHash, [shuffledIndex]: nextSelectedIndex },
- }
- }
-}
-
-export function deselectSeedWord (shuffledIndex) {
- return function update (state) {
- const {
- selectedSeedWords: prevSelectedSeedWords,
- selectedSeedWordsHash: prevSelectedSeedWordsHash,
- } = state
-
- const selectedSeedWords = [...prevSelectedSeedWords]
- const indexToRemove = prevSelectedSeedWordsHash[shuffledIndex]
- selectedSeedWords.splice(indexToRemove, 1)
- const selectedSeedWordsHash = Object.keys(prevSelectedSeedWordsHash).reduce((acc, index) => {
- const output = { ...acc }
- const selectedSeedWordIndex = prevSelectedSeedWordsHash[index]
-
- if (selectedSeedWordIndex < indexToRemove) {
- output[index] = selectedSeedWordIndex
- } else if (selectedSeedWordIndex > indexToRemove) {
- output[index] = selectedSeedWordIndex - 1
- }
-
- return output
- }, {})
-
- return {
- selectedSeedWords,
- selectedSeedWordsHash,
- }
- }
-}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js
new file mode 100644
index 000000000..cdb881921
--- /dev/null
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js
@@ -0,0 +1,126 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import { DragSource, DropTarget } from 'react-dnd'
+
+class DraggableSeed extends Component {
+
+ static propTypes = {
+ // React DnD Props
+ connectDragSource: PropTypes.func.isRequired,
+ connectDropTarget: PropTypes.func.isRequired,
+ isDragging: PropTypes.bool,
+ isOver: PropTypes.bool,
+ canDrop: PropTypes.bool,
+ // Own Props
+ onClick: PropTypes.func.isRequired,
+ setHoveringIndex: PropTypes.func.isRequired,
+ index: PropTypes.number,
+ draggingSeedIndex: PropTypes.number,
+ word: PropTypes.string,
+ className: PropTypes.string,
+ selected: PropTypes.bool,
+ droppable: PropTypes.bool,
+ }
+
+ static defaultProps = {
+ className: '',
+ onClick () {},
+ }
+
+ componentWillReceiveProps (nextProps) {
+ const { isOver, setHoveringIndex } = this.props
+ if (isOver && !nextProps.isOver) {
+ setHoveringIndex(-1)
+ }
+ }
+
+ render () {
+ const {
+ connectDragSource,
+ connectDropTarget,
+ isDragging,
+ index,
+ word,
+ selected,
+ className,
+ onClick,
+ isOver,
+ canDrop,
+ } = this.props
+
+ return connectDropTarget(connectDragSource(
+ <div
+ key={index}
+ className={classnames('btn-secondary confirm-seed-phrase__seed-word', className, {
+ 'confirm-seed-phrase__seed-word--selected btn-primary': selected,
+ 'confirm-seed-phrase__seed-word--dragging': isDragging,
+ 'confirm-seed-phrase__seed-word--empty': !word,
+ 'confirm-seed-phrase__seed-word--active-drop': !isOver && canDrop,
+ 'confirm-seed-phrase__seed-word--drop-hover': isOver && canDrop,
+ })}
+ onClick={onClick}
+ >
+ { word }
+ </div>
+ ))
+ }
+}
+
+const SEEDWORD = 'SEEDWORD'
+
+const seedSource = {
+ beginDrag (props) {
+ setTimeout(() => props.setDraggingSeedIndex(props.seedIndex), 0)
+ return {
+ seedIndex: props.seedIndex,
+ word: props.word,
+ }
+ },
+ canDrag (props) {
+ return props.draggable
+ },
+ endDrag (props, monitor) {
+ const dropTarget = monitor.getDropResult()
+
+ if (!dropTarget) {
+ setTimeout(() => props.setDraggingSeedIndex(-1), 0)
+ return
+ }
+
+ props.onDrop(dropTarget.targetIndex)
+ },
+}
+
+const seedTarget = {
+ drop (props) {
+ return {
+ targetIndex: props.index,
+ }
+ },
+ canDrop (props) {
+ return props.droppable
+ },
+ hover (props) {
+ props.setHoveringIndex(props.index)
+ },
+}
+
+const collectDrag = (connect, monitor) => {
+ return {
+ connectDragSource: connect.dragSource(),
+ isDragging: monitor.isDragging(),
+ }
+}
+
+const collectDrop = (connect, monitor) => {
+ return {
+ connectDropTarget: connect.dropTarget(),
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop(),
+ }
+}
+
+export default DropTarget(SEEDWORD, seedTarget, collectDrop)(DragSource(SEEDWORD, seedSource, collectDrag)(DraggableSeed))
+
+
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
index 93137618c..f025a503f 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
@@ -3,37 +3,58 @@
margin-bottom: 12px;
}
- &__selected-seed-words {
- min-height: 190px;
- max-width: 496px;
- border: 1px solid #CDCDCD;
- border-radius: 6px;
- background-color: $white;
- margin: 24px 0 36px;
- padding: 12px;
- }
-
&__shuffled-seed-words {
- max-width: 496px;
+ max-width: 575px;
}
&__seed-word {
- display: inline-block;
- color: #5B5D67;
- background-color: #E7E7E7;
+ display: inline-flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ justify-content: center;
padding: 8px 18px;
- min-width: 64px;
+ width: 128px;
+ height: 41px;
margin: 4px;
text-align: center;
+ border-radius: 4px;
+ cursor: move;
+
+ &--shuffled {
+ cursor: pointer;
+ margin: 6px;
+ }
&--selected {
- background-color: #85D1CC;
color: $white;
}
- &--shuffled {
- cursor: pointer;
- margin: 6px;
+ &--dragging {
+ margin: 0;
+ }
+
+ &--empty {
+ background-color: transparent;
+ border-color: transparent;
+ cursor: default;
+
+ &:hover,
+ &:active {
+ background-color: transparent;
+ border-color: transparent;
+ cursor: default;
+ box-shadow: none !important;
+ }
+ }
+
+ &--hidden {
+ display: none !important;
+ }
+
+ &--drop-hover {
+ background-color: transparent;
+ border-color: transparent;
+ color: transparent;
}
@media screen and (max-width: 575px) {
@@ -42,7 +63,37 @@
}
}
- button {
- margin-top: 0xp;
+ &__selected-seed-words {
+ display: flex;
+ flex-flow: row wrap;
+ min-height: 161px;
+ max-width: 575px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: $white;
+ margin: 24px 0 36px;
+ padding: 12px;
+
+ &__pending-seed {
+ display: none;
+ }
+
+ &__selected-seed {
+ display: inline-flex;
+
+ &:hover {
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
+ }
+ }
+
+ &--dragging {
+ .confirm-seed-phrase__selected-seed-words__pending-seed {
+ display: inline-flex;
+ }
+
+ .confirm-seed-phrase__selected-seed-words__selected-seed {
+ display: none;
+ }
+ }
}
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
index ee352d74e..4e9948a0e 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
@@ -130,7 +130,7 @@ export default class RevealSeedPhrase extends PureComponent {
</div>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={this.handleNext}
disabled={!isShowingSeedPhrase}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
index 9a9f84049..0b19af18c 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
@@ -8,6 +8,8 @@ import {
INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE,
DEFAULT_ROUTE,
} from '../../../helpers/constants/routes'
+import HTML5Backend from 'react-dnd-html5-backend'
+import {DragDropContextProvider} from 'react-dnd'
export default class SeedPhrase extends PureComponent {
static propTypes = {
@@ -28,43 +30,45 @@ export default class SeedPhrase extends PureComponent {
const { seedPhrase } = this.props
return (
- <div className="first-time-flow__wrapper">
- <div className="app-header__logo-container">
- <img
- className="app-header__metafox-logo app-header__metafox-logo--horizontal"
- src="/images/logo/metamask-logo-horizontal.svg"
- height={30}
- />
- <img
- className="app-header__metafox-logo app-header__metafox-logo--icon"
- src="/images/logo/metamask-fox.svg"
- height={42}
- width={42}
- />
+ <DragDropContextProvider backend={HTML5Backend}>
+ <div className="first-time-flow__wrapper">
+ <div className="app-header__logo-container">
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--horizontal"
+ src="/images/logo/metamask-logo-horizontal.svg"
+ height={30}
+ />
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--icon"
+ src="/images/logo/metamask-fox.svg"
+ height={42}
+ width={42}
+ />
+ </div>
+ <Switch>
+ <Route
+ exact
+ path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
+ render={props => (
+ <ConfirmSeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_SEED_PHRASE_ROUTE}
+ render={props => (
+ <RevealSeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ </Switch>
</div>
- <Switch>
- <Route
- exact
- path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
- render={props => (
- <ConfirmSeedPhrase
- { ...props }
- seedPhrase={seedPhrase}
- />
- )}
- />
- <Route
- exact
- path={INITIALIZE_SEED_PHRASE_ROUTE}
- render={props => (
- <RevealSeedPhrase
- { ...props }
- seedPhrase={seedPhrase}
- />
- )}
- />
- </Switch>
- </div>
+ </DragDropContextProvider>
)
}
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js
new file mode 100644
index 000000000..8339a6f6f
--- /dev/null
+++ b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js
@@ -0,0 +1,169 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import ConfirmSeedPhrase from '../confirm-seed-phrase/confirm-seed-phrase.component'
+
+function shallowRender (props = {}, context = {}) {
+ return shallow(
+ <ConfirmSeedPhrase {...props} />,
+ {
+ context: {
+ t: str => str + '_t',
+ ...context,
+ },
+ }
+ )
+}
+
+describe('ConfirmSeedPhrase Component', () => {
+ it('should render correctly', () => {
+ const root = shallowRender({
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ })
+
+ assert.equal(
+ root.find('.confirm-seed-phrase__seed-word--shuffled').length,
+ 12,
+ 'should render 12 seed phrases'
+ )
+ })
+
+ it('should add/remove selected on click', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ assert.deepEqual(
+ root.state().selectedSeedIndices,
+ [0, 1, 2],
+ 'should add seed phrase to selected on click',
+ )
+
+ // Click on a selected seed to remove
+ root.state()
+ root.update()
+ root.state()
+ root.find('.confirm-seed-phrase__seed-word--shuffled').at(1).simulate('click')
+ assert.deepEqual(
+ root.state().selectedSeedIndices,
+ [0, 2],
+ 'should remove seed phrase from selected when click again',
+ )
+ })
+
+ it('should render correctly on hover', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ // Dragging Seed # 2 to 0 placeth
+ root.instance().setDraggingSeedIndex(2)
+ root.instance().setHoveringIndex(0)
+
+ root.update()
+
+ const pendingSeeds = root.find('.confirm-seed-phrase__selected-seed-words__pending-seed')
+
+ assert.equal(pendingSeeds.at(0).props().seedIndex, 2)
+ assert.equal(pendingSeeds.at(1).props().seedIndex, 0)
+ assert.equal(pendingSeeds.at(2).props().seedIndex, 1)
+ })
+
+ it('should insert seed in place on drop', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ // Drop Seed # 2 to 0 placeth
+ root.instance().setDraggingSeedIndex(2)
+ root.instance().setHoveringIndex(0)
+ root.instance().onDrop(0)
+
+ root.update()
+
+ assert.deepEqual(root.state().selectedSeedIndices, [2, 0, 1])
+ assert.deepEqual(root.state().pendingSeedIndices, [2, 0, 1])
+ })
+
+ it('should submit correctly', () => {
+ const originalSeed = ['鼠', '牛', '虎', '兔', '龍', '蛇', '馬', '羊', '猴', '雞', '狗', '豬']
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const shuffled = root.state().shuffledSeedWords
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+
+ originalSeed.forEach(seed => {
+ const seedIndex = shuffled.findIndex(s => s === seed)
+ seeds.at(seedIndex).simulate('click')
+ })
+
+ root.update()
+
+ root.find('.first-time-flow__button').simulate('click')
+ assert.deepEqual(metricsEventSpy.args[0][0], {
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Seed Phrase Setup',
+ name: 'Verify Complete',
+ },
+ })
+ assert.equal(pushSpy.args[0][0], '/initialize/end-of-flow')
+ })
+})
diff --git a/ui/app/pages/first-time-flow/select-action/select-action.component.js b/ui/app/pages/first-time-flow/select-action/select-action.component.js
index b25a15514..5af29a505 100644
--- a/ui/app/pages/first-time-flow/select-action/select-action.component.js
+++ b/ui/app/pages/first-time-flow/select-action/select-action.component.js
@@ -95,7 +95,7 @@ export default class SelectAction extends PureComponent {
</div>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={this.handleCreate}
>
diff --git a/ui/app/pages/first-time-flow/welcome/welcome.component.js b/ui/app/pages/first-time-flow/welcome/welcome.component.js
index 3b8d6eb17..c720d2572 100644
--- a/ui/app/pages/first-time-flow/welcome/welcome.component.js
+++ b/ui/app/pages/first-time-flow/welcome/welcome.component.js
@@ -56,7 +56,7 @@ export default class Welcome extends PureComponent {
<div>{ t('happyToSeeYou') }</div>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={this.handleContinue}
>
diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js
index 29d93a9fa..4d96c3131 100644
--- a/ui/app/pages/home/home.component.js
+++ b/ui/app/pages/home/home.component.js
@@ -23,21 +23,27 @@ export default class Home extends PureComponent {
providerRequests: PropTypes.array,
}
+ componentWillMount () {
+ const {
+ history,
+ unconfirmedTransactionsCount = 0,
+ } = this.props
+
+ if (unconfirmedTransactionsCount > 0) {
+ history.push(CONFIRM_TRANSACTION_ROUTE)
+ }
+ }
+
componentDidMount () {
const {
history,
suggestedTokens = {},
- unconfirmedTransactionsCount = 0,
} = this.props
// suggested new tokens
if (Object.keys(suggestedTokens).length > 0) {
history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE)
}
-
- if (unconfirmedTransactionsCount > 0) {
- history.push(CONFIRM_TRANSACTION_ROUTE)
- }
}
render () {
@@ -45,6 +51,7 @@ export default class Home extends PureComponent {
forgottenPassword,
seedWords,
providerRequests,
+ history,
} = this.props
// seed words
@@ -69,7 +76,7 @@ export default class Home extends PureComponent {
query="(min-width: 576px)"
render={() => <WalletView />}
/>
- <TransactionView />
+ { !history.location.pathname.match(/^\/confirm-transaction/) ? <TransactionView /> : null }
</div>
</div>
)
diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js
index 7508654dc..d0a5d7b47 100644
--- a/ui/app/pages/home/home.container.js
+++ b/ui/app/pages/home/home.container.js
@@ -3,7 +3,7 @@ import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction'
-
+``
const mapStateToProps = state => {
const { metamask, appState } = state
const {
diff --git a/ui/app/pages/keychains/reveal-seed.js b/ui/app/pages/keychains/reveal-seed.js
index edc9db5a0..e83e3fd98 100644
--- a/ui/app/pages/keychains/reveal-seed.js
+++ b/ui/app/pages/keychains/reveal-seed.js
@@ -116,7 +116,7 @@ class RevealSeedPage extends Component {
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'page-container__footer-button',
onClick: event => this.handleSubmit(event),
diff --git a/ui/app/pages/mobile-sync/index.js b/ui/app/pages/mobile-sync/index.js
index 0938ad103..00a514534 100644
--- a/ui/app/pages/mobile-sync/index.js
+++ b/ui/app/pages/mobile-sync/index.js
@@ -315,7 +315,7 @@ class MobileSyncPage extends Component {
}, this.context.t('cancel')),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'new-account-create-form__button',
onClick: event => this.handleSubmit(event),
diff --git a/ui/app/pages/provider-approval/provider-approval.component.js b/ui/app/pages/provider-approval/provider-approval.component.js
index 1f1d68da7..70d3d0007 100644
--- a/ui/app/pages/provider-approval/provider-approval.component.js
+++ b/ui/app/pages/provider-approval/provider-approval.component.js
@@ -4,9 +4,9 @@ import ProviderPageContainer from '../../components/app/provider-page-container'
export default class ProviderApproval extends Component {
static propTypes = {
- approveProviderRequest: PropTypes.func.isRequired,
+ approveProviderRequestByOrigin: PropTypes.func.isRequired,
+ rejectProviderRequestByOrigin: PropTypes.func.isRequired,
providerRequest: PropTypes.object.isRequired,
- rejectProviderRequest: PropTypes.func.isRequired,
};
static contextTypes = {
@@ -14,13 +14,13 @@ export default class ProviderApproval extends Component {
};
render () {
- const { approveProviderRequest, providerRequest, rejectProviderRequest } = this.props
+ const { approveProviderRequestByOrigin, providerRequest, rejectProviderRequestByOrigin } = this.props
return (
<ProviderPageContainer
- approveProviderRequest={approveProviderRequest}
+ approveProviderRequestByOrigin={approveProviderRequestByOrigin}
+ rejectProviderRequestByOrigin={rejectProviderRequestByOrigin}
origin={providerRequest.origin}
tabID={providerRequest.tabID}
- rejectProviderRequest={rejectProviderRequest}
siteImage={providerRequest.siteImage}
siteTitle={providerRequest.siteTitle}
/>
diff --git a/ui/app/pages/provider-approval/provider-approval.container.js b/ui/app/pages/provider-approval/provider-approval.container.js
index d53c0ae4d..1e167ddb7 100644
--- a/ui/app/pages/provider-approval/provider-approval.container.js
+++ b/ui/app/pages/provider-approval/provider-approval.container.js
@@ -1,11 +1,11 @@
import { connect } from 'react-redux'
import ProviderApproval from './provider-approval.component'
-import { approveProviderRequest, rejectProviderRequest } from '../../store/actions'
+import { approveProviderRequestByOrigin, rejectProviderRequestByOrigin } from '../../store/actions'
function mapDispatchToProps (dispatch) {
return {
- approveProviderRequest: tabID => dispatch(approveProviderRequest(tabID)),
- rejectProviderRequest: tabID => dispatch(rejectProviderRequest(tabID)),
+ approveProviderRequestByOrigin: origin => dispatch(approveProviderRequestByOrigin(origin)),
+ rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)),
}
}
diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js
index e06d88c90..9eeac2da2 100644
--- a/ui/app/pages/routes/index.js
+++ b/ui/app/pages/routes/index.js
@@ -5,12 +5,13 @@ import { Route, Switch, withRouter, matchPath } from 'react-router-dom'
import { compose } from 'recompose'
import actions from '../../store/actions'
import log from 'loglevel'
-import { getMetaMaskAccounts, getNetworkIdentifier } from '../../selectors/selectors'
+import IdleTimer from 'react-idle-timer'
+import {getMetaMaskAccounts, getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors'
// init
import FirstTimeFlow from '../first-time-flow'
// accounts
-const SendTransactionScreen = require('../../components/app/send/send.container')
+const SendTransactionScreen = require('../send/send.container')
const ConfirmTransaction = require('../confirm-transaction')
// slideout menu
@@ -98,7 +99,9 @@ class Routes extends Component {
}
renderRoutes () {
- return (
+ const { autoLogoutTimeLimit, setLastActiveTime } = this.props
+
+ const routes = (
<Switch>
<Route path={LOCK_ROUTE} component={Lock} exact />
<Route path={INITIALIZE_ROUTE} component={FirstTimeFlow} />
@@ -116,6 +119,16 @@ class Routes extends Component {
<Authenticated path={DEFAULT_ROUTE} component={Home} exact />
</Switch>
)
+
+ if (autoLogoutTimeLimit > 0) {
+ return (
+ <IdleTimer onAction={setLastActiveTime} throttle={1000}>
+ {routes}
+ </IdleTimer>
+ )
+ }
+
+ return routes
}
onInitializationUnlockPage () {
@@ -267,6 +280,10 @@ class Routes extends Component {
name = this.context.t('connectingToKovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby')
+ } else if (providerName === 'localhost') {
+ name = this.context.t('connectingToLocalhost')
+ } else if (providerName === 'goerli') {
+ name = this.context.t('connectingToGoerli')
} else {
name = this.context.t('connectingTo', [providerId])
}
@@ -288,6 +305,10 @@ class Routes extends Component {
name = this.context.t('kovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby')
+ } else if (providerName === 'localhost') {
+ name = this.context.t('localhost')
+ } else if (providerName === 'goerli') {
+ name = this.context.t('goerli')
} else {
name = this.context.t('unknownNetwork')
}
@@ -314,6 +335,7 @@ Routes.propTypes = {
networkDropdownOpen: PropTypes.bool,
showNetworkDropdown: PropTypes.func,
hideNetworkDropdown: PropTypes.func,
+ setLastActiveTime: PropTypes.func,
history: PropTypes.object,
location: PropTypes.object,
dispatch: PropTypes.func,
@@ -336,6 +358,7 @@ Routes.propTypes = {
t: PropTypes.func,
providerId: PropTypes.string,
providerRequests: PropTypes.array,
+ autoLogoutTimeLimit: PropTypes.number,
}
function mapStateToProps (state) {
@@ -350,6 +373,7 @@ function mapStateToProps (state) {
} = appState
const accounts = getMetaMaskAccounts(state)
+ const { autoLogoutTimeLimit = 0 } = preferencesSelector(state)
const {
identities,
@@ -401,6 +425,7 @@ function mapStateToProps (state) {
Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
providerId: getNetworkIdentifier(state),
+ autoLogoutTimeLimit,
// state needed to get account dropdown temporarily rendering from app bar
identities,
@@ -410,7 +435,7 @@ function mapStateToProps (state) {
}
}
-function mapDispatchToProps (dispatch, ownProps) {
+function mapDispatchToProps (dispatch) {
return {
dispatch,
hideSidebar: () => dispatch(actions.hideSidebar()),
@@ -419,6 +444,7 @@ function mapDispatchToProps (dispatch, ownProps) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
+ setLastActiveTime: () => dispatch(actions.setLastActiveTime()),
}
}
diff --git a/ui/app/components/app/send/README.md b/ui/app/pages/send/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/README.md
+++ b/ui/app/pages/send/README.md
diff --git a/ui/app/components/app/send/account-list-item/account-list-item-README.md b/ui/app/pages/send/account-list-item/account-list-item-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/account-list-item/account-list-item-README.md
+++ b/ui/app/pages/send/account-list-item/account-list-item-README.md
diff --git a/ui/app/components/app/send/account-list-item/account-list-item.component.js b/ui/app/pages/send/account-list-item/account-list-item.component.js
index 18e77b4f9..e6cca39b9 100644
--- a/ui/app/components/app/send/account-list-item/account-list-item.component.js
+++ b/ui/app/pages/send/account-list-item/account-list-item.component.js
@@ -1,11 +1,11 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { checksumAddress } from '../../../../helpers/utils/util'
-import Identicon from '../../../ui/identicon'
-import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common'
-import Tooltip from '../../../ui/tooltip-v2'
+import { checksumAddress } from '../../../helpers/utils/util'
+import Identicon from '../../../components/ui/identicon'
+import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
+import Tooltip from '../../../components/ui/tooltip-v2'
export default class AccountListItem extends Component {
diff --git a/ui/app/components/app/send/account-list-item/account-list-item.container.js b/ui/app/pages/send/account-list-item/account-list-item.container.js
index bc9a60f49..21f800306 100644
--- a/ui/app/components/app/send/account-list-item/account-list-item.container.js
+++ b/ui/app/pages/send/account-list-item/account-list-item.container.js
@@ -8,7 +8,7 @@ import {
getIsMainnet,
isBalanceCached,
preferencesSelector,
-} from '../../../../selectors/selectors'
+} from '../../../selectors/selectors'
import AccountListItem from './account-list-item.component'
export default connect(mapStateToProps)(AccountListItem)
diff --git a/ui/app/components/app/send/account-list-item/index.js b/ui/app/pages/send/account-list-item/index.js
index 907485cf7..907485cf7 100644
--- a/ui/app/components/app/send/account-list-item/index.js
+++ b/ui/app/pages/send/account-list-item/index.js
diff --git a/ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js b/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js
index 5df9f77d6..bec88402d 100644
--- a/ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js
+++ b/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js
@@ -3,15 +3,15 @@ import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
-import Identicon from '../../../../ui/identicon'
-import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
+import Identicon from '../../../../components/ui/identicon'
+import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display'
const utilsMethodStubs = {
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
}
const AccountListItem = proxyquire('../account-list-item.component.js', {
- '../../../../helpers/utils/util': utilsMethodStubs,
+ '../../../helpers/utils/util': utilsMethodStubs,
}).default
diff --git a/ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js
index 19a9a02d0..1580fd497 100644
--- a/ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js
+++ b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../account-list-item.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
@@ -15,7 +15,7 @@ proxyquire('../account-list-item.container.js', {
getCurrentCurrency: () => `mockCurrentCurrency`,
getNativeCurrency: () => `mockNativeCurrency`,
},
- '../../../../selectors/selectors': {
+ '../../../selectors/selectors': {
isBalanceCached: () => `mockBalanceIsCached`,
preferencesSelector: ({ showFiatInTestnets }) => ({
showFiatInTestnets,
diff --git a/ui/app/components/app/send/index.js b/ui/app/pages/send/index.js
index b5114babc..b5114babc 100644
--- a/ui/app/components/app/send/index.js
+++ b/ui/app/pages/send/index.js
diff --git a/ui/app/components/app/send/send-content/index.js b/ui/app/pages/send/send-content/index.js
index 891c17e6a..891c17e6a 100644
--- a/ui/app/components/app/send/send-content/index.js
+++ b/ui/app/pages/send/send-content/index.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/README.md b/ui/app/pages/send/send-content/send-amount-row/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/README.md
+++ b/ui/app/pages/send/send-content/send-amount-row/README.md
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
new file mode 100644
index 000000000..7901ccef6
--- /dev/null
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
@@ -0,0 +1,75 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+
+export default class AmountMaxButton extends Component {
+
+ static propTypes = {
+ balance: PropTypes.string,
+ buttonDataLoading: PropTypes.bool,
+ clearMaxAmount: PropTypes.func,
+ inError: PropTypes.bool,
+ gasTotal: PropTypes.string,
+ maxModeOn: PropTypes.bool,
+ selectedToken: PropTypes.object,
+ setAmountToMax: PropTypes.func,
+ setMaxModeTo: PropTypes.func,
+ tokenBalance: PropTypes.string,
+
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ setMaxAmount () {
+ const {
+ balance,
+ gasTotal,
+ selectedToken,
+ setAmountToMax,
+ tokenBalance,
+ } = this.props
+
+ setAmountToMax({
+ balance,
+ gasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
+
+ onMaxClick = () => {
+ const { setMaxModeTo, clearMaxAmount, maxModeOn } = this.props
+ const { metricsEvent } = this.context
+
+ metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Edit Screen',
+ name: 'Clicked "Amount Max"',
+ },
+ })
+ if (!maxModeOn) {
+ setMaxModeTo(true)
+ this.setMaxAmount()
+ } else {
+ setMaxModeTo(false)
+ clearMaxAmount()
+ }
+ }
+
+ render () {
+ const { maxModeOn, buttonDataLoading, inError } = this.props
+
+ return (
+ <div className={'send-v2__amount-max'} onClick={buttonDataLoading || inError ? null : this.onMaxClick}>
+ <input type="checkbox" checked={maxModeOn} />
+ <div className={classnames('send-v2__amount-max__button', { 'send-v2__amount-max__button__disabled': buttonDataLoading || inError })}>
+ {this.context.t('max')}
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
index 16c5a0db5..e444589a1 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
@@ -5,16 +5,17 @@ import {
getSendFromBalance,
getTokenBalance,
} from '../../../send.selectors.js'
+import { getBasicGasEstimateLoadingStatus } from '../../../../../selectors/custom-gas'
import { getMaxModeOn } from './amount-max-button.selectors.js'
import { calcMaxAmount } from './amount-max-button.utils.js'
import {
updateSendAmount,
setMaxModeTo,
-} from '../../../../../../store/actions'
+} from '../../../../../store/actions'
import AmountMaxButton from './amount-max-button.component'
import {
updateSendErrors,
-} from '../../../../../../ducks/send/send.duck'
+} from '../../../../../ducks/send/send.duck'
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)
@@ -22,6 +23,7 @@ function mapStateToProps (state) {
return {
balance: getSendFromBalance(state),
+ buttonDataLoading: getBasicGasEstimateLoadingStatus(state),
gasTotal: getGasTotal(state),
maxModeOn: getMaxModeOn(state),
selectedToken: getSelectedToken(state),
@@ -35,6 +37,9 @@ function mapDispatchToProps (dispatch) {
dispatch(updateSendErrors({ amount: null }))
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
},
+ clearMaxAmount: () => {
+ dispatch(updateSendAmount('0'))
+ },
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
}
}
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
index 69fec1994..69fec1994 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
index f4c8fad8a..a570e49b4 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
@@ -1,7 +1,7 @@
const {
multiplyCurrencies,
subtractCurrencies,
-} = require('../../../../../../helpers/utils/conversion-util')
+} = require('../../../../../helpers/utils/conversion-util')
const ethUtil = require('ethereumjs-util')
function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js
index ee8271494..ee8271494 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
index b04d3897f..f986b26bb 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
@@ -26,7 +26,12 @@ describe('AmountMaxButton Component', function () {
setAmountToMax={propsMethodSpies.setAmountToMax}
setMaxModeTo={propsMethodSpies.setMaxModeTo}
tokenBalance={'mockTokenBalance'}
- />, { context: { t: str => str + '_t' } })
+ />, {
+ context: {
+ t: str => str + '_t',
+ metricsEvent: () => {},
+ },
+ })
instance = wrapper.instance()
})
@@ -60,7 +65,7 @@ describe('AmountMaxButton Component', function () {
assert(wrapper.exists('.send-v2__amount-max'))
})
- it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => {
+ it('should call setMaxModeTo and setMaxAmount when the checkbox is checked', () => {
const {
onClick,
} = wrapper.find('.send-v2__amount-max').props()
@@ -76,11 +81,6 @@ describe('AmountMaxButton Component', function () {
)
})
- it('should not render anything when maxModeOn is true', () => {
- wrapper.setProps({ maxModeOn: true })
- assert.ok(!wrapper.exists('.send-v2__amount-max'))
- })
-
it('should render the expected text when maxModeOn is false', () => {
wrapper.setProps({ maxModeOn: false })
assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t')
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
index f446e330c..dcee8fda0 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
@@ -29,8 +29,9 @@ proxyquire('../amount-max-button.container.js', {
},
'./amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` },
'./amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 },
- '../../../../../../store/actions': actionSpies,
- '../../../../../../ducks/send/send.duck': duckActionSpies,
+ '../../../../../selectors/custom-gas': { getBasicGasEstimateLoadingStatus: (s) => `mockButtonDataLoading:${s}`},
+ '../../../../../store/actions': actionSpies,
+ '../../../../../ducks/send/send.duck': duckActionSpies,
})
describe('amount-max-button container', () => {
@@ -40,6 +41,7 @@ describe('amount-max-button container', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
balance: 'mockBalance:mockState',
+ buttonDataLoading: 'mockButtonDataLoading:mockState',
gasTotal: 'mockGasTotal:mockState',
maxModeOn: 'mockMaxModeOn:mockState',
selectedToken: 'mockSelectedToken:mockState',
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
index 655fe1969..655fe1969 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
index 1ee858f67..1ee858f67 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/index.js b/ui/app/pages/send/send-content/send-amount-row/index.js
index abc6852fe..abc6852fe 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/index.js
+++ b/ui/app/pages/send/send-content/send-amount-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js
index e725e7eda..10e90c419 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js
@@ -2,8 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper'
import AmountMaxButton from './amount-max-button'
-import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input'
-import UserPreferencedTokenInput from '../../../user-preferenced-token-input'
+import UserPreferencedCurrencyInput from '../../../../components/app/user-preferenced-currency-input'
+import UserPreferencedTokenInput from '../../../../components/app/user-preferenced-token-input'
export default class SendAmountRow extends Component {
@@ -110,7 +110,7 @@ export default class SendAmountRow extends Component {
showError={inError}
errorType={'amount'}
>
- {!inError && gasTotal && <AmountMaxButton />}
+ {gasTotal && <AmountMaxButton inError={inError} />}
{ this.renderInput() }
</SendRowWrapper>
)
diff --git a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.container.js b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.container.js
index 0646355ab..2b3470da4 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.container.js
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.container.js
@@ -17,10 +17,10 @@ import { getAmountErrorObject, getGasFeeErrorObject } from '../../send.utils'
import {
setMaxModeTo,
updateSendAmount,
-} from '../../../../../store/actions'
+} from '../../../../store/actions'
import {
updateSendErrors,
-} from '../../../../../ducks/send/send.duck'
+} from '../../../../ducks/send/send.duck'
import SendAmountRow from './send-amount-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)
diff --git a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.scss
diff --git a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.selectors.js
index fb08c7ed7..fb08c7ed7 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
index 14a71129f..62e0676db 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
@@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import AmountMaxButton from '../amount-max-button/amount-max-button.container'
-import UserPreferencedTokenInput from '../../../../user-preferenced-token-input'
+import UserPreferencedTokenInput from '../../../../../components/app/user-preferenced-token-input'
const propsMethodSpies = {
setMaxModeTo: sinon.spy(),
diff --git a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-container.test.js
index 6d20202b0..dada1c5e9 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-container.test.js
@@ -37,8 +37,8 @@ proxyquire('../send-amount-row.container.js', {
getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }),
getGasFeeErrorObject: (mockDataObject) => ({ ...mockDataObject, mockGasFeeErrorChange: true }),
},
- '../../../../../store/actions': actionSpies,
- '../../../../../ducks/send/send.duck': duckActionSpies,
+ '../../../../store/actions': actionSpies,
+ '../../../../ducks/send/send.duck': duckActionSpies,
})
describe('send-amount-row container', () => {
diff --git a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
index 4672cb8a7..4672cb8a7 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
diff --git a/ui/app/pages/send/send-content/send-asset-row/index.js b/ui/app/pages/send/send-content/send-asset-row/index.js
new file mode 100644
index 000000000..ba424a083
--- /dev/null
+++ b/ui/app/pages/send/send-content/send-asset-row/index.js
@@ -0,0 +1 @@
+export { default } from './send-asset-row.container'
diff --git a/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js
new file mode 100644
index 000000000..de2d9462f
--- /dev/null
+++ b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js
@@ -0,0 +1,152 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SendRowWrapper from '../send-row-wrapper'
+import Identicon from '../../../../components/ui/identicon/identicon.component'
+import TokenBalance from '../../../../components/ui/token-balance'
+import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display'
+import {PRIMARY} from '../../../../helpers/constants/common'
+
+export default class SendAssetRow extends Component {
+ static propTypes = {
+ tokens: PropTypes.arrayOf(
+ PropTypes.shape({
+ address: PropTypes.string,
+ decimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ symbol: PropTypes.string,
+ })
+ ).isRequired,
+ accounts: PropTypes.object.isRequired,
+ selectedAddress: PropTypes.string.isRequired,
+ selectedTokenAddress: PropTypes.string,
+ setSelectedToken: PropTypes.func.isRequired,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ state = {
+ isShowingDropdown: false,
+ }
+
+ openDropdown = () => this.setState({ isShowingDropdown: true })
+
+ closeDropdown = () => this.setState({ isShowingDropdown: false })
+
+ selectToken = address => {
+ this.setState({
+ isShowingDropdown: false,
+ }, () => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Send Screen',
+ name: 'User clicks "Assets" dropdown',
+ },
+ customVariables: {
+ assetSelected: address ? 'ERC20' : 'ETH',
+ },
+ })
+ this.props.setSelectedToken(address)
+ })
+ }
+
+ render () {
+ const { t } = this.context
+
+ return (
+ <SendRowWrapper label={`${t('asset')}:`}>
+ <div className="send-v2__asset-dropdown">
+ { this.renderSelectedToken() }
+ { this.renderAssetDropdown() }
+ </div>
+ </SendRowWrapper>
+ )
+ }
+
+ renderSelectedToken () {
+ const { selectedTokenAddress } = this.props
+ const token = this.props.tokens.find(({ address }) => address === selectedTokenAddress)
+ return (
+ <div
+ className="send-v2__asset-dropdown__input-wrapper"
+ onClick={this.openDropdown}
+ >
+ { token ? this.renderAsset(token) : this.renderEth() }
+ </div>
+ )
+ }
+
+ renderAssetDropdown () {
+ return this.state.isShowingDropdown && (
+ <div>
+ <div
+ className="send-v2__asset-dropdown__close-area"
+ onClick={this.closeDropdown}
+ />
+ <div className="send-v2__asset-dropdown__list">
+ { this.renderEth() }
+ { this.props.tokens.map(token => this.renderAsset(token)) }
+ </div>
+ </div>
+ )
+ }
+
+ renderEth () {
+ const { t } = this.context
+ const { accounts, selectedAddress } = this.props
+
+ const balanceValue = accounts[selectedAddress] ? accounts[selectedAddress].balance : ''
+
+ return (
+ <div
+ className="send-v2__asset-dropdown__asset"
+ onClick={() => this.selectToken()}
+ >
+ <div className="send-v2__asset-dropdown__asset-icon">
+ <Identicon diameter={36} />
+ </div>
+ <div className="send-v2__asset-dropdown__asset-data">
+ <div className="send-v2__asset-dropdown__symbol">ETH</div>
+ <div className="send-v2__asset-dropdown__name">
+ <span className="send-v2__asset-dropdown__name__label">{`${t('balance')}:`}</span>
+ <UserPreferencedCurrencyDisplay
+ value={balanceValue}
+ type={PRIMARY}
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+
+ renderAsset (token) {
+ const { address, symbol } = token
+ const { t } = this.context
+
+ return (
+ <div
+ key={address} className="send-v2__asset-dropdown__asset"
+ onClick={() => this.selectToken(address)}
+ >
+ <div className="send-v2__asset-dropdown__asset-icon">
+ <Identicon address={address} diameter={36} />
+ </div>
+ <div className="send-v2__asset-dropdown__asset-data">
+ <div className="send-v2__asset-dropdown__symbol">
+ { symbol }
+ </div>
+ <div className="send-v2__asset-dropdown__name">
+ <span className="send-v2__asset-dropdown__name__label">{`${t('balance')}:`}</span>
+ <TokenBalance
+ token={token}
+ withSymbol
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/pages/send/send-content/send-asset-row/send-asset-row.container.js b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.container.js
new file mode 100644
index 000000000..57b62fba1
--- /dev/null
+++ b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.container.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux'
+import SendAssetRow from './send-asset-row.component'
+import {getMetaMaskAccounts} from '../../../../selectors/selectors'
+import { setSelectedToken } from '../../../../store/actions'
+
+function mapStateToProps (state) {
+ return {
+ tokens: state.metamask.tokens,
+ selectedAddress: state.metamask.selectedAddress,
+ selectedTokenAddress: state.metamask.selectedTokenAddress,
+ accounts: getMetaMaskAccounts(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ setSelectedToken: address => dispatch(setSelectedToken(address)),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendAssetRow)
diff --git a/ui/app/components/app/send/send-content/send-content.component.js b/ui/app/pages/send/send-content/send-content.component.js
index 2c09ceb19..d799806c7 100644
--- a/ui/app/components/app/send/send-content/send-content.component.js
+++ b/ui/app/pages/send/send-content/send-content.component.js
@@ -1,11 +1,12 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerContent from '../../../ui/page-container/page-container-content.component'
+import PageContainerContent from '../../../components/ui/page-container/page-container-content.component'
import SendAmountRow from './send-amount-row'
import SendFromRow from './send-from-row'
import SendGasRow from './send-gas-row'
import SendHexDataRow from './send-hex-data-row'
import SendToRow from './send-to-row'
+import SendAssetRow from './send-asset-row'
export default class SendContent extends Component {
@@ -26,6 +27,7 @@ export default class SendContent extends Component {
updateGas={this.updateGas}
scanQrCode={ _ => this.props.scanQrCode()}
/>
+ <SendAssetRow />
<SendAmountRow updateGas={this.updateGas} />
<SendGasRow />
{(this.props.showHexData && (
diff --git a/ui/app/components/app/send/send-content/send-dropdown-list/index.js b/ui/app/pages/send/send-content/send-dropdown-list/index.js
index 04af6536c..04af6536c 100644
--- a/ui/app/components/app/send/send-content/send-dropdown-list/index.js
+++ b/ui/app/pages/send/send-content/send-dropdown-list/index.js
diff --git a/ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js b/ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js
index 0d026bc69..0d026bc69 100644
--- a/ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js
+++ b/ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js
diff --git a/ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js b/ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
index b92dd4dfe..b92dd4dfe 100644
--- a/ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
+++ b/ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/index.js b/ui/app/pages/send/send-content/send-from-row/index.js
index 0a79726b2..0a79726b2 100644
--- a/ui/app/components/app/send/send-content/send-from-row/index.js
+++ b/ui/app/pages/send/send-content/send-from-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/send-from-row.component.js b/ui/app/pages/send/send-content/send-from-row/send-from-row.component.js
index dfa53e970..dfa53e970 100644
--- a/ui/app/components/app/send/send-content/send-from-row/send-from-row.component.js
+++ b/ui/app/pages/send/send-content/send-from-row/send-from-row.component.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js b/ui/app/pages/send/send-content/send-from-row/send-from-row.container.js
index fe3ac9aa1..fe3ac9aa1 100644
--- a/ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js
+++ b/ui/app/pages/send/send-content/send-from-row/send-from-row.container.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/send-from-row.selectors.js b/ui/app/pages/send/send-content/send-from-row/send-from-row.selectors.js
index 03ef4806b..03ef4806b 100644
--- a/ui/app/components/app/send/send-content/send-from-row/send-from-row.selectors.js
+++ b/ui/app/pages/send/send-content/send-from-row/send-from-row.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-component.test.js
index 18811c57e..18811c57e 100644
--- a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js
+++ b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-container.test.js
index fd771ea77..fd771ea77 100644
--- a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-container.test.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
index ecb57bbc3..ecb57bbc3 100644
--- a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
diff --git a/ui/app/components/app/send/send-content/send-gas-row/README.md b/ui/app/pages/send/send-content/send-gas-row/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/README.md
+++ b/ui/app/pages/send/send-content/send-gas-row/README.md
diff --git a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
index 48088607a..3f5587318 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
+++ b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
-import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../../../../../../helpers/constants/common'
+import UserPreferencedCurrencyDisplay from '../../../../../components/app/user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common'
export default class GasFeeDisplay extends Component {
diff --git a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/index.js
index dba0edb7b..dba0edb7b 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js
+++ b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/index.js
diff --git a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
index cb4180508..eedd43221 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import {shallow} from 'enzyme'
import GasFeeDisplay from '../gas-fee-display.component'
-import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display'
+import UserPreferencedCurrencyDisplay from '../../../../../../components/app/user-preferenced-currency-display'
import sinon from 'sinon'
diff --git a/ui/app/components/app/send/send-content/send-gas-row/index.js b/ui/app/pages/send/send-content/send-gas-row/index.js
index 3c7ff1d5f..3c7ff1d5f 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/index.js
+++ b/ui/app/pages/send/send-content/send-gas-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js
index 424a65b20..4c09ed564 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.component.js
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js
@@ -2,26 +2,31 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper'
import GasFeeDisplay from './gas-fee-display/gas-fee-display.component'
-import GasPriceButtonGroup from '../../../gas-customization/gas-price-button-group'
-import AdvancedGasInputs from '../../../gas-customization/advanced-gas-inputs'
+import GasPriceButtonGroup from '../../../../components/app/gas-customization/gas-price-button-group'
+import AdvancedGasInputs from '../../../../components/app/gas-customization/advanced-gas-inputs'
export default class SendGasRow extends Component {
static propTypes = {
+ balance: PropTypes.string,
conversionRate: PropTypes.number,
convertedCurrency: PropTypes.string,
gasFeeError: PropTypes.bool,
gasLoadingError: PropTypes.bool,
gasTotal: PropTypes.string,
+ maxModeOn: PropTypes.bool,
showCustomizeGasModal: PropTypes.func,
+ selectedToken: PropTypes.object,
+ setAmountToMax: PropTypes.func,
setGasPrice: PropTypes.func,
setGasLimit: PropTypes.func,
+ tokenBalance: PropTypes.string,
gasPriceButtonGroupProps: PropTypes.object,
gasButtonGroupShown: PropTypes.bool,
advancedInlineGasShown: PropTypes.bool,
resetGasButtons: PropTypes.func,
- gasPrice: PropTypes.number,
- gasLimit: PropTypes.number,
+ gasPrice: PropTypes.string,
+ gasLimit: PropTypes.string,
insufficientBalance: PropTypes.bool,
}
@@ -47,6 +52,23 @@ export default class SendGasRow extends Component {
</div>
}
+ setMaxAmount () {
+ const {
+ balance,
+ gasTotal,
+ selectedToken,
+ setAmountToMax,
+ tokenBalance,
+ } = this.props
+
+ setAmountToMax({
+ balance,
+ gasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
+
renderContent () {
const {
conversionRate,
@@ -57,6 +79,7 @@ export default class SendGasRow extends Component {
gasPriceButtonGroupProps,
gasButtonGroupShown,
advancedInlineGasShown,
+ maxModeOn,
resetGasButtons,
setGasPrice,
setGasLimit,
@@ -71,7 +94,7 @@ export default class SendGasRow extends Component {
className="gas-price-button-group--small"
showCheck={false}
{...gasPriceButtonGroupProps}
- handleGasPriceSelection={(...args) => {
+ handleGasPriceSelection={async (...args) => {
metricsEvent({
eventOpts: {
category: 'Transactions',
@@ -79,7 +102,10 @@ export default class SendGasRow extends Component {
name: 'Changed Gas Button',
},
})
- gasPriceButtonGroupProps.handleGasPriceSelection(...args)
+ await gasPriceButtonGroupProps.handleGasPriceSelection(...args)
+ if (maxModeOn) {
+ this.setMaxAmount()
+ }
}}
/>
{ this.renderAdvancedOptionsButton() }
@@ -89,7 +115,12 @@ export default class SendGasRow extends Component {
convertedCurrency={convertedCurrency}
gasLoadingError={gasLoadingError}
gasTotal={gasTotal}
- onReset={resetGasButtons}
+ onReset={() => {
+ resetGasButtons()
+ if (maxModeOn) {
+ this.setMaxAmount()
+ }
+ }}
onClick={() => showCustomizeGasModal()}
/>
const advancedGasInputs = <div>
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js
index f81670c02..10eaa50b8 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js
@@ -6,29 +6,37 @@ import {
getGasPrice,
getGasLimit,
getSendAmount,
+ getSendFromBalance,
+ getTokenBalance,
} from '../../send.selectors.js'
import {
+ getMaxModeOn,
+} from '../send-amount-row/amount-max-button/amount-max-button.selectors'
+import {
isBalanceSufficient,
calcGasTotal,
} from '../../send.utils.js'
+import { calcMaxAmount } from '../send-amount-row/amount-max-button/amount-max-button.utils'
import {
getBasicGasEstimateLoadingStatus,
getRenderableEstimateDataForSmallButtonsFromGWEI,
getDefaultActiveButtonIndex,
-} from '../../../../../selectors/custom-gas'
+} from '../../../../selectors/custom-gas'
import {
showGasButtonGroup,
-} from '../../../../../ducks/send/send.duck'
+ updateSendErrors,
+} from '../../../../ducks/send/send.duck'
import {
resetCustomData,
setCustomGasPrice,
setCustomGasLimit,
-} from '../../../../../ducks/gas/gas.duck'
+} from '../../../../ducks/gas/gas.duck'
import { getGasLoadingError, gasFeeIsInError, getGasButtonGroupShown } from './send-gas-row.selectors.js'
-import { showModal, setGasPrice, setGasLimit, setGasTotal } from '../../../../../store/actions'
-import { getAdvancedInlineGasShown, getCurrentEthBalance, getSelectedToken } from '../../../../../selectors/selectors'
+import { showModal, setGasPrice, setGasLimit, setGasTotal, updateSendAmount } from '../../../../store/actions'
+import { getAdvancedInlineGasShown, getCurrentEthBalance, getSelectedToken } from '../../../../selectors/selectors'
import SendGasRow from './send-gas-row.component'
+
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow)
function mapStateToProps (state) {
@@ -49,6 +57,7 @@ function mapStateToProps (state) {
})
return {
+ balance: getSendFromBalance(state),
conversionRate,
convertedCurrency: getCurrentCurrency(state),
gasTotal,
@@ -65,6 +74,9 @@ function mapStateToProps (state) {
gasPrice,
gasLimit,
insufficientBalance,
+ maxModeOn: getMaxModeOn(state),
+ selectedToken: getSelectedToken(state),
+ tokenBalance: getTokenBalance(state),
}
}
@@ -85,6 +97,10 @@ function mapDispatchToProps (dispatch) {
dispatch(setGasTotal(calcGasTotal(newLimit, gasPrice)))
}
},
+ setAmountToMax: maxAmountDataObject => {
+ dispatch(updateSendErrors({ amount: null }))
+ dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
+ },
showGasButtonGroup: () => dispatch(showGasButtonGroup()),
resetCustomData: () => dispatch(resetCustomData()),
}
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.selectors.js
index 79c838543..79c838543 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
index 08f26854e..0cbc92621 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
@@ -6,7 +6,7 @@ import SendGasRow from '../send-gas-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import GasFeeDisplay from '../gas-fee-display/gas-fee-display.component'
-import GasPriceButtonGroup from '../../../../gas-customization/gas-price-button-group'
+import GasPriceButtonGroup from '../../../../../components/app/gas-customization/gas-price-button-group'
const propsMethodSpies = {
showCustomizeGasModal: sinon.spy(),
diff --git a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
index d1f753639..4acb310f8 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
@@ -32,7 +32,7 @@ proxyquire('../send-gas-row.container.js', {
return () => ({})
},
},
- '../../../../../selectors/selectors': {
+ '../../../../selectors/selectors': {
getCurrentEthBalance: (s) => `mockCurrentEthBalance:${s}`,
getAdvancedInlineGasShown: (s) => `mockAdvancedInlineGasShown:${s}`,
getSelectedToken: () => false,
@@ -44,6 +44,11 @@ proxyquire('../send-gas-row.container.js', {
getGasPrice: (s) => `mockGasPrice:${s}`,
getGasLimit: (s) => `mockGasLimit:${s}`,
getSendAmount: (s) => `mockSendAmount:${s}`,
+ getSendFromBalance: (s) => `mockBalance:${s}`,
+ getTokenBalance: (s) => `mockTokenBalance:${s}`,
+ },
+ '../send-amount-row/amount-max-button/amount-max-button.selectors': {
+ getMaxModeOn: (s) => `mockMaxModeOn:${s}`,
},
'../../send.utils.js': {
isBalanceSufficient: ({
@@ -59,14 +64,14 @@ proxyquire('../send-gas-row.container.js', {
gasFeeIsInError: (s) => `mockGasFeeError:${s}`,
getGasButtonGroupShown: (s) => `mockGetGasButtonGroupShown:${s}`,
},
- '../../../../../store/actions': actionSpies,
- '../../../../../selectors/custom-gas': {
+ '../../../../store/actions': actionSpies,
+ '../../../../selectors/custom-gas': {
getBasicGasEstimateLoadingStatus: (s) => `mockBasicGasEstimateLoadingStatus:${s}`,
getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => `mockGasButtonInfo:${s}`,
getDefaultActiveButtonIndex: (gasButtonInfo, gasPrice) => gasButtonInfo.length + gasPrice.length,
},
- '../../../../../ducks/send/send.duck': sendDuckSpies,
- '../../../../../ducks/gas/gas.duck': gasDuckSpies,
+ '../../../../ducks/send/send.duck': sendDuckSpies,
+ '../../../../ducks/gas/gas.duck': gasDuckSpies,
})
describe('send-gas-row container', () => {
@@ -75,6 +80,7 @@ describe('send-gas-row container', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
+ balance: 'mockBalance:mockState',
conversionRate: 'mockConversionRate:mockState',
convertedCurrency: 'mockConvertedCurrency:mockState',
gasTotal: 'mockGasTotal:mockState',
@@ -91,6 +97,9 @@ describe('send-gas-row container', () => {
gasLimit: 'mockGasLimit:mockState',
gasPrice: 'mockGasPrice:mockState',
insufficientBalance: false,
+ maxModeOn: 'mockMaxModeOn:mockState',
+ selectedToken: false,
+ tokenBalance: 'mockTokenBalance:mockState',
})
})
diff --git a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
index bd3c9a257..bd3c9a257 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
diff --git a/ui/app/components/app/send/send-content/send-hex-data-row/index.js b/ui/app/pages/send/send-content/send-hex-data-row/index.js
index 08c341067..08c341067 100644
--- a/ui/app/components/app/send/send-content/send-hex-data-row/index.js
+++ b/ui/app/pages/send/send-content/send-hex-data-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js
index 62a74a77b..62a74a77b 100644
--- a/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js
+++ b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js
diff --git a/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.container.js b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.container.js
index 76c929d08..8b1c540c3 100644
--- a/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.container.js
+++ b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.container.js
@@ -1,7 +1,7 @@
import { connect } from 'react-redux'
import {
updateSendHexData,
-} from '../../../../../store/actions'
+} from '../../../../store/actions'
import SendHexDataRow from './send-hex-data-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendHexDataRow)
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/index.js b/ui/app/pages/send/send-content/send-row-wrapper/index.js
index d17545dcc..d17545dcc 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/index.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/index.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/index.js
index c00617f83..c00617f83 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/index.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
index 61bc7bab7..0be01996a 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
+import classnames from 'classnames'
export default class SendRowErrorMessage extends Component {
@@ -19,7 +20,7 @@ export default class SendRowErrorMessage extends Component {
return (
errorMessage
- ? <div className="send-v2__error">{this.context.t(errorMessage)}</div>
+ ? <div className={classnames('send-v2__error', {'send-v2__error-amount': errorType === 'amount'})}>{this.context.t(errorMessage)}</div>
: null
)
}
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
index 59622047f..59622047f 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js
index 2304a43d2..2304a43d2 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
index eecff165d..2013e3200 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../send-row-error-message.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/index.js
index fd4d19ef7..fd4d19ef7 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/index.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js
index f1caa8f99..f1caa8f99 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js
index 7df14fd96..7df14fd96 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js
index bd803d833..bd803d833 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
index 225bf056c..6c0739f0e 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../send-row-warning-message.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper-README.md
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js
index 94309bd96..075b86633 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js
@@ -18,7 +18,7 @@ export default class SendRowWrapper extends Component {
t: PropTypes.func,
};
- render () {
+ renderAmountFormRow () {
const {
children,
errorType = '',
@@ -34,7 +34,39 @@ export default class SendRowWrapper extends Component {
<div className="send-v2__form-row">
<div className="send-v2__form-label">
{label}
- {showError && <SendRowErrorMessage errorType={errorType}/>}
+ {customLabelContent}
+ </div>
+ <div className="send-v2__form-field-container">
+ <div className="send-v2__form-field">
+ {formField}
+ </div>
+ <div>
+ {showError && <SendRowErrorMessage errorType={errorType} />}
+ {!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ renderFormRow () {
+ const {
+ children,
+ errorType = '',
+ label,
+ showError = false,
+ showWarning = false,
+ warningType = '',
+ } = this.props
+
+ const formField = Array.isArray(children) ? children[1] || children[0] : children
+ const customLabelContent = (Array.isArray(children) && children.length) > 1 ? children[0] : null
+
+ return (
+ <div className="send-v2__form-row">
+ <div className="send-v2__form-label">
+ {label}
+ {showError && <SendRowErrorMessage errorType={errorType} />}
{!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
{customLabelContent}
</div>
@@ -45,4 +77,14 @@ export default class SendRowWrapper extends Component {
)
}
+ render () {
+ const {
+ errorType = '',
+ } = this.props
+
+ return (
+ errorType === 'amount' ? this.renderAmountFormRow() : this.renderFormRow()
+ )
+ }
+
}
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.scss
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js b/ui/app/pages/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
index 30280e1d0..30280e1d0 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-to-row/index.js b/ui/app/pages/send/send-content/send-to-row/index.js
index 121f15148..121f15148 100644
--- a/ui/app/components/app/send/send-content/send-to-row/index.js
+++ b/ui/app/pages/send/send-content/send-to-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md b/ui/app/pages/send/send-content/send-to-row/send-to-row-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row-README.md
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.component.js
index e8a55cb2a..9baf327c1 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper'
-import EnsInput from '../../../ens-input'
+import EnsInput from '../../../../components/app/ens-input'
import { getToErrorObject, getToWarningObject } from './send-to-row.utils.js'
export default class SendToRow extends Component {
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.container.js
index 30865d295..2cbe9fcd0 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.container.js
@@ -14,13 +14,13 @@ import {
} from './send-to-row.selectors.js'
import {
updateSendTo,
-} from '../../../../../store/actions'
+} from '../../../../store/actions'
import {
updateSendErrors,
updateSendWarnings,
openToDropdown,
closeToDropdown,
-} from '../../../../../ducks/send/send.duck'
+} from '../../../../ducks/send/send.duck'
import SendToRow from './send-to-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.selectors.js
index a6160d335..a6160d335 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js
index 60e75d34c..b3b0d2da3 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js
@@ -4,22 +4,21 @@ const {
KNOWN_RECIPIENT_ADDRESS_ERROR,
INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
} = require('../../send.constants')
-const { isValidAddress, isEthNetwork } = require('../../../../../helpers/utils/util')
-import { checkExistingAddresses } from '../../../../../pages/add-token/util'
+const { isValidAddress, isEthNetwork } = require('../../../../helpers/utils/util')
+import { checkExistingAddresses } from '../../../add-token/util'
const ethUtil = require('ethereumjs-util')
const contractMap = require('eth-contract-metadata')
-function getToErrorObject (to, toError = null, hasHexData = false, tokens = [], selectedToken = null, network) {
+function getToErrorObject (to, toError = null, hasHexData = false, _, __, network) {
if (!to) {
if (!hasHexData) {
toError = REQUIRED_ERROR
}
} else if (!isValidAddress(to, network) && !toError) {
toError = isEthNetwork(network) ? INVALID_RECIPIENT_ADDRESS_ERROR : INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR
- } else if (selectedToken && (ethUtil.toChecksumAddress(to) in contractMap || checkExistingAddresses(to, tokens))) {
- toError = KNOWN_RECIPIENT_ADDRESS_ERROR
}
+
return { to: toError }
}
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-component.test.js
index d4d054057..c180d97f1 100644
--- a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-component.test.js
@@ -16,7 +16,7 @@ const SendToRow = proxyquire('../send-to-row.component.js', {
}).default
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
-import EnsInput from '../../../../ens-input'
+import EnsInput from '../../../../../components/app/ens-input'
const propsMethodSpies = {
closeToDropdown: sinon.spy(),
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-container.test.js
index 94b4f1024..bb8702e9a 100644
--- a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-container.test.js
@@ -36,8 +36,8 @@ proxyquire('../send-to-row.container.js', {
sendToIsInWarning: (s) => `mockInWarning:${s}`,
getTokens: (s) => `mockTokens:${s}`,
},
- '../../../../../store/actions': actionSpies,
- '../../../../../ducks/send/send.duck': duckActionSpies,
+ '../../../../store/actions': actionSpies,
+ '../../../../ducks/send/send.duck': duckActionSpies,
})
describe('send-to-row container', () => {
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
index 0fa342d1e..0fa342d1e 100644
--- a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js
index 95882d640..f8a6dd96f 100644
--- a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js
@@ -13,7 +13,7 @@ const stubs = {
}
const toRowUtils = proxyquire('../send-to-row.utils.js', {
- '../../../../../helpers/utils/util': {
+ '../../../../helpers/utils/util': {
isValidAddress: stubs.isValidAddress,
},
})
@@ -55,9 +55,9 @@ describe('send-to-row utils', () => {
})
})
- it('should return a known address recipient if to is truthy but part of state tokens', () => {
+ it('should return null if to is truthy but part of state tokens', () => {
assert.deepEqual(getToErrorObject('0xabc123', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
@@ -67,14 +67,14 @@ describe('send-to-row utils', () => {
})
})
- it('should return a known address recipient if to is truthy but part of contract metadata', () => {
+ it('should return null if to is truthy but part of contract metadata', () => {
assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
it('should null if to is truthy part of contract metadata but selectedToken falsy', () => {
assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
})
diff --git a/ui/app/components/app/send/send-content/tests/send-content-component.test.js b/ui/app/pages/send/send-content/tests/send-content-component.test.js
index 7d102c930..521c6523e 100644
--- a/ui/app/components/app/send/send-content/tests/send-content-component.test.js
+++ b/ui/app/pages/send/send-content/tests/send-content-component.test.js
@@ -3,12 +3,13 @@ import assert from 'assert'
import { shallow } from 'enzyme'
import SendContent from '../send-content.component.js'
-import PageContainerContent from '../../../../ui/page-container/page-container-content.component'
+import PageContainerContent from '../../../../components/ui/page-container/page-container-content.component'
import SendAmountRow from '../send-amount-row/send-amount-row.container'
import SendFromRow from '../send-from-row/send-from-row.container'
import SendGasRow from '../send-gas-row/send-gas-row.container'
import SendToRow from '../send-to-row/send-to-row.container'
import SendHexDataRow from '../send-hex-data-row/send-hex-data-row.container'
+import SendAssetRow from '../send-asset-row/send-asset-row.container'
describe('SendContent Component', function () {
let wrapper
@@ -32,9 +33,10 @@ describe('SendContent Component', function () {
const PageContainerContentChild = wrapper.find(PageContainerContent).children()
assert(PageContainerContentChild.childAt(0).is(SendFromRow))
assert(PageContainerContentChild.childAt(1).is(SendToRow))
- assert(PageContainerContentChild.childAt(2).is(SendAmountRow))
- assert(PageContainerContentChild.childAt(3).is(SendGasRow))
- assert(PageContainerContentChild.childAt(4).is(SendHexDataRow))
+ assert(PageContainerContentChild.childAt(2).is(SendAssetRow))
+ assert(PageContainerContentChild.childAt(3).is(SendAmountRow))
+ assert(PageContainerContentChild.childAt(4).is(SendGasRow))
+ assert(PageContainerContentChild.childAt(5).is(SendHexDataRow))
})
it('should not render the SendHexDataRow if props.showHexData is false', () => {
@@ -42,9 +44,10 @@ describe('SendContent Component', function () {
const PageContainerContentChild = wrapper.find(PageContainerContent).children()
assert(PageContainerContentChild.childAt(0).is(SendFromRow))
assert(PageContainerContentChild.childAt(1).is(SendToRow))
- assert(PageContainerContentChild.childAt(2).is(SendAmountRow))
- assert(PageContainerContentChild.childAt(3).is(SendGasRow))
- assert.equal(PageContainerContentChild.childAt(4).exists(), false)
+ assert(PageContainerContentChild.childAt(2).is(SendAssetRow))
+ assert(PageContainerContentChild.childAt(3).is(SendAmountRow))
+ assert(PageContainerContentChild.childAt(4).is(SendGasRow))
+ assert.equal(PageContainerContentChild.childAt(5).exists(), false)
})
})
})
diff --git a/ui/app/components/app/send/send-footer/README.md b/ui/app/pages/send/send-footer/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-footer/README.md
+++ b/ui/app/pages/send/send-footer/README.md
diff --git a/ui/app/components/app/send/send-footer/index.js b/ui/app/pages/send/send-footer/index.js
index 58e91d622..58e91d622 100644
--- a/ui/app/components/app/send/send-footer/index.js
+++ b/ui/app/pages/send/send-footer/index.js
diff --git a/ui/app/components/app/send/send-footer/send-footer.component.js b/ui/app/pages/send/send-footer/send-footer.component.js
index cc891a9b3..16a8fdde2 100644
--- a/ui/app/components/app/send/send-footer/send-footer.component.js
+++ b/ui/app/pages/send/send-footer/send-footer.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerFooter from '../../../ui/page-container/page-container-footer'
-import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
+import PageContainerFooter from '../../../components/ui/page-container/page-container-footer'
+import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes'
export default class SendFooter extends Component {
@@ -27,6 +27,7 @@ export default class SendFooter extends Component {
unapprovedTxs: PropTypes.object,
update: PropTypes.func,
sendErrors: PropTypes.object,
+ gasChangedLabel: PropTypes.string,
}
static contextTypes = {
@@ -57,6 +58,7 @@ export default class SendFooter extends Component {
update,
toAccounts,
history,
+ gasChangedLabel,
} = this.props
const { metricsEvent } = this.context
@@ -91,6 +93,9 @@ export default class SendFooter extends Component {
action: 'Edit Screen',
name: 'Complete',
},
+ customVariables: {
+ gasChanged: gasChangedLabel,
+ },
})
history.push(CONFIRM_TRANSACTION_ROUTE)
})
diff --git a/ui/app/components/app/send/send-footer/send-footer.container.js b/ui/app/pages/send/send-footer/send-footer.container.js
index ea3fd7ee4..68f4dc7c3 100644
--- a/ui/app/components/app/send/send-footer/send-footer.container.js
+++ b/ui/app/pages/send/send-footer/send-footer.container.js
@@ -6,7 +6,7 @@ import {
signTokenTx,
signTx,
updateTransaction,
-} from '../../../../store/actions'
+} from '../../../store/actions'
import SendFooter from './send-footer.component'
import {
getGasLimit,
@@ -31,10 +31,21 @@ import {
constructTxParams,
constructUpdatedTx,
} from './send-footer.utils'
+import {
+ getRenderableEstimateDataForSmallButtonsFromGWEI,
+ getDefaultActiveButtonIndex,
+} from '../../../selectors/custom-gas'
export default connect(mapStateToProps, mapDispatchToProps)(SendFooter)
function mapStateToProps (state) {
+ const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state)
+ const gasPrice = getGasPrice(state)
+ const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, gasPrice)
+ const gasChangedLabel = activeButtonIndex >= 0
+ ? gasButtonInfo[activeButtonIndex].labelKey
+ : 'custom'
+
return {
amount: getSendAmount(state),
data: getSendHexData(state),
@@ -50,6 +61,7 @@ function mapStateToProps (state) {
tokenBalance: getTokenBalance(state),
unapprovedTxs: getUnapprovedTxs(state),
sendErrors: getSendErrors(state),
+ gasChangedLabel,
}
}
diff --git a/ui/app/components/app/send/send-footer/send-footer.scss b/ui/app/pages/send/send-footer/send-footer.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-footer/send-footer.scss
+++ b/ui/app/pages/send/send-footer/send-footer.scss
diff --git a/ui/app/components/app/send/send-footer/send-footer.selectors.js b/ui/app/pages/send/send-footer/send-footer.selectors.js
index e20addfdc..e20addfdc 100644
--- a/ui/app/components/app/send/send-footer/send-footer.selectors.js
+++ b/ui/app/pages/send/send-footer/send-footer.selectors.js
diff --git a/ui/app/components/app/send/send-footer/send-footer.utils.js b/ui/app/pages/send/send-footer/send-footer.utils.js
index abb2ebc77..91ac29014 100644
--- a/ui/app/components/app/send/send-footer/send-footer.utils.js
+++ b/ui/app/pages/send/send-footer/send-footer.utils.js
@@ -38,6 +38,7 @@ function constructUpdatedTx ({
}) {
const unapprovedTx = unapprovedTxs[editingTransactionId]
const txParamsData = unapprovedTx.txParams.data ? unapprovedTx.txParams.data : data
+
const editingTx = {
...unapprovedTx,
txParams: Object.assign(
diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-component.test.js b/ui/app/pages/send/send-footer/tests/send-footer-component.test.js
index 6683ca8c0..56fc95df2 100644
--- a/ui/app/components/app/send/send-footer/tests/send-footer-component.test.js
+++ b/ui/app/pages/send/send-footer/tests/send-footer-component.test.js
@@ -2,10 +2,10 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
-import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../../helpers/constants/routes'
+import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
import SendFooter from '../send-footer.component.js'
-import PageContainerFooter from '../../../../ui/page-container/page-container-footer'
+import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'
const propsMethodSpies = {
addToAddressBookIfNew: sinon.spy(),
diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js b/ui/app/pages/send/send-footer/tests/send-footer-container.test.js
index 878b0aa19..118ebf356 100644
--- a/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js
+++ b/ui/app/pages/send/send-footer/tests/send-footer-container.test.js
@@ -28,7 +28,7 @@ proxyquire('../send-footer.container.js', {
return () => ({})
},
},
- '../../../../store/actions': actionSpies,
+ '../../../store/actions': actionSpies,
'../send.selectors': {
getGasLimit: (s) => `mockGasLimit:${s}`,
getGasPrice: (s) => `mockGasPrice:${s}`,
@@ -46,6 +46,10 @@ proxyquire('../send-footer.container.js', {
},
'./send-footer.selectors': { isSendFormInError: (s) => `mockInError:${s}` },
'./send-footer.utils': utilsStubs,
+ '../../../selectors/custom-gas': {
+ getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => ([{ labelKey: `mockLabel:${s}` }]),
+ getDefaultActiveButtonIndex: () => 0,
+ },
})
describe('send-footer container', () => {
@@ -68,6 +72,7 @@ describe('send-footer container', () => {
tokenBalance: 'mockTokenBalance:mockState',
unapprovedTxs: 'mockUnapprovedTxs:mockState',
sendErrors: 'mockSendErrors:mockState',
+ gasChangedLabel: 'mockLabel:mockState',
})
})
diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js b/ui/app/pages/send/send-footer/tests/send-footer-selectors.test.js
index 8de032f57..8de032f57 100644
--- a/ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js
+++ b/ui/app/pages/send/send-footer/tests/send-footer-selectors.test.js
diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js b/ui/app/pages/send/send-footer/tests/send-footer-utils.test.js
index 28ff0c891..f4705e691 100644
--- a/ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js
+++ b/ui/app/pages/send/send-footer/tests/send-footer-utils.test.js
@@ -149,7 +149,6 @@ describe('send-footer utils', () => {
},
},
})
-
assert.deepEqual(result, {
unapprovedTxParam: 'someOtherParam',
txParams: {
diff --git a/ui/app/components/app/send/send-header/README.md b/ui/app/pages/send/send-header/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-header/README.md
+++ b/ui/app/pages/send/send-header/README.md
diff --git a/ui/app/components/app/send/send-header/index.js b/ui/app/pages/send/send-header/index.js
index 0b17f0b7d..0b17f0b7d 100644
--- a/ui/app/components/app/send/send-header/index.js
+++ b/ui/app/pages/send/send-header/index.js
diff --git a/ui/app/components/app/send/send-header/send-header.component.js b/ui/app/pages/send/send-header/send-header.component.js
index f216954ef..76e35494a 100644
--- a/ui/app/components/app/send/send-header/send-header.component.js
+++ b/ui/app/pages/send/send-header/send-header.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerHeader from '../../../ui/page-container/page-container-header'
-import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
+import PageContainerHeader from '../../../components/ui/page-container/page-container-header'
+import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'
export default class SendHeader extends Component {
@@ -25,7 +25,6 @@ export default class SendHeader extends Component {
return (
<PageContainerHeader
onClose={() => this.onClose()}
- subtitle={this.context.t(...this.props.subtitleParams)}
title={this.context.t(this.props.titleKey)}
/>
)
diff --git a/ui/app/components/app/send/send-header/send-header.container.js b/ui/app/pages/send/send-header/send-header.container.js
index ce53fba9a..1a9c5e9c0 100644
--- a/ui/app/components/app/send/send-header/send-header.container.js
+++ b/ui/app/pages/send/send-header/send-header.container.js
@@ -1,5 +1,5 @@
import { connect } from 'react-redux'
-import { clearSend } from '../../../../store/actions'
+import { clearSend } from '../../../store/actions'
import SendHeader from './send-header.component'
import { getSubtitleParams, getTitleKey } from './send-header.selectors'
diff --git a/ui/app/components/app/send/send-header/send-header.selectors.js b/ui/app/pages/send/send-header/send-header.selectors.js
index d7c9d3766..d7c9d3766 100644
--- a/ui/app/components/app/send/send-header/send-header.selectors.js
+++ b/ui/app/pages/send/send-header/send-header.selectors.js
diff --git a/ui/app/components/app/send/send-header/tests/send-header-component.test.js b/ui/app/pages/send/send-header/tests/send-header-component.test.js
index db2ee8967..91ac7e343 100644
--- a/ui/app/components/app/send/send-header/tests/send-header-component.test.js
+++ b/ui/app/pages/send/send-header/tests/send-header-component.test.js
@@ -2,10 +2,10 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
-import { DEFAULT_ROUTE } from '../../../../../helpers/constants/routes'
+import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
import SendHeader from '../send-header.component.js'
-import PageContainerHeader from '../../../../ui/page-container/page-container-header'
+import PageContainerHeader from '../../../../components/ui/page-container/page-container-header'
const propsMethodSpies = {
clearSend: sinon.spy(),
@@ -57,10 +57,8 @@ describe('SendHeader Component', function () {
it('should pass the correct props to PageContainerHeader', () => {
const {
onClose,
- subtitle,
title,
} = wrapper.find(PageContainerHeader).props()
- assert.equal(subtitle, 'mockSubtitleKeymockVal')
assert.equal(title, 'mockTitleKey')
assert.equal(SendHeader.prototype.onClose.callCount, 0)
onClose()
diff --git a/ui/app/components/app/send/send-header/tests/send-header-container.test.js b/ui/app/pages/send/send-header/tests/send-header-container.test.js
index 634c3424b..fdad8aab3 100644
--- a/ui/app/components/app/send/send-header/tests/send-header-container.test.js
+++ b/ui/app/pages/send/send-header/tests/send-header-container.test.js
@@ -17,7 +17,7 @@ proxyquire('../send-header.container.js', {
return () => ({})
},
},
- '../../../../store/actions': actionSpies,
+ '../../../store/actions': actionSpies,
'./send-header.selectors': {
getTitleKey: (s) => `mockTitleKey:${s}`,
getSubtitleParams: (s) => `mockSubtitleParams:${s}`,
diff --git a/ui/app/components/app/send/send-header/tests/send-header-selectors.test.js b/ui/app/pages/send/send-header/tests/send-header-selectors.test.js
index e0c6a3ab3..e0c6a3ab3 100644
--- a/ui/app/components/app/send/send-header/tests/send-header-selectors.test.js
+++ b/ui/app/pages/send/send-header/tests/send-header-selectors.test.js
diff --git a/ui/app/components/app/send/send.component.js b/ui/app/pages/send/send.component.js
index a38b681b0..5f0c9c9f2 100644
--- a/ui/app/components/app/send/send.component.js
+++ b/ui/app/pages/send/send.component.js
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
-import PersistentForm from '../../../../lib/persistent-form'
+import PersistentForm from '../../../lib/persistent-form'
import {
getAmountErrorObject,
getGasFeeErrorObject,
@@ -112,6 +112,7 @@ export default class SendTransactionScreen extends PersistentForm {
gasTotal: prevGasTotal,
tokenBalance: prevTokenBalance,
network: prevNetwork,
+ selectedToken: prevSelectedToken,
} = prevProps
const uninitialized = [prevBalance, prevGasTotal].every(n => n === null)
@@ -161,6 +162,13 @@ export default class SendTransactionScreen extends PersistentForm {
this.updateGas()
}
}
+
+ const prevTokenAddress = prevSelectedToken && prevSelectedToken.address
+ const selectedTokenAddress = selectedToken && selectedToken.address
+
+ if (selectedTokenAddress && prevTokenAddress !== selectedTokenAddress) {
+ this.updateSendToken()
+ }
}
componentDidMount () {
@@ -171,18 +179,7 @@ export default class SendTransactionScreen extends PersistentForm {
}
componentWillMount () {
- const {
- from: { address },
- selectedToken,
- tokenContract,
- updateSendTokenBalance,
- } = this.props
-
- updateSendTokenBalance({
- selectedToken,
- tokenContract,
- address,
- })
+ this.updateSendToken()
// Show QR Scanner modal if ?scan=true
if (window.location.search === '?scan=true') {
@@ -199,6 +196,21 @@ export default class SendTransactionScreen extends PersistentForm {
this.props.resetSendState()
}
+ updateSendToken () {
+ const {
+ from: { address },
+ selectedToken,
+ tokenContract,
+ updateSendTokenBalance,
+ } = this.props
+
+ updateSendTokenBalance({
+ selectedToken,
+ tokenContract,
+ address,
+ })
+ }
+
render () {
const { history, showHexData } = this.props
diff --git a/ui/app/components/app/send/send.constants.js b/ui/app/pages/send/send.constants.js
index 36549038e..d3fa38d10 100644
--- a/ui/app/components/app/send/send.constants.js
+++ b/ui/app/pages/send/send.constants.js
@@ -1,5 +1,5 @@
const ethUtil = require('ethereumjs-util')
-const { conversionUtil, multiplyCurrencies } = require('../../../helpers/utils/conversion-util')
+const { conversionUtil, multiplyCurrencies } = require('../../helpers/utils/conversion-util')
const MIN_GAS_PRICE_DEC = '0'
const MIN_GAS_PRICE_HEX = (parseInt(MIN_GAS_PRICE_DEC)).toString(16)
diff --git a/ui/app/components/app/send/send.container.js b/ui/app/pages/send/send.container.js
index e65463b93..69adbb765 100644
--- a/ui/app/components/app/send/send.container.js
+++ b/ui/app/pages/send/send.container.js
@@ -2,6 +2,10 @@ import { connect } from 'react-redux'
import SendEther from './send.component'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
+const {
+ getSelectedAddress,
+} = require('../../selectors/selectors')
+
import {
getAmountConversionRate,
getBlockGasLimit,
@@ -12,7 +16,6 @@ import {
getGasTotal,
getPrimaryCurrency,
getRecentBlocks,
- getSelectedAddress,
getSelectedToken,
getSelectedTokenContract,
getSelectedTokenToFiatRate,
@@ -31,21 +34,21 @@ import {
setGasTotal,
showQrScanner,
qrCodeDetected,
-} from '../../../store/actions'
+} from '../../store/actions'
import {
resetSendState,
updateSendErrors,
-} from '../../../ducks/send/send.duck'
+} from '../../ducks/send/send.duck'
import {
fetchBasicGasEstimates,
-} from '../../../ducks/gas/gas.duck'
+} from '../../ducks/gas/gas.duck'
import {
calcGasTotal,
} from './send.utils.js'
import {
SEND_ROUTE,
-} from '../../../helpers/constants/routes'
+} from '../../helpers/constants/routes'
module.exports = compose(
withRouter,
diff --git a/ui/app/components/app/send/send.scss b/ui/app/pages/send/send.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send.scss
+++ b/ui/app/pages/send/send.scss
diff --git a/ui/app/components/app/send/send.selectors.js b/ui/app/pages/send/send.selectors.js
index 2ec677ad1..d4035df28 100644
--- a/ui/app/components/app/send/send.selectors.js
+++ b/ui/app/pages/send/send.selectors.js
@@ -1,18 +1,19 @@
-const { valuesFor } = require('../../../helpers/utils/util')
+const { valuesFor } = require('../../helpers/utils/util')
const abi = require('human-standard-token-abi')
const {
multiplyCurrencies,
-} = require('../../../helpers/utils/conversion-util')
+} = require('../../helpers/utils/conversion-util')
const {
getMetaMaskAccounts,
-} = require('../../../selectors/selectors')
+ getSelectedAddress,
+} = require('../../selectors/selectors')
const {
estimateGasPriceFromRecentBlocks,
calcGasTotal,
} = require('./send.utils')
import {
getFastPriceEstimateInHexWEI,
-} from '../../../selectors/custom-gas'
+} from '../../selectors/custom-gas'
const selectors = {
accountsWithSendEtherInfoSelector,
@@ -33,7 +34,6 @@ const selectors = {
getPrimaryCurrency,
getRecentBlocks,
getSelectedAccount,
- getSelectedAddress,
getSelectedIdentity,
getSelectedToken,
getSelectedTokenContract,
@@ -149,12 +149,6 @@ function getSelectedAccount (state) {
return accounts[selectedAddress]
}
-function getSelectedAddress (state) {
- const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0]
-
- return selectedAddress
-}
-
function getSelectedIdentity (state) {
const selectedAddress = getSelectedAddress(state)
const identities = state.metamask.identities
@@ -246,9 +240,7 @@ function getSendTo (state) {
function getSendToAccounts (state) {
const fromAccounts = accountsWithSendEtherInfoSelector(state)
const addressBookAccounts = getAddressBook(state)
- const allAccounts = [...fromAccounts, ...addressBookAccounts]
- // TODO: figure out exactly what the below returns and put a descriptive variable name on it
- return Object.entries(allAccounts).map(([key, account]) => account)
+ return [...fromAccounts, ...addressBookAccounts]
}
function getSendWarnings (state) {
diff --git a/ui/app/components/app/send/send.utils.js b/ui/app/pages/send/send.utils.js
index 7609d46ea..4acc174f9 100644
--- a/ui/app/components/app/send/send.utils.js
+++ b/ui/app/pages/send/send.utils.js
@@ -5,10 +5,10 @@ const {
multiplyCurrencies,
conversionGreaterThan,
conversionLessThan,
-} = require('../../../helpers/utils/conversion-util')
+} = require('../../helpers/utils/conversion-util')
const {
calcTokenAmount,
-} = require('../../../helpers/utils/token-util')
+} = require('../../helpers/utils/token-util')
const {
BASE_TOKEN_GAS_COST,
INSUFFICIENT_FUNDS_ERROR,
diff --git a/ui/app/components/app/send/tests/send-component.test.js b/ui/app/pages/send/tests/send-component.test.js
index 738c14839..81955cc1d 100644
--- a/ui/app/components/app/send/tests/send-component.test.js
+++ b/ui/app/pages/send/tests/send-component.test.js
@@ -3,7 +3,7 @@ import assert from 'assert'
import proxyquire from 'proxyquire'
import { shallow } from 'enzyme'
import sinon from 'sinon'
-import timeout from '../../../../../lib/test-timeout'
+import timeout from '../../../../lib/test-timeout'
import SendHeader from '../send-header/send-header.container'
import SendContent from '../send-content/send-content.component'
diff --git a/ui/app/components/app/send/tests/send-container.test.js b/ui/app/pages/send/tests/send-container.test.js
index 9538b67b3..131c42f59 100644
--- a/ui/app/components/app/send/tests/send-container.test.js
+++ b/ui/app/pages/send/tests/send-container.test.js
@@ -24,7 +24,7 @@ proxyquire('../send.container.js', {
},
},
'react-router-dom': { withRouter: () => {} },
- 'recompose': { compose: (arg1, arg2) => () => arg2() },
+ 'recompose': { compose: (_, arg2) => () => arg2() },
'./send.selectors': {
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
getBlockGasLimit: (s) => `mockBlockGasLimit:${s}`,
@@ -35,7 +35,6 @@ proxyquire('../send.container.js', {
getGasTotal: (s) => `mockGasTotal:${s}`,
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
getRecentBlocks: (s) => `mockRecentBlocks:${s}`,
- getSelectedAddress: (s) => `mockSelectedAddress:${s}`,
getSelectedToken: (s) => `mockSelectedToken:${s}`,
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`,
@@ -47,11 +46,15 @@ proxyquire('../send.container.js', {
getTokenBalance: (s) => `mockTokenBalance:${s}`,
getQrCodeData: (s) => `mockQrCodeData:${s}`,
},
- '../../../store/actions': actionSpies,
- '../../../ducks/send/send.duck': duckActionSpies,
+ '../../selectors/selectors': {
+ getSelectedAddress: (s) => `mockSelectedAddress:${s}`,
+ },
+ '../../store/actions': actionSpies,
+ '../../ducks/send/send.duck': duckActionSpies,
'./send.utils.js': {
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
},
+
})
describe('send container', () => {
diff --git a/ui/app/components/app/send/tests/send-selectors-test-data.js b/ui/app/pages/send/tests/send-selectors-test-data.js
index cff26a191..cff26a191 100644
--- a/ui/app/components/app/send/tests/send-selectors-test-data.js
+++ b/ui/app/pages/send/tests/send-selectors-test-data.js
diff --git a/ui/app/components/app/send/tests/send-selectors.test.js b/ui/app/pages/send/tests/send-selectors.test.js
index cdc86fe59..ccc126795 100644
--- a/ui/app/components/app/send/tests/send-selectors.test.js
+++ b/ui/app/pages/send/tests/send-selectors.test.js
@@ -20,7 +20,6 @@ const {
getPrimaryCurrency,
getRecentBlocks,
getSelectedAccount,
- getSelectedAddress,
getSelectedIdentity,
getSelectedToken,
getSelectedTokenContract,
@@ -274,14 +273,6 @@ describe('send selectors', () => {
})
})
- describe('getSelectedAddress()', () => {
- it('should', () => {
- assert.equal(
- getSelectedAddress(mockState),
- '0xd85a4b6a394794842887b8284293d69163007bbb'
- )
- })
- })
describe('getSelectedIdentity()', () => {
it('should return the identity object of the currently selected address', () => {
diff --git a/ui/app/components/app/send/tests/send-utils.test.js b/ui/app/pages/send/tests/send-utils.test.js
index fc4c6deed..bf9cba14a 100644
--- a/ui/app/components/app/send/tests/send-utils.test.js
+++ b/ui/app/pages/send/tests/send-utils.test.js
@@ -9,7 +9,7 @@ import {
const {
addCurrencies,
subtractCurrencies,
-} = require('../../../../helpers/utils/conversion-util')
+} = require('../../../helpers/utils/conversion-util')
const {
INSUFFICIENT_FUNDS_ERROR,
@@ -17,12 +17,12 @@ const {
} = require('../send.constants')
const stubs = {
- addCurrencies: sinon.stub().callsFake((a, b, obj) => {
+ addCurrencies: sinon.stub().callsFake((a, b) => {
if (String(a).match(/^0x.+/)) a = Number(String(a).slice(2))
if (String(b).match(/^0x.+/)) b = Number(String(b).slice(2))
return a + b
}),
- conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
+ conversionUtil: sinon.stub().callsFake((val) => parseInt(val, 16)),
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value),
multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`),
calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d),
@@ -32,7 +32,7 @@ const stubs = {
}
const sendUtils = proxyquire('../send.utils.js', {
- '../../../helpers/utils/conversion-util': {
+ '../../helpers/utils/conversion-util': {
addCurrencies: stubs.addCurrencies,
conversionUtil: stubs.conversionUtil,
conversionGTE: stubs.conversionGTE,
@@ -40,7 +40,7 @@ const sendUtils = proxyquire('../send.utils.js', {
conversionGreaterThan: stubs.conversionGreaterThan,
conversionLessThan: stubs.conversionLessThan,
},
- '../../../helpers/utils/token-util': { calcTokenAmount: stubs.calcTokenAmount },
+ '../../helpers/utils/token-util': { calcTokenAmount: stubs.calcTokenAmount },
'ethereumjs-abi': {
rawEncode: stubs.rawEncode,
},
diff --git a/ui/app/components/app/send/to-autocomplete.component.js b/ui/app/pages/send/to-autocomplete.component.js
index 183967c58..183967c58 100644
--- a/ui/app/components/app/send/to-autocomplete.component.js
+++ b/ui/app/pages/send/to-autocomplete.component.js
diff --git a/ui/app/components/app/send/to-autocomplete/index.js b/ui/app/pages/send/to-autocomplete/index.js
index 244d301d1..244d301d1 100644
--- a/ui/app/components/app/send/to-autocomplete/index.js
+++ b/ui/app/pages/send/to-autocomplete/index.js
diff --git a/ui/app/components/app/send/to-autocomplete/to-autocomplete.js b/ui/app/pages/send/to-autocomplete/to-autocomplete.js
index d3db8cb59..11f86acf3 100644
--- a/ui/app/components/app/send/to-autocomplete/to-autocomplete.js
+++ b/ui/app/pages/send/to-autocomplete/to-autocomplete.js
@@ -1,11 +1,12 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
+const copyToClipboard = require('copy-to-clipboard')
const inherits = require('util').inherits
const AccountListItem = require('../account-list-item/account-list-item.component').default
const connect = require('react-redux').connect
-const Tooltip = require('../../../ui/tooltip')
-const checksumAddress = require('../../../../helpers/utils/util').checksumAddress
+const Tooltip = require('../../../components/ui/tooltip')
+const checksumAddress = require('../../../helpers/utils/util').checksumAddress
ToAutoComplete.contextTypes = {
t: PropTypes.func,
@@ -84,7 +85,7 @@ ToAutoComplete.prototype.handleInputEvent = function (event = {}, cb) {
cb && cb(event.target.value)
}
-ToAutoComplete.prototype.componentDidUpdate = function (nextProps, nextState) {
+ToAutoComplete.prototype.componentDidUpdate = function (nextProps) {
if (this.props.to !== nextProps.to) {
this.handleInputEvent()
}
@@ -93,24 +94,34 @@ ToAutoComplete.prototype.componentDidUpdate = function (nextProps, nextState) {
ToAutoComplete.prototype.render = function () {
const {
to,
+ recipient,
dropdownOpen,
onChange,
inError,
qrScanner,
} = this.props
- return h('div.send-v2__to-autocomplete', {}, [
+ const isRecipientToDiff = recipient && recipient !== to
+
+ return h('div.send-v2__to-autocomplete', {style: {
+ borderColor: inError ? 'red' : null,
+ }}, [
h(`input.send-v2__to-autocomplete__input${qrScanner ? '.with-qr' : ''}`, {
placeholder: this.context.t('recipientAddress'),
className: inError ? `send-v2__error-border` : '',
- value: to,
+ value: recipient,
onChange: event => onChange(event.target.value),
onFocus: event => this.handleInputEvent(event),
- style: {
- borderColor: inError ? 'red' : null,
- },
}),
+ isRecipientToDiff && h(Tooltip, {title: this.context.t('copyToClipboard')},
+ h('div.send-v2__to-autocomplete__resolved', {
+ onClick: (event) => {
+ event.preventDefault()
+ event.stopPropagation()
+ copyToClipboard(to)
+ },
+ }, to)),
qrScanner && h(Tooltip, {
title: this.context.t('scanQrCode'),
position: 'bottom',
diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
index d1cad1746..3d27fe349 100644
--- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
+++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
@@ -24,6 +24,8 @@ export default class AdvancedTab extends PureComponent {
setAdvancedInlineGasFeatureFlag: PropTypes.func,
advancedInlineGas: PropTypes.bool,
showFiatInTestnets: PropTypes.bool,
+ autoLogoutTimeLimit: PropTypes.number,
+ setAutoLogoutTimeLimit: PropTypes.func.isRequired,
setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired,
}
@@ -49,7 +51,7 @@ export default class AdvancedTab extends PureComponent {
<TextField
type="text"
id="new-rpc"
- placeholder={t('rpcURL')}
+ placeholder={t('rpcUrl')}
value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => {
@@ -189,7 +191,7 @@ export default class AdvancedTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="primary"
+ type="secondary"
large
onClick={event => {
event.preventDefault()
@@ -219,7 +221,7 @@ export default class AdvancedTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="primary"
+ type="secondary"
large
onClick={() => {
window.logStateString((err, result) => {
@@ -251,7 +253,7 @@ export default class AdvancedTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="secondary"
+ type="warning"
large
className="settings-tab__button--orange"
onClick={event => {
@@ -355,6 +357,48 @@ export default class AdvancedTab extends PureComponent {
)
}
+ renderAutoLogoutTimeLimit () {
+ const { t } = this.context
+ const {
+ autoLogoutTimeLimit,
+ setAutoLogoutTimeLimit,
+ } = this.props
+
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('autoLogoutTimeLimit') }</span>
+ <div className="settings-page__content-description">
+ { t('autoLogoutTimeLimitDescription') }
+ </div>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <TextField
+ type="number"
+ id="autoTimeout"
+ placeholder="5"
+ value={this.state.autoLogoutTimeLimit}
+ defaultValue={autoLogoutTimeLimit}
+ onChange={e => this.setState({ autoLogoutTimeLimit: Math.max(Number(e.target.value), 0) })}
+ fullWidth
+ margin="dense"
+ min={0}
+ />
+ <button
+ className="button btn-primary settings-tab__rpc-save-button"
+ onClick={() => {
+ setAutoLogoutTimeLimit(this.state.autoLogoutTimeLimit)
+ }}
+ >
+ { t('save') }
+ </button>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
renderContent () {
const { warning } = this.props
@@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent {
{ this.renderAdvancedGasInputInline() }
{ this.renderHexDataOptIn() }
{ this.renderShowConversionInTestnets() }
+ { this.renderAutoLogoutTimeLimit() }
</div>
)
}
diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
index 69d7e07e6..bcac55f5e 100644
--- a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
+++ b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
@@ -8,10 +8,11 @@ import {
setFeatureFlag,
showModal,
setShowFiatConversionOnTestnetsPreference,
+ setAutoLogoutTimeLimit,
} from '../../../store/actions'
import {preferencesSelector} from '../../../selectors/selectors'
-const mapStateToProps = state => {
+export const mapStateToProps = state => {
const { appState: { warning }, metamask } = state
const {
featureFlags: {
@@ -19,17 +20,18 @@ const mapStateToProps = state => {
advancedInlineGas,
} = {},
} = metamask
- const { showFiatInTestnets } = preferencesSelector(state)
+ const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state)
return {
warning,
sendHexData,
advancedInlineGas,
showFiatInTestnets,
+ autoLogoutTimeLimit,
}
}
-const mapDispatchToProps = dispatch => {
+export const mapDispatchToProps = dispatch => {
return {
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)),
@@ -39,6 +41,9 @@ const mapDispatchToProps = dispatch => {
setShowFiatConversionOnTestnetsPreference: value => {
return dispatch(setShowFiatConversionOnTestnetsPreference(value))
},
+ setAutoLogoutTimeLimit: value => {
+ return dispatch(setAutoLogoutTimeLimit(value))
+ },
}
}
diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
new file mode 100644
index 000000000..f81329533
--- /dev/null
+++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
@@ -0,0 +1,44 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+import AdvancedTab from '../advanced-tab.component'
+import TextField from '../../../../components/ui/text-field'
+
+describe('AdvancedTab Component', () => {
+ it('should render correctly', () => {
+ const root = shallow(
+ <AdvancedTab />,
+ {
+ context: {
+ t: s => `_${s}`,
+ },
+ }
+ )
+
+ assert.equal(root.find('.settings-page__content-row').length, 8)
+ })
+
+ it('should update autoLogoutTimeLimit', () => {
+ const setAutoLogoutTimeLimitSpy = sinon.spy()
+ const root = shallow(
+ <AdvancedTab
+ setAutoLogoutTimeLimit={setAutoLogoutTimeLimitSpy}
+ />,
+ {
+ context: {
+ t: s => `_${s}`,
+ },
+ }
+ )
+
+ const autoTimeout = root.find('.settings-page__content-row').last()
+ const textField = autoTimeout.find(TextField)
+
+ textField.props().onChange({ target: { value: 1440 } })
+ assert.equal(root.state().autoLogoutTimeLimit, 1440)
+
+ autoTimeout.find('button').simulate('click')
+ assert.equal(setAutoLogoutTimeLimitSpy.args[0][0], 1440)
+ })
+})
diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
new file mode 100644
index 000000000..62122073d
--- /dev/null
+++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
@@ -0,0 +1,46 @@
+import assert from 'assert'
+import { mapStateToProps, mapDispatchToProps } from '../advanced-tab.container'
+
+const defaultState = {
+ appState: {
+ warning: null,
+ },
+ metamask: {
+ featureFlags: {
+ sendHexData: false,
+ advancedInlineGas: false,
+ },
+ preferences: {
+ autoLogoutTimeLimit: 0,
+ showFiatInTestnets: false,
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ },
+}
+
+describe('AdvancedTab Container', () => {
+ it('should map state to props correctly', () => {
+ const props = mapStateToProps(defaultState)
+ const expected = {
+ warning: null,
+ sendHexData: false,
+ advancedInlineGas: false,
+ showFiatInTestnets: false,
+ autoLogoutTimeLimit: 0,
+ }
+
+ assert.deepEqual(props, expected)
+ })
+
+ it('should map dispatch to props correctly', () => {
+ const props = mapDispatchToProps(() => 'mockDispatch')
+
+ assert.ok(typeof props.setHexDataFeatureFlag === 'function')
+ assert.ok(typeof props.setRpcTarget === 'function')
+ assert.ok(typeof props.displayWarning === 'function')
+ assert.ok(typeof props.showResetAccountConfirmationModal === 'function')
+ assert.ok(typeof props.setAdvancedInlineGasFeatureFlag === 'function')
+ assert.ok(typeof props.setShowFiatConversionOnTestnetsPreference === 'function')
+ assert.ok(typeof props.setAutoLogoutTimeLimit === 'function')
+ })
+})
diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss
index 52208dc85..66959ba93 100644
--- a/ui/app/pages/settings/index.scss
+++ b/ui/app/pages/settings/index.scss
@@ -1,5 +1,7 @@
@import 'info-tab/index';
+@import 'networks-tab/index';
+
@import 'settings-tab/index';
.settings-page {
@@ -13,7 +15,6 @@
flex-flow: row nowrap;
padding: 12px 24px;
align-items: center;
- border-bottom: 1px solid $alto;
flex: 0 0 auto;
&__title {
@@ -22,6 +23,45 @@
}
}
+ &__subheader {
+ padding: 16px 4px;
+ font-size: 20px;
+ border-bottom: 1px solid $alto;
+ margin-right: 24px;
+
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__sub-header {
+ height: 72px;
+ border-bottom: 1px solid #D8D8D8;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ @media screen and (max-width: 575px) {
+ height: 69px;
+ position: relative;
+ text-align: center;
+ }
+ }
+
+ &__sub-header-text {
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 24px;
+ line-height: 24px;
+ color: black;
+
+ @media screen and (max-width: 575px) {
+ font-size: 16px;
+ width: 100%;
+ }
+ }
+
&__back-button {
display: none;
@@ -49,8 +89,9 @@
&__content {
display: flex;
flex-flow: row nowrap;
- height: auto;
+ height: 100%;
overflow: auto;
+ border-top: 1px solid #D8D8D8;
&__tabs {
display: flex;
@@ -58,9 +99,15 @@
flex: 1 1 auto;
@media screen and (min-width: 576px) {
- flex: 0 0 32%;
+ flex: 0 0 40%;
max-width: 210px;
- border-right: 1px solid $alto;
+ padding-top: 8px;
+ }
+
+ .tab-bar__tab {
+ @media screen and (min-width: 576px) {
+ padding: 16px 24px 0;
+ }
}
}
@@ -76,6 +123,10 @@
&__body {
padding: 12px 24px;
+
+ @media screen and (min-width: 576px) {
+ padding: 12px;
+ }
}
&__content-row {
@@ -89,7 +140,6 @@
min-width: 0;
display: flex;
flex-direction: column;
- padding: 0 5px;
min-height: 71px;
@media screen and (max-width: 575px) {
diff --git a/ui/app/pages/settings/info-tab/index.scss b/ui/app/pages/settings/info-tab/index.scss
index 43ad6f652..9cc7e21b2 100644
--- a/ui/app/pages/settings/info-tab/index.scss
+++ b/ui/app/pages/settings/info-tab/index.scss
@@ -30,7 +30,7 @@
}
&__link-text {
- color: $curious-blue;
+ @extend %link;
}
&__version-number {
diff --git a/ui/app/pages/settings/networks-tab/index.js b/ui/app/pages/settings/networks-tab/index.js
new file mode 100644
index 000000000..362004498
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/index.js
@@ -0,0 +1 @@
+export { default } from './networks-tab.container'
diff --git a/ui/app/pages/settings/networks-tab/index.scss b/ui/app/pages/settings/networks-tab/index.scss
new file mode 100644
index 000000000..b0020437d
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/index.scss
@@ -0,0 +1,200 @@
+.networks-tab {
+ &__content {
+ margin-top: 24px;
+ display: flex;
+ height: 100%;
+ max-width: 739px;
+ justify-content: space-between;
+
+ @media screen and (max-width: 575px) {
+ margin-top: 0px;
+ }
+ }
+
+ &__body {
+ padding: 12px 24px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ @media screen and (max-width: 575px) {
+ padding: 0;
+ }
+ }
+
+ &__back-button {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: block;
+ background-image: url('/images/caret-left-black.svg');
+ width: 18px;
+ height: 18px;
+ opacity: .5;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ margin-right: 16px;
+ cursor: pointer;
+ position: absolute;
+ margin-left: 10px;
+ }
+ }
+
+ &__network-form {
+ flex: 0.5 0 auto;
+ max-width: 343px;
+ max-height: 465px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+
+ .page-container__footer {
+ border-top: none;
+
+ @media screen and (max-width: 575px) {
+ width: 93%;
+ }
+
+ header {
+ padding: 10px 0px;
+ }
+ }
+
+ @media screen and (max-width: 575px) {
+ display: flex;
+ flex: auto;
+ max-width: 100%;
+ max-height: 100%;
+ align-items: center;
+ width: 100%;
+ margin-top: 10px;
+ }
+ }
+
+ &__network-form-row {
+ @media screen and (max-width: 575px) {
+ display: flex;
+ flex-direction: column;
+ width: 93%;
+ }
+ }
+
+ &__network-form-label {
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 20px;
+ color: #000000;
+ }
+
+ &__networks-list {
+ flex: 0.5 0 auto;
+ max-width: 343px;
+
+ @media screen and (max-width: 575px) {
+ max-width: 100vw;
+ width: 100vw;
+ overflow-y: scroll;
+ }
+ }
+
+ &__add-network-button-wrapper {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: flex;
+ padding-top: 19px;
+ padding-bottom: 23px;
+ justify-content: center;
+ align-items: center;
+ border-top: 1px solid #D8D8D8;
+
+ .button {
+ width: 178px;
+ }
+ }
+ }
+
+ &__add-network-header-button-wrapper {
+ padding-top: 15px;
+ padding-bottom: 21px;
+ justify-content: center;
+
+ .button {
+ width: 178px;
+ }
+
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__networks-list--selection {
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__networks-list-item {
+ display: flex;
+ padding: 13px 0px 13px 17px;
+ position: relative;
+
+ .menu-icon-circle {
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ @media screen and (max-width: 575px) {
+ padding: 20px 23px 21px 17px;
+ border-bottom: 1px solid #D8D8D8;
+ }
+ }
+
+ &__networks-list-item:last-of-type {
+ @media screen and (max-width: 575px) {
+ border-bottom: none;
+ }
+ }
+
+ &__networks-list-name {
+ margin-left: 11px;
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 16px;
+ line-height: 23px;
+ color: #6A737D;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ &__networks-list-arrow {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: block;
+ background-image: url('/images/caret-right.svg');
+ width: 24px;
+ height: 24px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ right: 10px;
+ cursor: pointer;
+ position: absolute;
+ width: 24px;
+ height: 24px;
+ }
+ }
+
+ &__networks-list-name--selected {
+ font-weight: bold;
+ color: #000000;
+ }
+} \ No newline at end of file
diff --git a/ui/app/pages/settings/networks-tab/network-form/index.js b/ui/app/pages/settings/networks-tab/network-form/index.js
new file mode 100644
index 000000000..89d9de42b
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/network-form/index.js
@@ -0,0 +1 @@
+export { default } from './network-form.component'
diff --git a/ui/app/pages/settings/networks-tab/network-form/network-form.component.js b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js
new file mode 100644
index 000000000..5e455b65e
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js
@@ -0,0 +1,225 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import validUrl from 'valid-url'
+import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'
+import TextField from '../../../../components/ui/text-field'
+
+export default class NetworksTab extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ metricsEvent: PropTypes.func.isRequired,
+ }
+
+ static propTypes = {
+ editRpc: PropTypes.func.isRequired,
+ rpcUrl: PropTypes.string,
+ chainId: PropTypes.string,
+ ticker: PropTypes.string,
+ viewOnly: PropTypes.bool,
+ networkName: PropTypes.string,
+ onClear: PropTypes.func.isRequired,
+ setRpcTarget: PropTypes.func.isRequired,
+ networksTabIsInAddMode: PropTypes.bool,
+ blockExplorerUrl: PropTypes.string,
+ rpcPrefs: PropTypes.object,
+ }
+
+ state = {
+ rpcUrl: this.props.rpcUrl,
+ chainId: this.props.chainId,
+ ticker: this.props.ticker,
+ networkName: this.props.networkName,
+ blockExplorerUrl: this.props.blockExplorerUrl,
+ errors: {},
+ }
+
+ componentDidUpdate (prevProps) {
+ const { rpcUrl: prevRpcUrl, networksTabIsInAddMode: prevAddMode } = prevProps
+ const {
+ rpcUrl,
+ chainId,
+ ticker,
+ networkName,
+ networksTabIsInAddMode,
+ blockExplorerUrl,
+ } = this.props
+
+ if (!prevAddMode && networksTabIsInAddMode) {
+ this.setState({
+ rpcUrl: '',
+ chainId: '',
+ ticker: '',
+ networkName: '',
+ blockExplorerUrl: '',
+ errors: {},
+ })
+ } else if (prevRpcUrl !== rpcUrl) {
+ this.setState({ rpcUrl, chainId, ticker, networkName, blockExplorerUrl, errors: {} })
+ }
+ }
+
+ componentWillUnmount () {
+ this.props.onClear()
+ this.setState({
+ rpcUrl: '',
+ chainId: '',
+ ticker: '',
+ networkName: '',
+ blockExplorerUrl: '',
+ errors: {},
+ })
+ }
+
+ stateIsUnchanged () {
+ const {
+ rpcUrl,
+ chainId,
+ ticker,
+ networkName,
+ blockExplorerUrl,
+ } = this.props
+
+ const {
+ rpcUrl: stateRpcUrl,
+ chainId: stateChainId,
+ ticker: stateTicker,
+ networkName: stateNetworkName,
+ blockExplorerUrl: stateBlockExplorerUrl,
+ } = this.state
+
+ return (
+ stateRpcUrl === rpcUrl &&
+ stateChainId === chainId &&
+ stateTicker === ticker &&
+ stateNetworkName === networkName &&
+ stateBlockExplorerUrl === blockExplorerUrl
+ )
+ }
+
+ renderFormTextField (fieldKey, textFieldId, onChange, value, optionalTextFieldKey) {
+ const { errors } = this.state
+ const { viewOnly } = this.props
+
+ return (
+ <div className="networks-tab__network-form-row">
+ <div className="networks-tab__network-form-label">{this.context.t(optionalTextFieldKey || fieldKey)}</div>
+ <TextField
+ type="text"
+ id={textFieldId}
+ onChange={onChange}
+ fullWidth
+ margin="dense"
+ value={value}
+ disabled={viewOnly}
+ error={errors[fieldKey]}
+ />
+ </div>
+ )
+ }
+
+ setStateWithValue = (stateKey, validator) => {
+ return (e) => {
+ validator && validator(e.target.value, stateKey)
+ this.setState({ [stateKey]: e.target.value })
+ }
+ }
+
+ setErrorTo = (errorKey, errorVal) => {
+ this.setState({
+ errors: {
+ ...this.state.errors,
+ [errorKey]: errorVal,
+ },
+ })
+ }
+
+ validateChainId = (chainId) => {
+ this.setErrorTo('chainId', !!chainId && Number.isNaN(parseInt(chainId))
+ ? `${this.context.t('invalidInput')} chainId`
+ : ''
+ )
+ }
+
+ validateUrl = (url, stateKey) => {
+ if (validUrl.isWebUri(url)) {
+ this.setErrorTo(stateKey, '')
+ } else {
+ const appendedRpc = `http://${url}`
+ const validWhenAppended = validUrl.isWebUri(appendedRpc) && !url.match(/^https?:\/\/$/)
+
+ this.setErrorTo(stateKey, this.context.t(validWhenAppended ? 'uriErrorMsg' : 'invalidRPC'))
+ }
+ }
+
+ render () {
+ const { setRpcTarget, viewOnly, rpcUrl: propsRpcUrl, editRpc, rpcPrefs = {} } = this.props
+ const {
+ networkName,
+ rpcUrl,
+ chainId,
+ ticker,
+ blockExplorerUrl,
+ errors,
+ } = this.state
+
+
+ return (
+ <div className="networks-tab__network-form">
+ {this.renderFormTextField(
+ 'networkName',
+ 'network-name',
+ this.setStateWithValue('networkName'),
+ networkName,
+ )}
+ {this.renderFormTextField(
+ 'rpcUrl',
+ 'rpc-url',
+ this.setStateWithValue('rpcUrl', this.validateUrl),
+ rpcUrl,
+ )}
+ {this.renderFormTextField(
+ 'chainId',
+ 'chainId',
+ this.setStateWithValue('chainId', this.validateChainId),
+ chainId,
+ 'optionalChainId',
+ )}
+ {this.renderFormTextField(
+ 'symbol',
+ 'network-ticker',
+ this.setStateWithValue('ticker'),
+ ticker,
+ 'optionalSymbol',
+ )}
+ {this.renderFormTextField(
+ 'blockExplorerUrl',
+ 'block-explorer-url',
+ this.setStateWithValue('blockExplorerUrl', this.validateUrl),
+ blockExplorerUrl,
+ 'optionalBlockExplorerUrl',
+ )}
+ <PageContainerFooter
+ cancelText={this.context.t('cancel')}
+ hideCancel={true}
+ onSubmit={() => {
+ if (propsRpcUrl && rpcUrl !== propsRpcUrl) {
+ editRpc(propsRpcUrl, rpcUrl, chainId, ticker, networkName, {
+ blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
+ ...rpcPrefs,
+ })
+ } else {
+ setRpcTarget(rpcUrl, chainId, ticker, networkName, {
+ blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
+ ...rpcPrefs,
+ })
+ }
+ }}
+ submitText={this.context.t('save')}
+ submitButtonType={'confirm'}
+ disabled={viewOnly || this.stateIsUnchanged() || Object.values(errors).some(x => x) || !rpcUrl}
+ />
+ </div>
+ )
+ }
+
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.component.js b/ui/app/pages/settings/networks-tab/networks-tab.component.js
new file mode 100644
index 000000000..2f921a892
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.component.js
@@ -0,0 +1,214 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { SETTINGS_ROUTE } from '../../../helpers/constants/routes'
+import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
+import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
+import classnames from 'classnames'
+import Button from '../../../components/ui/button'
+import NetworkForm from './network-form'
+import NetworkDropdownIcon from '../../../components/app/dropdowns/components/network-dropdown-icon'
+
+export default class NetworksTab extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ metricsEvent: PropTypes.func.isRequired,
+ }
+
+ static propTypes = {
+ editRpc: PropTypes.func.isRequired,
+ history: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired,
+ networkIsSelected: PropTypes.bool,
+ networksTabIsInAddMode: PropTypes.bool,
+ networksToRender: PropTypes.array.isRequired,
+ selectedNetwork: PropTypes.object,
+ setNetworksTabAddMode: PropTypes.func.isRequired,
+ setRpcTarget: PropTypes.func.isRequired,
+ setSelectedSettingsRpcUrl: PropTypes.func.isRequired,
+ providerUrl: PropTypes.string,
+ providerType: PropTypes.string,
+ networkDefaultedToProvider: PropTypes.bool,
+ }
+
+ componentWillMount () {
+ this.props.setSelectedSettingsRpcUrl(null)
+ }
+
+ isCurrentPath (pathname) {
+ return this.props.location.pathname === pathname
+ }
+
+ renderSubHeader () {
+ const {
+ networkIsSelected,
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ networksTabIsInAddMode,
+ networkDefaultedToProvider,
+ } = this.props
+
+ return (
+ <div className="settings-page__sub-header">
+ <div
+ className="networks-tab__back-button"
+ onClick={(networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode
+ ? () => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(null)
+ }
+ : () => this.props.history.push(SETTINGS_ROUTE)
+ }
+ />
+ <span className="settings-page__sub-header-text">{ this.context.t('networks') }</span>
+ <div className="networks-tab__add-network-header-button-wrapper">
+ <Button
+ type="primary"
+ onClick={event => {
+ event.preventDefault()
+ setSelectedSettingsRpcUrl(null)
+ setNetworksTabAddMode(true)
+ }}
+ >
+ { this.context.t('addNetwork') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+
+ renderNetworkListItem (network, selectRpcUrl) {
+ const {
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ networkIsSelected,
+ providerUrl,
+ providerType,
+ networksTabIsInAddMode,
+ } = this.props
+ const {
+ border,
+ iconColor,
+ label,
+ labelKey,
+ rpcUrl,
+ providerType: currentProviderType,
+ } = network
+
+ const listItemNetworkIsSelected = selectRpcUrl && selectRpcUrl === rpcUrl
+ const listItemUrlIsProviderUrl = rpcUrl === providerUrl
+ const listItemTypeIsProviderNonRpcType = providerType !== 'rpc' && currentProviderType === providerType
+ const listItemNetworkIsCurrentProvider = !networkIsSelected && !networksTabIsInAddMode && (listItemUrlIsProviderUrl || listItemTypeIsProviderNonRpcType)
+ const displayNetworkListItemAsSelected = listItemNetworkIsSelected || listItemNetworkIsCurrentProvider
+
+ return (
+ <div
+ key={'settings-network-list-item:' + rpcUrl}
+ className="networks-tab__networks-list-item"
+ onClick={ () => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(rpcUrl)
+ }}
+ >
+ <NetworkDropdownIcon
+ backgroundColor={iconColor || 'white'}
+ innerBorder={border}
+ />
+ <div className={ classnames('networks-tab__networks-list-name', {
+ 'networks-tab__networks-list-name--selected': displayNetworkListItemAsSelected,
+ }) }>
+ { label || this.context.t(labelKey) }
+ </div>
+ <div className="networks-tab__networks-list-arrow" />
+ </div>
+ )
+ }
+
+ renderNetworksList () {
+ const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props
+
+ return (
+ <div className={classnames('networks-tab__networks-list', {
+ 'networks-tab__networks-list--selection': (networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode,
+ })}>
+ { networksToRender.map(network => this.renderNetworkListItem(network, selectedNetwork.rpcUrl)) }
+ </div>
+ )
+ }
+
+ renderNetworksTabContent () {
+ const {
+ setRpcTarget,
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ selectedNetwork: {
+ labelKey,
+ label,
+ rpcUrl,
+ chainId,
+ ticker,
+ viewOnly,
+ rpcPrefs,
+ blockExplorerUrl,
+ },
+ networksTabIsInAddMode,
+ editRpc,
+ networkDefaultedToProvider,
+ } = this.props
+ const envIsPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
+
+ return (
+ <div className="networks-tab__content">
+ {this.renderNetworksList()}
+ {networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider)
+ ? <NetworkForm
+ setRpcTarget={setRpcTarget}
+ editRpc={editRpc}
+ networkName={label || labelKey && this.context.t(labelKey) || ''}
+ rpcUrl={rpcUrl}
+ chainId={chainId}
+ ticker={ticker}
+ onClear={() => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(null)
+ }}
+ viewOnly={viewOnly}
+ networksTabIsInAddMode={networksTabIsInAddMode}
+ rpcPrefs={rpcPrefs}
+ blockExplorerUrl={blockExplorerUrl}
+ />
+ : null
+ }
+ </div>
+ )
+ }
+
+ renderContent () {
+ const { setNetworksTabAddMode, setSelectedSettingsRpcUrl, networkIsSelected, networksTabIsInAddMode } = this.props
+
+ return (
+ <div className="networks-tab__body">
+ {this.renderSubHeader()}
+ {this.renderNetworksTabContent()}
+ {!networkIsSelected && !networksTabIsInAddMode
+ ? <div className="networks-tab__add-network-button-wrapper">
+ <Button
+ type="primary"
+ onClick={event => {
+ event.preventDefault()
+ setSelectedSettingsRpcUrl(null)
+ setNetworksTabAddMode(true)
+ }}
+ >
+ { this.context.t('addNetwork') }
+ </Button>
+ </div>
+ : null
+ }
+ </div>
+ )
+ }
+
+ render () {
+ return this.renderContent()
+ }
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.constants.js b/ui/app/pages/settings/networks-tab/networks-tab.constants.js
new file mode 100644
index 000000000..d3d1a01cc
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.constants.js
@@ -0,0 +1,50 @@
+const defaultNetworksData = [
+ {
+ labelKey: 'mainnet',
+ iconColor: '#29B6AF',
+ providerType: 'mainnet',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/mainnet',
+ chainId: '1',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+ {
+ labelKey: 'ropsten',
+ iconColor: '#FF4A8D',
+ providerType: 'ropsten',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/ropsten',
+ chainId: '3',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://ropsten.etherscan.io',
+ },
+ {
+ labelKey: 'kovan',
+ iconColor: '#9064FF',
+ providerType: 'kovan',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/kovan',
+ chainId: '4',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+ {
+ labelKey: 'rinkeby',
+ iconColor: '#F6C343',
+ providerType: 'rinkeby',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/rinkeby',
+ chainId: '42',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://rinkeby.etherscan.io',
+ },
+ {
+ labelKey: 'localhost',
+ iconColor: 'white',
+ border: '1px solid #6A737D',
+ providerType: 'localhost',
+ rpcUrl: 'http://localhost:8545/',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+]
+
+export {
+ defaultNetworksData,
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.container.js b/ui/app/pages/settings/networks-tab/networks-tab.container.js
new file mode 100644
index 000000000..a5d71f714
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.container.js
@@ -0,0 +1,77 @@
+import NetworksTab from './networks-tab.component'
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import {
+ setSelectedSettingsRpcUrl,
+ updateAndSetCustomRpc,
+ displayWarning,
+ setNetworksTabAddMode,
+ editRpc,
+} from '../../../store/actions'
+import { defaultNetworksData } from './networks-tab.constants'
+const defaultNetworks = defaultNetworksData.map(network => ({ ...network, viewOnly: true }))
+
+const mapStateToProps = state => {
+ const {
+ frequentRpcListDetail,
+ provider,
+ } = state.metamask
+ const {
+ networksTabSelectedRpcUrl,
+ networksTabIsInAddMode,
+ } = state.appState
+
+ const frequentRpcNetworkListDetails = frequentRpcListDetail.map(rpc => {
+ return {
+ label: rpc.nickname,
+ iconColor: '#6A737D',
+ providerType: 'rpc',
+ rpcUrl: rpc.rpcUrl,
+ chainId: rpc.chainId,
+ ticker: rpc.ticker,
+ blockExplorerUrl: rpc.rpcPrefs && rpc.rpcPrefs.blockExplorerUrl || '',
+ }
+ })
+
+ const networksToRender = [ ...defaultNetworks, ...frequentRpcNetworkListDetails ]
+ let selectedNetwork = networksToRender.find(network => network.rpcUrl === networksTabSelectedRpcUrl) || {}
+ const networkIsSelected = Boolean(selectedNetwork.rpcUrl)
+
+ let networkDefaultedToProvider = false
+ if (!networkIsSelected && !networksTabIsInAddMode) {
+ selectedNetwork = networksToRender.find(network => {
+ return network.rpcUrl === provider.rpcTarget || network.providerType !== 'rpc' && network.providerType === provider.type
+ }) || {}
+ networkDefaultedToProvider = true
+ }
+
+ return {
+ selectedNetwork,
+ networksToRender,
+ networkIsSelected,
+ networksTabIsInAddMode,
+ providerType: provider.type,
+ providerUrl: provider.rpcTarget,
+ networkDefaultedToProvider,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setSelectedSettingsRpcUrl: newRpcUrl => dispatch(setSelectedSettingsRpcUrl(newRpcUrl)),
+ setRpcTarget: (newRpc, chainId, ticker, nickname, rpcPrefs) => {
+ dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname, rpcPrefs))
+ },
+ displayWarning: warning => dispatch(displayWarning(warning)),
+ setNetworksTabAddMode: isInAddMode => dispatch(setNetworksTabAddMode(isInAddMode)),
+ editRpc: (oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs) => {
+ dispatch(editRpc(oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs))
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(NetworksTab)
diff --git a/ui/app/pages/settings/security-tab/security-tab.component.js b/ui/app/pages/settings/security-tab/security-tab.component.js
index 233561115..01a28bac7 100644
--- a/ui/app/pages/settings/security-tab/security-tab.component.js
+++ b/ui/app/pages/settings/security-tab/security-tab.component.js
@@ -39,7 +39,7 @@ export default class SecurityTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="primary"
+ type="secondary"
large
onClick={() => {
window.logStateString((err, result) => {
@@ -73,7 +73,7 @@ export default class SecurityTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="secondary"
+ type="warning"
large
className="settings-tab__button--orange"
onClick={event => {
@@ -101,7 +101,7 @@ export default class SecurityTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="secondary"
+ type="danger"
large
onClick={event => {
event.preventDefault()
diff --git a/ui/app/pages/settings/settings-tab/index.scss b/ui/app/pages/settings/settings-tab/index.scss
index ef32b0e4c..c1750af2c 100644
--- a/ui/app/pages/settings/settings-tab/index.scss
+++ b/ui/app/pages/settings/settings-tab/index.scss
@@ -6,19 +6,15 @@
}
&__advanced-link {
- color: $curious-blue;
+ @extend %small-link;
padding-left: 5px;
}
&__rpc-save-button {
align-self: flex-end;
padding: 5px;
- text-transform: uppercase;
- color: $dusty-gray;
cursor: pointer;
width: 25%;
- min-width: 80px;
- height: 33px;
}
&__button--red {
@@ -35,20 +31,6 @@
}
}
- &__button--orange {
- border-color: lighten($ecstasy, 20%);
- color: $ecstasy;
-
- &:active {
- background: lighten($ecstasy, 40%);
- border-color: $ecstasy;
- }
-
- &:hover {
- border-color: $ecstasy;
- }
- }
-
&__radio-buttons {
display: flex;
align-items: center;
diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js
index 061e65060..a2f137264 100644
--- a/ui/app/pages/settings/settings.component.js
+++ b/ui/app/pages/settings/settings.component.js
@@ -1,11 +1,12 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import { Switch, Route, matchPath } from 'react-router-dom'
+import { Switch, Route, matchPath, withRouter } from 'react-router-dom'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import TabBar from '../../components/app/tab-bar'
import c from 'classnames'
import SettingsTab from './settings-tab'
+import NetworksTab from './networks-tab'
import AdvancedTab from './advanced-tab'
import InfoTab from './info-tab'
import SecurityTab from './security-tab'
@@ -16,6 +17,7 @@ import {
GENERAL_ROUTE,
ABOUT_US_ROUTE,
SETTINGS_ROUTE,
+ NETWORKS_ROUTE,
} from '../../helpers/constants/routes'
const ROUTES_TO_I18N_KEYS = {
@@ -25,7 +27,7 @@ const ROUTES_TO_I18N_KEYS = {
[ABOUT_US_ROUTE]: 'about',
}
-export default class SettingsPage extends PureComponent {
+class SettingsPage extends PureComponent {
static propTypes = {
location: PropTypes.object,
history: PropTypes.object,
@@ -55,7 +57,7 @@ export default class SettingsPage extends PureComponent {
>
<div className="settings-page__header">
{
- !this.isCurrentPath(SETTINGS_ROUTE) && (
+ !this.isCurrentPath(SETTINGS_ROUTE) && !this.isCurrentPath(NETWORKS_ROUTE) && (
<div
className="settings-page__back-button"
onClick={() => history.push(SETTINGS_ROUTE)}
@@ -75,6 +77,7 @@ export default class SettingsPage extends PureComponent {
{ this.renderTabs() }
</div>
<div className="settings-page__content__modules">
+ { this.renderSubHeader() }
{ this.renderContent() }
</div>
</div>
@@ -82,6 +85,17 @@ export default class SettingsPage extends PureComponent {
)
}
+ renderSubHeader () {
+ const { t } = this.context
+ const { location: { pathname } } = this.props
+
+ return (
+ <div className="settings-page__subheader">
+ {t(ROUTES_TO_I18N_KEYS[pathname] || 'general')}
+ </div>
+ )
+ }
+
renderTabs () {
const { history, location } = this.props
const { t } = this.context
@@ -92,6 +106,7 @@ export default class SettingsPage extends PureComponent {
{ content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE },
{ content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE },
{ content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE },
+ { content: t('networks'), description: t('networkSettingsDescription'), key: NETWORKS_ROUTE },
{ content: t('about'), description: t('aboutSettingsDescription'), key: ABOUT_US_ROUTE },
]}
isActive={key => {
@@ -125,6 +140,11 @@ export default class SettingsPage extends PureComponent {
/>
<Route
exact
+ path={NETWORKS_ROUTE}
+ component={NetworksTab}
+ />
+ <Route
+ exact
path={SECURITY_ROUTE}
component={SecurityTab}
/>
@@ -135,3 +155,5 @@ export default class SettingsPage extends PureComponent {
)
}
}
+
+export default withRouter(SettingsPage)
diff --git a/ui/app/selectors/custom-gas.js b/ui/app/selectors/custom-gas.js
index ecffb37ca..5ba786f0f 100644
--- a/ui/app/selectors/custom-gas.js
+++ b/ui/app/selectors/custom-gas.js
@@ -18,7 +18,7 @@ import {
} from '../helpers/utils/formatters'
import {
calcGasTotal,
-} from '../components/app/send/send.utils'
+} from '../pages/send/send.utils'
import { addHexPrefix } from 'ethereumjs-util'
const selectors = {
diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js
index 554232f7b..c7cb80024 100644
--- a/ui/app/selectors/selectors.js
+++ b/ui/app/selectors/selectors.js
@@ -48,6 +48,8 @@ const selectors = {
getNumberOfAccounts,
getNumberOfTokens,
isEthereumNetwork,
+ getMetaMetricState,
+ getRpcPrefsForCurrentProvider,
}
module.exports = selectors
@@ -91,7 +93,8 @@ function getAccountType (state) {
}
function getSelectedAsset (state) {
- return getSelectedToken(state) || 'ETH'
+ const selectedToken = getSelectedToken(state)
+ return selectedToken && selectedToken.symbol || 'ETH'
}
function getCurrentNetworkId (state) {
@@ -164,7 +167,7 @@ function getSelectedToken (state) {
const tokens = state.metamask.tokens || []
const selectedTokenAddress = state.metamask.selectedTokenAddress
const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
- const sendToken = state.metamask.send.token
+ const sendToken = state.metamask.send && state.metamask.send.token
return selectedToken || sendToken || null
}
@@ -300,9 +303,10 @@ function isEthereumNetwork (state) {
MAINNET,
RINKEBY,
ROPSTEN,
+ GOERLI,
} = NETWORK_TYPES
- return [ KOVAN, MAINNET, RINKEBY, ROPSTEN].includes(networkType)
+ return [ KOVAN, MAINNET, RINKEBY, ROPSTEN, GOERLI].includes(networkType)
}
function preferencesSelector ({ metamask }) {
@@ -312,3 +316,22 @@ function preferencesSelector ({ metamask }) {
function getAdvancedInlineGasShown (state) {
return Boolean(state.metamask.featureFlags.advancedInlineGas)
}
+
+function getMetaMetricState (state) {
+ return {
+ network: getCurrentNetworkId(state),
+ activeCurrency: getSelectedAsset(state),
+ accountType: getAccountType(state),
+ metaMetricsId: state.metamask.metaMetricsId,
+ numberOfTokens: getNumberOfTokens(state),
+ numberOfAccounts: getNumberOfAccounts(state),
+ participateInMetaMetrics: state.metamask.participateInMetaMetrics,
+ }
+}
+
+function getRpcPrefsForCurrentProvider (state) {
+ const { frequentRpcListDetail, provider } = state.metamask
+ const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
+ const { rpcPrefs = {} } = selectRpcInfo || {}
+ return rpcPrefs
+}
diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js
index 7d369fdb9..7f6cbea1f 100644
--- a/ui/app/store/actions.js
+++ b/ui/app/store/actions.js
@@ -5,7 +5,7 @@ const { getTokenAddressFromTokenObject } = require('../helpers/utils/util')
const {
calcTokenBalance,
estimateGas,
-} = require('../components/app/send/send.utils')
+} = require('../pages/send/send.utils')
const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../helpers/utils/i18n-helper')
const log = require('loglevel')
@@ -239,6 +239,7 @@ var actions = {
updateAndSetCustomRpc: updateAndSetCustomRpc,
setRpcTarget: setRpcTarget,
delRpcTarget: delRpcTarget,
+ editRpc: editRpc,
setProviderType: setProviderType,
SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH',
setHardwareWalletDefaultHdPath,
@@ -316,6 +317,7 @@ var actions = {
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseNativeCurrencyAsPrimaryCurrencyPreference,
setShowFiatConversionOnTestnetsPreference,
+ setAutoLogoutTimeLimit,
// Migration of users to new UI
setCompletedUiMigration,
@@ -343,12 +345,21 @@ var actions = {
createCancelTransaction,
createSpeedUpTransaction,
- approveProviderRequest,
- rejectProviderRequest,
+ approveProviderRequestByOrigin,
+ rejectProviderRequestByOrigin,
clearApprovedOrigins,
setFirstTimeFlowType,
SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE',
+
+ SET_SELECTED_SETTINGS_RPC_URL: 'SET_SELECTED_SETTINGS_RPC_URL',
+ setSelectedSettingsRpcUrl,
+ SET_NETWORKS_TAB_ADD_MODE: 'SET_NETWORKS_TAB_ADD_MODE',
+ setNetworksTabAddMode,
+
+ // AppStateController-related actions
+ SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME',
+ setLastActiveTime,
}
module.exports = actions
@@ -761,7 +772,7 @@ function addNewAccount () {
function checkHardwareStatus (deviceName, hdPath) {
log.debug(`background.checkHardwareStatus`, deviceName, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => {
@@ -782,10 +793,10 @@ function checkHardwareStatus (deviceName, hdPath) {
function forgetDevice (deviceName) {
log.debug(`background.forgetDevice`, deviceName)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.forgetDevice(deviceName, (err, response) => {
+ background.forgetDevice(deviceName, (err) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -803,7 +814,7 @@ function forgetDevice (deviceName) {
function connectHardware (deviceName, page, hdPath) {
log.debug(`background.connectHardware`, deviceName, page, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.connectHardware(deviceName, page, hdPath, (err, accounts) => {
@@ -824,10 +835,10 @@ function connectHardware (deviceName, page, hdPath) {
function unlockHardwareWalletAccount (index, deviceName, hdPath) {
log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err, accounts) => {
+ background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -848,7 +859,7 @@ function showInfoPage () {
}
function showQrScanner (ROUTE) {
- return (dispatch, getState) => {
+ return (dispatch) => {
return WebcamUtils.checkStatus()
.then(status => {
if (!status.environmentReady) {
@@ -987,7 +998,7 @@ function signTypedMsg (msgData) {
function signTx (txData) {
return (dispatch) => {
- global.ethQuery.sendTransaction(txData, (err, data) => {
+ global.ethQuery.sendTransaction(txData, (err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
@@ -1020,7 +1031,6 @@ function setGasTotal (gasTotal) {
function updateGasData ({
gasPrice,
blockGasLimit,
- recentBlocks,
selectedAddress,
selectedToken,
to,
@@ -1402,7 +1412,7 @@ function cancelTx (txData) {
* @return {function(*): Promise<void>}
*/
function cancelTxs (txDataList) {
- return async (dispatch, getState) => {
+ return async (dispatch) => {
window.onbeforeunload = null
dispatch(actions.showLoadingIndication())
const txIds = txDataList.map(({id}) => id)
@@ -1807,7 +1817,7 @@ function removeSuggestedTokens () {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
window.onbeforeunload = null
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
background.removeSuggestedTokens((err, suggestedTokens) => {
dispatch(actions.hideLoadingIndication())
if (err) {
@@ -1826,7 +1836,7 @@ function removeSuggestedTokens () {
}
function addKnownMethodData (fourBytePrefix, methodData) {
- return (dispatch) => {
+ return () => {
background.addKnownMethodData(fourBytePrefix, methodData)
}
}
@@ -1931,7 +1941,7 @@ function setProviderType (type) {
return (dispatch, getState) => {
const { type: currentProviderType } = getState().metamask.provider
log.debug(`background.setProviderType`, type)
- background.setProviderType(type, (err, result) => {
+ background.setProviderType(type, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1958,10 +1968,10 @@ function setPreviousProvider (type) {
}
}
-function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) {
+function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
return (dispatch) => {
log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`)
- background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err, result) => {
+ background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1974,10 +1984,33 @@ function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) {
}
}
+function editRpc (oldRpc, newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
+ return (dispatch) => {
+ log.debug(`background.delRpcTarget: ${oldRpc}`)
+ background.delCustomRpc(oldRpc, (err) => {
+ if (err) {
+ log.error(err)
+ return dispatch(self.displayWarning('Had a problem removing network!'))
+ }
+ dispatch(actions.setSelectedToken())
+ background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
+ if (err) {
+ log.error(err)
+ return dispatch(actions.displayWarning('Had a problem changing networks!'))
+ }
+ dispatch({
+ type: actions.SET_RPC_TARGET,
+ value: newRpc,
+ })
+ })
+ })
+ }
+}
+
function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
return (dispatch) => {
log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
- background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err, result) => {
+ background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1990,7 +2023,7 @@ function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
function delRpcTarget (oldRpc) {
return (dispatch) => {
log.debug(`background.delRpcTarget: ${oldRpc}`)
- background.delCustomRpc(oldRpc, (err, result) => {
+ background.delCustomRpc(oldRpc, (err) => {
if (err) {
log.error(err)
return dispatch(self.displayWarning('Had a problem removing network!'))
@@ -2000,11 +2033,12 @@ function delRpcTarget (oldRpc) {
}
}
+
// Calls the addressBookController to add a new address.
function addToAddressBook (recipient, nickname = '') {
log.debug(`background.addToAddressBook`)
return (dispatch) => {
- background.setAddressBook(recipient, nickname, (err, result) => {
+ background.setAddressBook(recipient, nickname, (err) => {
if (err) {
log.error(err)
return dispatch(self.displayWarning('Address book failed to update'))
@@ -2273,7 +2307,7 @@ function pairUpdate (coin) {
}
}
-function shapeShiftSubview (network) {
+function shapeShiftSubview () {
var pair = 'btc_eth'
return (dispatch) => {
dispatch(actions.showSubLoadingIndication())
@@ -2309,7 +2343,7 @@ function coinShiftRquest (data, marketData) {
}
function buyWithShapeShift (data) {
- return dispatch => new Promise((resolve, reject) => {
+ return () => new Promise((resolve, reject) => {
shapeShiftRequest('shift', { method: 'POST', data}, (response) => {
if (response.error) {
return reject(response.error)
@@ -2356,7 +2390,7 @@ function shapeShiftRequest (query, options, cb) {
!options ? options = {} : null
options.method ? method = options.method : method = 'GET'
- var requestListner = function (request) {
+ var requestListner = function () {
try {
queryResponse = JSON.parse(this.responseText)
cb ? cb(queryResponse) : null
@@ -2439,6 +2473,10 @@ function setShowFiatConversionOnTestnetsPreference (value) {
return setPreference('showFiatInTestnets', value)
}
+function setAutoLogoutTimeLimit (value) {
+ return setPreference('autoLogoutTimeLimit', value)
+}
+
function setCompletedOnboarding () {
return async dispatch => {
dispatch(actions.showLoadingIndication())
@@ -2680,20 +2718,20 @@ function setPendingTokens (pendingTokens) {
}
}
-function approveProviderRequest (tabID) {
- return (dispatch) => {
- background.approveProviderRequest(tabID)
+function approveProviderRequestByOrigin (origin) {
+ return () => {
+ background.approveProviderRequestByOrigin(origin)
}
}
-function rejectProviderRequest (tabID) {
- return (dispatch) => {
- background.rejectProviderRequest(tabID)
+function rejectProviderRequestByOrigin (origin) {
+ return () => {
+ background.rejectProviderRequestByOrigin(origin)
}
}
function clearApprovedOrigins () {
- return (dispatch) => {
+ return () => {
background.clearApprovedOrigins()
}
}
@@ -2712,3 +2750,27 @@ function setFirstTimeFlowType (type) {
})
}
}
+
+function setSelectedSettingsRpcUrl (newRpcUrl) {
+ return {
+ type: actions.SET_SELECTED_SETTINGS_RPC_URL,
+ value: newRpcUrl,
+ }
+}
+
+function setNetworksTabAddMode (isInAddMode) {
+ return {
+ type: actions.SET_NETWORKS_TAB_ADD_MODE,
+ value: isInAddMode,
+ }
+}
+
+function setLastActiveTime () {
+ return (dispatch) => {
+ background.setLastActiveTime((err) => {
+ if (err) {
+ return dispatch(actions.displayWarning(err.message))
+ }
+ })
+ }
+}